diff --git a/core/Dispatch.js b/core/Dispatch.js index 6ef97706..2352f82d 100644 --- a/core/Dispatch.js +++ b/core/Dispatch.js @@ -38,7 +38,7 @@ var PathUtils = require('./Path'); function Dispatch () { this._nodes = {}; // a container for constant time lookup of nodes - this._queue = []; // The queue is used for two purposes + // The queue is used for two purposes // 1. It is used to list indicies in the // Nodes path which are then used to lookup // a node in the scene graph. @@ -64,49 +64,6 @@ Dispatch.prototype._setUpdater = function _setUpdater (updater) { for (var key in this._nodes) this._nodes[key]._setUpdater(updater); }; -/** - * Enque the children of a node within the dispatcher. Does not clear - * the dispatchers queue first. - * - * @method addChildrenToQueue - * @return {void} - * - * @param {Node} node from which to add children to the queue - */ -Dispatch.prototype.addChildrenToQueue = function addChildrenToQueue (node) { - var children = node.getChildren(); - var child; - for (var i = 0, len = children.length ; i < len ; i++) { - child = children[i]; - if (child) this._queue.push(child); - } -}; - -/** - * Returns the next item in the Dispatch's queue. - * - * @method next - * @return {Node} next node in the queue - */ -Dispatch.prototype.next = function next () { - return this._queue.shift(); -}; - -/** - * Returns the next node in the queue, but also adds its children to - * the end of the queue. Continually calling this method will result - * in a breadth first traversal of the render tree. - * - * @method breadthFirstNext - * @return {Node | undefined} the next node in the traversal if one exists - */ -Dispatch.prototype.breadthFirstNext = function breadthFirstNext () { - var child = this._queue.shift(); - if (!child) return void 0; - this.addChildrenToQueue(child); - return child; -}; - /** * Calls the onMount method for the node at a given path and * properly registers all of that nodes children to their proper @@ -254,13 +211,15 @@ Dispatch.prototype.show = function show (path) { if (components[i] && components[i].onShow) components[i].onShow(); + var queue = allocQueue(); - this.addChildrenToQueue(node); + addChildrenToQueue(node, queue); var child; - while ((child = this.breadthFirstNext())) + while ((child = breadthFirstNext(queue))) this.show(child.getLocation()); + deallocQueue(queue); }; /** @@ -288,13 +247,15 @@ Dispatch.prototype.hide = function hide (path) { if (components[i] && components[i].onHide) components[i].onHide(); + var queue = allocQueue(); - this.addChildrenToQueue(node); + addChildrenToQueue(node, queue); var child; - while ((child = this.breadthFirstNext())) + while ((child = breadthFirstNext(queue))) this.hide(child.getLocation()); + deallocQueue(queue); }; /** @@ -308,14 +269,16 @@ Dispatch.prototype.hide = function hide (path) { Dispatch.prototype.lookupNode = function lookupNode (location) { if (!location) throw new Error('lookupNode must be called with a path'); - this._queue.length = 0; - var path = this._queue; + var path = allocQueue(); _splitTo(location, path); for (var i = 0, len = path.length ; i < len ; i++) path[i] = this._nodes[path[i]]; + path.length = 0; + deallocQueue(path); + return path[path.length - 1]; }; @@ -336,16 +299,31 @@ Dispatch.prototype.dispatch = function dispatch (path, event, payload) { if (!event) throw new Error('dispatch requires an event name as it\'s second argument'); var node = this._nodes[path]; - + if (!node) return; - this.addChildrenToQueue(node); + payload.node = node; + + var queue = allocQueue(); + queue.push(node); + var child; + var components; + var i; + var len; - while ((child = this.breadthFirstNext())) + while ((child = breadthFirstNext(queue))) { if (child && child.onReceive) child.onReceive(event, payload); + components = child.getComponents(); + + for (i = 0, len = components.length ; i < len ; i++) + if (components[i] && components[i].onReceive) + components[i].onReceive(event, payload); + } + + deallocQueue(queue); }; /** @@ -391,6 +369,32 @@ Dispatch.prototype.dispatchUIEvent = function dispatchUIEvent (path, event, payl } }; +var queues = []; + +/** + * Helper method used for allocating a new queue or reusing a previously freed + * one if possible. + * + * @private + * + * @return {Array} allocated queue. + */ +function allocQueue() { + return queues.pop() || []; +} + +/** + * Helper method used for freeing a previously allocated queue. + * + * @private + * + * @param {Array} queue the queue to be relased to the pool. + * @return {undefined} undefined + */ +function deallocQueue(queue) { + queues.push(queue); +} + /** * _splitTo is a private method which takes a path and splits it at every '/' * pushing the result into the supplied array. This is a destructive change. @@ -419,4 +423,41 @@ function _splitTo (string, target) { return target; } +/** + * Enque the children of a node within the dispatcher. Does not clear + * the dispatchers queue first. + * + * @method addChildrenToQueue + * + * @param {Node} node from which to add children to the queue + * @param {Array} queue the queue used for retrieving the new child from + * + * @return {void} + */ +function addChildrenToQueue (node, queue) { + var children = node.getChildren(); + var child; + for (var i = 0, len = children.length ; i < len ; i++) { + child = children[i]; + if (child) queue.push(child); + } +} + +/** + * Returns the next node in the queue, but also adds its children to + * the end of the queue. Continually calling this method will result + * in a breadth first traversal of the render tree. + * + * @method breadthFirstNext + * @param {Array} queue the queue used for retrieving the new child from + * @return {Node | undefined} the next node in the traversal if one exists + */ +function breadthFirstNext (queue) { + var child = queue.shift(); + if (!child) return void 0; + addChildrenToQueue(child, queue); + return child; +} + + module.exports = new Dispatch(); diff --git a/core/FamousEngine.js b/core/FamousEngine.js index 82766e7c..e75cfa88 100644 --- a/core/FamousEngine.js +++ b/core/FamousEngine.js @@ -32,6 +32,7 @@ var UIManager = require('../renderers/UIManager'); var Compositor = require('../renderers/Compositor'); var RequestAnimationFrameLoop = require('../render-loops/RequestAnimationFrameLoop'); var TransformSystem = require('./TransformSystem'); +var OpacitySystem = require('./OpacitySystem'); var SizeSystem = require('./SizeSystem'); var Commands = require('./Commands'); @@ -158,6 +159,7 @@ FamousEngine.prototype._update = function _update () { SizeSystem.update(); TransformSystem.update(); + OpacitySystem.update(); while (nextQueue.length) queue.unshift(nextQueue.pop()); diff --git a/core/Node.js b/core/Node.js index eed38ad0..ec995513 100644 --- a/core/Node.js +++ b/core/Node.js @@ -1,4 +1,4 @@ -/** +/* * The MIT License (MIT) * * Copyright (c) 2015 Famous Industries Inc. @@ -29,7 +29,9 @@ var SizeSystem = require('./SizeSystem'); var Dispatch = require('./Dispatch'); var TransformSystem = require('./TransformSystem'); +var OpacitySystem = require('./OpacitySystem'); var Size = require('./Size'); +var Opacity = require('./Opacity'); var Transform = require('./Transform'); /** @@ -76,7 +78,6 @@ function Node () { this._mounted = false; this._shown = true; this._updater = null; - this._opacity = 1; this._UIEvents = []; this._updateQueue = []; @@ -96,6 +97,7 @@ function Node () { this._transformID = null; this._sizeID = null; + this._opacityID = null; if (!this.constructor.NO_DEFAULT_COMPONENTS) this._init(); } @@ -117,6 +119,7 @@ Node.NO_DEFAULT_COMPONENTS = false; Node.prototype._init = function _init () { this._transformID = this.addComponent(new Transform()); this._sizeID = this.addComponent(new Size()); + this._opacityID = this.addComponent(new Opacity()); }; /** @@ -244,7 +247,7 @@ Node.prototype.getValue = function getValue () { showState: { mounted: this.isMounted(), shown: this.isShown(), - opacity: this.getOpacity() || null + opacity: 1 }, offsets: { mountPoint: [0, 0, 0], @@ -272,6 +275,9 @@ Node.prototype.getValue = function getValue () { if (value.location) { var transform = TransformSystem.get(this.getId()); var size = SizeSystem.get(this.getId()); + var opacity = OpacitySystem.get(this.getId()); + + value.spec.showState.opacity = opacity.getOpacity(); for (i = 0 ; i < 3 ; i++) { value.spec.offsets.mountPoint[i] = transform.offsets.mountPoint[i]; @@ -319,7 +325,8 @@ Node.prototype.getComputedValue = function getComputedValue () { location: this.getId(), computedValues: { transform: this.isMounted() ? TransformSystem.get(this.getLocation()).getLocalTransform() : null, - size: this.isMounted() ? SizeSystem.get(this.getLocation()).get() : null + size: this.isMounted() ? SizeSystem.get(this.getLocation()).get() : null, + opacity: this.isMounted() ? OpacitySystem.get(this.getLocation()).get() : null }, children: [] }; @@ -372,13 +379,18 @@ Node.prototype.getParent = function getParent () { * next frame (if no update during this frame has been scheduled already). * If the node is currently being updated (which means one of the requesters * invoked requestsUpdate while being updated itself), an update will be - * scheduled on the next frame. + * scheduled on the next frame by falling back to the `requestUpdateOnNextTick` + * function. + * + * Components request their `onUpdate` method to be called during the next + * frame using this method. * * @method requestUpdate * - * @param {Object} requester If the requester has an `onUpdate` method, it - * will be invoked during the next update phase of - * the node. + * @param {Number} requester Id of the component (as returned by + * {@link Node#addComponent}) to be updated. The + * component's `onUpdate` method will be invoked + * during the next update cycle. * * @return {Node} this */ @@ -393,16 +405,25 @@ Node.prototype.requestUpdate = function requestUpdate (requester) { }; /** - * Schedules an update on the next tick. Similarily to - * {@link Node#requestUpdate}, `requestUpdateOnNextTick` schedules the node's - * `onUpdate` function to be invoked on the frame after the next invocation on + * Schedules an update on the next tick. + * + * This method is similar to {@link Node#requestUpdate}, but schedules an + * update on the **next** frame. It schedules the node's `onUpdate` function + * to be invoked on the frame after the next invocation on * the node's onUpdate function. * + * The primary use-case for this method is to request an update while being in + * an update phase (e.g. because an animation is still active). Most of the + * time, {@link Node#requestUpdate} is sufficient, since it automatically + * falls back to {@link Node#requestUpdateOnNextTick} when being invoked during + * the update phase. + * * @method requestUpdateOnNextTick * - * @param {Object} requester If the requester has an `onUpdate` method, it - * will be invoked during the next update phase of - * the node. + * @param {Number} requester Id of the component (as returned by + * {@link Node#addComponent}) to be updated. The + * component's `onUpdate` method will be invoked + * during the next update cycle. * * @return {Node} this */ @@ -459,7 +480,11 @@ Node.prototype.isShown = function isShown () { * @return {Number} Relative opacity of the node. */ Node.prototype.getOpacity = function getOpacity () { - return this._opacity; + if (!this.constructor.NO_DEFAULT_COMPONENTS) + return this.getComponent(this._opacityID).getOpacity(); + else if (this.isMounted()) + return OpacitySystem.get(this.getLocation()).getOpacity(); + else throw new Error('This node does not have access to an opacity component'); }; /** @@ -1048,28 +1073,20 @@ Node.prototype.setScale = function setScale (x, y, z) { /** * Sets the value of the opacity of this node. All of the node's - * components will have onOpacityChange called on them/ + * components will have onOpacityChange called on them. * * @method * - * @param {Number} val Value of the opacity. 1 is the default. + * @param {Number} val=1 Value of the opacity. 1 is the default. * * @return {Node} this */ Node.prototype.setOpacity = function setOpacity (val) { - if (val !== this._opacity) { - this._opacity = val; - if (!this._requestingUpdate) this._requestUpdate(); - - var i = 0; - var list = this._components; - var len = list.length; - var item; - for (; i < len ; i++) { - item = list[i]; - if (item && item.onOpacityChange) item.onOpacityChange(val); - } - } + if (!this.constructor.NO_DEFAULT_COMPONENTS) + this.getComponent(this._opacityID).setOpacity(val); + else if (this.isMounted()) + OpacitySystem.get(this.getLocation()).setOpacity(val); + else throw new Error('This node does not have access to an opacity component'); return this; }; @@ -1253,10 +1270,12 @@ Node.prototype.mount = function mount (path) { if (!this.constructor.NO_DEFAULT_COMPONENTS){ TransformSystem.registerTransformAtPath(path, this.getComponent(this._transformID)); + OpacitySystem.registerOpacityAtPath(path, this.getComponent(this._opacityID)); SizeSystem.registerSizeAtPath(path, this.getComponent(this._sizeID)); } else { TransformSystem.registerTransformAtPath(path); + OpacitySystem.registerOpacityAtPath(path); SizeSystem.registerSizeAtPath(path); } Dispatch.mount(path, this); @@ -1282,6 +1301,7 @@ Node.prototype.dismount = function dismount () { TransformSystem.deregisterTransformAtPath(path); SizeSystem.deregisterSizeAtPath(path); + OpacitySystem.deregisterOpacityAtPath(path); Dispatch.dismount(path); if (!this._requestingUpdate) this._requestUpdate(); diff --git a/core/Opacity.js b/core/Opacity.js new file mode 100644 index 00000000..83fb1bcf --- /dev/null +++ b/core/Opacity.js @@ -0,0 +1,144 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +function Opacity (parent) { + this.local = 1; + this.global = 1; + this.opacity = 1; + this.parent = parent ? parent : null; + this.breakPoint = false; + this.calculatingWorldOpacity = false; +} + +Opacity.WORLD_CHANGED = 1; +Opacity.LOCAL_CHANGED = 2; + +Opacity.prototype.reset = function reset () { + this.parent = null; + this.breakPoint = false; +}; + +Opacity.prototype.setParent = function setParent (parent) { + this.parent = parent; +}; + +Opacity.prototype.getParent = function getParent () { + return this.parent; +}; + +Opacity.prototype.setBreakPoint = function setBreakPoint () { + this.breakPoint = true; + this.calculatingWorldOpacity = true; +}; + +/** + * Set this node to calculate the world opacity. + * + * @method + * + * @return {undefined} undefined + */ +Opacity.prototype.setCalculateWorldOpacity = function setCalculateWorldOpacity () { + this.calculatingWorldOpacity = true; +}; + +Opacity.prototype.isBreakPoint = function isBreakPoint () { + return this.breakPoint; +}; + +Opacity.prototype.getLocalOpacity = function getLocalOpacity () { + return this.local; +}; + +Opacity.prototype.getWorldOpacity = function getWorldOpacity () { + if (!this.isBreakPoint() && !this.calculatingWorldOpacity) + throw new Error('This opacity is not calculating world transforms'); + return this.global; +}; + +Opacity.prototype.calculate = function calculate (node) { + if (!this.parent || this.parent.isBreakPoint()) + return this.fromNode(node); + else return this.fromNodeWithParent(node); +}; + +Opacity.prototype.getOpacity = function getOpacity () { + return this.opacity; +}; + +Opacity.prototype.setOpacity = function setOpacity (opacity) { + this.opacity = opacity; +}; + +Opacity.prototype.calculateWorldOpacity = function calculateWorldOpacity () { + var nearestBreakPoint = this.parent; + + var previousGlobal = this.global; + + while (nearestBreakPoint && !nearestBreakPoint.isBreakPoint()) + nearestBreakPoint = nearestBreakPoint.parent; + + if (nearestBreakPoint) { + this.global = nearestBreakPoint.getWorldOpacity() * this.local; + } + else { + this.global = this.local; + } + + return previousGlobal !== this.global; +}; + +Opacity.prototype.fromNode = function fromNode () { + var changed = 0; + + if (this.opacity !== this.local) + changed |= Opacity.LOCAL_CHANGED; + + this.local = this.opacity; + + if (this.calculatingWorldOpacity && this.calculateWorldOpacity()) + changed |= Opacity.WORLD_CHANGED; + + return changed; +}; + +Opacity.prototype.fromNodeWithParent = function fromNodeWithParent () { + var changed = 0; + + var previousLocal = this.local; + + this.local = this.parent.getLocalOpacity() * this.opacity; + + if (this.calculatingWorldOpacity && this.calculateWorldOpacity()) + changed |= Opacity.WORLD_CHANGED; + + if (previousLocal !== this.local) + changed |= Opacity.LOCAL_CHANGED; + + return changed; +}; + +module.exports = Opacity; diff --git a/core/OpacitySystem.js b/core/OpacitySystem.js new file mode 100644 index 00000000..219fed04 --- /dev/null +++ b/core/OpacitySystem.js @@ -0,0 +1,217 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +var PathUtils = require('./Path'); +var Opacity = require('./Opacity'); +var Dispatch = require('./Dispatch'); +var PathStore = require('./PathStore'); + +/** + * The opacity class is responsible for calculating the opacity of a particular + * node from the data on the node and its parent + * + * @constructor {OpacitySystem} + */ +function OpacitySystem () { + this.pathStore = new PathStore(); +} + +/** + * registers a new Opacity for the given path. This opacity will be updated + * when the OpacitySystem updates. + * + * @method registerOpacityAtPath + * + * @param {String} path path for the opacity to be registered to. + * @param {Opacity} [opacity] opacity to register. + * @return {undefined} undefined + */ +OpacitySystem.prototype.registerOpacityAtPath = function registerOpacityAtPath (path, opacity) { + if (!PathUtils.depth(path)) return this.pathStore.insert(path, opacity ? opacity : new Opacity()); + + var parent = this.pathStore.get(PathUtils.parent(path)); + + if (!parent) throw new Error( + 'No parent opacity registered at expected path: ' + PathUtils.parent(path) + ); + + if (opacity) opacity.setParent(parent); + + this.pathStore.insert(path, opacity ? opacity : new Opacity(parent)); +}; + +/** + * Deregisters a opacity registered at the given path. + * + * @method deregisterOpacityAtPath + * @return {void} + * + * @param {String} path at which to register the opacity + */ +OpacitySystem.prototype.deregisterOpacityAtPath = function deregisterOpacityAtPath (path) { + this.pathStore.remove(path); +}; + +/** + * Method which will make the opacity currently stored at the given path a breakpoint. + * A opacity being a breakpoint means that both a local and world opacity will be calculated + * for that point. The local opacity being the concatinated opacity of all ancestor opacities up + * until the nearest breakpoint, and the world being the concatinated opacity of all ancestor opacities. + * This method throws if no opacity is at the provided path. + * + * @method + * + * @param {String} path The path at which to turn the opacity into a breakpoint + * + * @return {undefined} undefined + */ +OpacitySystem.prototype.makeBreakPointAt = function makeBreakPointAt (path) { + var opacity = this.pathStore.get(path); + if (!opacity) throw new Error('No opacity Registered at path: ' + path); + opacity.setBreakPoint(); +}; + +/** + * Method that will make the opacity at this location calculate a world opacity. + * + * @method + * + * @param {String} path The path at which to make the opacity calculate a world matrix + * + * @return {undefined} undefined + */ +OpacitySystem.prototype.makeCalculateWorldOpacityAt = function makeCalculateWorldOpacityAt (path) { + var opacity = this.pathStore.get(path); + if (!opacity) throw new Error('No opacity opacity at path: ' + path); + opacity.setCalculateWorldOpacity(); +}; + +/** + * Returns the instance of the opacity class associated with the given path, + * or undefined if no opacity is associated. + * + * @method + * + * @param {String} path The path to lookup + * + * @return {Opacity | undefined} the opacity at that path is available, else undefined. + */ +OpacitySystem.prototype.get = function get (path) { + return this.pathStore.get(path); +}; + +/** + * update is called when the opacity system requires an update. + * It traverses the opacity array and evaluates the necessary opacities + * in the scene graph with the information from the corresponding node + * in the scene graph + * + * @method update + * @return {undefined} undefined + */ +OpacitySystem.prototype.update = function update () { + var opacities = this.pathStore.getItems(); + var paths = this.pathStore.getPaths(); + var opacity; + var changed; + var node; + var components; + + for (var i = 0, len = opacities.length ; i < len ; i++) { + node = Dispatch.getNode(paths[i]); + if (!node) continue; + components = node.getComponents(); + opacity = opacities[i]; + + if ((changed = opacity.calculate())) { + opacityChanged(node, components, opacity); + if (changed & Opacity.LOCAL_CHANGED) localOpacityChanged(node, components, opacity.getLocalOpacity()); + if (changed & Opacity.WORLD_CHANGED) worldOpacityChanged(node, components, opacity.getWorldOpacity()); + } + } +}; + +/** + * Private method to call when either the Local or World Opacity changes. + * Triggers 'onOpacityChange' methods on the node and all of the node's components + * + * @method + * @private + * + * @param {Node} node the node on which to trigger a change event if necessary + * @param {Array} components the components on which to trigger a change event if necessary + * @param {Opacity} opacity the opacity class that changed + * + * @return {undefined} undefined + */ +function opacityChanged (node, components, opacity) { + if (node.onOpacityChange) node.onOpacityChange(opacity); + for (var i = 0, len = components.length ; i < len ; i++) + if (components[i] && components[i].onOpacityChange) + components[i].onOpacityChange(opacity); +} + +/** + * Private method to call when the local opacity changes. Triggers 'onLocalOpacityChange' methods + * on the node and all of the node's components + * + * @method + * @private + * + * @param {Node} node the node on which to trigger a change event if necessary + * @param {Array} components the components on which to trigger a change event if necessary + * @param {Array} opacity the local opacity + * + * @return {undefined} undefined + */ +function localOpacityChanged (node, components, opacity) { + if (node.onLocalOpacityChange) node.onLocalOpacityChange(opacity); + for (var i = 0, len = components.length ; i < len ; i++) + if (components[i] && components[i].onLocalOpacityChange) + components[i].onLocalOpacityChange(opacity); +} + +/** + * Private method to call when the world opacity changes. Triggers 'onWorldOpacityChange' methods + * on the node and all of the node's components + * + * @method + * @private + * + * @param {Node} node the node on which to trigger a change event if necessary + * @param {Array} components the components on which to trigger a change event if necessary + * @param {Array} opacity the world opacity + * + * @return {undefined} undefined + */ +function worldOpacityChanged (node, components, opacity) { + if (node.onWorldOpacityChange) node.onWorldOpacityChange(opacity); + for (var i = 0, len = components.length ; i < len ; i++) + if (components[i] && components[i].onWorldOpacityChange) + components[i].onWorldOpacityChange(opacity); +} + +module.exports = new OpacitySystem(); diff --git a/core/Scene.js b/core/Scene.js index 5e7061a7..2162e128 100644 --- a/core/Scene.js +++ b/core/Scene.js @@ -30,6 +30,7 @@ var Node = require('./Node'); var Dispatch = require('./Dispatch'); var Commands = require('./Commands'); var TransformSystem = require('./TransformSystem'); +var OpacitySystem = require('./OpacitySystem'); var SizeSystem = require('./SizeSystem'); /** @@ -147,6 +148,7 @@ Scene.prototype.mount = function mount (path) { this._mounted = true; this._parent = this; TransformSystem.registerTransformAtPath(path); + OpacitySystem.registerOpacityAtPath(path); SizeSystem.registerSizeAtPath(path); }; diff --git a/core/test/dispatch/Dispatch.api.js b/core/test/dispatch/Dispatch.api.js index c7de920f..4e54ba9d 100644 --- a/core/test/dispatch/Dispatch.api.js +++ b/core/test/dispatch/Dispatch.api.js @@ -1,9 +1,6 @@ module.exports = [ '_setUpdater', - 'addChildrenToQueue', - 'next', - 'breadthFirstNext', 'mount', 'dismount', 'getNode', diff --git a/core/test/dispatch/Dispatch.spec.js b/core/test/dispatch/Dispatch.spec.js index 62db096c..3f5327cc 100644 --- a/core/test/dispatch/Dispatch.spec.js +++ b/core/test/dispatch/Dispatch.spec.js @@ -1,10 +1,11 @@ 'use strict'; -var rewire = require('rewire'); var api = require('./Dispatch.api'); +var rewire = require('rewire'); var Dispatch = rewire('../../../core/Dispatch'); -var PathUtilsStub = require('../path/Path.stub'); var NodeStub = require('../node/Node.stub'); +var PathUtilsStub = require('../path/Path.stub'); +var Node = require('../../../core/Node'); var test = require('tape'); Dispatch.__set__('PathUtils', PathUtilsStub); @@ -17,7 +18,7 @@ test('Dispatch singleton', function (t) { t.equal(typeof Dispatch, 'object', 'Dispatch should be an object'); t.end(); }); - + t.test('Dispatch should conform to its public api', function (t) { api.forEach(function (method) { @@ -37,9 +38,9 @@ test('Dispatch singleton', function (t) { t.test('._setUpdater method', function (t) { var testUpdater = 'a'; - + t.doesNotThrow( - Dispatch._setUpdater.bind(Dispatch, testUpdater), + Dispatch._setUpdater.bind(Dispatch, testUpdater), '._setUpdater should be callable' ); @@ -60,10 +61,10 @@ test('Dispatch singleton', function (t) { Dispatch.mount('body/0', stub2); t.notOk(stub2._setUpdater.getCall(0).calledWith(testUpdater), 'Nodes mounted with the Dispatch ' + - 'should have their updaters set to ' + + 'should have their updaters set to ' + 'the dispatch\'s current updater and not a previous one'); - t.ok(stub2._setUpdater.getCall(0).calledWith(testUpdater2), 'Nodes mounted with the Dispatch ' + + t.ok(stub2._setUpdater.getCall(0).calledWith(testUpdater2), 'Nodes mounted with the Dispatch ' + 'should have their updaters set to ' + 'the dispatch\'s current updater'); @@ -74,18 +75,6 @@ test('Dispatch singleton', function (t) { t.end(); }); - t.test('.addChildrenToQueue method', function (t) { - t.end(); - }); - - t.test('.next method', function (t) { - t.end(); - }); - - t.test('.breadthFirstNext method', function (t) { - t.end(); - }); - t.test('.mount method', function (t) { t.end(); }); @@ -111,7 +100,145 @@ test('Dispatch singleton', function (t) { }); t.test('.dispatch method', function (t) { - t.end(); + + var nodes; + + t.test('setup', function (t) { + nodes = { + 'path': new Node(), + 'path/1': new Node(), + 'path/1/2': new Node(), + 'path/1/1': new Node(), + 'path/1/2/3': new Node(), + 'path/1/2/4': new Node() + }; + + nodes.path.addChild(nodes['path/1']); + nodes['path/1'].addChild(nodes['path/1/2']); + nodes['path/1'].addChild(nodes['path/1/1']); + nodes['path/1/2'].addChild(nodes['path/1/2/3']); + nodes['path/1/2'].addChild(nodes['path/1/2/4']); + + for (var path in nodes) { + nodes[path].__path__ = path; + Dispatch.mount(path, nodes[path]); + } + + t.end(); + }); + + t.test('basic', function (t) { + + var received; + var expectedEvent; + var expectedPayload; + + t.test('setup', function (t) { + received = []; + + expectedEvent = 'event'; + expectedPayload = { some: 'payload' }; + + function onReceive(actualEvent, actualPayload) { + t.equal(actualEvent, expectedEvent, 'Node should receive expected event'); + t.equal(actualPayload, expectedPayload, 'Node should receive expected payload'); + + received.push(this.__path__); + } + + for (var path in nodes) { + nodes[path].onReceive = onReceive; + } + + t.end(); + }); + + t.test('exec', function (t) { + Dispatch.dispatch('path/1', expectedEvent, expectedPayload); + + t.equal( + received.indexOf('path/1'), 0, + 'path/1 should receive event dispatched via Dispatch.dispatch("path/1")' + ); + + t.deepEqual( + received, ['path/1', 'path/1/2', 'path/1/1', 'path/1/2/3', 'path/1/2/4'], + 'Dispatch.dispatch should trigger event first on parent, then on children' + ); + + t.end(); + }); + + t.test('teardown', function (t) { + for (var path in nodes) { + nodes[path].onReceive = null; + } + + t.end(); + }); + }); + + t.test('conflicting events', function (t) { + var received; + + t.test('setup', function (t) { + received = []; + + function onReceive(event, payload) { + received.push([this.__path__, event, payload]); + } + + for (var path in nodes) { + nodes[path].onReceive = onReceive; + } + + nodes['path/1/1'].onReceive = function (event, payload) { + received.push([this.__path__, event, payload]); + Dispatch.dispatch('path/1/2', 'event2', {payload: 2}); + }; + + t.end(); + }); + + t.test('exec', function (t) { + Dispatch.dispatch('path', 'event1', {payload: 1}); + + t.equal( + received.length, 9, + 'Dispatching an event while processing a previous event should result into the correct number of events being received by the nodes' + ); + + received.forEach(function (node) { + delete node[2].node; + }); + + t.deepEqual( + received, + [ + [ 'path', 'event1', { payload: 1 } ], + [ 'path/1', 'event1', { payload: 1 } ], + [ 'path/1/2', 'event1', { payload: 1 } ], + [ 'path/1/1', 'event1', { payload: 1 } ], + [ 'path/1/2', 'event2', { payload: 2 } ], + [ 'path/1/2/3', 'event2', { payload: 2 } ], + [ 'path/1/2/4', 'event2', { payload: 2 } ], + [ 'path/1/2/3', 'event1', { payload: 1 } ], + [ 'path/1/2/4', 'event1', { payload: 1 } ] + ], + 'Dispatching the second event should traverse the scene graph in the correctly' + ); + + t.end(); + }); + + t.test('teardown', function (t) { + for (var path in nodes) { + nodes[path].onReceive = null; + } + + t.end(); + }); + }); }); t.test('.dispatchUIEvents method', function (t) { diff --git a/core/test/opacity/Opacity.api.js b/core/test/opacity/Opacity.api.js new file mode 100644 index 00000000..dea43344 --- /dev/null +++ b/core/test/opacity/Opacity.api.js @@ -0,0 +1,41 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +module.exports = [ + 'reset', + 'setParent', + 'getParent', + 'setBreakPoint', + 'isBreakPoint', + 'getLocalOpacity', + 'getWorldOpacity', + 'calculate', + 'getOpacity', + 'setOpacity', + 'calculateWorldOpacity', + 'fromNode', + 'fromNodeWithParent' +]; diff --git a/core/test/opacity/Opacity.spec.js b/core/test/opacity/Opacity.spec.js new file mode 100644 index 00000000..b884556e --- /dev/null +++ b/core/test/opacity/Opacity.spec.js @@ -0,0 +1,214 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +var test = require('tape'); +var api = require('./Opacity.api'); +var Opacity = require('../../Opacity'); +var OpacityStub = require('./Opacity.stub'); + +test('Opacity class', function (t) { + + t.test('Opacity constructor' , function (t) { + t.ok(Opacity, 'There should be a transform module'); + t.equal(Opacity.constructor, Function, 'Opacity should be a function'); + + t.doesNotThrow(function () { + return new Opacity(); + }, 'Opacity should be callable with new'); + + t.doesNotThrow(function () { + return new Opacity(new OpacityStub()); + }, 'Opacity should be callable with new and another transform as an argument'); + + t.equal((new Opacity()).constructor, Opacity, 'Opacity should be a constructor function'); + + var transform = new Opacity(); + + api.forEach(function (method) { + t.ok( + transform[method] && transform[method].constructor === Function, + 'Opacity should have a ' + method + ' method' + ); + }); + + t.equal(Opacity.WORLD_CHANGED, 1, 'Opacity should have a static property WORLD_CHANGED that equals 1'); + t.equal(Opacity.LOCAL_CHANGED, 2, 'Opacity should have a static property LOCAL_CHANGED that equals 2'); + + var parent = new OpacityStub(); + transform = new Opacity(parent); + + t.equal(transform.getParent(), parent, 'Opacity constructor should have its parent set to the first argument'); + + t.notOk(transform.isBreakPoint(), 'Transforms should not be a breakpoint by default'); + + t.end(); + }); + + t.test('calculate', function (t) { + var opacities = []; + + var opacity; + + for (var i = 0; i < 5; i++) { + opacity = new Opacity(opacities[i - 1]); + + opacities.push(opacity); + opacity.setOpacity(0.5); + } + + t.test('Root Opacity (no breakpoint): 0.5', function(t) { + t.equal( + opacities[0].calculate(), Opacity.LOCAL_CHANGED & ~Opacity.WORLD_CHANGED, + 'Calculating the root opacity should only change the local opacity' + ); + t.equal( + opacities[0].getLocalOpacity(), 0.5, + 'The root local opacity should be set to 0.5' + ); + + t.equal( + opacities[0].getOpacity(), 0.5, + 'The root opacity should still be set to 0.5 after calculation' + ); + t.throws( + function() { + opacities[0].getWorldOpacity(); + }, + /not calculating world transforms/, + 'Attempting to get the world opacity of the root opacity should throw an error, since no breakpoint has been set on it' + ); + + t.end(); + }); + + t.test('2nd Opacity (no breakpoint): 0.5', function(t) { + t.equal( + opacities[1].calculate(), Opacity.LOCAL_CHANGED & ~Opacity.WORLD_CHANGED, + 'Calculating the 2nd opacity (child of root) should only change the local opacity, since no breakpoint has been set on it' + ); + t.equal( + opacities[1].getLocalOpacity(), 0.25, + 'The 2nd opacity should have been multiplied with the root opacity' + ); + + t.equal( + opacities[1].getOpacity(), 0.5, + 'The 2nd opacity should still be set to 0.5 after calculation' + ); + t.throws( + function() { + opacities[1].getWorldOpacity(); + }, + /not calculating world transforms/, + 'Attempting to get the world opacity of the 2nd opacity should throw an error, since no breakpoint has been set on it' + ); + + t.end(); + }); + + t.test('3rd Opacity (no breakpoint): 0.5', function(t) { + t.equal( + opacities[2].calculate(), Opacity.LOCAL_CHANGED & ~Opacity.WORLD_CHANGED, + 'Calculating the 3rd opacity should only change the local opacity, since no breakpoint has been set on it' + ); + t.equal( + opacities[2].getLocalOpacity(), 0.125, + 'The 3rd opacity should have been multiplied with the root and 2nd opacity' + ); + + t.equal( + opacities[2].getOpacity(), 0.5, + 'The 3rd opacity should still be set to 0.5 after calculation' + ); + t.throws( + function() { + opacities[2].getWorldOpacity(); + }, + /not calculating world transforms/, + 'Attempting to get the world opacity of the 3rd opacity should throw an error, since no breakpoint has been set on it' + ); + + t.end(); + }); + + t.test('4th Opacity (breakpoint): 0.5', function(t) { + opacities[3].setBreakPoint(); + t.equal( + opacities[3].calculate(), Opacity.LOCAL_CHANGED | Opacity.WORLD_CHANGED, + 'Calculating the 4th opacity should have checked if the world opacity changed, since it has a breakpoint set on it' + ); + + t.equal( + opacities[3].isBreakPoint(), true, + 'The 4th opacity should still have breakpoint after calculation' + ); + t.equal( + opacities[3].getLocalOpacity(), 0.0625, + 'The 4th local opacity should have been multiplied with the root, 2nd and 3rd opacity' + ); + t.equal( + opacities[3].getOpacity(), 0.5, + 'The 4th opacity should still be set to 0.5 after calculation' + ); + t.doesNotThrow(function() { + opacities[3].getWorldOpacity(); + }, 'Attempting to calculate the world on the 4th opacity should not throw an error, since a breakpoint has been set on it'); + + t.equal( + opacities[3].getWorldOpacity(), 0.0625, + 'The 4th world opacity should be have been multiplied with all previous opacities' + ); + + t.end(); + }); + + t.test('5th Opacity (no breakpoint): 0.5', function(t) { + t.equal( + opacities[4].calculate(), Opacity.LOCAL_CHANGED & ~Opacity.WORLD_CHANGED, + 'Calculating the 5th opacity should only change the local opacity, since no breakpoint has been set on it' + ); + t.equal( + opacities[4].getLocalOpacity(), 0.5, + 'The 5th local opacity should be equivalent to the opacity set on it, it should not have been multiplied, since the 4th node has a breakpoint set on it' + ); + + t.equal( + opacities[4].getOpacity(), 0.5, + 'The 5th opacity should still be set to 0.5 after calculation' + ); + + t.throws( + function() { + opacities[4].getWorldOpacity(); + }, + /not calculating world transforms/, + 'Attempting to get the world opacity of the 4th opacity should throw an error, since no breakpoint has been set on it' + ); + + t.end(); + }); + }); +}); diff --git a/core/test/opacity/Opacity.stub.js b/core/test/opacity/Opacity.stub.js new file mode 100644 index 00000000..79e57470 --- /dev/null +++ b/core/test/opacity/Opacity.stub.js @@ -0,0 +1,36 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Famous Industries Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +'use strict'; + +var api = require('./Opacity.api'); +var sinon = require('sinon'); + +function Opacity (parent) { + api.forEach(function (method) { + this[method] = sinon.stub(); + }.bind(this)); +} + +module.exports = Opacity; diff --git a/core/test/opacitySystem/OpacitySystem.api.js b/core/test/opacitySystem/OpacitySystem.api.js new file mode 100644 index 00000000..f19d00d9 --- /dev/null +++ b/core/test/opacitySystem/OpacitySystem.api.js @@ -0,0 +1,15 @@ +module.exports = [ + 'reset', + 'setParent', + 'getParent', + 'setBreakPoint', + 'isBreakPoint', + 'getLocalOpacity', + 'getWorldOpacity', + 'calculate', + 'getOpacity', + 'setOpacity', + 'calculateWorldOpacity', + 'fromNode', + 'fromNodeWithParent' +]; diff --git a/core/test/opacitySystem/OpacitySystem.spec.js b/core/test/opacitySystem/OpacitySystem.spec.js new file mode 100644 index 00000000..e69de29b diff --git a/core/test/opacitySystem/OpacitySystem.stub.js b/core/test/opacitySystem/OpacitySystem.stub.js new file mode 100644 index 00000000..c1d2b23f --- /dev/null +++ b/core/test/opacitySystem/OpacitySystem.stub.js @@ -0,0 +1,12 @@ +'use strict'; + +var api = require('./OpacitySystem.api'); +var sinon = require('sinon'); + +var OpacitySystem = {}; + +api.forEach(function (method) { + OpacitySystem[method] = sinon.stub(); +}); + +module.exports = OpacitySystem; diff --git a/dom-renderables/DOMElement.js b/dom-renderables/DOMElement.js index 05f9d12b..09ba6624 100644 --- a/dom-renderables/DOMElement.js +++ b/dom-renderables/DOMElement.js @@ -26,6 +26,7 @@ var CallbackStore = require('../utilities/CallbackStore'); var TransformSystem = require('../core/TransformSystem'); +var OpacitySystem = require('../core/OpacitySystem'); var Commands = require('../core/Commands'); var Size = require('../core/Size'); @@ -79,7 +80,6 @@ function DOMElement(node, options) { this._callbacks = new CallbackStore(); this.setProperty('display', node.isShown() ? 'block' : 'none'); - this.onOpacityChange(node.getOpacity()); if (!options) return; @@ -172,6 +172,7 @@ DOMElement.prototype.onMount = function onMount(node, id) { this._UIEvents = node.getUIEvents().slice(0); TransformSystem.makeBreakPointAt(node.getLocation()); this.onSizeModeChange.apply(this, node.getSizeMode()); + OpacitySystem.makeBreakPointAt(node.getLocation()); this.draw(); this.setAttribute('data-fa-path', node.getLocation()); }; @@ -291,6 +292,8 @@ DOMElement.prototype.onSizeChange = function onSizeChange(x, y) { * @return {DOMElement} this */ DOMElement.prototype.onOpacityChange = function onOpacityChange(opacity) { + opacity = opacity.getLocalOpacity(); + return this.setProperty('opacity', opacity); }; @@ -468,6 +471,7 @@ DOMElement.prototype.init = function init () { this._changeQueue.push(Commands.INIT_DOM, this._tagName); this._initialized = true; this.onTransformChange(TransformSystem.get(this._node.getLocation())); + this.onOpacityChange(OpacitySystem.get(this._node.getLocation())); var size = this._node.getSize(); this.onSizeChange(size[0], size[1]); if (!this._requestingUpdate) this._requestUpdate(); diff --git a/package.json b/package.json index c8537c0b..b87089dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "famous", - "version": "0.7.1", + "name": "infamous-engine", + "version": "0.7.1-webpack.0", "description": "", "main": "index.js", "scripts": { @@ -35,17 +35,17 @@ "author": "Famous", "license": "MIT", "devDependencies": { - "browserify": "^10.2.1", + "browserify": "^11.0.0", "colors": "^1.1.0", - "eslint": "^0.21.2", + "eslint": "^0.24.1", "glob": "^5.0.10", "istanbul": "^0.3.15", "rewire": "^2.3.3", "sinon": "^1.14.1", "smokestack": "^3.2.2", "tap-closer": "^1.0.0", - "tape": "^4.0.0", "tap-spec": "^4.0.0", + "tape": "^4.0.0", "uglify-js": "^2.4.17" }, "dependencies": { diff --git a/renderers/Compositor.js b/renderers/Compositor.js index 386c00de..789a007d 100644 --- a/renderers/Compositor.js +++ b/renderers/Compositor.js @@ -270,7 +270,8 @@ Compositor.prototype.giveSizeFor = function giveSizeFor(iterator, commands) { if (context) { var size = context.getRootSize(); this.sendResize(selector, size); - } else { + } + else { this.getOrSetContext(selector); } }; diff --git a/webgl-geometries/Geometry.js b/webgl-geometries/Geometry.js index 1d492bf8..2079279a 100644 --- a/webgl-geometries/Geometry.js +++ b/webgl-geometries/Geometry.js @@ -52,11 +52,11 @@ function Geometry(options) { if (this.options.buffers) { var len = this.options.buffers.length; - for (var i = 0; i < len;) { + for (var i = 0; i < len; i++) { this.spec.bufferNames.push(this.options.buffers[i].name); this.spec.bufferValues.push(this.options.buffers[i].data); this.spec.bufferSpacings.push(this.options.buffers[i].size || this.DEFAULT_BUFFER_SIZE); - this.spec.invalidations.push(i++); + this.spec.invalidations.push(i); } } } diff --git a/webgl-geometries/primitives/Box.js b/webgl-geometries/primitives/Box.js index 860d4fba..8a87c8e3 100644 --- a/webgl-geometries/primitives/Box.js +++ b/webgl-geometries/primitives/Box.js @@ -52,6 +52,8 @@ var boxData = [ * @return {Object} constructed geometry */ function BoxGeometry(options) { + if (!(this instanceof BoxGeometry)) return new BoxGeometry(options); + options = options || {}; var vertices = []; @@ -87,7 +89,10 @@ function BoxGeometry(options) { { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +BoxGeometry.prototype = Object.create(Geometry.prototype); +BoxGeometry.prototype.constructor = BoxGeometry; + module.exports = BoxGeometry; diff --git a/webgl-geometries/primitives/Circle.js b/webgl-geometries/primitives/Circle.js index 9ef43694..c93c03f8 100644 --- a/webgl-geometries/primitives/Circle.js +++ b/webgl-geometries/primitives/Circle.js @@ -40,6 +40,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function Circle (options) { + if (!(this instanceof Circle)) return new Circle(options); + options = options || {}; var detail = options.detail || 30; var buffers = getCircleBuffers(detail, true); @@ -58,9 +60,12 @@ function Circle (options) { { name: 'indices', data: buffers.indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Circle.prototype = Object.create(Geometry.prototype); +Circle.prototype.constructor = Circle; + function getCircleTexCoords (vertices) { var textureCoords = []; var nFaces = vertices.length / 3; diff --git a/webgl-geometries/primitives/Cylinder.js b/webgl-geometries/primitives/Cylinder.js index fae1cf59..7ed4a269 100644 --- a/webgl-geometries/primitives/Cylinder.js +++ b/webgl-geometries/primitives/Cylinder.js @@ -41,6 +41,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function Cylinder (options) { + if (!(this instanceof Cylinder)) return new Cylinder(options); + options = options || {}; var radius = options.radius || 1; var detail = options.detail || 15; @@ -63,9 +65,12 @@ function Cylinder (options) { { name: 'indices', data: buffers.indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Cylinder.prototype = Object.create(Geometry.prototype); +Cylinder.prototype.constructor = Cylinder; + /** * Function used in iterative construction of parametric primitive. * diff --git a/webgl-geometries/primitives/GeodesicSphere.js b/webgl-geometries/primitives/GeodesicSphere.js index c2938624..af8a95e3 100644 --- a/webgl-geometries/primitives/GeodesicSphere.js +++ b/webgl-geometries/primitives/GeodesicSphere.js @@ -1,18 +1,18 @@ /** * The MIT License (MIT) - * + * * Copyright (c) 2015 Famous Industries Inc. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -36,10 +36,12 @@ var GeometryHelper = require('../GeometryHelper'); * * @param {Object} options Parameters that alter the * vertex buffers of the generated geometry. - * + * * @return {Object} constructed geometry */ function GeodesicSphere (options) { + if (!(this instanceof GeodesicSphere)) return new GeodesicSphere(options); + var t = (1 + Math.sqrt(5)) * 0.5; var vertices = [ @@ -72,7 +74,10 @@ function GeodesicSphere (options) { { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +GeodesicSphere.prototype = Object.create(Geometry.prototype); +GeodesicSphere.prototype.constructor = GeodesicSphere; + module.exports = GeodesicSphere; diff --git a/webgl-geometries/primitives/Icosahedron.js b/webgl-geometries/primitives/Icosahedron.js index d83c4e52..9dbcef21 100644 --- a/webgl-geometries/primitives/Icosahedron.js +++ b/webgl-geometries/primitives/Icosahedron.js @@ -1,18 +1,18 @@ /** * The MIT License (MIT) - * + * * Copyright (c) 2015 Famous Industries Inc. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -36,12 +36,13 @@ var GeometryHelper = require('../GeometryHelper'); * * @param {Object} options Parameters that alter the * vertex buffers of the generated geometry. - * + * * @return {Object} constructed geometry */ -function Icosahedron( options ) -{ - options = options || {}; +function Icosahedron(options) { + if (!(this instanceof Icosahedron)) return new Icosahedron(options); + + options = options || {}; var t = ( 1 + Math.sqrt( 5 ) ) / 2; var vertices = [ @@ -70,7 +71,10 @@ function Icosahedron( options ) { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Icosahedron.prototype = Object.create(Geometry.prototype); +Icosahedron.prototype.constructor = Icosahedron; + module.exports = Icosahedron; diff --git a/webgl-geometries/primitives/ParametricCone.js b/webgl-geometries/primitives/ParametricCone.js index 4448c2f8..fa7e779e 100644 --- a/webgl-geometries/primitives/ParametricCone.js +++ b/webgl-geometries/primitives/ParametricCone.js @@ -40,7 +40,9 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function ParametricCone (options) { - options = options || {}; + if (!(this instanceof ParametricCone)) return new ParametricCone(options); + + options = options || {}; var detail = options.detail || 15; var radius = options.radius || 1 / Math.PI; @@ -61,9 +63,12 @@ function ParametricCone (options) { { name: 'indices', data: buffers.indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +ParametricCone.prototype = Object.create(Geometry.prototype); +ParametricCone.prototype.constructor = ParametricCone; + /** * function used in iterative construction of parametric primitive. * diff --git a/webgl-geometries/primitives/Plane.js b/webgl-geometries/primitives/Plane.js index 7c7cbeca..6d610ff6 100644 --- a/webgl-geometries/primitives/Plane.js +++ b/webgl-geometries/primitives/Plane.js @@ -40,6 +40,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function Plane(options) { + if (!(this instanceof Plane)) return new Plane(options); + options = options || {}; var detailX = options.detailX || options.detail || 1; var detailY = options.detailY || options.detail || 1; @@ -83,7 +85,10 @@ function Plane(options) { { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Plane.prototype = Object.create(Geometry.prototype); +Plane.prototype.constructor = Plane; + module.exports = Plane; diff --git a/webgl-geometries/primitives/Sphere.js b/webgl-geometries/primitives/Sphere.js index d9cca9c9..da1489b8 100644 --- a/webgl-geometries/primitives/Sphere.js +++ b/webgl-geometries/primitives/Sphere.js @@ -40,6 +40,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function ParametricSphere (options) { + if (!(this instanceof ParametricSphere)) return new ParametricSphere(options); + options = options || {}; var detail = options.detail || 10; var detailX = options.detailX || detail; @@ -59,9 +61,12 @@ function ParametricSphere (options) { { name: 'indices', data: buffers.indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +ParametricSphere.prototype = Object.create(Geometry.prototype); +ParametricSphere.prototype.constructor = ParametricSphere; + /** * Function used in iterative construction of parametric primitive. * diff --git a/webgl-geometries/primitives/Tetrahedron.js b/webgl-geometries/primitives/Tetrahedron.js index 152c06bd..452b389c 100644 --- a/webgl-geometries/primitives/Tetrahedron.js +++ b/webgl-geometries/primitives/Tetrahedron.js @@ -40,6 +40,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function Tetrahedron(options) { + if (!(this instanceof Tetrahedron)) return new Tetrahedron(options); + var textureCoords = []; var normals = []; var detail; @@ -95,7 +97,10 @@ function Tetrahedron(options) { { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Tetrahedron.prototype = Object.create(Geometry.prototype); +Tetrahedron.prototype.constructor = Tetrahedron; + module.exports = Tetrahedron; diff --git a/webgl-geometries/primitives/Torus.js b/webgl-geometries/primitives/Torus.js index 0f76572f..81c7ac25 100644 --- a/webgl-geometries/primitives/Torus.js +++ b/webgl-geometries/primitives/Torus.js @@ -41,8 +41,10 @@ var GeometryHelper = require('../GeometryHelper'); */ function Torus(options) { - options = options || {}; - var detail = options.detail || 30; + if (!(this instanceof Torus)) return new Torus(options); + + options = options || {}; + var detail = options.detail || 30; var holeRadius = options.holeRadius || 0.80; var tubeRadius = options.tubeRadius || 0.20; @@ -59,9 +61,12 @@ function Torus(options) { { name: 'indices', data: buffers.indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Torus.prototype = Object.create(Geometry.prototype); +Torus.prototype.constructor = Torus; + /** * function used in iterative construction of parametric primitive. * diff --git a/webgl-geometries/primitives/Triangle.js b/webgl-geometries/primitives/Triangle.js index 2e70fdc9..0bb0b491 100644 --- a/webgl-geometries/primitives/Triangle.js +++ b/webgl-geometries/primitives/Triangle.js @@ -40,6 +40,8 @@ var GeometryHelper = require('../GeometryHelper'); * @return {Object} constructed geometry */ function Triangle (options) { + if (!(this instanceof Triangle)) return new Triangle(options); + options = options || {}; var detail = options.detail || 1; var normals = []; @@ -57,11 +59,10 @@ function Triangle (options) { 1, -1, 0 ]; - while(--detail) GeometryHelper.subdivide(indices, vertices, textureCoords); + while (--detail) GeometryHelper.subdivide(indices, vertices, textureCoords); - if (options.backface !== false) { + if (options.backface !== false) GeometryHelper.addBackfaceTriangles(vertices, indices); - } normals = GeometryHelper.computeNormals(vertices, indices); @@ -72,7 +73,10 @@ function Triangle (options) { { name: 'indices', data: indices, size: 1 } ]; - return new Geometry(options); + Geometry.call(this, options); } +Triangle.prototype = Object.create(Geometry.prototype); +Triangle.prototype.constructor = Triangle; + module.exports = Triangle; diff --git a/webgl-geometries/test/Primitives.spec.js b/webgl-geometries/test/Primitives.spec.js index d2f2d0fd..98f0609f 100644 --- a/webgl-geometries/test/Primitives.spec.js +++ b/webgl-geometries/test/Primitives.spec.js @@ -58,17 +58,18 @@ test('Primitives', function(t) { } t.end(); }); - + t.test('Primitives.optionsParameter', function(t) { for (var name in primitives) { var primitive = new primitives[name]({type:'POINTS'}); t.ok(primitive instanceof Geometry, 'should be an instance of a static geometry'); + t.ok(primitive instanceof primitives[name], 'should be an instance of its constructor function'); t.notEquals(primitive.spec.bufferNames.indexOf('a_texCoord'), -1, 'should contain a texCoord buffer'); t.notEquals(primitive.spec.bufferNames.indexOf('a_normals'), -1, 'should contain a normal buffer'); t.notEquals(primitive.spec.bufferNames.indexOf('a_pos'), -1, 'should contain a pos buffer'); - + t.equals(primitive.spec.type, 'POINTS', 'draw type should be passed through'); if (name !== 'Circle') { @@ -77,8 +78,7 @@ test('Primitives', function(t) { } t.end(); }); - + t.end(); }); - diff --git a/webgl-renderables/Mesh.js b/webgl-renderables/Mesh.js index accb1d1d..57f0eb63 100644 --- a/webgl-renderables/Mesh.js +++ b/webgl-renderables/Mesh.js @@ -23,10 +23,15 @@ */ 'use strict'; + +// TODO This will be removed once `Mesh#setGeometry` no longer accepts +// geometries defined by name. var Geometry = require('../webgl-geometries'); + var Commands = require('../core/Commands'); var TransformSystem = require('../core/TransformSystem'); -var defaultGeometry = new Geometry.Plane(); +var Plane = require('../webgl-geometries/primitives/Plane'); +var OpacitySystem = require('../core/OpacitySystem'); /** * The Mesh class is responsible for providing the API for how @@ -53,7 +58,7 @@ function Mesh (node, options) { this._requestingUpdate = false; this._inDraw = false; this.value = { - geometry: defaultGeometry, + geometry: Plane(), drawOptions: null, color: null, expressions: {}, @@ -108,7 +113,15 @@ Mesh.prototype.getDrawOptions = function getDrawOptions () { Mesh.prototype.setGeometry = function setGeometry (geometry, options) { if (typeof geometry === 'string') { if (!Geometry[geometry]) throw 'Invalid geometry: "' + geometry + '".'; - else geometry = new Geometry[geometry](options); + else { + console.warn( + 'Mesh#setGeometry using the geometry registry is deprecated!\n' + + 'Instantiate the geometry directly via `new ' + geometry + + '(options)` instead!' + ); + + geometry = new Geometry[geometry](options); + } } if (this.value.geometry !== geometry || this._inDraw) { @@ -496,6 +509,7 @@ Mesh.prototype.onMount = function onMount (node, id) { this._id = id; TransformSystem.makeCalculateWorldMatrixAt(node.getLocation()); + OpacitySystem.makeCalculateWorldOpacityAt(node.getLocation()); this.draw(); }; @@ -601,7 +615,7 @@ Mesh.prototype.onOpacityChange = function onOpacityChange (opacity) { if (this._initialized) { this._changeQueue.push(Commands.GL_UNIFORMS); this._changeQueue.push('u_opacity'); - this._changeQueue.push(opacity); + this._changeQueue.push(opacity.getWorldOpacity()); } this._requestUpdate(); @@ -644,9 +658,9 @@ Mesh.prototype._requestUpdate = function _requestUpdate () { Mesh.prototype.init = function init () { this._initialized = true; this.onTransformChange(TransformSystem.get(this._node.getLocation())); + this.onOpacityChange(OpacitySystem.get(this._node.getLocation())); var size = this._node.getSize(); this.onSizeChange(size[0], size[1], size[2]); - this.onOpacityChange(this._node.getOpacity()); this._requestUpdate(); }; diff --git a/webgl-renderers/Buffer.js b/webgl-renderers/Buffer.js index 981cdc75..a6bf69da 100644 --- a/webgl-renderers/Buffer.js +++ b/webgl-renderers/Buffer.js @@ -56,15 +56,9 @@ function Buffer(target, type, gl) { */ Buffer.prototype.subData = function subData() { var gl = this.gl; - var data = []; - - // to prevent against maximum call-stack issue. - for (var i = 0, chunk = 10000; i < this.data.length; i += chunk) - data = Array.prototype.concat.apply(data, this.data.slice(i, i + chunk)); - this.buffer = this.buffer || gl.createBuffer(); gl.bindBuffer(this.target, this.buffer); - gl.bufferData(this.target, new this.type(data), gl.STATIC_DRAW); + gl.bufferData(this.target, new this.type(this.data), gl.STATIC_DRAW); }; module.exports = Buffer; diff --git a/webgl-renderers/Program.js b/webgl-renderers/Program.js index 80e28f79..6de8b37b 100644 --- a/webgl-renderers/Program.js +++ b/webgl-renderers/Program.js @@ -31,8 +31,6 @@ var vertexWrapper = require('../webgl-shaders').vertex; var fragmentWrapper = require('../webgl-shaders').fragment; var Debug = require('./Debug'); -var VERTEX_SHADER = 35633; -var FRAGMENT_SHADER = 35632; var identityMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; var header = 'precision mediump float;\n'; @@ -183,28 +181,27 @@ Program.prototype.registerMaterial = function registerMaterial(name, material) { this.registeredMaterials[material._id] |= mask; - if (type === 'float') { - this.definitionFloat.push(material.defines); - this.definitionFloat.push('float fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); - this.applicationFloat.push('if (int(abs(ID)) == ' + material._id + ') return fa_' + material._id + '();'); - } - - if (type === 'vec3') { - this.definitionVec3.push(material.defines); - this.definitionVec3.push('vec3 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); - this.applicationVec3.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); - } - - if (type === 'vec4') { - this.definitionVec4.push(material.defines); - this.definitionVec4.push('vec4 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); - this.applicationVec4.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); - } - - if (type === 'vert') { - this.definitionVert.push(material.defines); - this.definitionVert.push('vec3 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); - this.applicationVert.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); + switch (type) { + case 'float': + this.definitionFloat.push(material.defines); + this.definitionFloat.push('float fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); + this.applicationFloat.push('if (int(abs(ID)) == ' + material._id + ') return fa_' + material._id + '();'); + break; + case 'vec3': + this.definitionVec3.push(material.defines); + this.definitionVec3.push('vec3 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); + this.applicationVec3.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); + break; + case 'vec4': + this.definitionVec4.push(material.defines); + this.definitionVec4.push('vec4 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); + this.applicationVec4.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); + break; + case 'vert': + this.definitionVert.push(material.defines); + this.definitionVert.push('vec3 fa_' + material._id + '() {\n ' + compiled.glsl + ' \n}'); + this.applicationVert.push('if (int(abs(ID.x)) == ' + material._id + ') return fa_' + material._id + '();'); + break; } return this.resetProgram(); @@ -250,9 +247,8 @@ Program.prototype.resetProgram = function resetProgram() { fragmentHeader.push('uniform sampler2D u_textures[7];\n'); - if (this.applicationVert.length) { + if (this.applicationVert.length) vertexHeader.push('uniform sampler2D u_textures[7];\n'); - } for(i = 0; i < this.uniformNames.length; i++) { name = this.uniformNames[i]; @@ -290,12 +286,12 @@ Program.prototype.resetProgram = function resetProgram() { this.gl.attachShader( program, - this.compileShader(this.gl.createShader(VERTEX_SHADER), vertexSource) + this.compileShader(this.gl.createShader(this.gl.VERTEX_SHADER), vertexSource) ); this.gl.attachShader( program, - this.compileShader(this.gl.createShader(FRAGMENT_SHADER), fragmentSource) + this.compileShader(this.gl.createShader(this.gl.FRAGMENT_SHADER), fragmentSource) ); this.gl.linkProgram(program); @@ -386,7 +382,7 @@ Program.prototype.setUniforms = function (uniformNames, uniformValue) { name = uniformNames[i]; value = uniformValue[i]; - // Retreive the cached location of the uniform, + // Retrieve the cached location of the uniform, // requesting a new location from the WebGL context // if it does not yet exist. diff --git a/webgl-renderers/WebGLRenderer.js b/webgl-renderers/WebGLRenderer.js index 8c175579..1f419a59 100644 --- a/webgl-renderers/WebGLRenderer.js +++ b/webgl-renderers/WebGLRenderer.js @@ -64,7 +64,7 @@ function WebGLRenderer(canvas, compositor) { this.canvas = canvas; this.compositor = compositor; - var gl = this.gl = this.getWebGLContext(this.canvas); + var gl = this.getWebGLContext(this.canvas); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.polygonOffset(0.1, 0.1); @@ -76,7 +76,9 @@ function WebGLRenderer(canvas, compositor) { gl.enable(gl.CULL_FACE); gl.cullFace(gl.BACK); - this.meshRegistry = new Registry(); + this.meshRegistry = {}; + this.meshRegistryKeys = []; + this.cutoutRegistry = new Registry(); this.lightRegistry = new Registry(); @@ -147,24 +149,17 @@ function WebGLRenderer(canvas, compositor) { * @return {Object} WebGLContext WebGL context */ WebGLRenderer.prototype.getWebGLContext = function getWebGLContext(canvas) { + if (this.gl) return this.gl; + var names = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl']; - var context; - for (var i = 0, len = names.length; i < len; i++) { - try { - context = canvas.getContext(names[i]); - } - catch (error) { - console.error('Error creating WebGL context: ' + error.toString()); - } - if (context) return context; - } + for (var i = 0, len = names.length; i < len && !this.gl; i++) + this.gl = canvas.getContext(names[i]); - if (!context) { - console.error('Could not retrieve WebGL context. Please refer to https://www.khronos.org/webgl/ for requirements'); - return false; - } + if (!this.gl) + throw new Error('Could not retrieve WebGL context. Please refer to https://www.khronos.org/webgl/ for requirements'); + return this.gl; }; /** @@ -217,7 +212,8 @@ WebGLRenderer.prototype.createMesh = function createMesh(path) { visible: true }; - this.meshRegistry.register(path, mesh); + this.meshRegistry[path] = mesh; + this.meshRegistryKeys.push(path); return mesh; }; @@ -284,7 +280,7 @@ WebGLRenderer.prototype.getOrSetCutout = function getOrSetCutout(path) { * @return {undefined} undefined */ WebGLRenderer.prototype.setMeshVisibility = function setMeshVisibility(path, visibility) { - var mesh = this.meshRegistry.get(path) || this.createMesh(path); + var mesh = this.meshRegistry[path] || this.createMesh(path); mesh.visible = visibility; }; @@ -298,7 +294,10 @@ WebGLRenderer.prototype.setMeshVisibility = function setMeshVisibility(path, vis * @return {undefined} undefined */ WebGLRenderer.prototype.removeMesh = function removeMesh(path) { - this.meshRegistry.unregister(path); + delete this.meshRegistry[path]; + var index = this.meshRegistryKeys.indexOf(path); + + if (index !== -1) this.meshRegistryKeys.splice(index, 1); }; /** @@ -336,7 +335,7 @@ WebGLRenderer.prototype.setCutoutUniform = function setCutoutUniform(path, unifo * @return {WebGLRenderer} this */ WebGLRenderer.prototype.setMeshOptions = function(path, options) { - var mesh = this.meshRegistry.get(path) || this.createMesh(path); + var mesh = this.meshRegistry[path] || this.createMesh(path); mesh.options = options; return this; @@ -414,7 +413,7 @@ WebGLRenderer.prototype.setLightColor = function setLightColor(path, r, g, b) { * @return {WebGLRenderer} this */ WebGLRenderer.prototype.handleMaterialInput = function handleMaterialInput(path, name, material) { - var mesh = this.meshRegistry.get(path) || this.createMesh(path); + var mesh = this.meshRegistry[path] || this.createMesh(path); material = compileMaterial(material, mesh.textures.length); // Set uniforms to enable texture! @@ -450,7 +449,7 @@ WebGLRenderer.prototype.handleMaterialInput = function handleMaterialInput(path, * @return {undefined} undefined */ WebGLRenderer.prototype.setGeometry = function setGeometry(path, geometry, drawType, dynamic) { - var mesh = this.meshRegistry.get(path) || this.createMesh(path); + var mesh = this.meshRegistry[path] || this.createMesh(path); mesh.geometry = geometry; mesh.drawType = drawType; @@ -471,7 +470,7 @@ WebGLRenderer.prototype.setGeometry = function setGeometry(path, geometry, drawT * @return {undefined} undefined */ WebGLRenderer.prototype.setMeshUniform = function setMeshUniform(path, uniformName, uniformValue) { - var mesh = this.meshRegistry.get(path) || this.createMesh(path); + var mesh = this.meshRegistry[path] || this.createMesh(path); var index = mesh.uniformKeys.indexOf(uniformName); @@ -517,7 +516,7 @@ WebGLRenderer.prototype.draw = function draw(renderState) { this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); this.textureManager.update(time); - this.meshRegistryKeys = sorter(this.meshRegistry.getKeys(), this.meshRegistry.getKeyToValue()); + this.meshRegistryKeys = sorter(this.meshRegistryKeys, this.meshRegistry); this.setGlobalUniforms(renderState); this.drawCutouts(); @@ -538,10 +537,10 @@ WebGLRenderer.prototype.drawMeshes = function drawMeshes() { var buffers; var mesh; - var meshes = this.meshRegistry.getValues(); + var paths = this.meshRegistryKeys; - for(var i = 0; i < meshes.length; i++) { - mesh = meshes[i]; + for(var i = 0; i < paths.length; i++) { + mesh = this.meshRegistry[paths[i]]; if (!mesh) continue; diff --git a/webgl-renderers/radixSort.js b/webgl-renderers/radixSort.js index a005f2e7..7d7d123a 100644 --- a/webgl-renderers/radixSort.js +++ b/webgl-renderers/radixSort.js @@ -107,7 +107,7 @@ function radixSort(list, registry) { div = floatToInt(comp(list, registry, i)); out[++buckets[div & radixMask]] = mutator(list, registry, i, div ^= div >> 31 | 0x80000000); } - + swap = out; out = list; list = swap; diff --git a/webgl-renderers/test/radixSort.spec.js b/webgl-renderers/test/radixSort.spec.js index 524d6e61..ddac1dc2 100644 --- a/webgl-renderers/test/radixSort.spec.js +++ b/webgl-renderers/test/radixSort.spec.js @@ -1,18 +1,18 @@ /** * The MIT License (MIT) - * + * * Copyright (c) 2015 Famous Industries Inc. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -27,28 +27,27 @@ var test = require('tape'); var radixSort = require('../radixSort'); test('radixSort', function(t) { - var registry = {}; - var meshList = []; - while (meshList.length < 1e3) { - var path = Math.random(); - registry[path] = mockMesh(); - meshList[meshList.length] = path; - } - radixSort(meshList, registry); + t.test('dense array', function(t) { + var registry = {}; + var meshList = []; + while (meshList.length < 1e3) { + var path = Math.random(); + registry[path] = mockMesh(); + meshList[meshList.length] = path; + } - t.test('sort', function(t) { - t.equals(checkSorted(meshList), true, 'should be sorted by depth'); + radixSort(meshList, registry); + t.ok(checkSorted(meshList), 'should be sorted by depth'); t.end(); }); - t.end(); }); function checkSorted (list, registry){ var a, b; - for (var i= 0; i < test.length - 1; i++) { + for (var i = 0; i < test.length - 1; i++) { a = list[i].uniformValues[1][14]; b = list[i+1].uniformValues[1][14]; if (a < b) return false; diff --git a/webgl-shaders/index.js b/webgl-shaders/index.js index dfb87204..ea848433 100644 --- a/webgl-shaders/index.js +++ b/webgl-shaders/index.js @@ -24,11 +24,12 @@ 'use strict'; -var glslify = require('glslify'); +var VertexShader = require('./VertexShader.glsl') +var FragmentShader = require('./FragmentShader.glsl') var shaders = { - vertex: glslify('./VertexShader.glsl'), - fragment: glslify('./FragmentShader.glsl') + vertex: VertexShader, + fragment: FragmentShader }; module.exports = shaders;