]> _ Git - fluidbook-html5.git/commitdiff
wip #809 @4
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 22 Aug 2018 18:46:10 +0000 (20:46 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 22 Aug 2018 18:46:10 +0000 (20:46 +0200)
js/libs/fluidbook/fluidbook.3dflip.js
js/libs/fluidbook/fluidbook.js
js/libs/fluidbook/fluidbook.loader.js
js/libs/fluidbook/fluidbook.pagetransitions.js
js/libs/fluidbook/fluidbook.share.js
js/libs/fluidbook/fluidbook.utils.js
js/libs/fluidbook/fluidbook.zoom.js
js/libs/fluidbook/forms/fluidbook.form.bulle.js
js/libs/threejs/CanvasRenderer.js [new file with mode: 0644]
js/libs/threejs/Projector.js [new file with mode: 0644]

index 4d648a904a275c0ddbd90771f78e4060748f6851..fc24ac7e6458707cb88eeca4fd33b6f425c9ee46 100644 (file)
@@ -20,19 +20,27 @@ function Fluidbook3DFlip(fluidbook) {
     this.textures = [];
 
     // Performance settings
+    this.renderEngine = 'webgl';
     this.veryLowPerf = this.fluidbook.support.IE > 0;
-    this.lowPerf = this.veryLowPerf || this.fluidbook.support.android || this.fluidbook.support.iOS || this.fluidbook.support.edge;
-    this.enableLight = !this.lowPerf;
-    this.enableShadow = !this.lowPerf && this.enableLight;
+    if (this.veryLowPerf) {
+        this.renderEngine = 'canvas';
+    }
+    this.lowPerf = this.renderEngine === 'canvas' || this.veryLowPerf || this.fluidbook.support.edge || this.fluidbook.support.android || this.fluidbook.support.iOS;
+    if (this.fluidbook.support.android || this.fluidbook.support.iOS) {
+        this.renderEngine = 'webgl';
+    }
+    this.enableLight = !this.lowPerf && this.renderEngine === 'webgl';
+    this.enableShadow = !this.lowPerf && this.enableLight && this.renderEngine === 'webgl';
     this.antialias = false;
     this.animationTime = parseFloat(this.fluidbook.datas.mobileTransitionDuration);
+
     if (this.veryLowPerf) {
         this.qualityRatio = .7;
-        this.triangles = 1;
+        this.triangles = 2;
         this.animationTime /= 1.5;
-    } else if (this.lowPerf) {
+    } else if (this.lowPerf || this.veryLowPerf) {
         this.qualityRatio = 1.2;
-        this.triangles = 2;
+        this.triangles = 3;
         this.animationTime /= 1.25;
     } else {
         this.qualityRatio = 2;
@@ -118,16 +126,17 @@ Fluidbook3DFlip.prototype = {
         geometry.merge(geometry2, new THREE.Matrix4().makeRotationY(Math.PI), 1);
 
         var roughness = 0.8;
+        var overdraw = this.renderEngine === 'canvas';
 
         if (this.enableLight) {
             this.textures = [
-                new THREE.MeshStandardMaterial({side: THREE.FrontSide, roughness: roughness}),
-                new THREE.MeshStandardMaterial({side: THREE.FrontSide, roughness: roughness}),
+                new THREE.MeshStandardMaterial({side: THREE.FrontSide, roughness: roughness, overdraw: overdraw}),
+                new THREE.MeshStandardMaterial({side: THREE.FrontSide, roughness: roughness, overdraw: overdraw}),
             ];
         } else {
             this.textures = [
-                new THREE.MeshBasicMaterial({side: THREE.FrontSide}),
-                new THREE.MeshBasicMaterial({side: THREE.FrontSide}),
+                new THREE.MeshBasicMaterial({side: THREE.FrontSide, overdraw: overdraw}),
+                new THREE.MeshBasicMaterial({side: THREE.FrontSide, overdraw: overdraw}),
             ];
         }
         geometry.translate(this.fluidbook.datas.width * this.geometryScale * 0.5, 0, 0);
@@ -149,7 +158,12 @@ Fluidbook3DFlip.prototype = {
         this.modifier.reset();
         this.modifier.addModifier(this.bend);
 
-        this.renderer = new THREE.WebGLRenderer({antialias: this.antialias, alpha: true});
+        if (this.renderEngine === 'webgl') {
+            this.renderer = new THREE.WebGLRenderer({antialias: this.antialias, alpha: true});
+        } else if (this.renderEngine == 'canvas') {
+            this.renderer = new THREE.CanvasRenderer({antialias: this.antialias, alpha: true});
+        }
+
         if (this.enableShadow) {
             this.renderer.shadowMap.enabled = true;
             this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
@@ -163,9 +177,18 @@ Fluidbook3DFlip.prototype = {
         this.initAnimations();
 
         this.animate(true);
+
+        $(this.fluidbook).on('fluidbook.ready', function () {
+            $this.guessCameraZoom(true);
+        })
+
         this.resize();
     },
 
+    clean: function () {
+        this.disposeTextures();
+    },
+
     initAnimations: function () {
         var force = 0.65;
         var easeOut = Power0.easeOut;
@@ -200,7 +223,7 @@ Fluidbook3DFlip.prototype = {
 
     prepareTurn: function (turning, callback) {
         var $this = this;
-        this.disposeTextures();
+
 
         this.prepareTexture(turning.flip[1], function (t1) {
             $this.prepareTexture(turning.flip[0], function (t2) {
@@ -234,17 +257,6 @@ Fluidbook3DFlip.prototype = {
         }
     },
 
-    // prepareTexture: function (page, callback) {
-    //     this.disposeTextures();
-    //
-    //     var $this = this;
-    //     this.fluidbook.loader.loadTexture(page, function () {
-    //         var texture = new THREE.Texture($this.fluidbook.loader.getTexture(page));
-    //         texture.needsUpdate = true;
-    //         callback(texture);
-    //     });
-    // },
-
     prepareTexture: function (page, callback) {
         var texture = new THREE.Texture(this.fluidbook.loader.getTexture(page));
         texture.needsUpdate = true;
@@ -275,7 +287,6 @@ Fluidbook3DFlip.prototype = {
                 ease: Power0.easeIn,
                 onComplete: function () {
                     $this.turnRunning = false;
-                    $this.disposeTextures();
                     this.tlname = '';
                     callback();
                 }
index fced84d68fa3d163fa72b0b6a668059e36d93fc8..6553d8367a61429186d77d8c513335e338b9d186 100644 (file)
@@ -157,7 +157,6 @@ Fluidbook.prototype = {
         this.displayLoader();
         var $this = this;
         this.loader.preloadStart(function () {
-            console.log('preload done');
             $this.ready();
         });
     },
index b37a2a6e9369ad321088479ea068baa3b9de2b71..194917f955ecdb01a8b5baac0875333efd0348be 100644 (file)
@@ -5,9 +5,12 @@ function FluidbookLoader(fluidbook) {
     this.textures = [];
     this.links = [];
     this.toPreload = [];
+    this.preloaded = [];
     this.imagesErrors = [];
     this.shadeLeft = null;
     this.shadeRight = null;
+    this.transitionPages = [];
+    this.cleanTimeout = null;
 
     this.init();
 }
@@ -32,6 +35,7 @@ FluidbookLoader.prototype = {
 
     preloadStart: function (callback) {
         this.toPreload = [1, 2, 3, 4, 5];
+        this.preloaded = this.toPreload.slice(0);
         this.preloadPages(callback);
     },
 
@@ -43,6 +47,8 @@ FluidbookLoader.prototype = {
             return;
         }
 
+        this.transitionPages = pages.slice(0);
+
         var $callback = callback;
         var $page = $pages.shift();
 
@@ -58,10 +64,11 @@ FluidbookLoader.prototype = {
     arePreloadedPages: function (pages) {
         for (var i in pages) {
             var page = pages[i];
-            if (this.backgrounds[page] == undefined) {
+            if (this.backgrounds[page] === undefined) {
                 return false;
             }
         }
+        this.preloaded.push(page);
         return true;
     },
 
@@ -96,10 +103,7 @@ FluidbookLoader.prototype = {
         }
 
         var $this = this;
-        if (this.backgrounds[page] != undefined) {
-            callback();
-            return;
-        }
+        var $_callback;
 
         if (this.fluidbook.support.transitions3dacc) {
             $_callback = function () {
@@ -109,12 +113,21 @@ FluidbookLoader.prototype = {
             $_callback = callback;
         }
 
-        this.backgrounds[page] = this.loadImage(this.getBackgroundURL(page), null, null, null, function () {
+        var $__callback = function () {
             if ($this.getVersionToLoad(page) === 'textasvector') {
                 $this._loadTexts(page, $_callback);
             } else {
                 $_callback();
             }
+        };
+
+        if (this.backgrounds[page] !== undefined && this.backgrounds[page] !== null) {
+            $__callback();
+            return;
+        }
+
+        this.backgrounds[page] = this.loadImage(this.getBackgroundURL(page), null, null, null, function () {
+            $__callback();
         });
     },
 
@@ -137,22 +150,38 @@ FluidbookLoader.prototype = {
         for (var i = min; i <= max; i++) {
             this.toPreload.push(i);
         }
-
+        this.preloaded=this.toPreload.slice(0);
         this.cleanPreloaded();
         this.preloadPages();
     },
-    cleanPreloaded: function () {
+
+    _cleanPreloaded: function () {
         for (var i = 1; i <= this.fluidbook.getMaxPage(); i++) {
-            if (this.backgrounds[i] !== undefined && this.toPreload.indexOf(i) === -1) {
+            if (this.preloaded.indexOf(i) === -1 && this.transitionPages.indexOf(i) === -1) {
                 this.deletePage(i);
             }
         }
     },
+    cleanPreloaded: function () {
+        var $this = this;
+        clearTimeout(this.cleanTimeout);
+        this.cleanTimeout = setTimeout(function () {
+            $this.fluidbook.executeWhenNetwork(function () {
+                $this._cleanPreloaded();
+            })
+        }, 10000);
+    },
     deletePage: function (page) {
-        delete this.backgrounds[page];
-        delete this.texts[page];
-        delete this.links[page];
-        if (this.textures[page] !== undefined) {
+        if (this.backgrounds[page] !== undefined) {
+            delete this.backgrounds[page];
+        }
+        if (this.texts[page] !== undefined) {
+            delete this.texts[page];
+        }
+        if (this.links[page] !== undefined) {
+            delete this.links[page];
+        }
+        if (this.textures[page] !== undefined && this.textures[page] !== null) {
             delete this.textures[page];
         }
     },
@@ -306,7 +335,8 @@ FluidbookLoader.prototype = {
             callback();
             return;
         }
-        if (this.textures[page] != undefined) {
+
+        if (this.textures[page] !== undefined && this.textures[page] !== null) {
             callback();
         } else {
             this._loadTexture(page, callback);
@@ -319,6 +349,7 @@ FluidbookLoader.prototype = {
             return;
         }
 
+        var $this = this;
         var d = 1024;
         var c = document.getElementById("pscanvas");
         c.width = d;
@@ -357,15 +388,15 @@ FluidbookLoader.prototype = {
 
         var i = new Image();
         i.onload = function () {
+            $this.textures[page] = i;
             callback();
         }
         i.src = c.toDataURL("image/png");
-
-        this.textures[page] = i;
     },
 
     getTexture: function (page) {
-        return this.textures[page];
+        var res = this.textures[page];
+        return res;
     },
 
     loadTexts: function (pageNr, callback) {
index fe749e0d8d564048cbf2d9703c6944a4ed80bd51..cd0e94fa3433189041af808e56c952ed31108980 100644 (file)
@@ -99,13 +99,14 @@ FluidbookPageTransition.prototype = {
             this.fluidbook.displayLoader();
         }
 
+        $this.fluidbook.pauseNetwork(3000);
         var currentDoublePage = $("#currentDoublePage");
 
         this.fluidbook.loader.preloadPagesBeforeTransition(turning.end, function () {
+            $this.flip3d.clean();
             $this.flip3d.prepareTurn(turning, function () {
                 $this.fluidbook.loader.setContentsInDoublePage(currentDoublePage, turning.flat, true, function () {
                     $this.beforeTransition(pageNr, 3, turning);
-                    $this.fluidbook.pauseNetwork(2000);
                     $this.flip3d.playTurn(turning.dir, function () {
                         $this.fluidbook.loader.setContentsInDoublePage(currentDoublePage, turning.end, true, function () {
                             $this.afterTransition(pageNr);
index 201ea1514df255d095b21d685089099621504d7a..0e7b8d2083ad556f912597d03d45ea429ed1bfa3 100644 (file)
@@ -76,14 +76,12 @@ FluidbookShare.prototype = {
         }
 
         var e = url.split(':');
-        console.log(e);
         if (e.length == 1) {
             return relativeToAbsoluteURL(url);
         } else {
             if (e[0] == 'http' || e[0] == 'https') {
                 return url
             } else if (e[0] == 'article') {
-                console.log(e[1]);
                 return relativeToAbsoluteURL('./p/' + this.getSEOArticle(e[1]).url);
             }
         }
@@ -128,7 +126,6 @@ FluidbookShare.prototype = {
         body = body.replace(/\\r\\n/g, "\n");
         body = body.replace(/\\r/g, "\n");
         body = body.replace(/\\n/g, "\r\n");
-        console.log(body);
         return body;
     },
 
@@ -182,7 +179,6 @@ FluidbookShare.prototype = {
     },
 
     openShare: function (url, p2, callback) {
-        console.log('open share');
         var view;
         if (url == undefined || url == null || url == 'undefined' || !url) {
             url = '';
@@ -192,7 +188,6 @@ FluidbookShare.prototype = {
         view += '<div class="content">';
         view += this.getShareLinks(false, url);
         view += '</div>';
-        ;
 
         $("#view").append('<div class="mview" data-menu="share">' + view + '</div>');
         if (callback != undefined) {
index 27e7659b316033b682e5e1299c123d2e808bd106..8c1a89f52f457f9832fae89426b002ee2f3ea791 100644 (file)
@@ -81,7 +81,6 @@ function getSpriteIcon(icon, attrs) {
 }
 
 function relativeToAbsoluteURL(relative) {
-    console.log(relative);
     var link = document.createElement("a");
     link.href = relative;
     return link.href;
index 8e8015a9acaca267cb3c6d2bd9935dc6fbac961f..737ff61642809d290b75df2c7be8e46b01638450 100644 (file)
@@ -169,7 +169,9 @@ FluidbookZoom.prototype = {
         var hiddenElements = $("header,footer,#interface,#links a.bookmark");
 
         if (this.zoom != 1) {
-            this.fluidbook.help.hide(); // Hide the help view if it's showing
+            if (this.fluidbook.help !== undefined) {
+                this.fluidbook.help.hide(); // Hide the help view if it's showing
+            }
             $("#shadow").hide();
 
             if (!$('header').hasClass('hidden')) {
index 2c57d76cf8463938f258eda8f04ce60cfae8d336..40cc5a2fe7ac72c7a40589d00dc823bd2ef69c19 100644 (file)
@@ -55,7 +55,6 @@ FluidbookBulleForm.prototype = {
                        return false;
                });
                $('#formBulle form').submit(function() {
-                       console.log('submit');
                        $(this).ajaxSubmit({
                                url: 'https://workshop.fluidbook.com/services/bulle',
                                success: function(data) {
diff --git a/js/libs/threejs/CanvasRenderer.js b/js/libs/threejs/CanvasRenderer.js
new file mode 100644 (file)
index 0000000..1e86eb7
--- /dev/null
@@ -0,0 +1,1153 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.SpriteCanvasMaterial = function ( parameters ) {
+
+    THREE.Material.call( this );
+
+    this.type = 'SpriteCanvasMaterial';
+
+    this.color = new THREE.Color( 0xffffff );
+    this.program = function () {};
+
+    this.setValues( parameters );
+
+};
+
+THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+THREE.SpriteCanvasMaterial.prototype.constructor = THREE.SpriteCanvasMaterial;
+THREE.SpriteCanvasMaterial.prototype.isSpriteCanvasMaterial = true;
+
+THREE.SpriteCanvasMaterial.prototype.clone = function () {
+
+    var material = new THREE.SpriteCanvasMaterial();
+
+    material.copy( this );
+    material.color.copy( this.color );
+    material.program = this.program;
+
+    return material;
+
+};
+
+//
+
+THREE.CanvasRenderer = function ( parameters ) {
+
+    console.log( 'THREE.CanvasRenderer', THREE.REVISION );
+
+    parameters = parameters || {};
+
+    var _this = this,
+        _renderData, _elements, _lights,
+        _projector = new THREE.Projector(),
+
+        _canvas = parameters.canvas !== undefined
+            ? parameters.canvas
+            : document.createElement( 'canvas' ),
+
+        _canvasWidth = _canvas.width,
+        _canvasHeight = _canvas.height,
+        _canvasWidthHalf = Math.floor( _canvasWidth / 2 ),
+        _canvasHeightHalf = Math.floor( _canvasHeight / 2 ),
+
+        _viewportX = 0,
+        _viewportY = 0,
+        _viewportWidth = _canvasWidth,
+        _viewportHeight = _canvasHeight,
+
+        _pixelRatio = 1,
+
+        _context = _canvas.getContext( '2d', {
+            alpha: parameters.alpha === true
+        } ),
+
+        _clearColor = new THREE.Color( 0x000000 ),
+        _clearAlpha = parameters.alpha === true ? 0 : 1,
+
+        _contextGlobalAlpha = 1,
+        _contextGlobalCompositeOperation = 0,
+        _contextStrokeStyle = null,
+        _contextFillStyle = null,
+        _contextLineWidth = null,
+        _contextLineCap = null,
+        _contextLineJoin = null,
+        _contextLineDash = [],
+
+        _v1, _v2, _v3,
+
+        _v1x, _v1y, _v2x, _v2y, _v3x, _v3y,
+
+        _color = new THREE.Color(),
+
+        _diffuseColor = new THREE.Color(),
+        _emissiveColor = new THREE.Color(),
+
+        _lightColor = new THREE.Color(),
+
+        _patterns = {},
+
+        _uvs,
+        _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,
+
+        _clipBox = new THREE.Box2(),
+        _clearBox = new THREE.Box2(),
+        _elemBox = new THREE.Box2(),
+
+        _ambientLight = new THREE.Color(),
+        _directionalLights = new THREE.Color(),
+        _pointLights = new THREE.Color(),
+
+        _vector3 = new THREE.Vector3(), // Needed for PointLight
+        _centroid = new THREE.Vector3(),
+        _normal = new THREE.Vector3(),
+        _normalViewMatrix = new THREE.Matrix3();
+
+    /* TODO
+    _canvas.mozImageSmoothingEnabled = false;
+    _canvas.webkitImageSmoothingEnabled = false;
+    _canvas.msImageSmoothingEnabled = false;
+    _canvas.imageSmoothingEnabled = false;
+    */
+
+    // dash+gap fallbacks for Firefox and everything else
+
+    if ( _context.setLineDash === undefined ) {
+
+        _context.setLineDash = function () {};
+
+    }
+
+    this.domElement = _canvas;
+
+    this.autoClear = true;
+    this.sortObjects = true;
+    this.sortElements = true;
+
+    this.info = {
+
+        render: {
+
+            vertices: 0,
+            faces: 0
+
+        }
+
+    };
+
+    // API
+
+    this.getContext = function () {
+
+        return _context;
+
+    };
+
+    this.getContextAttributes = function () {
+
+        return _context.getContextAttributes();
+
+    };
+
+    this.getPixelRatio = function () {
+
+        return _pixelRatio;
+
+    };
+
+    this.setPixelRatio = function ( value ) {
+
+        if ( value !== undefined ) _pixelRatio = value;
+
+    };
+
+    this.setSize = function ( width, height, updateStyle ) {
+
+        _canvasWidth = width * _pixelRatio;
+        _canvasHeight = height * _pixelRatio;
+
+        _canvas.width = _canvasWidth;
+        _canvas.height = _canvasHeight;
+
+        _canvasWidthHalf = Math.floor( _canvasWidth / 2 );
+        _canvasHeightHalf = Math.floor( _canvasHeight / 2 );
+
+        if ( updateStyle !== false ) {
+
+            _canvas.style.width = width + 'px';
+            _canvas.style.height = height + 'px';
+
+        }
+
+        _clipBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
+        _clipBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
+
+        _clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
+        _clearBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
+
+        _contextGlobalAlpha = 1;
+        _contextGlobalCompositeOperation = 0;
+        _contextStrokeStyle = null;
+        _contextFillStyle = null;
+        _contextLineWidth = null;
+        _contextLineCap = null;
+        _contextLineJoin = null;
+
+        this.setViewport( 0, 0, width, height );
+
+    };
+
+    this.setViewport = function ( x, y, width, height ) {
+
+        _viewportX = x * _pixelRatio;
+        _viewportY = y * _pixelRatio;
+
+        _viewportWidth = width * _pixelRatio;
+        _viewportHeight = height * _pixelRatio;
+
+    };
+
+    this.setScissor = function () {};
+    this.setScissorTest = function () {};
+
+    this.setClearColor = function ( color, alpha ) {
+
+        _clearColor.set( color );
+        _clearAlpha = alpha !== undefined ? alpha : 1;
+
+        _clearBox.min.set( - _canvasWidthHalf, - _canvasHeightHalf );
+        _clearBox.max.set(   _canvasWidthHalf,   _canvasHeightHalf );
+
+    };
+
+    this.setClearColorHex = function ( hex, alpha ) {
+
+        console.warn( 'THREE.CanvasRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+        this.setClearColor( hex, alpha );
+
+    };
+
+    this.getClearColor = function () {
+
+        return _clearColor;
+
+    };
+
+    this.getClearAlpha = function () {
+
+        return _clearAlpha;
+
+    };
+
+    this.getMaxAnisotropy = function () {
+
+        return 0;
+
+    };
+
+    this.clear = function () {
+
+        if ( _clearBox.isEmpty() === false ) {
+
+            _clearBox.intersect( _clipBox );
+            _clearBox.expandByScalar( 2 );
+
+            _clearBox.min.x =   _clearBox.min.x + _canvasWidthHalf;
+            _clearBox.min.y = - _clearBox.min.y + _canvasHeightHalf;           // higher y value !
+            _clearBox.max.x =   _clearBox.max.x + _canvasWidthHalf;
+            _clearBox.max.y = - _clearBox.max.y + _canvasHeightHalf;           // lower y value !
+
+            if ( _clearAlpha < 1 ) {
+
+                _context.clearRect(
+                    _clearBox.min.x | 0,
+                    _clearBox.max.y | 0,
+                    ( _clearBox.max.x - _clearBox.min.x ) | 0,
+                    ( _clearBox.min.y - _clearBox.max.y ) | 0
+                );
+
+            }
+
+            if ( _clearAlpha > 0 ) {
+
+                setOpacity( 1 );
+                setBlending( THREE.NormalBlending );
+
+                setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' );
+
+                _context.fillRect(
+                    _clearBox.min.x | 0,
+                    _clearBox.max.y | 0,
+                    ( _clearBox.max.x - _clearBox.min.x ) | 0,
+                    ( _clearBox.min.y - _clearBox.max.y ) | 0
+                );
+
+            }
+
+            _clearBox.makeEmpty();
+
+        }
+
+    };
+
+    // compatibility
+
+    this.clearColor = function () {};
+    this.clearDepth = function () {};
+    this.clearStencil = function () {};
+
+    this.render = function ( scene, camera ) {
+
+        if ( camera.isCamera === undefined ) {
+
+            console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' );
+            return;
+
+        }
+
+        var background = scene.background;
+
+        if ( background && background.isColor ) {
+
+            setOpacity( 1 );
+            setBlending( THREE.NormalBlending );
+
+            setFillStyle( background.getStyle() );
+            _context.fillRect( 0, 0, _canvasWidth, _canvasHeight );
+
+        } else if ( this.autoClear === true ) {
+
+            this.clear();
+
+        }
+
+        _this.info.render.vertices = 0;
+        _this.info.render.faces = 0;
+
+        _context.setTransform( _viewportWidth / _canvasWidth, 0, 0, - _viewportHeight / _canvasHeight, _viewportX, _canvasHeight - _viewportY );
+        _context.translate( _canvasWidthHalf, _canvasHeightHalf );
+
+        _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
+        _elements = _renderData.elements;
+        _lights = _renderData.lights;
+
+        _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
+
+        /* DEBUG
+        setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
+        _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
+        */
+
+        calculateLights();
+
+        for ( var e = 0, el = _elements.length; e < el; e ++ ) {
+
+            var element = _elements[ e ];
+
+            var material = element.material;
+
+            if ( material === undefined || material.opacity === 0 ) continue;
+
+            _elemBox.makeEmpty();
+
+            if ( element instanceof THREE.RenderableSprite ) {
+
+                _v1 = element;
+                _v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+                renderSprite( _v1, element, material );
+
+            } else if ( element instanceof THREE.RenderableLine ) {
+
+                _v1 = element.v1; _v2 = element.v2;
+
+                _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+                _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+
+                _elemBox.setFromPoints( [
+                    _v1.positionScreen,
+                    _v2.positionScreen
+                ] );
+
+                if ( _clipBox.intersectsBox( _elemBox ) === true ) {
+
+                    renderLine( _v1, _v2, element, material );
+
+                }
+
+            } else if ( element instanceof THREE.RenderableFace ) {
+
+                _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
+
+                if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
+                if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
+                if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
+
+                _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+                _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+                _v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
+
+                if ( material.overdraw > 0 ) {
+
+                    expand( _v1.positionScreen, _v2.positionScreen, material.overdraw );
+                    expand( _v2.positionScreen, _v3.positionScreen, material.overdraw );
+                    expand( _v3.positionScreen, _v1.positionScreen, material.overdraw );
+
+                }
+
+                _elemBox.setFromPoints( [
+                    _v1.positionScreen,
+                    _v2.positionScreen,
+                    _v3.positionScreen
+                ] );
+
+                if ( _clipBox.intersectsBox( _elemBox ) === true ) {
+
+                    renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material );
+
+                }
+
+            }
+
+            /* DEBUG
+            setLineWidth( 1 );
+            setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
+            _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
+            */
+
+            _clearBox.union( _elemBox );
+
+        }
+
+        /* DEBUG
+        setLineWidth( 1 );
+        setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
+        _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
+        */
+
+        _context.setTransform( 1, 0, 0, 1, 0, 0 );
+
+    };
+
+    //
+
+    function calculateLights() {
+
+        _ambientLight.setRGB( 0, 0, 0 );
+        _directionalLights.setRGB( 0, 0, 0 );
+        _pointLights.setRGB( 0, 0, 0 );
+
+        for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+            var light = _lights[ l ];
+            var lightColor = light.color;
+
+            if ( light.isAmbientLight ) {
+
+                _ambientLight.add( lightColor );
+
+            } else if ( light.isDirectionalLight ) {
+
+                // for sprites
+
+                _directionalLights.add( lightColor );
+
+            } else if ( light.isPointLight ) {
+
+                // for sprites
+
+                _pointLights.add( lightColor );
+
+            }
+
+        }
+
+    }
+
+    function calculateLight( position, normal, color ) {
+
+        for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+            var light = _lights[ l ];
+
+            _lightColor.copy( light.color );
+
+            if ( light.isDirectionalLight ) {
+
+                var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
+
+                var amount = normal.dot( lightPosition );
+
+                if ( amount <= 0 ) continue;
+
+                amount *= light.intensity;
+
+                color.add( _lightColor.multiplyScalar( amount ) );
+
+            } else if ( light.isPointLight ) {
+
+                var lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
+
+                var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
+
+                if ( amount <= 0 ) continue;
+
+                amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
+
+                if ( amount == 0 ) continue;
+
+                amount *= light.intensity;
+
+                color.add( _lightColor.multiplyScalar( amount ) );
+
+            }
+
+        }
+
+    }
+
+    function renderSprite( v1, element, material ) {
+
+        setOpacity( material.opacity );
+        setBlending( material.blending );
+
+        var scaleX = element.scale.x * _canvasWidthHalf;
+        var scaleY = element.scale.y * _canvasHeightHalf;
+
+        var dist = Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite
+        _elemBox.min.set( v1.x - dist, v1.y - dist );
+        _elemBox.max.set( v1.x + dist, v1.y + dist );
+
+        if ( material.isSpriteMaterial ) {
+
+            var texture = material.map;
+
+            if ( texture !== null ) {
+
+                var pattern = _patterns[ texture.id ];
+
+                if ( pattern === undefined || pattern.version !== texture.version ) {
+
+                    pattern = textureToPattern( texture );
+                    _patterns[ texture.id ] = pattern;
+
+                }
+
+                if ( pattern.canvas !== undefined ) {
+
+                    setFillStyle( pattern.canvas );
+
+                    var bitmap = texture.image;
+
+                    var ox = bitmap.width * texture.offset.x;
+                    var oy = bitmap.height * texture.offset.y;
+
+                    var sx = bitmap.width * texture.repeat.x;
+                    var sy = bitmap.height * texture.repeat.y;
+
+                    var cx = scaleX / sx;
+                    var cy = scaleY / sy;
+
+                    _context.save();
+                    _context.translate( v1.x, v1.y );
+                    if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+                    _context.translate( - scaleX / 2, - scaleY / 2 );
+                    _context.scale( cx, cy );
+                    _context.translate( - ox, - oy );
+                    _context.fillRect( ox, oy, sx, sy );
+                    _context.restore();
+
+                }
+
+            } else {
+
+                // no texture
+
+                setFillStyle( material.color.getStyle() );
+
+                _context.save();
+                _context.translate( v1.x, v1.y );
+                if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+                _context.scale( scaleX, - scaleY );
+                _context.fillRect( - 0.5, - 0.5, 1, 1 );
+                _context.restore();
+
+            }
+
+        } else if ( material.isSpriteCanvasMaterial ) {
+
+            setStrokeStyle( material.color.getStyle() );
+            setFillStyle( material.color.getStyle() );
+
+            _context.save();
+            _context.translate( v1.x, v1.y );
+            if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+            _context.scale( scaleX, scaleY );
+
+            material.program( _context );
+
+            _context.restore();
+
+        } else if ( material.isPointsMaterial ) {
+
+            setFillStyle( material.color.getStyle() );
+
+            _context.save();
+            _context.translate( v1.x, v1.y );
+            if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+            _context.scale( scaleX * material.size, - scaleY * material.size );
+            _context.fillRect( - 0.5, - 0.5, 1, 1 );
+            _context.restore();
+
+        }
+
+        /* DEBUG
+        setStrokeStyle( 'rgb(255,255,0)' );
+        _context.beginPath();
+        _context.moveTo( v1.x - 10, v1.y );
+        _context.lineTo( v1.x + 10, v1.y );
+        _context.moveTo( v1.x, v1.y - 10 );
+        _context.lineTo( v1.x, v1.y + 10 );
+        _context.stroke();
+        */
+
+    }
+
+    function renderLine( v1, v2, element, material ) {
+
+        setOpacity( material.opacity );
+        setBlending( material.blending );
+
+        _context.beginPath();
+        _context.moveTo( v1.positionScreen.x, v1.positionScreen.y );
+        _context.lineTo( v2.positionScreen.x, v2.positionScreen.y );
+
+        if ( material.isLineBasicMaterial ) {
+
+            setLineWidth( material.linewidth );
+            setLineCap( material.linecap );
+            setLineJoin( material.linejoin );
+
+            if ( material.vertexColors !== THREE.VertexColors ) {
+
+                setStrokeStyle( material.color.getStyle() );
+
+            } else {
+
+                var colorStyle1 = element.vertexColors[ 0 ].getStyle();
+                var colorStyle2 = element.vertexColors[ 1 ].getStyle();
+
+                if ( colorStyle1 === colorStyle2 ) {
+
+                    setStrokeStyle( colorStyle1 );
+
+                } else {
+
+                    try {
+
+                        var grad = _context.createLinearGradient(
+                            v1.positionScreen.x,
+                            v1.positionScreen.y,
+                            v2.positionScreen.x,
+                            v2.positionScreen.y
+                        );
+                        grad.addColorStop( 0, colorStyle1 );
+                        grad.addColorStop( 1, colorStyle2 );
+
+                    } catch ( exception ) {
+
+                        grad = colorStyle1;
+
+                    }
+
+                    setStrokeStyle( grad );
+
+                }
+
+            }
+
+            if ( material.isLineDashedMaterial ) {
+
+                setLineDash( [ material.dashSize, material.gapSize ] );
+
+            }
+
+            _context.stroke();
+            _elemBox.expandByScalar( material.linewidth * 2 );
+
+            if ( material.isLineDashedMaterial ) {
+
+                setLineDash( [] );
+
+            }
+
+        }
+
+    }
+
+    function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) {
+
+        _this.info.render.vertices += 3;
+        _this.info.render.faces ++;
+
+        setOpacity( material.opacity );
+        setBlending( material.blending );
+
+        _v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
+        _v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
+        _v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
+
+        drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y );
+
+        if ( ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) && material.map === null ) {
+
+            _diffuseColor.copy( material.color );
+            _emissiveColor.copy( material.emissive );
+
+            if ( material.vertexColors === THREE.FaceColors ) {
+
+                _diffuseColor.multiply( element.color );
+
+            }
+
+            _color.copy( _ambientLight );
+
+            _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
+
+            calculateLight( _centroid, element.normalModel, _color );
+
+            _color.multiply( _diffuseColor ).add( _emissiveColor );
+
+            material.wireframe === true
+                ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+                : fillPath( _color );
+
+        } else if ( material.isMeshBasicMaterial || material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
+
+            if ( material.map !== null ) {
+
+                var mapping = material.map.mapping;
+
+                if ( mapping === THREE.UVMapping ) {
+
+                    _uvs = element.uvs;
+                    patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map );
+
+                }
+
+            } else if ( material.envMap !== null ) {
+
+                if ( material.envMap.mapping === THREE.SphericalReflectionMapping ) {
+
+                    _normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix );
+                    _uv1x = 0.5 * _normal.x + 0.5;
+                    _uv1y = 0.5 * _normal.y + 0.5;
+
+                    _normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix );
+                    _uv2x = 0.5 * _normal.x + 0.5;
+                    _uv2y = 0.5 * _normal.y + 0.5;
+
+                    _normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix );
+                    _uv3x = 0.5 * _normal.x + 0.5;
+                    _uv3y = 0.5 * _normal.y + 0.5;
+
+                    patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap );
+
+                }
+
+            } else {
+
+                _color.copy( material.color );
+
+                if ( material.vertexColors === THREE.FaceColors ) {
+
+                    _color.multiply( element.color );
+
+                }
+
+                material.wireframe === true
+                    ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+                    : fillPath( _color );
+
+            }
+
+        } else if ( material.isMeshNormalMaterial ) {
+
+            _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix );
+
+            _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+            material.wireframe === true
+                ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+                : fillPath( _color );
+
+        } else {
+
+            _color.setRGB( 1, 1, 1 );
+
+            material.wireframe === true
+                ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+                : fillPath( _color );
+
+        }
+
+    }
+
+    //
+
+    function drawTriangle( x0, y0, x1, y1, x2, y2 ) {
+
+        _context.beginPath();
+        _context.moveTo( x0, y0 );
+        _context.lineTo( x1, y1 );
+        _context.lineTo( x2, y2 );
+        _context.closePath();
+
+    }
+
+    function strokePath( color, linewidth, linecap, linejoin ) {
+
+        setLineWidth( linewidth );
+        setLineCap( linecap );
+        setLineJoin( linejoin );
+        setStrokeStyle( color.getStyle() );
+
+        _context.stroke();
+
+        _elemBox.expandByScalar( linewidth * 2 );
+
+    }
+
+    function fillPath( color ) {
+
+        setFillStyle( color.getStyle() );
+        _context.fill();
+
+    }
+
+    function textureToPattern( texture ) {
+
+        if ( texture.version === 0 ||
+            texture instanceof THREE.CompressedTexture ||
+            texture instanceof THREE.DataTexture ) {
+
+            return {
+                canvas: undefined,
+                version: texture.version
+            };
+
+        }
+
+        var image = texture.image;
+
+        if ( image.complete === false ) {
+
+            return {
+                canvas: undefined,
+                version: 0
+            };
+
+        }
+
+        var repeatX = texture.wrapS === THREE.RepeatWrapping || texture.wrapS === THREE.MirroredRepeatWrapping;
+        var repeatY = texture.wrapT === THREE.RepeatWrapping || texture.wrapT === THREE.MirroredRepeatWrapping;
+
+        var mirrorX = texture.wrapS === THREE.MirroredRepeatWrapping;
+        var mirrorY = texture.wrapT === THREE.MirroredRepeatWrapping;
+
+        //
+
+        var canvas = document.createElement( 'canvas' );
+        canvas.width = image.width * ( mirrorX ? 2 : 1 );
+        canvas.height = image.height * ( mirrorY ? 2 : 1 );
+
+        var context = canvas.getContext( '2d' );
+        context.setTransform( 1, 0, 0, - 1, 0, image.height );
+        context.drawImage( image, 0, 0 );
+
+        if ( mirrorX === true ) {
+
+            context.setTransform( - 1, 0, 0, - 1, image.width, image.height );
+            context.drawImage( image, - image.width, 0 );
+
+        }
+
+        if ( mirrorY === true ) {
+
+            context.setTransform( 1, 0, 0, 1, 0, 0 );
+            context.drawImage( image, 0, image.height );
+
+        }
+
+        if ( mirrorX === true && mirrorY === true ) {
+
+            context.setTransform( - 1, 0, 0, 1, image.width, 0 );
+            context.drawImage( image, - image.width, image.height );
+
+        }
+
+        var repeat = 'no-repeat';
+
+        if ( repeatX === true && repeatY === true ) {
+
+            repeat = 'repeat';
+
+        } else if ( repeatX === true ) {
+
+            repeat = 'repeat-x';
+
+        } else if ( repeatY === true ) {
+
+            repeat = 'repeat-y';
+
+        }
+
+        var pattern = _context.createPattern( canvas, repeat );
+
+        if ( texture.onUpdate ) texture.onUpdate( texture );
+
+        return {
+            canvas: pattern,
+            version: texture.version
+        };
+
+    }
+
+    function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+        var pattern = _patterns[ texture.id ];
+
+        if ( pattern === undefined || pattern.version !== texture.version ) {
+
+            pattern = textureToPattern( texture );
+            _patterns[ texture.id ] = pattern;
+
+        }
+
+        if ( pattern.canvas !== undefined ) {
+
+            setFillStyle( pattern.canvas );
+
+        } else {
+
+            setFillStyle( 'rgba( 0, 0, 0, 1)' );
+            _context.fill();
+            return;
+
+        }
+
+        // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+        var a, b, c, d, e, f, det, idet,
+            offsetX = texture.offset.x / texture.repeat.x,
+            offsetY = texture.offset.y / texture.repeat.y,
+            width = texture.image.width * texture.repeat.x,
+            height = texture.image.height * texture.repeat.y;
+
+        u0 = ( u0 + offsetX ) * width;
+        v0 = ( v0 + offsetY ) * height;
+
+        u1 = ( u1 + offsetX ) * width;
+        v1 = ( v1 + offsetY ) * height;
+
+        u2 = ( u2 + offsetX ) * width;
+        v2 = ( v2 + offsetY ) * height;
+
+        x1 -= x0; y1 -= y0;
+        x2 -= x0; y2 -= y0;
+
+        u1 -= u0; v1 -= v0;
+        u2 -= u0; v2 -= v0;
+
+        det = u1 * v2 - u2 * v1;
+
+        if ( det === 0 ) return;
+
+        idet = 1 / det;
+
+        a = ( v2 * x1 - v1 * x2 ) * idet;
+        b = ( v2 * y1 - v1 * y2 ) * idet;
+        c = ( u1 * x2 - u2 * x1 ) * idet;
+        d = ( u1 * y2 - u2 * y1 ) * idet;
+
+        e = x0 - a * u0 - c * v0;
+        f = y0 - b * u0 - d * v0;
+
+        _context.save();
+        _context.transform( a, b, c, d, e, f );
+        _context.fill();
+        _context.restore();
+
+    }
+
+    /*
+    function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
+
+        // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+        var a, b, c, d, e, f, det, idet,
+        width = image.width - 1,
+        height = image.height - 1;
+
+        u0 *= width; v0 *= height;
+        u1 *= width; v1 *= height;
+        u2 *= width; v2 *= height;
+
+        x1 -= x0; y1 -= y0;
+        x2 -= x0; y2 -= y0;
+
+        u1 -= u0; v1 -= v0;
+        u2 -= u0; v2 -= v0;
+
+        det = u1 * v2 - u2 * v1;
+
+        idet = 1 / det;
+
+        a = ( v2 * x1 - v1 * x2 ) * idet;
+        b = ( v2 * y1 - v1 * y2 ) * idet;
+        c = ( u1 * x2 - u2 * x1 ) * idet;
+        d = ( u1 * y2 - u2 * y1 ) * idet;
+
+        e = x0 - a * u0 - c * v0;
+        f = y0 - b * u0 - d * v0;
+
+        _context.save();
+        _context.transform( a, b, c, d, e, f );
+        _context.clip();
+        _context.drawImage( image, 0, 0 );
+        _context.restore();
+
+    }
+    */
+
+    // Hide anti-alias gaps
+
+    function expand( v1, v2, pixels ) {
+
+        var x = v2.x - v1.x, y = v2.y - v1.y,
+            det = x * x + y * y, idet;
+
+        if ( det === 0 ) return;
+
+        idet = pixels / Math.sqrt( det );
+
+        x *= idet; y *= idet;
+
+        v2.x += x; v2.y += y;
+        v1.x -= x; v1.y -= y;
+
+    }
+
+    // Context cached methods.
+
+    function setOpacity( value ) {
+
+        if ( _contextGlobalAlpha !== value ) {
+
+            _context.globalAlpha = value;
+            _contextGlobalAlpha = value;
+
+        }
+
+    }
+
+    function setBlending( value ) {
+
+        if ( _contextGlobalCompositeOperation !== value ) {
+
+            if ( value === THREE.NormalBlending ) {
+
+                _context.globalCompositeOperation = 'source-over';
+
+            } else if ( value === THREE.AdditiveBlending ) {
+
+                _context.globalCompositeOperation = 'lighter';
+
+            } else if ( value === THREE.SubtractiveBlending ) {
+
+                _context.globalCompositeOperation = 'darker';
+
+            } else if ( value === THREE.MultiplyBlending ) {
+
+                _context.globalCompositeOperation = 'multiply';
+
+            }
+
+            _contextGlobalCompositeOperation = value;
+
+        }
+
+    }
+
+    function setLineWidth( value ) {
+
+        if ( _contextLineWidth !== value ) {
+
+            _context.lineWidth = value;
+            _contextLineWidth = value;
+
+        }
+
+    }
+
+    function setLineCap( value ) {
+
+        // "butt", "round", "square"
+
+        if ( _contextLineCap !== value ) {
+
+            _context.lineCap = value;
+            _contextLineCap = value;
+
+        }
+
+    }
+
+    function setLineJoin( value ) {
+
+        // "round", "bevel", "miter"
+
+        if ( _contextLineJoin !== value ) {
+
+            _context.lineJoin = value;
+            _contextLineJoin = value;
+
+        }
+
+    }
+
+    function setStrokeStyle( value ) {
+
+        if ( _contextStrokeStyle !== value ) {
+
+            _context.strokeStyle = value;
+            _contextStrokeStyle = value;
+
+        }
+
+    }
+
+    function setFillStyle( value ) {
+
+        if ( _contextFillStyle !== value ) {
+
+            _context.fillStyle = value;
+            _contextFillStyle = value;
+
+        }
+
+    }
+
+    function setLineDash( value ) {
+
+        if ( _contextLineDash.length !== value.length ) {
+
+            _context.setLineDash( value );
+            _contextLineDash = value;
+
+        }
+
+    }
+
+};
diff --git a/js/libs/threejs/Projector.js b/js/libs/threejs/Projector.js
new file mode 100644 (file)
index 0000000..5187f4e
--- /dev/null
@@ -0,0 +1,1064 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author julianwa / https://github.com/julianwa
+ */
+
+THREE.RenderableObject = function () {
+
+    this.id = 0;
+
+    this.object = null;
+    this.z = 0;
+    this.renderOrder = 0;
+
+};
+
+//
+
+THREE.RenderableFace = function () {
+
+    this.id = 0;
+
+    this.v1 = new THREE.RenderableVertex();
+    this.v2 = new THREE.RenderableVertex();
+    this.v3 = new THREE.RenderableVertex();
+
+    this.normalModel = new THREE.Vector3();
+
+    this.vertexNormalsModel = [new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3()];
+    this.vertexNormalsLength = 0;
+
+    this.color = new THREE.Color();
+    this.material = null;
+    this.uvs = [new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2()];
+
+    this.z = 0;
+    this.renderOrder = 0;
+
+};
+
+//
+
+THREE.RenderableVertex = function () {
+
+    this.position = new THREE.Vector3();
+    this.positionWorld = new THREE.Vector3();
+    this.positionScreen = new THREE.Vector4();
+
+    this.visible = true;
+
+};
+
+THREE.RenderableVertex.prototype.copy = function (vertex) {
+
+    this.positionWorld.copy(vertex.positionWorld);
+    this.positionScreen.copy(vertex.positionScreen);
+
+};
+
+//
+
+THREE.RenderableLine = function () {
+
+    this.id = 0;
+
+    this.v1 = new THREE.RenderableVertex();
+    this.v2 = new THREE.RenderableVertex();
+
+    this.vertexColors = [new THREE.Color(), new THREE.Color()];
+    this.material = null;
+
+    this.z = 0;
+    this.renderOrder = 0;
+
+};
+
+//
+
+THREE.RenderableSprite = function () {
+
+    this.id = 0;
+
+    this.object = null;
+
+    this.x = 0;
+    this.y = 0;
+    this.z = 0;
+
+    this.rotation = 0;
+    this.scale = new THREE.Vector2();
+
+    this.material = null;
+    this.renderOrder = 0;
+
+};
+
+//
+
+THREE.Projector = function () {
+
+    var _object, _objectCount, _objectPool = [], _objectPoolLength = 0,
+        _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0,
+        _face, _faceCount, _facePool = [], _facePoolLength = 0,
+        _line, _lineCount, _linePool = [], _linePoolLength = 0,
+        _sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0,
+
+        _renderData = {objects: [], lights: [], elements: []},
+
+        _vector3 = new THREE.Vector3(),
+        _vector4 = new THREE.Vector4(),
+
+        _clipBox = new THREE.Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1)),
+        _boundingBox = new THREE.Box3(),
+        _points3 = new Array(3),
+
+        _viewMatrix = new THREE.Matrix4(),
+        _viewProjectionMatrix = new THREE.Matrix4(),
+
+        _modelMatrix,
+        _modelViewProjectionMatrix = new THREE.Matrix4(),
+
+        _normalMatrix = new THREE.Matrix3(),
+
+        _frustum = new THREE.Frustum(),
+
+        _clippedVertex1PositionScreen = new THREE.Vector4(),
+        _clippedVertex2PositionScreen = new THREE.Vector4();
+
+    //
+
+    this.projectVector = function (vector, camera) {
+
+        console.warn('THREE.Projector: .projectVector() is now vector.project().');
+        vector.project(camera);
+
+    };
+
+    this.unprojectVector = function (vector, camera) {
+
+        console.warn('THREE.Projector: .unprojectVector() is now vector.unproject().');
+        vector.unproject(camera);
+
+    };
+
+    this.pickingRay = function () {
+
+        console.error('THREE.Projector: .pickingRay() is now raycaster.setFromCamera().');
+
+    };
+
+    //
+
+    var RenderList = function () {
+
+        var normals = [];
+        var colors = [];
+        var uvs = [];
+
+        var object = null;
+        var material = null;
+
+        var normalMatrix = new THREE.Matrix3();
+
+        function setObject(value) {
+
+            object = value;
+            material = object.material;
+
+            normalMatrix.getNormalMatrix(object.matrixWorld);
+
+            normals.length = 0;
+            colors.length = 0;
+            uvs.length = 0;
+
+        }
+
+        function projectVertex(vertex) {
+
+            var position = vertex.position;
+            var positionWorld = vertex.positionWorld;
+            var positionScreen = vertex.positionScreen;
+
+            positionWorld.copy(position).applyMatrix4(_modelMatrix);
+            positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix);
+
+            var invW = 1 / positionScreen.w;
+
+            positionScreen.x *= invW;
+            positionScreen.y *= invW;
+            positionScreen.z *= invW;
+
+            vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 &&
+                positionScreen.y >= -1 && positionScreen.y <= 1 &&
+                positionScreen.z >= -1 && positionScreen.z <= 1;
+
+        }
+
+        function pushVertex(x, y, z) {
+
+            _vertex = getNextVertexInPool();
+            _vertex.position.set(x, y, z);
+
+            projectVertex(_vertex);
+
+        }
+
+        function pushNormal(x, y, z) {
+
+            normals.push(x, y, z);
+
+        }
+
+        function pushColor(r, g, b) {
+
+            colors.push(r, g, b);
+
+        }
+
+        function pushUv(x, y) {
+
+            uvs.push(x, y);
+
+        }
+
+        function checkTriangleVisibility(v1, v2, v3) {
+
+            if (v1.visible === true || v2.visible === true || v3.visible === true) return true;
+
+            _points3[0] = v1.positionScreen;
+            _points3[1] = v2.positionScreen;
+            _points3[2] = v3.positionScreen;
+
+            return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3));
+
+        }
+
+        function checkBackfaceCulling(v1, v2, v3) {
+
+            return ((v3.positionScreen.x - v1.positionScreen.x) *
+                (v2.positionScreen.y - v1.positionScreen.y) -
+                (v3.positionScreen.y - v1.positionScreen.y) *
+                (v2.positionScreen.x - v1.positionScreen.x)) < 0;
+
+        }
+
+        function pushLine(a, b) {
+
+            var v1 = _vertexPool[a];
+            var v2 = _vertexPool[b];
+
+            // Clip
+
+            v1.positionScreen.copy(v1.position).applyMatrix4(_modelViewProjectionMatrix);
+            v2.positionScreen.copy(v2.position).applyMatrix4(_modelViewProjectionMatrix);
+
+            if (clipLine(v1.positionScreen, v2.positionScreen) === true) {
+
+                // Perform the perspective divide
+                v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w);
+                v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w);
+
+                _line = getNextLineInPool();
+                _line.id = object.id;
+                _line.v1.copy(v1);
+                _line.v2.copy(v2);
+                _line.z = Math.max(v1.positionScreen.z, v2.positionScreen.z);
+                _line.renderOrder = object.renderOrder;
+
+                _line.material = object.material;
+
+                if (object.material.vertexColors === THREE.VertexColors) {
+
+                    _line.vertexColors[0].fromArray(colors, a * 3);
+                    _line.vertexColors[1].fromArray(colors, b * 3);
+
+                }
+
+                _renderData.elements.push(_line);
+
+            }
+
+        }
+
+        function pushTriangle(a, b, c, material) {
+
+            var v1 = _vertexPool[a];
+            var v2 = _vertexPool[b];
+            var v3 = _vertexPool[c];
+
+            if (checkTriangleVisibility(v1, v2, v3) === false) return;
+
+            if (material.side === THREE.DoubleSide || checkBackfaceCulling(v1, v2, v3) === true) {
+
+                _face = getNextFaceInPool();
+
+                _face.id = object.id;
+                _face.v1.copy(v1);
+                _face.v2.copy(v2);
+                _face.v3.copy(v3);
+                _face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3;
+                _face.renderOrder = object.renderOrder;
+
+                // face normal
+                _vector3.subVectors(v3.position, v2.position);
+                _vector4.subVectors(v1.position, v2.position);
+                _vector3.cross(_vector4);
+                _face.normalModel.copy(_vector3);
+                _face.normalModel.applyMatrix3(normalMatrix).normalize();
+
+                for (var i = 0; i < 3; i++) {
+
+                    var normal = _face.vertexNormalsModel[i];
+                    normal.fromArray(normals, arguments[i] * 3);
+                    normal.applyMatrix3(normalMatrix).normalize();
+
+                    var uv = _face.uvs[i];
+                    uv.fromArray(uvs, arguments[i] * 2);
+
+                }
+
+                _face.vertexNormalsLength = 3;
+
+                _face.material = material;
+
+                _renderData.elements.push(_face);
+
+            }
+
+        }
+
+        return {
+            setObject: setObject,
+            projectVertex: projectVertex,
+            checkTriangleVisibility: checkTriangleVisibility,
+            checkBackfaceCulling: checkBackfaceCulling,
+            pushVertex: pushVertex,
+            pushNormal: pushNormal,
+            pushColor: pushColor,
+            pushUv: pushUv,
+            pushLine: pushLine,
+            pushTriangle: pushTriangle
+        };
+
+    };
+
+    var renderList = new RenderList();
+
+    function projectObject(object) {
+
+        if (object.visible === false) return;
+
+        if (object instanceof THREE.Light) {
+
+            _renderData.lights.push(object);
+
+        } else if (object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points) {
+
+            if (object.material.visible === false) return;
+            if (object.frustumCulled === true && _frustum.intersectsObject(object) === false) return;
+
+            addObject(object);
+
+        } else if (object instanceof THREE.Sprite) {
+
+            if (object.material.visible === false) return;
+            if (object.frustumCulled === true && _frustum.intersectsSprite(object) === false) return;
+
+            addObject(object);
+
+        }
+
+        var children = object.children;
+
+        for (var i = 0, l = children.length; i < l; i++) {
+
+            projectObject(children[i]);
+
+        }
+
+    }
+
+    function addObject(object) {
+
+        _object = getNextObjectInPool();
+        _object.id = object.id;
+        _object.object = object;
+
+        _vector3.setFromMatrixPosition(object.matrixWorld);
+        _vector3.applyMatrix4(_viewProjectionMatrix);
+        _object.z = _vector3.z;
+        _object.renderOrder = object.renderOrder;
+
+        _renderData.objects.push(_object);
+
+    }
+
+    this.projectScene = function (scene, camera, sortObjects, sortElements) {
+
+        _faceCount = 0;
+        _lineCount = 0;
+        _spriteCount = 0;
+
+        _renderData.elements.length = 0;
+
+        if (scene.autoUpdate === true) scene.updateMatrixWorld();
+        if (camera.parent === null) camera.updateMatrixWorld();
+
+        _viewMatrix.copy(camera.matrixWorldInverse);
+        _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix);
+
+        _frustum.setFromMatrix(_viewProjectionMatrix);
+
+        //
+
+        _objectCount = 0;
+
+        _renderData.objects.length = 0;
+        _renderData.lights.length = 0;
+
+        projectObject(scene);
+
+        if (sortObjects === true) {
+
+            _renderData.objects.sort(painterSort);
+
+        }
+
+        //
+
+        var objects = _renderData.objects;
+
+        for (var o = 0, ol = objects.length; o < ol; o++) {
+
+            var object = objects[o].object;
+            var geometry = object.geometry;
+
+            renderList.setObject(object);
+
+            _modelMatrix = object.matrixWorld;
+
+            _vertexCount = 0;
+
+            if (object instanceof THREE.Mesh) {
+
+                if (geometry instanceof THREE.BufferGeometry) {
+
+                    var material = object.material;
+
+                    var isMultiMaterial = Array.isArray(material);
+
+                    var attributes = geometry.attributes;
+                    var groups = geometry.groups;
+
+                    if (attributes.position === undefined) continue;
+
+                    var positions = attributes.position.array;
+
+                    for (var i = 0, l = positions.length; i < l; i += 3) {
+
+                        renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]);
+
+                    }
+
+                    if (attributes.normal !== undefined) {
+
+                        var normals = attributes.normal.array;
+
+                        for (var i = 0, l = normals.length; i < l; i += 3) {
+
+                            renderList.pushNormal(normals[i], normals[i + 1], normals[i + 2]);
+
+                        }
+
+                    }
+
+                    if (attributes.uv !== undefined) {
+
+                        var uvs = attributes.uv.array;
+
+                        for (var i = 0, l = uvs.length; i < l; i += 2) {
+
+                            renderList.pushUv(uvs[i], uvs[i + 1]);
+
+                        }
+
+                    }
+
+                    if (geometry.index !== null) {
+
+                        var indices = geometry.index.array;
+
+                        if (groups.length > 0) {
+
+                            for (var g = 0; g < groups.length; g++) {
+
+                                var group = groups[g];
+
+                                material = isMultiMaterial === true
+                                    ? object.material[group.materialIndex]
+                                    : object.material;
+
+                                if (material === undefined) continue;
+
+                                for (var i = group.start, l = group.start + group.count; i < l; i += 3) {
+
+                                    renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2], material);
+
+                                }
+
+                            }
+
+                        } else {
+
+                            for (var i = 0, l = indices.length; i < l; i += 3) {
+
+                                renderList.pushTriangle(indices[i], indices[i + 1], indices[i + 2], material);
+
+                            }
+
+                        }
+
+                    } else {
+
+                        if (groups.length > 0) {
+
+                            for (var g = 0; g < groups.length; g++) {
+
+                                var group = groups[g];
+
+                                material = isMultiMaterial === true
+                                    ? object.material[group.materialIndex]
+                                    : object.material;
+
+                                if (material === undefined) continue;
+
+                                for (var i = group.start, l = group.start + group.count; i < l; i += 3) {
+
+                                    renderList.pushTriangle(i, i + 1, i + 2, material);
+
+                                }
+
+                            }
+
+                        } else {
+
+                            for (var i = 0, l = positions.length / 3; i < l; i += 3) {
+
+                                renderList.pushTriangle(i, i + 1, i + 2, material);
+
+                            }
+
+                        }
+
+                    }
+
+                } else if (geometry instanceof THREE.Geometry) {
+
+                    var vertices = geometry.vertices;
+                    var faces = geometry.faces;
+                    var faceVertexUvs = geometry.faceVertexUvs[0];
+
+                    _normalMatrix.getNormalMatrix(_modelMatrix);
+
+                    var material = object.material;
+
+                    var isMultiMaterial = Array.isArray(material);
+
+                    for (var v = 0, vl = vertices.length; v < vl; v++) {
+
+                        var vertex = vertices[v];
+
+                        _vector3.copy(vertex);
+
+                        if (material.morphTargets === true) {
+
+                            var morphTargets = geometry.morphTargets;
+                            var morphInfluences = object.morphTargetInfluences;
+
+                            for (var t = 0, tl = morphTargets.length; t < tl; t++) {
+
+                                var influence = morphInfluences[t];
+
+                                if (influence === 0) continue;
+
+                                var target = morphTargets[t];
+                                var targetVertex = target.vertices[v];
+
+                                _vector3.x += (targetVertex.x - vertex.x) * influence;
+                                _vector3.y += (targetVertex.y - vertex.y) * influence;
+                                _vector3.z += (targetVertex.z - vertex.z) * influence;
+
+                            }
+
+                        }
+
+                        renderList.pushVertex(_vector3.x, _vector3.y, _vector3.z);
+
+                    }
+
+                    for (var f = 0, fl = faces.length; f < fl; f++) {
+
+                        var face = faces[f];
+
+                        material = isMultiMaterial === true
+                            ? object.material[face.materialIndex]
+                            : object.material;
+
+                        if (material === undefined) continue;
+
+                        var side = material.side;
+
+                        var v1 = _vertexPool[face.a];
+                        var v2 = _vertexPool[face.b];
+                        var v3 = _vertexPool[face.c];
+
+                        if (renderList.checkTriangleVisibility(v1, v2, v3) === false) continue;
+
+                        var visible = renderList.checkBackfaceCulling(v1, v2, v3);
+
+                        if (side !== THREE.DoubleSide) {
+
+                            if (side === THREE.FrontSide && visible === false) continue;
+                            if (side === THREE.BackSide && visible === true) continue;
+
+                        }
+
+                        _face = getNextFaceInPool();
+
+                        _face.id = object.id;
+                        _face.v1.copy(v1);
+                        _face.v2.copy(v2);
+                        _face.v3.copy(v3);
+
+                        _face.normalModel.copy(face.normal);
+
+                        if (visible === false && (side === THREE.BackSide || side === THREE.DoubleSide)) {
+
+                            _face.normalModel.negate();
+
+                        }
+
+                        _face.normalModel.applyMatrix3(_normalMatrix).normalize();
+
+                        var faceVertexNormals = face.vertexNormals;
+
+                        for (var n = 0, nl = Math.min(faceVertexNormals.length, 3); n < nl; n++) {
+
+                            var normalModel = _face.vertexNormalsModel[n];
+                            normalModel.copy(faceVertexNormals[n]);
+
+                            if (visible === false && (side === THREE.BackSide || side === THREE.DoubleSide)) {
+
+                                normalModel.negate();
+
+                            }
+
+                            normalModel.applyMatrix3(_normalMatrix).normalize();
+
+                        }
+
+                        _face.vertexNormalsLength = faceVertexNormals.length;
+
+                        var vertexUvs = faceVertexUvs[f];
+
+                        if (vertexUvs !== undefined) {
+
+                            for (var u = 0; u < 3; u++) {
+
+                                _face.uvs[u].copy(vertexUvs[u]);
+
+                            }
+
+                        }
+
+                        _face.color = face.color;
+                        _face.material = material;
+
+                        _face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3;
+                        _face.renderOrder = object.renderOrder;
+
+                        _renderData.elements.push(_face);
+
+                    }
+
+                }
+
+            } else if (object instanceof THREE.Line) {
+
+                _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix);
+
+                if (geometry instanceof THREE.BufferGeometry) {
+
+                    var attributes = geometry.attributes;
+
+                    if (attributes.position !== undefined) {
+
+                        var positions = attributes.position.array;
+
+                        for (var i = 0, l = positions.length; i < l; i += 3) {
+
+                            renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]);
+
+                        }
+
+                        if (attributes.color !== undefined) {
+
+                            var colors = attributes.color.array;
+
+                            for (var i = 0, l = colors.length; i < l; i += 3) {
+
+                                renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]);
+
+                            }
+
+                        }
+
+                        if (geometry.index !== null) {
+
+                            var indices = geometry.index.array;
+
+                            for (var i = 0, l = indices.length; i < l; i += 2) {
+
+                                renderList.pushLine(indices[i], indices[i + 1]);
+
+                            }
+
+                        } else {
+
+                            var step = object instanceof THREE.LineSegments ? 2 : 1;
+
+                            for (var i = 0, l = (positions.length / 3) - 1; i < l; i += step) {
+
+                                renderList.pushLine(i, i + 1);
+
+                            }
+
+                        }
+
+                    }
+
+                } else if (geometry instanceof THREE.Geometry) {
+
+                    var vertices = object.geometry.vertices;
+
+                    if (vertices.length === 0) continue;
+
+                    v1 = getNextVertexInPool();
+                    v1.positionScreen.copy(vertices[0]).applyMatrix4(_modelViewProjectionMatrix);
+
+                    var step = object instanceof THREE.LineSegments ? 2 : 1;
+
+                    for (var v = 1, vl = vertices.length; v < vl; v++) {
+
+                        v1 = getNextVertexInPool();
+                        v1.positionScreen.copy(vertices[v]).applyMatrix4(_modelViewProjectionMatrix);
+
+                        if ((v + 1) % step > 0) continue;
+
+                        v2 = _vertexPool[_vertexCount - 2];
+
+                        _clippedVertex1PositionScreen.copy(v1.positionScreen);
+                        _clippedVertex2PositionScreen.copy(v2.positionScreen);
+
+                        if (clipLine(_clippedVertex1PositionScreen, _clippedVertex2PositionScreen) === true) {
+
+                            // Perform the perspective divide
+                            _clippedVertex1PositionScreen.multiplyScalar(1 / _clippedVertex1PositionScreen.w);
+                            _clippedVertex2PositionScreen.multiplyScalar(1 / _clippedVertex2PositionScreen.w);
+
+                            _line = getNextLineInPool();
+
+                            _line.id = object.id;
+                            _line.v1.positionScreen.copy(_clippedVertex1PositionScreen);
+                            _line.v2.positionScreen.copy(_clippedVertex2PositionScreen);
+
+                            _line.z = Math.max(_clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z);
+                            _line.renderOrder = object.renderOrder;
+
+                            _line.material = object.material;
+
+                            if (object.material.vertexColors === THREE.VertexColors) {
+
+                                _line.vertexColors[0].copy(object.geometry.colors[v]);
+                                _line.vertexColors[1].copy(object.geometry.colors[v - 1]);
+
+                            }
+
+                            _renderData.elements.push(_line);
+
+                        }
+
+                    }
+
+                }
+
+            } else if (object instanceof THREE.Points) {
+
+                _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix);
+
+                if (geometry instanceof THREE.Geometry) {
+
+                    var vertices = object.geometry.vertices;
+
+                    for (var v = 0, vl = vertices.length; v < vl; v++) {
+
+                        var vertex = vertices[v];
+
+                        _vector4.set(vertex.x, vertex.y, vertex.z, 1);
+                        _vector4.applyMatrix4(_modelViewProjectionMatrix);
+
+                        pushPoint(_vector4, object, camera);
+
+                    }
+
+                } else if (geometry instanceof THREE.BufferGeometry) {
+
+                    var attributes = geometry.attributes;
+
+                    if (attributes.position !== undefined) {
+
+                        var positions = attributes.position.array;
+
+                        for (var i = 0, l = positions.length; i < l; i += 3) {
+
+                            _vector4.set(positions[i], positions[i + 1], positions[i + 2], 1);
+                            _vector4.applyMatrix4(_modelViewProjectionMatrix);
+
+                            pushPoint(_vector4, object, camera);
+
+                        }
+
+                    }
+
+                }
+
+            } else if (object instanceof THREE.Sprite) {
+
+                _vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1);
+                _vector4.applyMatrix4(_viewProjectionMatrix);
+
+                pushPoint(_vector4, object, camera);
+
+            }
+
+        }
+
+        if (sortElements === true) {
+
+            _renderData.elements.sort(painterSort);
+
+        }
+
+        return _renderData;
+
+    };
+
+    function pushPoint(_vector4, object, camera) {
+
+        var invW = 1 / _vector4.w;
+
+        _vector4.z *= invW;
+
+        if (_vector4.z >= -1 && _vector4.z <= 1) {
+
+            _sprite = getNextSpriteInPool();
+            _sprite.id = object.id;
+            _sprite.x = _vector4.x * invW;
+            _sprite.y = _vector4.y * invW;
+            _sprite.z = _vector4.z;
+            _sprite.renderOrder = object.renderOrder;
+            _sprite.object = object;
+
+            _sprite.rotation = object.rotation;
+
+            _sprite.scale.x = object.scale.x * Math.abs(_sprite.x - (_vector4.x + camera.projectionMatrix.elements[0]) / (_vector4.w + camera.projectionMatrix.elements[12]));
+            _sprite.scale.y = object.scale.y * Math.abs(_sprite.y - (_vector4.y + camera.projectionMatrix.elements[5]) / (_vector4.w + camera.projectionMatrix.elements[13]));
+
+            _sprite.material = object.material;
+
+            _renderData.elements.push(_sprite);
+
+        }
+
+    }
+
+    // Pools
+
+    function getNextObjectInPool() {
+
+        if (_objectCount === _objectPoolLength) {
+
+            var object = new THREE.RenderableObject();
+            _objectPool.push(object);
+            _objectPoolLength++;
+            _objectCount++;
+            return object;
+
+        }
+
+        return _objectPool[_objectCount++];
+
+    }
+
+    function getNextVertexInPool() {
+
+        if (_vertexCount === _vertexPoolLength) {
+
+            var vertex = new THREE.RenderableVertex();
+            _vertexPool.push(vertex);
+            _vertexPoolLength++;
+            _vertexCount++;
+            return vertex;
+
+        }
+
+        return _vertexPool[_vertexCount++];
+
+    }
+
+    function getNextFaceInPool() {
+
+        if (_faceCount === _facePoolLength) {
+
+            var face = new THREE.RenderableFace();
+            _facePool.push(face);
+            _facePoolLength++;
+            _faceCount++;
+            return face;
+
+        }
+
+        return _facePool[_faceCount++];
+
+
+    }
+
+    function getNextLineInPool() {
+
+        if (_lineCount === _linePoolLength) {
+
+            var line = new THREE.RenderableLine();
+            _linePool.push(line);
+            _linePoolLength++;
+            _lineCount++;
+            return line;
+
+        }
+
+        return _linePool[_lineCount++];
+
+    }
+
+    function getNextSpriteInPool() {
+
+        if (_spriteCount === _spritePoolLength) {
+
+            var sprite = new THREE.RenderableSprite();
+            _spritePool.push(sprite);
+            _spritePoolLength++;
+            _spriteCount++;
+            return sprite;
+
+        }
+
+        return _spritePool[_spriteCount++];
+
+    }
+
+    //
+
+    function painterSort(a, b) {
+
+        if (a.renderOrder !== b.renderOrder) {
+
+            return a.renderOrder - b.renderOrder;
+
+        } else if (a.z !== b.z) {
+
+            return b.z - a.z;
+
+        } else if (a.id !== b.id) {
+
+            return a.id - b.id;
+
+        } else {
+
+            return 0;
+
+        }
+
+    }
+
+    function clipLine(s1, s2) {
+
+        var alpha1 = 0, alpha2 = 1,
+
+            // Calculate the boundary coordinate of each vertex for the near and far clip planes,
+            // Z = -1 and Z = +1, respectively.
+
+            bc1near = s1.z + s1.w,
+            bc2near = s2.z + s2.w,
+            bc1far = -s1.z + s1.w,
+            bc2far = -s2.z + s2.w;
+
+        if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) {
+
+            // Both vertices lie entirely within all clip planes.
+            return true;
+
+        } else if ((bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0)) {
+
+            // Both vertices lie entirely outside one of the clip planes.
+            return false;
+
+        } else {
+
+            // The line segment spans at least one clip plane.
+
+            if (bc1near < 0) {
+
+                // v1 lies outside the near plane, v2 inside
+                alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near));
+
+            } else if (bc2near < 0) {
+
+                // v2 lies outside the near plane, v1 inside
+                alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near));
+
+            }
+
+            if (bc1far < 0) {
+
+                // v1 lies outside the far plane, v2 inside
+                alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far));
+
+            } else if (bc2far < 0) {
+
+                // v2 lies outside the far plane, v2 inside
+                alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far));
+
+            }
+
+            if (alpha2 < alpha1) {
+
+                // The line segment spans two boundaries, but is outside both of them.
+                // (This can't happen when we're only clipping against just near/far but good
+                //  to leave the check here for future usage if other clip planes are added.)
+                return false;
+
+            } else {
+
+                // Update the s1 and s2 vertices to match the clipped line segment.
+                s1.lerp(s2, alpha1);
+                s2.lerp(s1, 1 - alpha2);
+
+                return true;
+
+            }
+
+        }
+
+    }
+
+};