From 97b681e6dddd3a8cf57e2e83ed5275ef034b72a0 Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Tue, 5 Aug 2014 06:52:57 -0300 Subject: [PATCH 01/15] Added a point using a THREE JS line --- assets/globe/globe2.js | 294 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 assets/globe/globe2.js diff --git a/assets/globe/globe2.js b/assets/globe/globe2.js new file mode 100644 index 0000000..601b778 --- /dev/null +++ b/assets/globe/globe2.js @@ -0,0 +1,294 @@ +var DAT = DAT || {}; + +DAT.Globe = function(container, opts) { + opts = opts || {}; + + var imgDir = opts.imgDir || '/globe/'; + + var Shaders = { + 'earth' : { + uniforms: { + 'texture': { type: 't', value: null } + }, + vertexShader: [ + 'varying vec3 vNormal;', + 'varying vec2 vUv;', + 'void main() {', + 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + 'vNormal = normalize( normalMatrix * normal );', + 'vUv = uv;', + '}' + ].join('\n'), + fragmentShader: [ + 'uniform sampler2D texture;', + 'varying vec3 vNormal;', + 'varying vec2 vUv;', + 'void main() {', + 'vec3 diffuse = texture2D( texture, vUv ).xyz;', + 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );', + 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );', + 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );', + '}' + ].join('\n') + }, + 'atmosphere' : { + uniforms: {}, + vertexShader: [ + 'varying vec3 vNormal;', + 'void main() {', + 'vNormal = normalize( normalMatrix * normal );', + 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + '}' + ].join('\n'), + fragmentShader: [ + 'varying vec3 vNormal;', + 'void main() {', + 'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 );', + 'gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;', + '}' + ].join('\n') + } + }; + + var camera, scene, renderer, w, h; + var mesh, atmosphere, point; + + var overRenderer; + + var curZoomSpeed = 0; + var zoomSpeed = 50; + + var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 }; + var rotation = { x: 0, y: 0 }, + target = { x: Math.PI*3/2, y: Math.PI / 6.0 }, + targetOnDown = { x: 0, y: 0 }; + + var distance = 100000, distanceTarget = 100000; + var padding = 40; + var PI_HALF = Math.PI / 2; + + + function init() { + + container.style.color = '#fff'; + container.style.font = '13px/20px Arial, sans-serif'; + + var shader, uniforms, material; + w = container.offsetWidth || window.innerWidth; + h = container.offsetHeight || window.innerHeight; + + camera = new THREE.PerspectiveCamera(30, w / h, 1, 10000); + camera.position.z = distance; + + scene = new THREE.Scene(); + + var geometry = new THREE.SphereGeometry(200, 40, 30); + + shader = Shaders['earth']; + uniforms = THREE.UniformsUtils.clone(shader.uniforms); + + uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg'); + + material = new THREE.ShaderMaterial({ + + uniforms: uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + + }); + + mesh = new THREE.Mesh(geometry, material); + mesh.rotation.y = Math.PI; + scene.add(mesh); + + shader = Shaders['atmosphere']; + uniforms = THREE.UniformsUtils.clone(shader.uniforms); + + material = new THREE.ShaderMaterial({ + + uniforms: uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: THREE.BackSide, + blending: THREE.AdditiveBlending, + transparent: true + + }); + + mesh = new THREE.Mesh(geometry, material); + mesh.scale.set( 1.1, 1.1, 1.1 ); + scene.add(mesh); + + geometry = new THREE.CubeGeometry(0.75, 0.75, 1); + geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-0.5)); + + point = new THREE.Mesh(geometry); + + renderer = new THREE.WebGLRenderer({antialias: true}); + renderer.setSize(w, h); + + renderer.domElement.style.position = 'absolute'; + + container.appendChild(renderer.domElement); + + container.addEventListener('mousedown', onMouseDown, false); + + container.addEventListener('mousewheel', onMouseWheel, false); + + document.addEventListener('keydown', onDocumentKeyDown, false); + + window.addEventListener('resize', onWindowResize, false); + + container.addEventListener('mouseover', function() { + overRenderer = true; + }, false); + + container.addEventListener('mouseout', function() { + overRenderer = false; + }, false); + } + + + + function onMouseDown(event) { + event.preventDefault(); + + container.addEventListener('mousemove', onMouseMove, false); + container.addEventListener('mouseup', onMouseUp, false); + container.addEventListener('mouseout', onMouseOut, false); + + mouseOnDown.x = - event.clientX; + mouseOnDown.y = event.clientY; + + targetOnDown.x = target.x; + targetOnDown.y = target.y; + + container.style.cursor = 'move'; + } + + function onMouseMove(event) { + mouse.x = - event.clientX; + mouse.y = event.clientY; + + var zoomDamp = distance/1000; + + target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp; + target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp; + + target.y = target.y > PI_HALF ? PI_HALF : target.y; + target.y = target.y < - PI_HALF ? - PI_HALF : target.y; + } + + function onMouseUp(event) { + container.removeEventListener('mousemove', onMouseMove, false); + container.removeEventListener('mouseup', onMouseUp, false); + container.removeEventListener('mouseout', onMouseOut, false); + container.style.cursor = 'auto'; + } + + function onMouseOut(event) { + container.removeEventListener('mousemove', onMouseMove, false); + container.removeEventListener('mouseup', onMouseUp, false); + container.removeEventListener('mouseout', onMouseOut, false); + } + + function onMouseWheel(event) { + event.preventDefault(); + if (overRenderer) { + zoom(event.wheelDeltaY * 0.3); + } + return false; + } + + + function onDocumentKeyDown(event) { + switch (event.keyCode) { + case 38: + zoom(100); + event.preventDefault(); + break; + case 40: + zoom(-100); + event.preventDefault(); + break; + } + } + + function onWindowResize( event ) { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize( window.innerWidth, window.innerHeight ); + } + + + function zoom(delta) { + distanceTarget -= delta; + distanceTarget = distanceTarget > 1000 ? 1000 : distanceTarget; + distanceTarget = distanceTarget < 350 ? 350 : distanceTarget; + } + + function animate() { + requestAnimationFrame(animate); + render(); + } + + + function render() { + zoom(curZoomSpeed); + + rotation.x += (target.x - rotation.x) * 0.1; + rotation.y += (target.y - rotation.y) * 0.1; + distance += (distanceTarget - distance) * 0.3; + + camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y); + camera.position.y = distance * Math.sin(rotation.y); + camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y); + + camera.lookAt(mesh.position); + + renderer.render(scene, camera); + } + + + this.addData = function(arr){ + for(var k=0; k< 2; k++){ + //-32.9479009,-60.6650597, 0.7 + var lat = -32.9479009+k, lng = -60.6650597+k, mag = 100, + phi = (90 - lat) * Math.PI / 180, + theta = (180 - lng) * Math.PI / 180, + + x = 300 * Math.sin(phi) * Math.cos(theta), + y = 300 * Math.cos(phi), + z = 300 * Math.sin(phi) * Math.sin(theta), + vertex = new THREE.Vector3(x, y, z); + + console.log(lat); + + window.geometry = new THREE.CubeGeometry(0.75, 0.75, 1);; + geometry.vertices.push( + vertex + ); + + var line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x2685AA, opacity: 1 } ) ); + scene.add(line); + + window.scene = scene; + window.vertex = vertex; + window.geo = geometry; + + // Changing this we update + // geo.verticesNeedUpdate + } + + + } + + this.animate = animate; + + this.resetData = function(){ + console.log('resetdata'); + } + + init(); + +} \ No newline at end of file From 8b5cdc98be1464a7be7282e2c70d0865c2a5c18a Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Mon, 11 Aug 2014 18:01:50 -0300 Subject: [PATCH 02/15] Refactored the globe api. --- assets/globe/boot.js | 53 ++++---- assets/globe/globe.js | 207 +++++++---------------------- assets/globe/globe2.js | 294 ----------------------------------------- assets/js/app.js | 1 + 4 files changed, 76 insertions(+), 479 deletions(-) delete mode 100644 assets/globe/globe2.js diff --git a/assets/globe/boot.js b/assets/globe/boot.js index fe8175a..da622d0 100644 --- a/assets/globe/boot.js +++ b/assets/globe/boot.js @@ -5,19 +5,26 @@ var container = document.getElementById('container'); var globe = new DAT.Globe(container); - var i, tweens = []; + var i ; - var settime = function(globe, t) { - new TWEEN.Tween(globe).to({time: t},500).easing(TWEEN.Easing.Cubic.EaseOut).start(); + globe.animate(); + + globe.locations = []; + + globe.parsePoints = function(list){ + console.log(JSON.stringify(list)); + this.locations = this.locations.concat(list); + for (var i = 0; i < list.length; i++) { + this.addPoint(list[i]); + } + this.updatePoints(); }; - TWEEN.start(); - globe.animate(); - globe.locations = []; globe.addPoint = function(obj){ + for (var i = 0; i < this.locations.length; i++) { if(this.locations[i].id == obj.id){ this.locations[i].timestamps = obj.timestamps; @@ -31,48 +38,48 @@ globe.updatePoints = function(){ var all = [], magnitude, ts, now = Date.now(), msecs, locationsKeep = [], timestampsKeep; - this.resetData(); - /** Loop over all of the locations to see if they have valid timestamps. */ for (var i = 0; i < this.locations.length; i++) { magnitude = 0; timestampsKeep = []; - /** - * Check each timestamp in the location and see if it is recent - * enough to be rendered. - */ + for (var k = 0; k < this.locations[i].timestamps.length; k++) { ts = this.locations[i].timestamps[k]; msecs = now - ts; if(5*60*1000 > msecs){ magnitude += 1-msecs/5/60/1000; - /** Only keep relevant timestamps for later. */ timestampsKeep.push(ts); } } if (magnitude) { - magnitude = magnitude/this.locations[i].timestamps.length; - all.push(this.locations[i].latitude, this.locations[i].longitude, magnitude); - /** Overwrite the old timestamps with relevant ones. */ + this.locations[i].magnitude = magnitude/this.locations[i].timestamps.length; + this.addData(this.locations[i]); + this.locations[i].timestamps = timestampsKeep; - /** Save locations with visible timestamps for later. */ locationsKeep.push(this.locations[i]); } } - this.addData(all, {format: 'magnitude', animated: true}); - this.createPoints(); - settime(globe, 0); - /** Overwrite the old locations with the useful ones. */ + + this.locations = locationsKeep; }; globe.parsePoints = function(list){ - this.locations.concat(list); + console.log(list); + this.locations = this.locations.concat(list); for (var i = 0; i < list.length; i++) { this.addPoint(list[i]); } - this.updatePoints(); + }; + +globe.autoUpdate = function() { + window.requestAnimationFrame(function() { + globe.updatePoints(); + setTimeout(function() {globe.autoUpdate()}, 1000); + }); +}; + document.body.style.backgroundImage = 'none'; // remove loading } diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 152782f..b7f4c3d 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -1,26 +1,8 @@ -/** -* dat.globe Javascript WebGL Globe Toolkit -* http://dataarts.github.com/dat.globe -* -* Copyright 2011 Data Arts Team, Google Creative Lab -* -* Licensed under the Apache License, Version 2.0 (the 'License'); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -*/ - var DAT = DAT || {}; DAT.Globe = function(container, opts) { opts = opts || {}; - var colorFn = opts.colorFn || function(x) { - var c = new THREE.Color(); - c.setHSL( ( 0.6 - ( x * 0.5 ) ), 1.0, 0.5 ); - return c; - }; var imgDir = opts.imgDir || '/globe/'; var Shaders = { @@ -85,6 +67,7 @@ DAT.Globe = function(container, opts) { var padding = 40; var PI_HALF = Math.PI / 2; + function init() { container.style.color = '#fff'; @@ -165,105 +148,7 @@ DAT.Globe = function(container, opts) { }, false); } - addData = function(data, opts) { - var lat, lng, size, color, i, step, colorFnWrapper; - - opts.animated = opts.animated || false; - this.is_animated = opts.animated; - opts.format = opts.format || 'magnitude'; // other option is 'legend' - if (opts.format === 'magnitude') { - step = 3; - colorFnWrapper = function(data, i) { return colorFn(data[i+2]); } - } else if (opts.format === 'legend') { - step = 4; - colorFnWrapper = function(data, i) { return colorFn(data[i+3]); } - } else { - throw('error: format not supported: '+opts.format); - } - - if (opts.animated) { - if (this._baseGeometry === undefined) { - this._baseGeometry = new THREE.Geometry(); - for (i = 0; i < data.length; i += step) { - lat = data[i]; - lng = data[i + 1]; -// size = data[i + 2]; - color = colorFnWrapper(data,i); - size = 0; - addPoint(lat, lng, size, color, this._baseGeometry); - } - } - if(this._morphTargetId === undefined) { - this._morphTargetId = 0; - } else { - this._morphTargetId += 1; - } - opts.name = opts.name || 'morphTarget'+this._morphTargetId; - } - var subgeo = new THREE.Geometry(); - for (i = 0; i < data.length; i += step) { - lat = data[i]; - lng = data[i + 1]; - color = colorFnWrapper(data,i); - size = data[i + 2]; - size = size*200; - addPoint(lat, lng, size, color, subgeo); - } - if (opts.animated) { - this._baseGeometry.morphTargets.push({'name': opts.name, vertices: subgeo.vertices}); - } else { - this._baseGeometry = subgeo; - } - - }; - - function createPoints() { - if (this._baseGeometry !== undefined) { - if (this.is_animated === false) { - this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({ - color: 0xffffff, - vertexColors: THREE.FaceColors, - morphTargets: false - })); - } else { - if (this._baseGeometry.morphTargets.length < 8) { - var padding = 8-this._baseGeometry.morphTargets.length; - for(var i=0; i<=padding; i++) { - this._baseGeometry.morphTargets.push({'name': 'morphPadding'+i, vertices: this._baseGeometry.vertices}); - } - } - this.points = new THREE.Mesh(this._baseGeometry, new THREE.MeshBasicMaterial({ - color: 0xffffff, - vertexColors: THREE.FaceColors, - morphTargets: true - })); - } - scene.add(this.points); - } - } - - function addPoint(lat, lng, size, color, subgeo) { - - var phi = (90 - lat) * Math.PI / 180; - var theta = (180 - lng) * Math.PI / 180; - - point.position.x = 200 * Math.sin(phi) * Math.cos(theta); - point.position.y = 200 * Math.cos(phi); - point.position.z = 200 * Math.sin(phi) * Math.sin(theta); - - point.lookAt(mesh.position); - - point.scale.z = Math.max( size, 0.1 ); // avoid non-invertible matrix - point.updateMatrix(); - - for (var i = 0; i < point.geometry.faces.length; i++) { - point.geometry.faces[i].color = color; - - } - - THREE.GeometryUtils.merge(subgeo, point); - } function onMouseDown(event) { event.preventDefault(); @@ -315,6 +200,7 @@ DAT.Globe = function(container, opts) { return false; } + function onDocumentKeyDown(event) { switch (event.keyCode) { case 38: @@ -334,6 +220,7 @@ DAT.Globe = function(container, opts) { renderer.setSize( window.innerWidth, window.innerHeight ); } + function zoom(delta) { distanceTarget -= delta; distanceTarget = distanceTarget > 1000 ? 1000 : distanceTarget; @@ -345,6 +232,7 @@ DAT.Globe = function(container, opts) { render(); } + function render() { zoom(curZoomSpeed); @@ -361,56 +249,51 @@ DAT.Globe = function(container, opts) { renderer.render(scene, camera); } - function resetData() - { - if (this.points === undefined) return; - this.is_animated = false; - - scene.remove(this.points); - delete this.points; - delete this._morphTargetId; - delete this._baseGeometry; + this.addData = function(point){ + //-32.9479009,-60.6650597, 0.7 + var lat = point.latitude, lng = point.longitude, mag = point.magnitude, + phi = (90 - lat) * Math.PI / 180, + theta = (180 - lng) * Math.PI / 180, + scale = 200*(1+point.magnitude), + line, geometry, + x = scale * Math.sin(phi) * Math.cos(theta), + y = scale * Math.cos(phi), + z = scale * Math.sin(phi) * Math.sin(theta), + vertex, material; + + if (undefined === point.vertex){ + vertex = new THREE.Vector3(x, y, z); + geometry = new THREE.CubeGeometry(0.75, 0.75, 1); + geometry.vertices.push( + vertex + ); + + line = new THREE.Line( geometry, material = new THREE.LineBasicMaterial( { color: 0x2685AA, opacity: 1 } ) ); + + scene.add(line); + point.vertex = vertex; + point.geo = geometry; + point.material = material; + }else{ + var c = new THREE.Color(); + c.setHSL( ( 0.6 - ( point.magnitude * 0.5 ) ), 1.0, 0.5 ); + point.material.color = c; + + point.vertex.setX(x); + point.vertex.setY(y); + point.vertex.setZ(z); + point.geo.verticesNeedUpdate = true; + window.point = point; + } } - init(); this.animate = animate; + this.resetData = function(){ + console.log('resetdata'); + } - this.__defineGetter__('time', function() { - return this._time || 0; - }); - - this.__defineSetter__('time', function(t) { - var validMorphs = []; - var morphDict = this.points.morphTargetDictionary; - for(var k in morphDict) { - if(k.indexOf('morphPadding') < 0) { - validMorphs.push(morphDict[k]); - } - } - validMorphs.sort(); - var l = validMorphs.length-1; - var scaledt = t*l+1; - var index = Math.floor(scaledt); - for (i=0;i= 0) { - this.points.morphTargetInfluences[lastIndex] = 1 - leftover; - } - this.points.morphTargetInfluences[index] = leftover; - this._time = t; - }); - - this.addData = addData; - this.createPoints = createPoints; - this.renderer = renderer; - this.scene = scene; - this.resetData = resetData; - - return this; + init(); -}; +} \ No newline at end of file diff --git a/assets/globe/globe2.js b/assets/globe/globe2.js deleted file mode 100644 index 601b778..0000000 --- a/assets/globe/globe2.js +++ /dev/null @@ -1,294 +0,0 @@ -var DAT = DAT || {}; - -DAT.Globe = function(container, opts) { - opts = opts || {}; - - var imgDir = opts.imgDir || '/globe/'; - - var Shaders = { - 'earth' : { - uniforms: { - 'texture': { type: 't', value: null } - }, - vertexShader: [ - 'varying vec3 vNormal;', - 'varying vec2 vUv;', - 'void main() {', - 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - 'vNormal = normalize( normalMatrix * normal );', - 'vUv = uv;', - '}' - ].join('\n'), - fragmentShader: [ - 'uniform sampler2D texture;', - 'varying vec3 vNormal;', - 'varying vec2 vUv;', - 'void main() {', - 'vec3 diffuse = texture2D( texture, vUv ).xyz;', - 'float intensity = 1.05 - dot( vNormal, vec3( 0.0, 0.0, 1.0 ) );', - 'vec3 atmosphere = vec3( 1.0, 1.0, 1.0 ) * pow( intensity, 3.0 );', - 'gl_FragColor = vec4( diffuse + atmosphere, 1.0 );', - '}' - ].join('\n') - }, - 'atmosphere' : { - uniforms: {}, - vertexShader: [ - 'varying vec3 vNormal;', - 'void main() {', - 'vNormal = normalize( normalMatrix * normal );', - 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - '}' - ].join('\n'), - fragmentShader: [ - 'varying vec3 vNormal;', - 'void main() {', - 'float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 0, 1.0 ) ), 12.0 );', - 'gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity;', - '}' - ].join('\n') - } - }; - - var camera, scene, renderer, w, h; - var mesh, atmosphere, point; - - var overRenderer; - - var curZoomSpeed = 0; - var zoomSpeed = 50; - - var mouse = { x: 0, y: 0 }, mouseOnDown = { x: 0, y: 0 }; - var rotation = { x: 0, y: 0 }, - target = { x: Math.PI*3/2, y: Math.PI / 6.0 }, - targetOnDown = { x: 0, y: 0 }; - - var distance = 100000, distanceTarget = 100000; - var padding = 40; - var PI_HALF = Math.PI / 2; - - - function init() { - - container.style.color = '#fff'; - container.style.font = '13px/20px Arial, sans-serif'; - - var shader, uniforms, material; - w = container.offsetWidth || window.innerWidth; - h = container.offsetHeight || window.innerHeight; - - camera = new THREE.PerspectiveCamera(30, w / h, 1, 10000); - camera.position.z = distance; - - scene = new THREE.Scene(); - - var geometry = new THREE.SphereGeometry(200, 40, 30); - - shader = Shaders['earth']; - uniforms = THREE.UniformsUtils.clone(shader.uniforms); - - uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg'); - - material = new THREE.ShaderMaterial({ - - uniforms: uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - - }); - - mesh = new THREE.Mesh(geometry, material); - mesh.rotation.y = Math.PI; - scene.add(mesh); - - shader = Shaders['atmosphere']; - uniforms = THREE.UniformsUtils.clone(shader.uniforms); - - material = new THREE.ShaderMaterial({ - - uniforms: uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: THREE.BackSide, - blending: THREE.AdditiveBlending, - transparent: true - - }); - - mesh = new THREE.Mesh(geometry, material); - mesh.scale.set( 1.1, 1.1, 1.1 ); - scene.add(mesh); - - geometry = new THREE.CubeGeometry(0.75, 0.75, 1); - geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-0.5)); - - point = new THREE.Mesh(geometry); - - renderer = new THREE.WebGLRenderer({antialias: true}); - renderer.setSize(w, h); - - renderer.domElement.style.position = 'absolute'; - - container.appendChild(renderer.domElement); - - container.addEventListener('mousedown', onMouseDown, false); - - container.addEventListener('mousewheel', onMouseWheel, false); - - document.addEventListener('keydown', onDocumentKeyDown, false); - - window.addEventListener('resize', onWindowResize, false); - - container.addEventListener('mouseover', function() { - overRenderer = true; - }, false); - - container.addEventListener('mouseout', function() { - overRenderer = false; - }, false); - } - - - - function onMouseDown(event) { - event.preventDefault(); - - container.addEventListener('mousemove', onMouseMove, false); - container.addEventListener('mouseup', onMouseUp, false); - container.addEventListener('mouseout', onMouseOut, false); - - mouseOnDown.x = - event.clientX; - mouseOnDown.y = event.clientY; - - targetOnDown.x = target.x; - targetOnDown.y = target.y; - - container.style.cursor = 'move'; - } - - function onMouseMove(event) { - mouse.x = - event.clientX; - mouse.y = event.clientY; - - var zoomDamp = distance/1000; - - target.x = targetOnDown.x + (mouse.x - mouseOnDown.x) * 0.005 * zoomDamp; - target.y = targetOnDown.y + (mouse.y - mouseOnDown.y) * 0.005 * zoomDamp; - - target.y = target.y > PI_HALF ? PI_HALF : target.y; - target.y = target.y < - PI_HALF ? - PI_HALF : target.y; - } - - function onMouseUp(event) { - container.removeEventListener('mousemove', onMouseMove, false); - container.removeEventListener('mouseup', onMouseUp, false); - container.removeEventListener('mouseout', onMouseOut, false); - container.style.cursor = 'auto'; - } - - function onMouseOut(event) { - container.removeEventListener('mousemove', onMouseMove, false); - container.removeEventListener('mouseup', onMouseUp, false); - container.removeEventListener('mouseout', onMouseOut, false); - } - - function onMouseWheel(event) { - event.preventDefault(); - if (overRenderer) { - zoom(event.wheelDeltaY * 0.3); - } - return false; - } - - - function onDocumentKeyDown(event) { - switch (event.keyCode) { - case 38: - zoom(100); - event.preventDefault(); - break; - case 40: - zoom(-100); - event.preventDefault(); - break; - } - } - - function onWindowResize( event ) { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize( window.innerWidth, window.innerHeight ); - } - - - function zoom(delta) { - distanceTarget -= delta; - distanceTarget = distanceTarget > 1000 ? 1000 : distanceTarget; - distanceTarget = distanceTarget < 350 ? 350 : distanceTarget; - } - - function animate() { - requestAnimationFrame(animate); - render(); - } - - - function render() { - zoom(curZoomSpeed); - - rotation.x += (target.x - rotation.x) * 0.1; - rotation.y += (target.y - rotation.y) * 0.1; - distance += (distanceTarget - distance) * 0.3; - - camera.position.x = distance * Math.sin(rotation.x) * Math.cos(rotation.y); - camera.position.y = distance * Math.sin(rotation.y); - camera.position.z = distance * Math.cos(rotation.x) * Math.cos(rotation.y); - - camera.lookAt(mesh.position); - - renderer.render(scene, camera); - } - - - this.addData = function(arr){ - for(var k=0; k< 2; k++){ - //-32.9479009,-60.6650597, 0.7 - var lat = -32.9479009+k, lng = -60.6650597+k, mag = 100, - phi = (90 - lat) * Math.PI / 180, - theta = (180 - lng) * Math.PI / 180, - - x = 300 * Math.sin(phi) * Math.cos(theta), - y = 300 * Math.cos(phi), - z = 300 * Math.sin(phi) * Math.sin(theta), - vertex = new THREE.Vector3(x, y, z); - - console.log(lat); - - window.geometry = new THREE.CubeGeometry(0.75, 0.75, 1);; - geometry.vertices.push( - vertex - ); - - var line = new THREE.Line( geometry, new THREE.LineBasicMaterial( { color: 0x2685AA, opacity: 1 } ) ); - scene.add(line); - - window.scene = scene; - window.vertex = vertex; - window.geo = geometry; - - // Changing this we update - // geo.verticesNeedUpdate - } - - - } - - this.animate = animate; - - this.resetData = function(){ - console.log('resetdata'); - } - - init(); - -} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js index 2fc354c..441e9dd 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -23,6 +23,7 @@ // The globe just connected to sails. Let's get the data! socket.get('/ready', function(res) { globe.parsePoints(res); + globe.autoUpdate(); }); }); From 4773f5a007633a556f2c0dc2f925c11b0f3f659c Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Mon, 11 Aug 2014 18:43:49 -0300 Subject: [PATCH 03/15] Added comments for globe.addData method --- assets/globe/globe.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 9df0771..a4d2d4f 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -296,6 +296,14 @@ DAT.Globe = function(container, opts) { this.locations = locationsKeep; }; + /** + * Receives a point object and represent it on the globe. If the point already existed, + * it updates its color and size. + * Point is an object with the following attributes: + * - latitude + * - longitude + * - magnitude + */ this.addData = function(point){ //-32.9479009,-60.6650597, 0.7 var lat = point.latitude, lng = point.longitude, mag = point.magnitude, @@ -312,6 +320,7 @@ DAT.Globe = function(container, opts) { color.setHSL( ( 0.6 - ( point.magnitude * 0.5 ) ), 1.0, 0.5 ); if (undefined === point.vertex){ + // the point is not in the globe yet vertex = new THREE.Vector3(x, y, z); geometry = new THREE.CubeGeometry(0.75, 0.75, 1); geometry.vertices.push( @@ -325,13 +334,12 @@ DAT.Globe = function(container, opts) { point.geo = geometry; point.material = material; }else{ - + // The point is already in the globe point.material.color = color; point.vertex.setX(x); point.vertex.setY(y); point.vertex.setZ(z); point.geo.verticesNeedUpdate = true; - window.point = point; } } From 4550051a0a75c83ab0b978d1f31eeb24001c4b9c Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Mon, 25 Aug 2014 19:09:44 -0300 Subject: [PATCH 04/15] Fancy lines --- assets/globe/fancyLines.js | 53 ++++++++++++++++++++++++++++++++++++++ assets/globe/globe.js | 3 +++ 2 files changed, 56 insertions(+) create mode 100644 assets/globe/fancyLines.js diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js new file mode 100644 index 0000000..c3cc65b --- /dev/null +++ b/assets/globe/fancyLines.js @@ -0,0 +1,53 @@ +var step = 10; + +var textures = {}; + + +Particle = function(pointData, x, y, z){ + var vertex, geometry, particles, cx=0, cy=0, cz=0; + + this.inicialize =function(){ + vertex = new THREE.Vector3(0, 0, 0); + geometry = new THREE.Geometry(); + geometry.vertices.push(vertex); + + particleMaterial = new THREE.ParticleBasicMaterial({ + size: 20, + color: 0xffffff + }); + + particles = new THREE.ParticleSystem(geometry, particleMaterial); + + lineMaterial = new THREE.LineBasicMaterial({ + color: 0xffffff, + opacity: 0.4, + linewidth: 1 + }); + + particles.dynamic = true; + } + + this.getParticles = function(){ + return particles; + } + + this.updateParticle = function(nx,ny,nz){ + vertex.setX(nx); + vertex.setY(ny); + vertex.setZ(nz); + geometry.verticesNeedUpdate = true; + } + + this.inicialize(); +} + +DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ + var particle = new Particle(pointData, x,y,z); + textures[pointData.id] = particle; + scene.add(particle.getParticles()); +} + +DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z){ + var particle = textures[pointData.id]; + particle.updateParticle(x,y,z); +} \ No newline at end of file diff --git a/assets/globe/globe.js b/assets/globe/globe.js index a4d2d4f..9e223c1 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -82,6 +82,7 @@ DAT.Globe = function(container, opts) { camera.position.z = distance; scene = new THREE.Scene(); + this.scene = scene; var geometry = new THREE.SphereGeometry(200, 40, 30); @@ -328,6 +329,7 @@ DAT.Globe = function(container, opts) { ); line = new THREE.Line( geometry, material = new THREE.LineBasicMaterial( { color: color, opacity: 1 } ) ); + this.addLineTexture(point, x,y,z); scene.add(line); point.vertex = vertex; @@ -340,6 +342,7 @@ DAT.Globe = function(container, opts) { point.vertex.setY(y); point.vertex.setZ(z); point.geo.verticesNeedUpdate = true; + this.updateLineTexture(point,x,y,z); } } From 9f09c5e3b4ab153e71a1f7edf2330dc2ac431c4f Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Tue, 26 Aug 2014 00:13:30 -0300 Subject: [PATCH 05/15] Animated particles --- assets/globe/fancyLines.js | 58 ++++++++++++++++++++++++-------------- assets/globe/globe.js | 2 +- views/globe/main.ejs | 38 +++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index c3cc65b..804f1da 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -1,10 +1,12 @@ -var step = 10; +var step = 1, // speed of the particles + globeWidth=200; // width of the globe: + // if 200, particles start from the surface of the globe + // if 0, the particles start from the center of the globe -var textures = {}; Particle = function(pointData, x, y, z){ - var vertex, geometry, particles, cx=0, cy=0, cz=0; + var vertex, geometry, particles, cx=0, cy=0, cz= 0, currentStep=globeWidth; this.inicialize =function(){ vertex = new THREE.Vector3(0, 0, 0); @@ -18,36 +20,50 @@ Particle = function(pointData, x, y, z){ particles = new THREE.ParticleSystem(geometry, particleMaterial); - lineMaterial = new THREE.LineBasicMaterial({ - color: 0xffffff, - opacity: 0.4, - linewidth: 1 - }); - particles.dynamic = true; } - this.getParticles = function(){ - return particles; - } + this.updateParticle = function(bx,by,bz, max){ + var cx,cy,cz, d; + currentStep +=step; + + if(max == globeWidth){ + // the line stopped moving + currentStep = globeWidth; + }else if(currentStep> max){ + // The particle exceeded the line's height + currentStep=globeWidth; + } + + d = Math.pow(currentStep,2)/(Math.pow(bx,2) + Math.pow(by,2)+ Math.pow(bz,2)); - this.updateParticle = function(nx,ny,nz){ - vertex.setX(nx); - vertex.setY(ny); - vertex.setZ(nz); + cx = d*bx; + cy = d*by; + cz = d*bz; + + vertex.setX(cx); + vertex.setY(cy); + vertex.setZ(cz); geometry.verticesNeedUpdate = true; } + + this.getParticles = function(){ + return particles; + } + this.inicialize(); } +var textures = {}; + DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ - var particle = new Particle(pointData, x,y,z); - textures[pointData.id] = particle; - scene.add(particle.getParticles()); + var particle = new Particle(pointData, x,y,z); + textures[pointData.id] = particle; + scene.add(particle.getParticles()); } -DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z){ +DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z, max){ var particle = textures[pointData.id]; - particle.updateParticle(x,y,z); + particle.updateParticle(x,y,z, max); } \ No newline at end of file diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 9e223c1..4b047e5 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -342,7 +342,7 @@ DAT.Globe = function(container, opts) { point.vertex.setY(y); point.vertex.setZ(z); point.geo.verticesNeedUpdate = true; - this.updateLineTexture(point,x,y,z); + this.updateLineTexture(point,x,y,z, scale); } } diff --git a/views/globe/main.ejs b/views/globe/main.ejs index e8d4501..1967d2a 100644 --- a/views/globe/main.ejs +++ b/views/globe/main.ejs @@ -27,12 +27,50 @@ This is a Chrome Experiment + + + + + + + + From 05b75d2d671918c9a7611f30b7d6adba212bf9bd Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Tue, 26 Aug 2014 11:36:06 -0300 Subject: [PATCH 06/15] Fancy particles --- assets/globe/fancyLines.js | 13 +++++++++---- assets/images/particleA.png | Bin 0 -> 37672 bytes 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100755 assets/images/particleA.png diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index 804f1da..848ae46 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -1,4 +1,4 @@ -var step = 1, // speed of the particles +var step = 3, // speed of the particles globeWidth=200; // width of the globe: // if 200, particles start from the surface of the globe // if 0, the particles start from the center of the globe @@ -14,8 +14,13 @@ Particle = function(pointData, x, y, z){ geometry.vertices.push(vertex); particleMaterial = new THREE.ParticleBasicMaterial({ - size: 20, - color: 0xffffff + size: 100, + color: 0xffffff, + map: THREE.ImageUtils.loadTexture( + "images/particleA.png" + ), + blending: THREE.AdditiveBlending, + transparent: true }); particles = new THREE.ParticleSystem(geometry, particleMaterial); @@ -66,4 +71,4 @@ DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z, max){ var particle = textures[pointData.id]; particle.updateParticle(x,y,z, max); -} \ No newline at end of file +} diff --git a/assets/images/particleA.png b/assets/images/particleA.png new file mode 100755 index 0000000000000000000000000000000000000000..b9a18be90a9e75ee2c0cddcf27abfa3c19f27c58 GIT binary patch literal 37672 zcmV)QK(xP!P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z004+nNklKXvh#e3(g^mlDt|5;5xw#NJIe#M?~<2(3G-eIk(s_VK9=DOJ*?(l?VkNS`8 z2IfDn>3?gx$8EvC_!|Y^{`ljMpFVwBGaCQ-vG7wAlUETM?D$sYZyufivz`z>6tk{^ z2mrX|2JQp>9lC+}4`==FHvI*=ztiY(i-~`W_aXQ?dVbZ?i&6CM@vMIN<_(@OT<}$H z@O$$C^Dk%mxQ1f38U1E+$MWslJw~_qTh1l6Iz!GAzKa|D-co`24`Kc9GyOHYkH1_t z`Ypb>9RdNayU<1mRQeIBYtG;~3}(GUa|z)axxw$m2h8u>^cSrED@=c@-LJajJ!t{& zbsNOlMEbt-pIo){Bzb=bok1t7Pvbwvu2Hb{EpDKSzxz~R{*_FB!R}X$ehUP;!~C}Z zAT3|wj3<{d^8OGyLy{H}PrwvXy~7RuBOoI4`?UVAGJRYseDAUQWuwQntJi+<%>?Se zf$EBT_!%Qx4@AF(u9U!7$`Ief4gOg^VE&_6|6QiP26eXDJuVtGF4_9hBXJ;gaC7C0 z<1dgvAN4QBtTSXG(5ffs5>|Sb8>}Ise~J&7-@oYxR*&nq(ym(tG_@}@j^ZT;?1=BBC`6kof z6x0#RHofc;YZ>C5834UkhC%eigq}?An{L%yW{3d3{|+oCglu_`af9^Y&<%bkslfam ztp6^|gGIksGX1;kzGU>Pq5s>q!Z5CwG2<$-2>V?aMC zB3e!bznKr1f3E2-TK^)6iSIN0yX+p9QU6A@MxMk$yrXrDQ%o)7!r3LXkOn+wxYH9B z+#vogHTPviRIUnsGdN`aiPm2Z&YR#qzRUFUcE7CzSVnJ~DuFvnfEY3b-Q3AcHJ=83 ze~6tSObd&iaEBY{g1LJsLIXcuuL{1m518MT^{)i}Z)5t!c%OwlxM=ZrHNJ3uCBvUZ z3P`gV0CP{Ni~gXou?K0OloRfCgY^CkO*H(#`%=Mo^#Suwv;Gp|&jxh3r^DEhW1z{u&$2J$)axWPpwC;Ypw)iy!_`2<;kTd&i(; z&?q4!xG1WUQ_zzZ23d|MUlo+#(D(BJ^IHf04c5QX*uoWONTdE@$j|#u))|&9{$7LL z_@TNsaQfuYW23@J1Ea@T?B=8X`F6#iQcfJi1?^mwTmU_TjTaFShEK2o4t=Mx*!&Z% ze@ozxZoxHI!eUgOe7-9cK;5eNBl1Hr6pHM?ZCfGXEU4M zMGeD$zBtA?A!z6wewDmfBC8+&v&C_)Wv04~9s0IDV16gozeM$Hz&r)Ovxs(bw_rwL zhVL_XT$stcCj%fN`UUF73{Y~0q9!2IUczZv$dVV;udM_*<=XsXXJ?tyLdgq4YVTy6+#=>&V;5~+p)qrq?z zUT0NLE&~)ip_CBNFN<~L@mJiyH}*_%oTh>mcIZ3%fccKre<$qO9{5WPkLNNOmec=M zRsEN}#qO~&#*_xi+I@ISIU&UP0f%Q893tliSwvJ$ z1zX@yxl8c%K489M>|eM3{6Fsq{KW?)-%eK&(Q+M`wfhZo#u<$Fjtmf8If9X(J*>Wg zcr(2jqrS%z%5E^L8GPf%xqR^1(ru|A%MN8rf>;mrZG6D|cCo(zdloqUZGj*0PSc;t z(9{kV?S5UFopF*v&47S}Br2o1kF+pPbcrtiKGku(Wlp&H2;{RR(MEdf?@Km>ju@c_TWiyAwF zpBc35318y|L4CZK3Uc)?`VO2e39{YdZ|DQ&x3qpN)sTyU--qmc;wqW{c5?HYN ztkJKH+~>o8xaewy33Z>QF;(C90re9Uy_onz*%`{7aJL(HMTh%#)Val}V2L0a;gFXp zvL(Uy^#Svptbdu;-v;`FAin|gY%+cSYY}1QsVRmVOO?f#zj<54N+mqZ&Bs21lW$-S z2F?C3IfM5f|C$lVd$u0c0}E~t@ji%gDj0u2mLN*tP^cuzwUASK^jrCW`CVFnV%nLy zp-rA~?G6oBDwH?W{jBqzw>$4e^Ix-*5V;qQ&e|2>-Y+D!F-4 z#OheE`WL3ekEd)-{Q(2lm|9#q4Z0611I#6a``jP^J)YJ3RYA_~(SJeeF{B02#CP)n z^UtyV_racW;4hl{nj0Vh5sBz>YRdTJvf;0$00bn0^5=w*gFqTDff!w4(ml`qpwljh zG4gw5Kt3Uqa8c0>bZie`CKaR(Qm%|AlvD1MeIFk%-`e_@sm+a`|DAzCh!{G!o)i?r1BR)(S}qI6a?rA>DGL}o1Dz} zK&DmvZXb9r4GPO^bndeLyI{}Oz@IIQG0j5==;E;^F9Q}eMqA8%VVnTg1)+#&t&K*u zqEj$Nm%zL;Ok-i8Cqx(s)k`mP7t0Ipp&P8Hg3&jE@SkCawwJ}J55N{I^bIlq;>M+y z`0cEJ5$c3bmInU2On)R6HBCO5GJK)s0qY}UaU*~Tq}&P43Hm*Jq>)Q#$r)zh!r%!W zItqfn$K&KOfcJGN70jT=BZ$gyNOK0h-3Kzli1emazcBB#{uKNyv5N1r{va9L8~8C# zpO(nfaY`oV23YR%VQKBpn$~Sy4~J-~%6eU}tiB=ihm13%o-p5)B!opb@Xb7*3Wn+# zgH+%n8s8r_`@k&ST`G~Gs2g4O0rQ=$e@pDoVvnt`C%q7pOP5W5c+8|ZTay^&M4@>o zv$PHjw?LqvIhDTon)3(W84^zz(NUZbmfc{e7kq!nqoB}7CKL66j^8t$kk+CP#BH)~ z>jU@=3g1nf&tl12hCo{^eq#o}a?Tm}P$m)GWcRsuXL0*o3}Z}XKp~Vl#4M1hOyBpV zPcFS(A(*YzczKEj`%*L*lh^nOrg_Bq22Gwd)K`w)7u>efE?XxpJAnJJO=bG7&vwW( zb2^4@^90`(i&mOgI?d~16v(n+4?i!CbXmF*q=4g zElFSn^LYNSVET78l{4RFmU*oonQe>#Sj+$b#+cy(jeP^M)}8x|HRlOwLMXdIdQV`O zS~C5GXAFM&Y97e^xZLl#$pCaO#Oe(tER?8%Ydk$;b=m3bw%wq<~?MFyc!J;$dAWZBMD{c@3?P8Eme`r3rl>IT2 zZt^}5gS^N?AU8mh2;jd*;k&u&#V9~~Uzg&2>L6>F2NB?SI$kcHC z^>0Kzse{}Jd-8!l1U!gq1%~4zTqRKKDl9eoRzrW1$ChZKa2C=Zg_+vA(+BUUo3Ib& zbD{y2l(76GL*#R*>fi4*OH_RU^mxnE{qnid%oo#xW_i%8p_?e6h!+}LX1-S94pImf zJH)si{_i9`^NIvN^v2eY8%y3i!-E(-K15mbOFAA;*UM$~2hq4T3o|^{mSIlVGB0V2 zLBy`>N*s_QNv-RYC1^K9Me&!Kqka@N5;Zx%GF+D;BGtvG+g289rYu;f2V$b=J*3cd zoB2-GpL?3rf)d5SChISM8M|5Vg-@$5e4(P#lngFHyDhVBrBu2shEqjEnp#Vlg+MyJ zXBrF(PpUpYEohJs{8nzz!@~Is@LL4K1Lj*a$N-=?2`BTKF7a=hD4ok<D}_`?djrMGS=VPFzp?A7~)PNymBo zB@{#qgKnyx6nauhq_Q^u3m?u5&}o!Mj3{Z)I{0vC#4&T7JfWv-S&V^9bfG2=O4$Ja ztQ5rSa#2Jyt76Yh&_5RoGd8wZ^=`KQxZaIW(c}@K4-DU4uUEGXrDXc!m-CcNQ6U7| z#>KJ@F|2&UauE!O@d!-h0=_f&W5dDPJDn!!ajX?^*$v`rN3TFt1>Y}pg8%Ld_pn(5 zw@?WbeZZ>_KtnT+M*$|W+}2_sCl@u##oB^1EYm)*xGm*3@3wyQ{+EM2m}vrMpZs_@ zUapr+67e3RGA+>Ew44IAP6%u|CTy6MQLi)yf89UEHlJ|@-xFqw4oo{pCOVCPjn+98 zyy&T@XX_sg*sqMI+lE=o`+NZADmhLG86VhC5JyIzV@1s}_(@y)sm?h|`((szAyB%{ z`jg4sj-upUTiboUEQe0r1E;DMBOyfW4C={1 zlVOK>3cv?CX<8T$fDi8<8UHC2c+yZzd|>j6tv&$W-bKyfqa=t2KnH`TOUJ#QbluG^)1E3sy2fa_D zXHZN$K`}`PGk5CygWnHxR$WR3IvnOTL~WNlrpu?2z$+6r`9N&wi4Ww2)4p;Vyu~qF zfsk2f`X+%snyFo(eM*w&d#&HcKRM7J#`tpJ_r2(Py`G#nB66MZ6%&oe2w-+je?=U+ z*jq;g%r59n9bl|zV+td7Zn5AvNMED0vNp4wXwx&dTrg8&ibQ-NO@j~T#+ zJ`kAmW*>m4=AA)IRw$7ec(izBoe?tc13%euDa;@0m{@-SEtf1c1w0w+4EGNZ^nr zHSLrTbej+OmYEVj5u8sL(#+DKjX=n}5Bxw;`;ZpCfemEC^K$H8u>KrAS_%CA|E||D z36xvO1#w^A=(AfO^5+XIIsg+}YtcL26tfUE;B!JuG=1Yi{b?73rYg7Ln$g>qQ^A-m zrhpFxADF`Wst+XDz$buUp&$#j%LEV)TW>jnl@!(q5p^Hxkt+grtAL8D`@T55kEYV`4LLRspHU z=m*U(-}=L{V15c1D4~EF+Kz>my98^+Kn@56x8;FQjt0#PIxd9#Y{xhDlqmRR()~K} z@vJ`!cp|c>{A@mWYHqi~;ds4X7n*2(shXEjuWS*;l8KXlU~9$!2*BUf7PkZknDz4+YFKJ}6_Ok(__k`ZGR|dPJnAKm=%R&Ea^sUT;~`ch?zaZ-H*0X+m5H`Gp&c z4lvy~nJ>i4P%!5VvD9Z?sy{b@^Zcn|Ff)UgsS3t_b+&O`@_{K_$0TWx4F*Ir778U& zDCC2-bOx3fp}-$ZX3{r1$Waox;8Mpoo#!{{)j41j)~7`I+ck$&zH4+ak~`C&n_5Vpj!TM-j!5M+lZb1OxIK0s!O zBLP3Szwr!bNMf3VQ07OERqQ)bU#^IX2 zun2*OhTApx3^YY7OO4*qs?rJ}PwIou4f*J2L@*GQy?$97c#MaFMmBY>3nps}fD)_} z1CU!AT4qH9d!K@R>|&rAX8>HP1hU|#%zKuhe+B~i;oftFJdV#=f62h(OpvCIVHLjU z;dp3nje5uIhOb|7LFBY54GJA#l0#=9P=>zrY!_iZ4Wlf#(^y5FbAtpCC8@ycosA&K z2SBBA5aT!RyZR;UC@I=k^C)X02*J@%Chgk84|M( zp;4GX<{kQrP7{}kE1i555Rgc5U`@r>ESDFf%w9e zK0r9aQLKHlH9)rK(;fcagM=U`1iFt>8gE19ErEeDM!&E$Ds-rh3K;5B~J)`O;na@;BN4SY@okW)TD zS{6r0D8)jcUJa~ip78FUO4ma8&jX<-)F&dj0{C(F87R-Xtk=GAK))CK;B-LCoDH%a zu)D24dO#BX$D8~lFn;KB49<(~5Z4`GQ5!V9w3R-h48<* z_DynpHVHt~T}=vrlUP1h~P^kG#w0jgN$f8A2k>Lg90d%1Xg>)>D;h+NBD=R zf{h}0Zu4u7;7OoAAi@%K31E2M`imuRnjWIu5M&gErfsK9(Gl8lyA#KjStj()?+W>i zUZm;0Kp3o_h=@WCl$c0}{GFhvnyVHvQ{YjP%XfoFk5B6-tS+ zppNjK{@x^D7RjIN7;HN2wOIQuf}g1!5cyPKxPe+UQv5_a9`a)`Nz^ZffB%lQZFjq! z148HtD*!NG3S)@2#(lhTG3@RjLS`gJ3O^`9RYjtSuOA-7M9wLkWx0Ab-0tIRfe_Ih zJ`f3dAdxU6H7!61%>zW?d_HP>hWdjKfLG0iCBf_p;F14JKnTj!u*da;z2SmDD4~NE zw>zLjJtC5ZV+013 z(ERi?HFGhY-|=e?CZ4(qUIIY%WzHisxZN6JQUwG7L3OvQs%TOOBY;+wY0H~;F;fFX z#dC;?E zpkD>VBk9?~*DDfIHh&@ZduS=P{#>OIz4Eqgce{Pt+*AXHbYC*Fq5=>NYpw{Vf8CT# zW-@x9Vi1uS9o!Gvz{Xe+9A@E%bv}pV>2kS%b#6g`;;;}X3Ic$jz+^4XVfx;VosB5i zPdm1}R4}*0z!M?`y3tY|825qtToBCoDoi5~UJ~>K0DPIy6ro|Y8ZIY+V*5AS2#c4t zWzqj#;Xf=EvfwAf`e#)6{){>n)Sfw`&w(HBuCPYVHr-VFu0tXiRig7-L5Rc%GQ1F? zDI55XAN1!ioCaV*CJb31VwclMb>QuF(uXp+?^Q-m5Ft=d5*Nm;JK_>dRkd9^!T2c{ zMsvOX<~R&RL^`XUo(gmMM$#w3td0_ikm+z3l{ zoM2WQw=w)DwQ}&VuzMV!q6zv3mOoFC$M`1~@`p=K$CEn~Aasp#_z!DlRoxx-?X7`n z88Z_CA^|Zf@c2wLA+W&@$nSn&toC+oEhduC0f<$BXjs#!paj5-%%tL$)j9C3I+uaU zD5{{q018(6z9&yhr(P7&Nh6(+PH3_}(St(ZH3z~?5O)gLMjlj_G#QXhTM1}%m z?2D0MonHqLA`)3^9UD4476=iBR5QG9Ga58?B{HD!((2%xNHMn>4@=6hl-rAjBhuNq zXrp&1rO9UdQ0kl4jpgT%GF_@Piesh`wu1h%s;=TVeWGjj$7hovfIRq_$sX@3z!3c} zSiiI1%jJ4H9j}+`ymdd9G2-Aq-k_qp!>;W*RUl`6h)4v8G<2+x2E;=0Ro6Z zPTU;M0|a{#Q1J+SCaHL;9tfa6B*#O}ez9!*?yl?QdOV%n`V&9f1du6dt_@%n zpzFKCes^nHBt&$kKj5Ch+J?G-fBFpEA1I)EY`k7hwIM1-Uapdg^A z3Id`608FZ)?hmL6APtqZf{?JN9qcbyE*)#9GZO0UJ}@tzR&RUx+$r$R{)$S3+4?=L zXhdTMus%jSmMwMH}`Xit<3ebl8m`qiP3ear)$cRKBL(|vM_ao-_2kocAVg5}kCN&Jg zfe;7@nFIh-t*8T6MC9sTcD7Ccg5Wq%Rs>b6s#@3FwjB~7f}$m51yNN$6$IR;&(A=- zN5%Jn>0ndjg|63rYHzccnvV*Rx*@ z2TW)nlJnwvISE9ibJ7@2n_>nRqkcj8vl0ClsLx6B1NpXBwi*i&G{!w@^sOHg>!0tx zUM|Peskz;B6xd*v9z{T+0oyYHlDdP#jBzm-!Ii^9r8*{y!S0d3;9M^tA_0)I4;uIx zGDbnzia!p|fY`g~G!SPZeP1&vP_N>MAQ?zf0Z;-0QW0e4u4{=%02owopl%679B7%2 z9L{Y`^y!^>AyI75<3zzg3&qAY9l$-MgY+1rxE}CyKroqmjuQ3OYL$~fN(U9qdB_IL zMA=iOVeK4>bT)7U`Ln214w;|Hj{WfOWBUNLgmTY-(V44*;Q_YqkH?df90yzy&OZ}r zq;{AF@`p%N_xqiw0wNnC;(@AR=Hb6&hzwCh#6&ewWtfPN5SbJS2$WEf6pkY0QbnV+1QkdP60z~B?p353R&!47Fa9nJ*|Va4nmpfHGmSw8M<0B ze~||(rlkEWm%p%S@)c5N%Tu4}Nv7WW=Yts=Gp9IOb%k6kNHn+yZ zLV$?CF5r(4XaG5c3tWPs2x zX8`lbe4!*_)MZ1kB516OhYr`onoatM2?&ilvckQfV@o(kiH7@pVmc^o3@0jw4JZg* z23R~QYyBLG+^lUVaL9-MEC-)K<5ABlr*{aEQ6cch4?)}8cG4XT?$~Z4( zoiR9ps1hk4A()}VMgU;gxUn+FF-=tRVP5e9Q)wF=dLO}-{2D)8So)PaJRukG)3b6j z1Yj`2kamHA- ze)rYw+8hptrn!Lv4p3*nHiQIICl?7vzz={-Tp2+G!^B925D4w`OArD;*EWa!p>1x1 zWn!Es9XrI{ufwhw7%w~Q_SdGt;rY}zF)PZbMuE`)qKE}WRR&T}D*&X##Kfqsf)E`f z7ox*Nf=Ed3V6j9Vnf@rEyOc03u<&(qqi(``*fhnK9(NI)7BjC)j5wQ_zAuLM0v}q~ zDC6YN3+H5GeokPO{hV&X1@9?SpFZ&)O8!Ie;|X_yWwID}OFGKv-G~6tG`GY4&@|1s zuya;BAR)0kEN^IjArT{V)*eqs+qM;IrXYjEf`Vd8 z^Q#p81JIA9@E?qg$F-Kfqj_Tdm!N+X`2Dnj^A`T5X%5F@)3w9RuHEZdzq@6q4hRiF zji{CB2PO|?)IXEXQVl{hgI9RoJigV8gor#%N+L3%jOrsCs3RmH@hwGBK$Sq1T*eT> z8mJcl1HA%M}R;NGuSG1wa{hN(?2zR1D0jP)JT?3Z$Z?VgP1VG(1-)ZjV8z zSk6`gz7J)RKrm9jz=vkWMIqM^V8gt?|K!eQ&s3si_A@hWSRj8QRudvLpVv8ucqaTO z)Vd{)3uzw`k;CyQD#(t5bR?&<{z0{Xgv?wsR%{5Ns%xtXh%#t{zyT;{%7_S}L#tq9 zk!a#n5i4Wrs&dLrcMUTs=pd^@RuP97+>?28mOwDjrUUv%7!npJ5cho#;ej7s9@QXd zX_D%(aIz(q7rv*<05F3iVkJvvi%jnE2ur7uLTMRUq`N6HJj~3w6HZqSAzfrYXJbz% z+}A7MNKlrT@-T1}n*K0{lG^>6t72pw=*Q5*{h^4^xo6PTzM zo$7Z6Afo+#*Y?&Sgh_Xd(^9O-K8CTCLJc<>r$Vx(7t!xKMGw*J4oyh+hbIc;>>;#? zEn}>ohhixV*-|)`J(!~Wp;!?Y`@e;6F(hc$(Ld(qr{d|`S@IC|12PI{Sg?L{EnP$& z=75ifW79OQ_JyG(K*UUlH6t@KATTL_RoqpTYKt%%r4!;$8gpmd^G0az{UHGMieN(g z!=L|retiW10qpyZ0SACWgDr|6fC?x97&e{lY0w%VfFRScsxD2lV{CziR4p)o1wjP? zCzAj`5E1|+3*!0+-b|P;9|eCIVlYoX9uqLv128Y$O^OFauX50Bl|Xb@WN!GsxGc5M zFpjiw?njx0&0?7f7V-`kmA|RZEkA5lK>vOf42Y1jvtdCoowtZyc^sC@epU&spPj53 z2FX5P+qH+&v27b1Q42FOV#Qc7BhWzKIEz%fGgWVU8q*SwO3;VLhx6+jAse!w3WOBK z#c4Irl#3k)3jmC$GE~4i96IM@6vG}W=UND0kOWm!T?x|kJtEVZup{G5nmVa)oIQCiv{<%U~*XgA&!ZGQSvBzbK6>0iVSQZt(Yo7E0k< zLtt4OHy6RU$Y^3Ik%U{`%H?l%-af2vlkktea2_K`bW;0{*Q2+1A*gy0gQ;Bq?;zTw zp&oVG=B{lH$79ztFfQ*g1QjzQ3Az3h4s55Wso8}+8Walf@yDOKwtYCAZkLPp9TO@9 zI#3*CScb3~7eq541MrA=3}Q>j3N9fy0raFBX6DXXMsnG}J$1dSn(GZ1c@R`v6$Dia zzzUXx6omnSSk$6`+kPig42Q@KaN*$_+y=Oupyj?E_ z5fSUU?%Ea;wSgN6=8^bkCL(aGC=oSXZ(LhIl>mgmg>V(O8bZm9;i#%AAyjpBYZ_M& zD66rm#DLN%0f1E?0bw93Kp;v?U2+u-{Q8CNQEDWbkdd9FNlXgk-H1D==(#8@8@Y8(6rioWy@x zn6az(UE57tqR8<={$`13$_iwW{kBlpowA01BDGUm{z6h7V{WN)qAC3-9G4dq(cg967YPrz>CLL#gnGg}!C5aJ*BTxmRfC4nR zfQTp(xYhz9kfIPFSd{qKys_(r^ZqIb2I{?!M5lSPpb`L04yORm*XDiAe^ml7#4YP{4S9y7UJwBZJrXeD+{eEBfZ5yqVHc0Fo z3k4Ak5FwsU4}_>zDncg%5>*CPP#yRlw$cx%oq!Zgy=$7=KiwGe|a=N=9K;=*pmZ4jzV_^&x7QR2U#->Yz$L{ zXoxGPt~Vs!DiZ=^m}C=3hz7AXm4iAcQ19x)@rbBTAAabr7h@__Aq0oFoU-4rF~+D^ z0$?J;#-V>i=-Ng_$a&X*$A+=83Ai{3MZMc~T|23BCbmGwb~3w@pN54h(`vR5#!njQ zba=Lu_VWv(0-T|xju>Q*6@?WPlo=?%MX~Y%o|&K9Y!JZZ^*^bsFn$NHv}StNGQvz;p#IkBq0|`w4*n4WVHKw*rx?ib+J62H3;nTs%8r zzAA{;##k$aNCfPbl#HUH%ISr?<_;qo1nc5I(Z`>Dx?V4;%BE`J3Phry=(faxkc;Wu zTMOa{ePJSN<#c*Do{m5N+rJN51OgD!K^Y@JWI#aC+TlG{X3>ESwL%C=NHXm0@pmK6 zGB${|2l*eU&bQ80^spKz8?utpXk#!^9N3`43kVErM~Mg-Ag9A2c+3C1;sJRVU|5gO(SQ4j%9 ztQrHg;i0YTsU8TB5C{oWD^oM}Dx$sZZnxv?qMQsP9 zDy$=MPe`gtgVu}`ZMe5^YItSVY=W0Af=XaiiF(*&RtKemP0!s5C@4bn2?|=I`7;11 zcTo0H0kAqMli>z%t+Ws5f3ICTkBmp`XWAAkhJW-L1L&6%lfcdy>;S5q0T0vdIO-pq z{!NGBEOw%TVR4lTP0N808OX^KhybjutGe$R0z_42LZOd8{`B(eucoeS+xl3O5e=dY z82}lPjVn5O189jsRRA3GDZoVB^__buWRSQvwb|_sKmX(J4-b!`dbzw2AqaRrz@S!< z$o0FFfYbt!s#QQcRrN}QqBz&P)0&{EZ1s(2EeTAq8%7meSPD>e3rVaN~|B1gl!wwFUbDoQmGkXq{;z>N8W2El_e5*njcL2UE-wm+Q=VP%*RkQip+ z0W!Iz|AYXhGE96r9^2bZne3oaMFJH-1Z704%7_Y1drGR(-J09^`t#^P>I*DM@eAr8GBF&QaC_C8g+pK z;wRh_lpYhmkO}gtQRiG{O97CX!O0V#DLC4We<1DVW=|o`2mS*3#~>w4G~~dO^N5a! z$9Y>9)SVQcNDREEv*bm-d- zS;kNVuJEmhlt@$!aYb~wTnHI@YZyflK*b_~3L-Iqwdh*EWK30)G1YE&KEH`VbH0!t z!#p1lU=@o*q`;_z;t~!Cz_9rp7_q1EqoLgo4n2%2gDU+h!7$ z0ZB`Ec}UWKmL)AvNlLek0F7M(W9*XRpM*}psQmS3dgIn_7X8ob*}M{%#<6m6D$t=T zJZwQ>0{5?z*C+vwUi0wqa66yX_SJr`y#=?p%5gix3;}?+m2V7I2!I&XcE`i%t@%u3 zz}jie5fd69B@%#&c)#2Cw;M5afC}V}jv)i0$Oz)dd?HjZ74vRa)%E54#>BU`S8{{F zAfoCkj#L$CFcK!P>a-Q87L^FC^98i?#uiePDG7MoXSou9AQi-X=zPL*G>bcBj1UE7 zl=~T7&_*(JVQRpe%wCiL-Sr2vGIGeCo}1x+^7to_BZcmg3;&_(##M=vZXyjLXm?V} zq;5bolMEV2X;3f!`Okm8UCspf`0z-GecM%4T~&q^A_KVJ)w|u{csRU$ z`NG8Q<%~{MF>A0#esg-{@bBqVfIMhBc%+6z6{b9=SmEV7lx5!P;1`^)P zTyaW`Vlri0kSlMp&@`UYlC5Kj7s@I?(*|(n^=5pa!2cj-@lO#2jZnvD{uA_%#E9dr z+TmP_sho8WBEV{-hsGKiuh+|;{`{BQZ+=Ex8D+#q=_nwS8kVr=xyt6r`BIgzeQPUoCL2beMb8Bf! zi(GJ7tWf&HhE2SaGuHykN`R6BNLS7*Do%10`d^`fjivyvKv2KIm;9fk|CPDY#XEK_ zFG40_CK_75OvH?ai5V3c1_~byq3t?C`uOqV>+|z&S0DBV+jna1VZV1aVulRh)>jcD zn#wQ$fijuhZddOrX&YB{R76Lno#9<=Du5c`>En+EQLOFS21MN90zB2_q6k35RlTeC zyUXkAZog|T7i)(@ET@ySMZIrB$tEZuii{$+c|G*J62+CMI-E=Zi9&Q6L9UTBs7#;Z zTCWXD+9Sxpg+7-H~Z$2${I9Z3m!c_h103+pT@>_= zb+zA9U5Qu~0a1e}K%l}zAR?en+s{a<0;A#x1OyaWp=U-lh=K$#Ym?L|uE#==00kz@ zuakH(5=Z9`9fEQ7!(5go?K6c8kl$1W1b9E$f4(9LN@fGb?a*6E{w;dB*qw0A`(u2* z$p3`YPPhspU_awoO8;>8ZtjUR_*Q9LWg)&Kp=KXH3T4|*0SM` zhW=F5jn%7_=U%>az^sVO$h+f_s_J&V7{Islg$Y4G0T>8WfXHpYRYXx>B&$L*FXKud zA#}&^SthsGmIMWvK`jA-xW-@DM4$HJYM{?SZwy}JG@((+>vKWbE5d^zOGcbu{=Xs$ zTD;2ud|f?Js(_a|0J+SU$ZG>;C_HM?(0XApR|R;$7|PrX>9}AiBlbr zQRB2|3P#A>F0ZO&1S*gt;}84#L)(A3>E|AHbzPkf&H0J|&E-N!0OIIQ>*^U5LQ)kT zPjeqvwSbfS1GvP7JpvO>n^7J77v~@T0X_@6L_++b^PV7Yk2Ct@(0rO|q{@$Ddg=#J zHy}FadW-yj@sBA|Kr0n+DWIo2CQE1y(|uvNd>Wp@lVktm?k@(wXYvr5&aoTt4h?z) z??5uA2@)MwZ%*>hq2V>uj+eJLCi>~8pWdFIPY(~>?J9l0t83|dB&>)L;l%WpJ^gs9 z-mHGQby9cccIa#cT{+GwyPs*Qp_j0*1 z;`P?JE;0;(AI6imNkNDRL=;pM35T`lFkFh@4tWxCYg~pSY!EFX4#yr3?++1)6@N*} zwB`z+&=vLo&KT(DV;&3hypzNy(tY|WpX?lqYT?3c2Fdh;$E(b{5D+X?Fl+6CR6&$4 z0MdEPWcsm)e>~t5^^ytwgByn*N@L~9-UWN(?fr|i1g@cTxJ2_sy58p^`>I5m52(7 zs(G=NL^|dl9v>bJ|2yNq{o^wu3b7T-fDZQP;0VBOM<;?KMy&t=DOk5eSH+=wbibw` z0jix(-W<}Ya}M;d4$fVLPzvOsAV}sUgG-Y2JX09}iK+)Ss+~)!xFt)}cS4|S4|tn< z6viNPl@q1{xCMs5=a4qw9||Fu)BQWa83hY?!*k@kpW$eACK~v4#P)W1X>U(Ie5~ub zZ<^!NleUei>OUF%p`sq?P3S6E6YXkpbHw#wk6^WRYi!BvD(TQ1YIk6;0fM@=U2hCq z3kV=Y?dkE=n1gmdv=^!2->jQ?2P@Xi$FPGt?(AStAW|#t_yB*7baV|Je<6kOcb9P$ z>3nK80>QY8ZUYLMD5N!Po|FMgC%%;H-~|g`><4EY0Hc-C_qQ0Ztoz)MIf~0hF=Cm0 z+@I~sV8j~*_-Ej{X+CV2f&<;BO^AqY6Kxp&2Tq-6zSvop!1eiso*oYmN9`;a^P{$Z zJssY<{&NQwsb*#*Q`dw5*6u$%f$awA85!nEz*hC%)O%?=Q&rG)ysK1^$PxjV-R|Mx z^78s}e*2*M*L?*5|8}*`2SDs}PP}slQNj@j(U2L))FcW`)2IxG3b;~O!Aa+a_d&)8 zK!hUGOnQide8UYZ-bgH@u`sScq-w4XUNZHie(*+n^#l=Umc!?k_*x=q^Zh)8B@h%= zz;OXkc9mf5VU$OLAxrcIcI=?PDg_JS-=z*7*1zF&CRlque?a-`fBj2yxt(jH zO;^>1RT0TlY>2C>A~bB+)Q`Oy(cYk;T?L}m?g)s680x(h3m~?&%*LD^tXS=>w5>7R z_wc#zKN|Y$p;G8RwW=ygr~(A23QVBZsYhwIt3{;g+tb6-;c#l&#;Rf3<9Vq3*q5N_5OB!lcqJsK-UW~7!cJ?_>H5?d|RSs-l?uzQZPo5mt|!0ay>z&oO+_PO)V5+kUZ5#Y3;ZCIh^y3GjZ5v_=7K)%{^k^^fV!*I)!KP&|1DFikQs8i|nMhOuil9}z$8 z-a#}(A8Yzpn^(n)qCxF1m)=yzr$;#K_0|v}02xy;8zcge>Ucz}2wFXy_^?N2?K-M! zBvKK9nomc2Yo%$KS-EnRZm(dBipWK<0{mE;tLSG{C1QlWv(@SNxT{}ZUIpNIIQA-^ zetBtMUah#}!7KtOA?l2R=3m|unvDIYn7_xM4A>OtA*1DAA%eah;oQ{hHGWc2M zK=1XThyu!1z*)&e>bI@{m;!uU0E~t{eNN-%lnFFDG&QIt$MXg8W5oz~Rvec*V0XSW zfDb?YNKdu)0t$u=v7xF$MPfFm1N4H`j{f`y{d@s3##90*3g)!e9@^%WfQ;FJC;*5k ziHee8qtKs4o|yh{I-FUku0^!BeQ(9~cDLJIF4r&5zw~N#SP2T_G1mjsnSNgav||}y zheiOQ1Z6%ave^?9;#(0Mg{iPS-bFmV0u;TfNCN#L~MvjZQs7W?4N$vfB2xiB`c0x zRo4SJob=WJk$idsw}QRi0U53dP1U@;kfN!oUIp2(3JZ$}fl*Kfma4i{_^>m7edJ%B z&s4Lv&^DLr%X4dO*V|z$IufA8@w}l4V2s&LeR5*NLcx0#bp!l*c9ojnL3)B^kq#$>!FYzeYp(4X z5wKI@v3Jal?jZvL_IlIZp?-J(1!+4ZV5-R&swx2;@tOh|G+OQVSefSa1ys2*?znhS zRS_mo1Bg8tK782s)~Z@IP~949?d9dUYunaZE9w?1(y&U|DSUHyIm+>s0&O&>Sl zPRytuAOktzH?!dq)Cm(v!LMcqsOx%t5&#hZ`o1rp1T&8|Z)*a4$2TKrfsMK?i~`g= zxs7i92s;j25E#ajC+Ki`AQ%+?wrN=11}!`=9f(@QGhj_bjLh!fJY?gB4XD4~*i`!u zPk1;;+aeo4X7m9Yv$f@GMj8UsuOH5#KtUDry}o-d7g zebcsuOQUvZ`x2_vsTVrPG)JU2w2LjGiq0&)!mtBkYH;bWc+0}n2S_yv-^+S8v;sEu z!nU-50|#i`?!H9_aOZORIiad5@Hg*FQ@D^Bf{}4^;IMTjx4A9X1h|6(eHV2#Pd=Sa zr{ScQ@mQqxdUZe2Erc_McGcs)-dWojfDb!!KzkNF2I8Bc$O#y3G6YfR`uE8w4%EZ$WEq%PIAZ9Mfg?v_d&z}*M{ z?p&s&)rH=Km#9PoUxT16a3Q2%#cnV<6H`Er!_n|FQV@WD{QKW)Mn!g`0(HfV_~Re` zWQZBTFdz2&_I&AWS69{Ha4_0GJ?tAxht3)#L@><6><&v}W@g5U(J+eYwc(%kyxRdP zR*bs}!5}e6Z>ob@g|6j&4I*$BM3kyBk4LrE+O9cYnzxtR`Pw!&>3X-Ht5y1mb&~GP z0fWGmv=hRben&S0CgZoWDJl?vjoReny0!rQ>p$CO|A`LpHLd~xQwHd|E_DDR3Jsc~ zW&h4|=2=IG2Oy^drg2yT4mhtP_m`4FFe5oEheA}hipr2v6d;KR5r6sR9|p)6V+hV% z8Ke(Cek5iQIqY|RwS$Ulmfc~GfOS<3J62SQ8QE<oU9Yz7Ky?`CCq#fwwGlWlMw)~n2C37-O?9v!fm&3x zr~wfq(MMO1IQ*ylV}3s`CGwde@P~i8uJawh_ZfHAku&|NQ8oAg)6m2vVqynQn1|_L zCN>7yZK*N@>$dCwR4NsVW=R5Dpo-A$l7%Mt&z8zyBKujb}%RpR9Cb!XxyPB>`$&ERbm9BRZz9P^sQP!My@K6oAQEG>qk2!3!MZ|Y+?uWx!(RWG2N8q& z?b6?RemC@A%MHxGn)MI6UZ=+SgnYe)IHFojn{&2@R|sXo4A?&Vxu5piMUrX zz6`_*(t%!78XRTF&zG)2;8iuahosOr&p}AeV-im6keQ)*4r%z&gRaI*N)Q;84PzY;O z7wf+u{`uF;0Amp#(@D+Kg1`Y6hz`foAp(l1+ubn8|BWFwhAKA5+#($T)QSiQSuhpX z_2J_WyTifkb_&qnZvERE)-~_;)e(>hdJ*gq3{(ON3WC4@VtduTh0c;O9o($7Vy%kU zu9dFuyH>4qZEJhmwoTvnZQB|~WW972iO$c@ecy{}4{BAq-d6hqf$Vp+2x#B;BE7Y( z$iM&X-~1(r!&@T%iumVW$^iyJq5`0(AfOv7S49<79-4_5R`GkES+tU4ue*z22T*2*41TU2TZPh;4hb-%Ha%*VF#s zkdsH$F8PHwHV9RO0b1G-7m+OD(KS`phz-?z4Hd)p7^{W5dkvh*+K_VwEKplm7& zpeog_?s^LVsCqkJRJ7}@72EV;#f~Mt%XK0n8zcfCs{Xmc|GxwL9;`ON+QHdVf#hyJ zSX0M?&Bx0$Eoar|zlT8RwxzQ%mEo$YjiHKJkpNVr?-is2B48jvtgD)LwH^=jr$78a z=8To1cGp|`{K||VYS>hD#fO86NZ*54s*PA{yN0SgPz$tI6}95V3$*QQ+e_E=mm5}< z*;jHpwC7jbcDC?xzpszi6ILx)$iVy%x_}FI!mR{lZMY0jhWL&u=xEu z{)htNINmz)J)}$(8Dk9B^{z5VTp1=r)wb)^qHs^sx$^FKQ#N(IJ3c-rvJs(o$b?P4tmR~0Zb=;hX^_SS0m`sOHFD{91P z<3QRfcst+1dXI$}Y0o#;1-Ut0Pus>C5W(LVa>H+O*9MCWrS(K|K-qw6LkSMbiV z{Y_je_7(JkJbK5w4OVPSMP!VrcNHNMqcIFt`mVK#+_lKWh=vH5Rd{Cz*bq}y)lVNj zJUl%xv2EJh^~!{(@Nbuw|9&vt)i#1+*f10Au&P7=x(*ep8nLbb;P(7Nreb$~YSVy1 zcWc~1cwATR457a30YQ3^>$R1hcf0+MKlVTW+%~PMbZyu7ov13&aaZr4{o7k_ZQonz zqya#SP}LQwVUf1ES!+e4w{m-Z?X2xZdJ(WA+8coXs2kOy0c*xggBHne6Gjs(kgR4@ z@BS&#EheJh9RwOr8lN|#y7M>h9;k=uU~4wS#9UVuyA!by)%I=Ex+8%)MgnpNwtzB& zDjNm0sudxd>gnSTrw<>9xVc=;&u>*lAljd=Y>Yd|=u=N8W8Rd-BJFlS*tIw9*wi%w zR>uP{-M)N5Vs~UU1CSMH8+*C9&V)8CANQi#_pOS8RcrhHa?zJJ^KjaK{ITn8bGwR& zs&;MHs~l>50qn?-5fug1Ud5{PO{;^r0(Mp1v+3a01E53uu4m`Kzj)=Z&fy<)HXC`L zeFp^F#5(yu<3BY7@=?x^IwJft<%#Jqs;~H2({UUkn$t-)^=^%0e}dN zlsz*k5h5v~GXYU$KqO>S{qV!b4?q4OT??X@*B1~0G|bezUKl{Sp8JlGEYr=Hzt(z2 zYRL3`uhtS9s8HLsI_!D3Z@)Z?qB-uhLD<#M_hft9LEAQiQ+H?p08z!&sx4UPo3`(| z>-Ab45B1}N1?YbLrSGj(J%K)`{kLoP+K8x{iY+MheJ7v;oUtl$v(hQ_s#U!sV~p6# z`D&$is_4<^9dMu0!4mfIyXX_%|5{t|o;ZVo^!wr=Q>ZvgH+VoN0`uR(ZN?SE-g)I- zB7oeHQ#8z+5F^)hJ;_A-u5X&gahs?xogIXuVpJ4Rm7KY-kPreHV}AJQ$B#dLY;QN) zbzgq{WDs_BZHO*!uXVi#04vr23~Fsg1^C>7+KZ#!@^ETT$8#)xX)-E7-vZ$+W+dMka~+|1!<4!ip4srmI+W97k zj;U#;QTt>W`)kx_8S{&N^N<@LBrUjE<|dP3{;!Y9B@fcEF$v*5`8$eSbjPbRGc!?L z)zfG|*R^fa=m`D1$pAz`?^K}aGzU>3K;Vk2%IxZTe>^hr+smt33#hxQCZgx(*TbQ% zi9oe&Zv=?QmXO>jj;eszlKIlwKkMzF_=2^ywrktN(<8D`!0YoDC%$6+ru{ z?{Bxh>tP(;v;C!hJW411?bhFJ(ssx!sAb&R(rp zE9y>+5$*dHk*dnD;qG?p#9Ec!s)C59Rk7CIu8jhUS^zj6k7D(5K3m)2I3xoeT**>E zv6+Xw>GoD~zN*kdsr=1CEJQS~`3l1xPY#)Lfr6sq`y5E;eX8DV?$mZBCZhd*KOJd` z0BzS@Z&!a6a4;{H)WHJ4IEx_Jn2P!I^u&aQux4{ z?e%8+4ge5ItpZp85w*5$ZF94|O4}-twYKkjYprb>?)N|1=BK~@@o)e5^~=jACtGsy zdl2H;uJ05SsHzyT?Hd3)NcF6!r~ruVt=PVo=5`ZMQ8=ECs%lkVU*FtRh_1I?6)U}H*S5VXx}2aYs;{rFqT-fJESP>w0r?se z(2$f_$^&V4L1VGTH;Vq>qzF!Z!?Sar3*AyA10oC~Sj>mRVLIBYYun~_6O~cN3(={M z**bXgBmY$(A{%C7o*o|DRB_jJYGrTucDZn6`rEbdn(p;Q ztiZTrz)Ig=u7{65D590V@2!Yv*D8@9^eQLNk7%D?nqO(x;cfr)WEH;r;}@|#fTD`1 zsJM{?=|!z*bGx{3UhnqydbKhsK4hqV1ysfiq1wE>IO97$oM7?<8M?)=u>wvgpu7#1 zngClEkAh#kXEBzu-I?DH3H*EL{H0ca-wQ)RW(FcgIvn=XbZ^_X*Xwo4bJLLh2x%~1 zDkzki0EEbFDq~I$4{j9f_3aEQAnKmhod+YJAVPo&U{O$q^F~Dki9{>{3JN0ipp_vo zY};N{2ocb&@dxZJ5jMeC+JvAeXFw=YT{WUcgV&z0$|wQW}uSrxf9?e#hwNP&om zw(pUNZ#O{%Hgap64~Qz&)sGdPY=5!#Z`b~6TP3`;t%&~g-~Zc}pMSo*zNo4SSe0I+ z6Dz7BqM}xME84cL0POaAtNq*CMJ<3h1(Qb6T~w^->+=gBh$<*NJU*(*%gZb3B&W_4 z0LvID&w*xJ3z{v#^G$#|8wi2;RlN+b7O^+=!j>|HLp6waJRHV-LfABov-nx7;2$mQ z19`7H@V6YLE~-PkH}2=uFA#}aEujjEu2qT_1(iS*M1T+#1&P#( z7*iQjcfCq?Vd4r=iGajCz=|cRc27^wuV=CLV|V>Sg*((|;58TJ8)wa^2cFJ#DyS{Y!E0sEu$Py71QTlcqXf3?z!NUv@8cKqRoPk;NjAOG@4 z1bzGRY*l(|JJDej#~l_XAgT^eb=q0b-EMEI{(8Q^sC%6WP>-OoB2L-*^8D;>d3btq zSpVhudF~m6E~F7WDpNpOw9`BXx(;ksx?uSyF!vyxWh@g?HO!7~KOT=TZYpW6&1JIq zp=<^JP;y*(6`H0(M3=Mxk&H3S{P^_fw*SAqzB#}f?@a-BGA^*XJs}`6tb%et$SREHkTLU^s|$3y)W1J7-9 z2C@RSyBTIHwz=K9>&15M{EoS`xT5m*div=nD>os7di{Xt%k?MLsAEaM36>cUgvWP{Vks(EBg5Kgn-w}<@M!7tf;CZ zi!dB06abub(5f=b-m0ot0YE@&T$?~qh6Qk@>f2Toh=7QCYk?Tis&cK$DnwWx_RZz` z^y81e{J(z#RbjeawXfjGHa{`!E7Mc|+_fF;Dg`ELuZ?R03^EO(L6>QTuGh-&(cY-S z2awOL{oL8ljZnovB)xUn%r$bGx7X@$`sE-0c>M8WBZAGXd43T`>sz(+*rqt;k65tM zTPsc53c&ub?-j1+Gma7|3E|D48}&K>qVoLZ%XIUH4tgR zY8m{jHvx3D2%f{aZrQOp>?9#N9Z%DtD!1G9a=FNu{QS*{Zh->^!S8;=kxjX`Nc&#^txT(t>X5%AjJXVqpA#hVG&2Uq*$`1XV_J{ovPk$S63^$ zab5zppbAnew5k>G7gQ0dyd09pqQ-tXyB7tHa^vfB$>a zw150B|E2Hk%a>=B(f$?0F+R|{T|=$_76nmxdpkSR+wJ$4^La8ane)I5DKc!R!m|g5 zk``E51hH5F=P4k~mkD=rpwSkLMHVQE@i?p%!PDt9;q}Y)dOn{gxE1Y(E7if_iE2UX z&Hr!eh@Ra)BI4WI+m|n29HB8BdW8P4BVyR%bCF+kSO-1M;lh*zL|Z@xE;u0DZRRdg+gSb6|WzI)d!9 zsjyS>P7z{IlpVp%>h1OQ_VnR=>plrLtq7>K18><{Yj%6v_aIhOdn>(4FLHT#Ih;;x z_<#P7|MBPl{@?oEzI^%OY#m$=46IsE5v$f^0v!x}fH(c+qM-Z3;e0)V%zL62!nof7 z@EQRNQCxH2Ct`M%E8urhqk3;2z)>h;jK|aQG{y4E<#Ils=dQ=J`Ec06hglvpuLy!% zKUl|T@7+u6g!=sS;R7PRy}mwwd3Nu)$BNEdiV^%q*jz0*sgKJjZi$J5NplhqWn=1H zZLRd@D-jyx>3+9kcPM++qVn$H*oo}+hx6;XJzo&9B2_`AN~LexhKR{jUEBWJv|LqR zF7^Scs%^CG=QBy&sRgPRE7x8dfrskkPByZlZXO0zRY1V1TzFSEZ=Ds}i}oVDidudB z^1MGD&945R|L6bs>;L+{SZlw0{vsk`)w+^FL_|P92ko57yx>CIx0lNW0QQFiD7>BD z)Ek_cHwWkh_Dk|nO#|&*)cS%8UE+Rn4pr8H7OY%BmoG3db#y0t2RJ=EOv`c4m-E}( zn@6K?cWvEbA0q6U zrtf=X(~Et*T-or;xs7OpNrh=eZf|EYELOPUzG(pmhQ_LNwpVKfq3y-Oz;`;j^!4R+ ze>~NP!~gw%{Gb2rfBRpbpI_Y7LlIzy6)sbaE1{{%Xz%Cq8DMA+zrDU?3R^9A#ZnQd z<`|!}0MMm6Kv#qNMj^Du7aCW`_~%1k-p+4tZ_ZmMTVIsKK^)3ML4+Uv!+tPi%K#ew z@DCq9AmYpO%jeIZT?2TA_mHf^^n-mSyAJ6|Ad>_Lk(le^DhX>M5<&}_B(THJMCKn=9*QcZ(1Y-6|h!OUq8P|ZxiJ< z^|p73>~?;A`1oVjHv|Nzwif|w1r(&SDk}AUr_|YQ7K>$nr;Ak{#eBkiCJHPqIpMG+I{rSt6Dc*a_(*p)H zBfRk6(?Dl2clm-Opg_#K-3}42*Bhu9cLuho1(}vhtJ^6;1R_9iIz6h@x7W8n{`D_! z&(GFcA^;0QV2FD_x!s6>KnW{*y@|CIu&FEC+x_YE_UYN$K4KL}Nqal?v{id?Vg#(~ zu6+~G-l~XNRZ;EEm-?`4yNh*$^D+`XDz7gu?o179<*j`=JwAN-{KdLS_;f{4)kr|=(@0h9GR;Pr=}esV_s`O~jnmtbfT>v+(EdJ|xNyjPEOir%yFkRJ7p zwVK=m*_f)TZf&d9G9n>bPzJCHtRfbNdQcfufB}e34<{@3e7^kYuYY-aeQnMcA`(S* z%jA%|rWI=epsK4K^X+_gM-*`<2y(ezyRL&sb&m=Py$tbH1m4cCz|`Ab06MF^wJ=NH zL`3_Gt?FIZcD;-L!|mc)VAeVXXy1JPa(Z}r{_@GHDya4LzX^bdXd>a6*WZn({rvXo z-r>{3!`tiY%>9RoAAf9Er>$EB0yJ=>)CauboYV>K5<%$Ki{UbYh=tk69KuO;(S*pv zKmGKhE2Dq?{K>tk{z(}+)(JS$m%ajMymLC%H;f2g{726<9o0<;OuRen`?kAWE)Mg^ z0PsQF+0hCB7A%7b8US&6JGCmem-8S0@|W|=+x2orLIxlOMKxp)scCNrNI-ztc74Vnutgy(*D(y@*<| zlhn;RxC4V5Jb(FgdU|?!eztZfc8BmU;kRQRh|si;c-~%L)f2$Y+fkz$VFUirr1zyo zYCm5>tTO$p74Yl;E+G460!e`z2qNN2*B2AzbkU>nA|NsS^oO6^9_L>_eH!CEjJ<;S zU7Sz!3ML73Kx&Y#deF?Kh#(0#&Jp*=10mjSHxO}0$*Dgb8zdNuLe>J1(+tq-^Yfqo z>wh`FyvQmi_4gR0tQe>ikaV@L6i=xF_FCq0m;e);@a0Uv+(@cGl{ z3H*$Ufrg(r6~XFVkXiANoon<~F6NrLTk%gp1e#MpCztyfV`BxpK@Gan501)!ye!m- zjfvm=3><9((j?A#;U5sUDg>pUzb0WikwcDwf0Iu_Cisa;>pod6|M zFK9Y36$C5*kdg%fQ2hJ<{2LH55hwy!QW60&+>7m+-tGRRy6(lcU5g@sX208=Utf{A zvtq@9Bo3e;BJPL|_wV`q2J}&+}i_&ycjO&2X8L} z(hU)ApZ*{J@CQWv_0y+c{_&5g_YaZUZLAcMmhf=YTli8u1@vkX7_TLw!|{lSm+Qri z(GcP=3+s@N2oixK{2eM+6i@=RlMjG(2gsljs3-yfF)0I5UEMA>rxHbg3h?rFv3>8F z(OutxDyo{w_F^*vDpeRal{hPVeSSGSJgDf~%gf<(e0zBvCSZpDgMhVGySHakd z_(!#v1aQ!Y?)LlJ?KU6O2PmLUC*&$p1`rYQT4NT0747hf1S}w855R7(yZ|tPiYlT} z72CBPA_$_X4AoWhcD7=Pm}Ib8MEv#Vzvt)99myp!!*G?VuFr38yThRuv9qepX_;LP zP*LssGa%ZbAt0_}AY&-D5b&O#e*N^r4?q0+>C?pg3{IjmmJj|eg#Y+5h-N8$gZ^s# zlWiwtIsn@X003aaJaZ!cF_93Q6*&>3(W$B49Zu7QfBMs(PN#><<$S(e&gb*^`h)6r z*28&6eU0{t)6o%gi+4UG(w9qn$47>PGSgX&t-d-2|BS*6t36;iTZ9TprANvi{+(h^k(0*XehdtlzI% zy`Gi^=PAWw!SO0M)$my~CQ0Mz;ydtH%>CBmN&CP^J`~ei6(i0q=Jnl7J#27&iO;{&Kyl%5>yw&^uFf zM+FEB#ejP;(+t#<2Zq@Z6cAE(4ijN-0Tp*u)!v$}w}haIN?>-??y}ay?r|$K2!TSAZ zE%S*8km+ivZ?N+rI0^dCh5rBzrRA>%Yv~h6vy6XKH}jm^1ac8SZQTW$F_)oiI8lxd^{6!Ze(3-||eP;o%)(6OqNBU%_g8uyXzYoEF?6StR z=vY3?rE#c?fVq)8>4J1(|20hlbD4k|ddr~FWIi5`2za?%ho3bnH#Er-lRhH#0U)Bu z2hfd-4L(2s7E~<}0TA7;7qv2oQ5kE5^Z9D~9+7}h)b^cK6$B6w+Y1(RF4|rOU;^%_ zYkhlpeS3M+VOxW^RsRmq+sVX51q6CIU(U!RosFzt3f5nW?g97-E8)Op0!k-E|LTWx z@Uu#w|H<{g{HULmKqm?ap8ze5K`w~l;%-2u$GfJ9@Hz$%N}>G*K_E^>FaA&IVRV?$ z6A+`2fzI>+5LIq17I_?6(aZ&X2T5*t{rUfyLMB#L}H-M{+Jy%9S zG;GZ6dd0C(O-lT@yS~0D2!bdY676m89&c@La}?0@F1f*W^&;Z)^9zV5fY>pLkMVUr znQWksK->QK^w9NhA~0^e(vk6;fKXulGr1Gseh>Ua1d(6IMhycG{l^l}u!WV_x#ng9 z;@+3)W{6Wb?<^_+1~J?stGSMX#=sxwA!1=%=w!Z`>*yI6B>|5Qos|wcM|&I#3LQoT z@OV5nx5jCDU7CVvtDHv)#qaI_g&gAvfU^$(qDuujo|`_rCe|7VF-2dDF@J49DiQYK;YZ(6hED-jzLP+#A_$PP2$=i0p z-m@skQ-)5YeJK0^58{NnATLb>3q>dUAFMr9MtB7R05Z}3a2PO;PgM=$_T$lvfg5;l z_ks_M{Q-g*Ah~C)n0uAJx2`-PB}J_d$G7vF73=6;Hl}M@8Q3SZzf_BA?}+Ske&-MWT@#OfX+z`2U7VU^?B1h>xe!?RIe! zeTb-tfn~cco5vsp@6aTXhcOO~=zlt#$v>khD}9TA(|tNOh0Gq5m`5@pa4DmpNlAO%~^$P zDeTD}k|dB+0)7%ez|+GC5wDlCh)#NG48_vwiKNG3U^*nV>;pqhj09@Kk^qMe7>R1v zcVpi|1=L{mc7AhGXH-OFEAGt3plglgkBo?|!|%Sac`~&r%!j5wT?0BQfF2$nUtV7B zvi|wBH181bJopI$yJo|G9{tBXVJ(%v8XsCV7r$D@Kk8kujDLJB&>{-TEyv2CAWs*R zDTTuwq8W|!n0lfl;I{?5BtV46)A4e>s+FPE_2Es_76`Jz)Z6nuszo1|m>&SJB1FW9 z^{zgb98H`~CU+HiUn0$MD(STAbvnfD0vn`(20qZ7>JmVN?ZX-d-Ry{W0b` zw#WTuFE200hljVGU|2E~i3YmoH2nMMCJz6L^nX_WQ`!Qg zi<;6w3Q>^tYdldN;CWaZfQ(DwF$sh|5%8fS!x<(44M`vO>@M)1-4=tvJ_A8Cgd<~~ zdQNrj>yBJ#&Icwn9C#&x8=w&pGUj}}Y2T00A3%XCbR~hR(^M-%)!E*eA@T2(z5W_$ z)i7N|BLL}GKsNkq68>}L?-J10TnU`%qnWfxRqTk(p}wFCDwV<>la%tILsQHdW|9PO z*zk|zlrWx7r_1Fst-G2I{_@9*hoaYagUL@q9|+*k=mXPhlffp@SBEmn;sp*u0x#Hs0n7x-lm)qxXKJik5zf=U?B z0V3GrTfZiW!l!k@O+JBB<&-}jsTzfU4IHN2{VkTiCH;>sj(|nH5;O6nd;Cqa-0X-28UEHpx2@#ImD z;d1Ip7&S@h{2Il{r<|;4VrLb=5FJhrx7*c@fy*7p(b?V~nDWDG_Aerem2q6%t#lru zu0#BGnp@Ceo!g-KAFmXbH-?kv6$Boi9-F3_O)f${sr5(bKg<*h;3rs;9+^Hw!}76$ zA2h6pePK~?CT22oqTT~N3SCFQ<5jKm`KJ?2Kkzp>GA?4$K%>{LxD0kUt`9@&28xR=|tr z0IJ$F&C}E4?RJ|_!_%O@2Ht;i*6)KQ55f2)aa8+i)DXz4KC|c_G^%tn_FSU>x9ETT z!op-_3Ym!Sm97i2hc97}iBhTO)>&z?Z2ygjSzhM2~SEL$bpcwiG;3uGfLdlOpf%{LwzxrQ+ z7Itmr2bFs379ip=ixx=6~U8~z}on{4;H{W6-J`$D`p zFJ27wEgpPx233WoxjjC8xZQ3Ll>uezSD()eD58M(45ZTPrwgxg@hm!qK;(|oZsF?Qg+<==_L%9Q^9bUK~KRqwO>8i1LrU46UU{5WK)N`?y9pO)Qr=ro~-m6tlzWy zs5O=iiUHUezPjCRPaj+!NNhS|{hC_8FW>ia4yfPy4Z+VWd z3;y$ehxQaw>!{D<&(nvem*~P~Kp>T!j)6)|GyuQB9o-tR$LsQJFNY$@aJ=rtPXHO2rcSLcH^yb*`!fBqs zk0!H*bQDt~9gYXrc#h6EHq1I6Glx^ZOoj@WJni-LF4?sNfT14FZwdR>6bKJyQL_5M zH-KJ#Z5-Nt0r^BJ#3z0rsWB3^$Zvk-WY3|mdN*uF|KWg4G5nYDk17BY(j5D@BmoRB zU#|op-w_+1_UY;A_3gE7+q6a)FPSFR^Tj~W>hdA~0v3uY-K1Fd>i{C^+D?-54&6Sv zThqvJ*4^-VFtgiI1>m>6bmL)zge0!73-gK9>EuX^va=Yda6<|6_|_a*zvi4Lu;2*# zN8m@-YTtP9#iz2?!@p(^=8I^d+|E)aB0u|=O5wR4Ncf+7D}m|Or>DoZZJV~i@bcMp zrj|fx*0=&`7JLBb!&wW$wE10f&qsJVotmK{k4%-Go}R9^8-$axRbBOcubM&px(a|4 ztcb>|V&j&lh97MHU(lEojlKbhqCVVX{V8M$p#Pl4!t@cmKz|W$s{F~1ge`=BP4hvy z{MBS2Ccn_%Tl)UIy?% z&DJCk-drvQ9~idF7*eXXud0yQP%pC!q>|HIp!zs`{uw;JO-AweibqkW5sZja6&rv z0b&AZxhYoa2}Ih7m1?-y76=)jgcr)xxPEIa0#O+29nh0JW8C_R4D|8w(H)NFwY_j6 zf1ZAL`ttcpbnZDK5s~ftTxVW&^Ff~8gh@OG63vjrOnbrfW1|g3K}qNa4gl z@`EAr0r18Be&;K2Ro@fl0s~#=D?AKA2N;OB+wZQIYn&JniOB6lS+?)R8X(VCgp+|W zFREp@yvUFyRWTHdI#V!Y9I8}uq?LIY{H#--*}<2M=s#+j@wE7O) zfh7JHGC-(sLx`Uwu=mL5eG)V*i^I^5fB+8<54WcAr4Z34al73>;|X_Ww(eAxIGn%Z zk4D-F%~PWZ1S*?;F7RhTe~vFokWWZ@dLlT;K>rL-+X#L(g@4$N{v)QN(!czAqt!#4oIseA58i0FrpAFr3o zln|yLRGG?JtNA_Nc)tf|w3B%;3WW8{Mo_}^Ft#{`g;8^ei} zM5BZm)6X``Hd02}qyecqKVtnMV1Kd=ySu@lo4yD@2?#x!=4;a+6b8nvb@wmV)?>d;3t!4mXtsF zqXjtB(BD}JWa`Z@HIxJUBFs-Iqe7a9ThYl-OHd=k)Q={`?;z0desPVJJbJxeGlQUr zt^$rDR%YSYnjCsX^c#ZQk~GLyATOD|ANc1di0j+X12V`bOF-oW8IWcT(A36D;3qfq zxIlfbg#SV#ESwTflR)MO)m;B9Z4#H2K}j(XuUiav(fNz zyslNwmZs`ptE!RcUhsB~83I!~rE>mb!#U(G~ z_WI|N0c17+<)D4BO}rTXixBPG51K!K> z%iXl&>p#>DqxrwBe+JWdUJPbw&8n`uuG4Q|@EaVU&=QE&(q^MR=+YdS&z7bfWzO}9Re9pTKJZFer4;A`@Y%EmoBhCQzI-^%7?wM4OPLml3+_QP`2<@>z`%*OZUgE z;84yN(n0<%=i$s_4`+p2gb-IE4OT%E zWS$LT*1rh-bGRqd_s!S7NxDn}8warFn^wb-4=j2@#s{LD01H?sDV*bfJ%jWeA$)l& zZjhWRmmXJV00xa|-VTpyJLNp+AHh=y*UiDWMFgu1S+x6%`FLF2iR z_9>o*k_SAo-3Kr|WBN-PolL(h0R0(v$jv1eDu>Xi)(hX%+@tgFRP)pTBxiGE|L`P@@HQ)ib)m-3;(#2HNSjhV{C6#1${RT3GRG`#T+z;l=NheNw1K}Vt9*NxyOy-~~ea|D6kR}J4)3}n#@tO?(; zKCo2$CR&F$6=0-wh{Vykl@eiVY`C6~n7gioQV(Qdj)s3oP)%G z8D}VGgKUVH_)%*uc$-nOI-+3AW$mx##9WC)BBKsU?LONcj*~zJ@#pn)y19gay!D3( ztOSAH3H>u8zopuDdJT-Z1tBZdaDK%gW`R&zEUfxKHV0g?PxJ09eYN zu&e8c|0z}2+cUs=2@pepqTR13h4P-ETZ_~!86Z~eK&b#Ivi|9rFA!~_DN>%g2#z$S zK@qbb;nxA7%-*f{`9QWuR%nM6P*zL~#RX0n{xe1!di)>(>U%q%Mfr8mC+^dflR$j7 zP*x#V935M18WiR%^SNOOP-QK8-TGs_=c49k5&UTJBN?9~>I2*XgjQJ54Fz$zDp>P@ zpaw|Q4f%wSR0UDMPu(C*VsTCw9iWFqSNBycP*%E_343`UtcBs3()d6T^AtipL<2Wj z+0laam**J@bDqmhU&sibbKpmFUnYHkuVRE2d|;&@F2eT`H=1^~3f8}n3c@lagWkf8 z>pq$q9z<@Jf4DRk7m&}kM6j^*X`vmUc}6relu}^~upIbTLw-C0>EZrR_qJu4r&#!w zz)zYtR>4ok0rEho+!k15Mb`@A^#)l|6X&*OW#==uq=GP1=dwWz=`lTasovEC{#hhD z@!buve3P@xg2IwMv>f#dyCoL-Q<+X%9Ot8|cOmxY4Ln!)rh6kw)bPglbjblW`M_c^ z5Yv_0^T0MA$d&|v>y<{Hdh(%5qK{3f9VBEnecyw35I24++b?v#@)!4nm3zG zK2Yi>=Szcv_3JY7$vI}+^V-6Du7rPBaDeaT1I2S%)_tIe-7_V@##9g_ft2ft6GPq; zh62ECLBv=i@lD18GKc0ST#*+0VYxtAj{2F^(<^~L5Bf9hhDGbo47FuCzWHKDODM41 zO^KX=A-D5mp*5DZ`#VUX_tXO^bzdwAGN~ZZO_Z@%svKH+3y9cSt0EZ(`NmSvm>t!n zepqZAsl}8mez|LwZz<%Jr+H`FWc^w^NU2o#LectR+4{FTz#YZF7Eakp`{#q)K=z(!*9eLj#-pb4QF41g{4 z<5sLcQ~COyK!nWPb$#Yt`PM0*G#r~*4wcxx=4yl`A6N|hG24?#14)7K71p0~dwoyg zTd@8)0~8%#TQRU$5Qo;g<^$`^vc;01P=RM&;DQ@KDeQYU5Yf~C&c1Q@wZXs87*79_ z^MTwUDoev->+PpB@GpS=C8N($`Z-J%OFUxN*Uiv>9Rh9ifh|C2u^`@*2bO%GSQ4bE zAUFgY7k^MOXGLM(v*)_r^|tp;{=%H~zgH1aV*j%Omm)&etv*mN%F=wzR$A%()}N*P zv#s#8x3BPs>4kAZTWtR^)X<#YvL$u~uH!ZY#C-Nz`LsqT7 z=m6i06uK7+mDtpGq=IyuCL`-x_zt4bk1OblrO zkQnK^1OK{I`~ApgX)83WrUYKf)^`@f_xL~-pyyh$+k9Xl6+mu)0dVR6Sb+a?1?*-3 zFt2n&{Dy|V=~K)wpHTyO{jfb)Tr;s0_}^vyg<<~X3bOXIl#+r_qXza#r{Cv z2XaYbD{EO0DsA`y#bh_%z1!{DuFbs1YleT9V=UKi`Q-<(SZHBY$SUAjwcmAKwfvNu ztbZHwxufUxZ5&{m4`j&FO?jYHq~1l|XGIN}mfZ^e!2;|vrm}tS%>b0K4t@ho+_KTC%7+33F#`AfI>8K6g97eWcJg^?@~i zxU-KN&o^$!R1mM0AVe#6JZwH)gM#0n4P0bE7g*48Qvgcp(dVX_6|5`Y-EgL|2{yFx7-EQBHOHfkxD7{BtXaBiHp-Y{x^)h%vOLgT) zvb$kVIlp`p+<#Z$`yK~)UxVyxd|-R{-{b~Z3jJl4DCgtG80%&r6RSqZ%b~wb4X~5| zAiH5dv6SLAMJOy&$Zw!t3;Zid;A^ZOz8UiQrVj8`J^@BSqR!kp?8(9F>LI3?p>CT4N9aeqc)xU)g ztihr9wnB>7>IMr5A^ryu5y`DV$7NH;_rJ-jXb4J2Wo@$irI5eX^oz~4WJAve*z=Xv z|25(Nz6|glA6TjfHkQSAr-H9^g9T66?e=*IbZ(d8_k}1U+o5Wd4e&f?>d0&2mpNRl_;>5>J=Qp?hulj7h&IjII5|pZf;IP4|NabczYnaI#b2EYzRnF+Jz=-s*}h-jfwE-(Up*66xM#&^ zHrV|)GX47kKP>FB)vTX?W9$D~2Y|A`D+5gOb5eK1p+XASo z4ImWQu`zwum1I6!7D0RqB}AdvT@3am6Zs0$zdP_lxh1#2W^Lj7_3K&z--f5SCl9QY z1lv-Mf3pnm^**o- z4lN_1d)xpLp>k#!R`46`_xouCY(dtK?^yrvE_YD4J0fP!;aU<-+-Lgbz<&?y`Hijr zTROms53Er`OFpn66}$%&EqQ1Bi?|`+yMF7Udu+Ayt)H&m0}nL6Z1Mh~g2E{t3!Ki~ z*1xy}=$lyoH+F!#eBdkK&?ZC#iCQ!7AR9aZ5sfihGQhnq@ty>*_Q4lsCO6uBAvY|X zP+7wDUk!VH1MB}@*?MDHypjrFLl12wq^HKdm=Fp#x(-0;)A+9D{xvS4MIE&+-tN=em;rQYcwnQ1 zdXuucX!;ui|344(|3(h*^>An@3#_|AIU6h`guEx1s_OcF@#MRIS`yImNucks`xVo_ zQ+fLB0{?fi{_pDm-_8dhFHl=A49b)cERV7Tw^V}$uHZyP?i0V4tryHh7fa|OrYTQZ zmJX}b)eY6}3jDvl^?z^1?tLY}ov8rwlAJ}RXuBuC;c&cOuM2qYj`#7~bi%e;{7O_` zoG#W4D$_Ndrx^Gl*X8}qK>u%=0lp#?WMXJ>&gM=xfZWN~B~KW_f7V~hzt6V~FeM1H zcyZxunMGuzCAsN#(}&{HqI*sMH?aQS&H>)<18a!rem7W32&s){3IHtI`?t-t_a=dD sH)Sj2HMDbw=|g#``|r*A0pR}|04VvQqjCDkI{*Lx07*qoM6N<$f*XZGQ2+n{ literal 0 HcmV?d00001 From ba65e85746b6882d6196ce6ff47ef1edf23c43a3 Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Mon, 8 Sep 2014 17:25:27 -0300 Subject: [PATCH 07/15] New version of three.js --- assets/globe/third-party/three.js | 35842 ++++++++++++++++++++++++++++ views/globe/main.ejs | 2 +- 2 files changed, 35843 insertions(+), 1 deletion(-) create mode 100644 assets/globe/third-party/three.js diff --git a/assets/globe/third-party/three.js b/assets/globe/third-party/three.js new file mode 100644 index 0000000..d98a124 --- /dev/null +++ b/assets/globe/third-party/three.js @@ -0,0 +1,35842 @@ +// File:src/Three.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +var THREE = { REVISION: '68' }; + +// browserify support +if ( typeof module === 'object' ) { + + module.exports = THREE; + +} + +// GL STATE CONSTANTS + +THREE.CullFaceNone = 0; +THREE.CullFaceBack = 1; +THREE.CullFaceFront = 2; +THREE.CullFaceFrontBack = 3; + +THREE.FrontFaceDirectionCW = 0; +THREE.FrontFaceDirectionCCW = 1; + +// SHADOWING TYPES + +THREE.BasicShadowMap = 0; +THREE.PCFShadowMap = 1; +THREE.PCFSoftShadowMap = 2; + +// MATERIAL CONSTANTS + +// side + +THREE.FrontSide = 0; +THREE.BackSide = 1; +THREE.DoubleSide = 2; + +// shading + +THREE.NoShading = 0; +THREE.FlatShading = 1; +THREE.SmoothShading = 2; + +// colors + +THREE.NoColors = 0; +THREE.FaceColors = 1; +THREE.VertexColors = 2; + +// blending modes + +THREE.NoBlending = 0; +THREE.NormalBlending = 1; +THREE.AdditiveBlending = 2; +THREE.SubtractiveBlending = 3; +THREE.MultiplyBlending = 4; +THREE.CustomBlending = 5; + +// custom blending equations +// (numbers start from 100 not to clash with other +// mappings to OpenGL constants defined in Texture.js) + +THREE.AddEquation = 100; +THREE.SubtractEquation = 101; +THREE.ReverseSubtractEquation = 102; + +// custom blending destination factors + +THREE.ZeroFactor = 200; +THREE.OneFactor = 201; +THREE.SrcColorFactor = 202; +THREE.OneMinusSrcColorFactor = 203; +THREE.SrcAlphaFactor = 204; +THREE.OneMinusSrcAlphaFactor = 205; +THREE.DstAlphaFactor = 206; +THREE.OneMinusDstAlphaFactor = 207; + +// custom blending source factors + +//THREE.ZeroFactor = 200; +//THREE.OneFactor = 201; +//THREE.SrcAlphaFactor = 204; +//THREE.OneMinusSrcAlphaFactor = 205; +//THREE.DstAlphaFactor = 206; +//THREE.OneMinusDstAlphaFactor = 207; +THREE.DstColorFactor = 208; +THREE.OneMinusDstColorFactor = 209; +THREE.SrcAlphaSaturateFactor = 210; + + +// TEXTURE CONSTANTS + +THREE.MultiplyOperation = 0; +THREE.MixOperation = 1; +THREE.AddOperation = 2; + +// Mapping modes + +THREE.UVMapping = function () {}; + +THREE.CubeReflectionMapping = function () {}; +THREE.CubeRefractionMapping = function () {}; + +THREE.SphericalReflectionMapping = function () {}; +THREE.SphericalRefractionMapping = function () {}; + +// Wrapping modes + +THREE.RepeatWrapping = 1000; +THREE.ClampToEdgeWrapping = 1001; +THREE.MirroredRepeatWrapping = 1002; + +// Filters + +THREE.NearestFilter = 1003; +THREE.NearestMipMapNearestFilter = 1004; +THREE.NearestMipMapLinearFilter = 1005; +THREE.LinearFilter = 1006; +THREE.LinearMipMapNearestFilter = 1007; +THREE.LinearMipMapLinearFilter = 1008; + +// Data types + +THREE.UnsignedByteType = 1009; +THREE.ByteType = 1010; +THREE.ShortType = 1011; +THREE.UnsignedShortType = 1012; +THREE.IntType = 1013; +THREE.UnsignedIntType = 1014; +THREE.FloatType = 1015; + +// Pixel types + +//THREE.UnsignedByteType = 1009; +THREE.UnsignedShort4444Type = 1016; +THREE.UnsignedShort5551Type = 1017; +THREE.UnsignedShort565Type = 1018; + +// Pixel formats + +THREE.AlphaFormat = 1019; +THREE.RGBFormat = 1020; +THREE.RGBAFormat = 1021; +THREE.LuminanceFormat = 1022; +THREE.LuminanceAlphaFormat = 1023; + +// Compressed texture formats + +THREE.RGB_S3TC_DXT1_Format = 2001; +THREE.RGBA_S3TC_DXT1_Format = 2002; +THREE.RGBA_S3TC_DXT3_Format = 2003; +THREE.RGBA_S3TC_DXT5_Format = 2004; + +/* +// Potential future PVRTC compressed texture formats +THREE.RGB_PVRTC_4BPPV1_Format = 2100; +THREE.RGB_PVRTC_2BPPV1_Format = 2101; +THREE.RGBA_PVRTC_4BPPV1_Format = 2102; +THREE.RGBA_PVRTC_2BPPV1_Format = 2103; +*/ + +// File:src/math/Color.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Color = function ( color ) { + + if ( arguments.length === 3 ) { + + return this.setRGB( arguments[ 0 ], arguments[ 1 ], arguments[ 2 ] ); + + } + + return this.set( color ) + +}; + +THREE.Color.prototype = { + + constructor: THREE.Color, + + r: 1, g: 1, b: 1, + + set: function ( value ) { + + if ( value instanceof THREE.Color ) { + + this.copy( value ); + + } else if ( typeof value === 'number' ) { + + this.setHex( value ); + + } else if ( typeof value === 'string' ) { + + this.setStyle( value ); + + } + + return this; + + }, + + setHex: function ( hex ) { + + hex = Math.floor( hex ); + + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; + + return this; + + }, + + setRGB: function ( r, g, b ) { + + this.r = r; + this.g = g; + this.b = b; + + return this; + + }, + + setHSL: function ( h, s, l ) { + + // h,s,l ranges are in 0.0 - 1.0 + + if ( s === 0 ) { + + this.r = this.g = this.b = l; + + } else { + + var hue2rgb = function ( p, q, t ) { + + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; + + }; + + var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + var q = ( 2 * l ) - p; + + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); + + } + + return this; + + }, + + setStyle: function ( style ) { + + // rgb(255,0,0) + + if ( /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test( style ) ) { + + var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec( style ); + + this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; + this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255; + this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255; + + return this; + + } + + // rgb(100%,0%,0%) + + if ( /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test( style ) ) { + + var color = /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec( style ); + + this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; + this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100; + this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100; + + return this; + + } + + // #ff0000 + + if ( /^\#([0-9a-f]{6})$/i.test( style ) ) { + + var color = /^\#([0-9a-f]{6})$/i.exec( style ); + + this.setHex( parseInt( color[ 1 ], 16 ) ); + + return this; + + } + + // #f00 + + if ( /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test( style ) ) { + + var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec( style ); + + this.setHex( parseInt( color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ] + color[ 3 ] + color[ 3 ], 16 ) ); + + return this; + + } + + // red + + if ( /^(\w+)$/i.test( style ) ) { + + this.setHex( THREE.ColorKeywords[ style ] ); + + return this; + + } + + + }, + + copy: function ( color ) { + + this.r = color.r; + this.g = color.g; + this.b = color.b; + + return this; + + }, + + copyGammaToLinear: function ( color ) { + + this.r = color.r * color.r; + this.g = color.g * color.g; + this.b = color.b * color.b; + + return this; + + }, + + copyLinearToGamma: function ( color ) { + + this.r = Math.sqrt( color.r ); + this.g = Math.sqrt( color.g ); + this.b = Math.sqrt( color.b ); + + return this; + + }, + + convertGammaToLinear: function () { + + var r = this.r, g = this.g, b = this.b; + + this.r = r * r; + this.g = g * g; + this.b = b * b; + + return this; + + }, + + convertLinearToGamma: function () { + + this.r = Math.sqrt( this.r ); + this.g = Math.sqrt( this.g ); + this.b = Math.sqrt( this.b ); + + return this; + + }, + + getHex: function () { + + return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; + + }, + + getHexString: function () { + + return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); + + }, + + getHSL: function ( optionalTarget ) { + + // h,s,l ranges are in 0.0 - 1.0 + + var hsl = optionalTarget || { h: 0, s: 0, l: 0 }; + + var r = this.r, g = this.g, b = this.b; + + var max = Math.max( r, g, b ); + var min = Math.min( r, g, b ); + + var hue, saturation; + var lightness = ( min + max ) / 2.0; + + if ( min === max ) { + + hue = 0; + saturation = 0; + + } else { + + var delta = max - min; + + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + + switch ( max ) { + + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; + + } + + hue /= 6; + + } + + hsl.h = hue; + hsl.s = saturation; + hsl.l = lightness; + + return hsl; + + }, + + getStyle: function () { + + return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; + + }, + + offsetHSL: function ( h, s, l ) { + + var hsl = this.getHSL(); + + hsl.h += h; hsl.s += s; hsl.l += l; + + this.setHSL( hsl.h, hsl.s, hsl.l ); + + return this; + + }, + + add: function ( color ) { + + this.r += color.r; + this.g += color.g; + this.b += color.b; + + return this; + + }, + + addColors: function ( color1, color2 ) { + + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; + + return this; + + }, + + addScalar: function ( s ) { + + this.r += s; + this.g += s; + this.b += s; + + return this; + + }, + + multiply: function ( color ) { + + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.r *= s; + this.g *= s; + this.b *= s; + + return this; + + }, + + lerp: function ( color, alpha ) { + + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; + + return this; + + }, + + equals: function ( c ) { + + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + + }, + + fromArray: function ( array ) { + + this.r = array[ 0 ]; + this.g = array[ 1 ]; + this.b = array[ 2 ]; + + return this; + + }, + + toArray: function () { + + return [ this.r, this.g, this.b ]; + + }, + + clone: function () { + + return new THREE.Color().setRGB( this.r, this.g, this.b ); + + } + +}; + +THREE.ColorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, +'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, +'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, +'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, +'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, +'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, +'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, +'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, +'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, +'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, +'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, +'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, +'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, +'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, +'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, +'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, +'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, +'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, +'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, +'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, +'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, +'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, +'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, +'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; + +// File:src/math/Quaternion.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Quaternion = function ( x, y, z, w ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._w = ( w !== undefined ) ? w : 1; + +}; + +THREE.Quaternion.prototype = { + + constructor: THREE.Quaternion, + + _x: 0,_y: 0, _z: 0, _w: 0, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this.onChangeCallback(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this.onChangeCallback(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this.onChangeCallback(); + + }, + + get w () { + + return this._w; + + }, + + set w ( value ) { + + this._w = value; + this.onChangeCallback(); + + }, + + set: function ( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this.onChangeCallback(); + + return this; + + }, + + copy: function ( quaternion ) { + + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; + + this.onChangeCallback(); + + return this; + + }, + + setFromEuler: function ( euler, update ) { + + if ( euler instanceof THREE.Euler === false ) { + + throw new Error( 'THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); + } + + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m + + var c1 = Math.cos( euler._x / 2 ); + var c2 = Math.cos( euler._y / 2 ); + var c3 = Math.cos( euler._z / 2 ); + var s1 = Math.sin( euler._x / 2 ); + var s2 = Math.sin( euler._y / 2 ); + var s3 = Math.sin( euler._z / 2 ); + + if ( euler.order === 'XYZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'YXZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( euler.order === 'ZXY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'ZYX' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( euler.order === 'YZX' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( euler.order === 'XZY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } + + if ( update !== false ) this.onChangeCallback(); + + return this; + + }, + + setFromAxisAngle: function ( axis, angle ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + + // assumes axis is normalized + + var halfAngle = angle / 2, s = Math.sin( halfAngle ); + + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); + + this.onChangeCallback(); + + return this; + + }, + + setFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + + trace = m11 + m22 + m33, + s; + + if ( trace > 0 ) { + + s = 0.5 / Math.sqrt( trace + 1.0 ); + + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; + + } else if ( m11 > m22 && m11 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; + + } else if ( m22 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; + + } else { + + s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this.onChangeCallback(); + + return this; + + }, + + setFromUnitVectors: function () { + + // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final + + // assumes direction vectors vFrom and vTo are normalized + + var v1, r; + + var EPS = 0.000001; + + return function ( vFrom, vTo ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + r = vFrom.dot( vTo ) + 1; + + if ( r < EPS ) { + + r = 0; + + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + + v1.set( - vFrom.y, vFrom.x, 0 ); + + } else { + + v1.set( 0, - vFrom.z, vFrom.y ); + + } + + } else { + + v1.crossVectors( vFrom, vTo ); + + } + + this._x = v1.x; + this._y = v1.y; + this._z = v1.z; + this._w = r; + + this.normalize(); + + return this; + + } + + }(), + + inverse: function () { + + this.conjugate().normalize(); + + return this; + + }, + + conjugate: function () { + + this._x *= - 1; + this._y *= - 1; + this._z *= - 1; + + this.onChangeCallback(); + + return this; + + }, + + dot: function ( v ) { + + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + + }, + + lengthSq: function () { + + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + + }, + + length: function () { + + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + + }, + + normalize: function () { + + var l = this.length(); + + if ( l === 0 ) { + + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; + + } else { + + l = 1 / l; + + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; + + } + + this.onChangeCallback(); + + return this; + + }, + + multiply: function ( q, p ) { + + if ( p !== undefined ) { + + console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); + return this.multiplyQuaternions( q, p ); + + } + + return this.multiplyQuaternions( this, q ); + + }, + + multiplyQuaternions: function ( a, b ) { + + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm + + var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; + + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + + this.onChangeCallback(); + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); + return vector.applyQuaternion( this ); + + }, + + slerp: function ( qb, t ) { + + var x = this._x, y = this._y, z = this._z, w = this._w; + + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + + var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + + if ( cosHalfTheta < 0 ) { + + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; + + cosHalfTheta = - cosHalfTheta; + + } else { + + this.copy( qb ); + + } + + if ( cosHalfTheta >= 1.0 ) { + + this._w = w; + this._x = x; + this._y = y; + this._z = z; + + return this; + + } + + var halfTheta = Math.acos( cosHalfTheta ); + var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); + + if ( Math.abs( sinHalfTheta ) < 0.001 ) { + + this._w = 0.5 * ( w + this._w ); + this._x = 0.5 * ( x + this._x ); + this._y = 0.5 * ( y + this._y ); + this._z = 0.5 * ( z + this._z ); + + return this; + + } + + var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); + + this.onChangeCallback(); + + return this; + + }, + + equals: function ( quaternion ) { + + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + + }, + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + this._w = array[ 3 ]; + + this.onChangeCallback(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._w ]; + + }, + + onChange: function ( callback ) { + + this.onChangeCallback = callback; + + return this; + + }, + + onChangeCallback: function () {}, + + clone: function () { + + return new THREE.Quaternion( this._x, this._y, this._z, this._w ); + + } + +}; + +THREE.Quaternion.slerp = function ( qa, qb, qm, t ) { + + return qm.copy( qa ).slerp( qb, t ); + +} + +// File:src/math/Vector2.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author philogb / http://blog.thejit.org/ + * @author egraether / http://egraether.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + +THREE.Vector2 = function ( x, y ) { + + this.x = x || 0; + this.y = y || 0; + +}; + +THREE.Vector2.prototype = { + + constructor: THREE.Vector2, + + set: function ( x, y ) { + + this.x = x; + this.y = y; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; + + }, + + multiply: function ( v ) { + + this.x *= v.x; + this.y *= v.y; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.x *= s; + this.y *= s; + + return this; + + }, + + divide: function ( v ) { + + this.x /= v.x; + this.y /= v.y; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + + } + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + return this; + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector2(); + max = new THREE.Vector2(); + + } + + min.set( minVal, minVal ); + max.set( maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + + return this; + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y ]; + + }, + + clone: function () { + + return new THREE.Vector2( this.x, this.y ); + + } + +}; + +// File:src/math/Vector3.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author *kile / http://kile.stravaganza.org/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Vector3 = function ( x, y, z ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + +}; + +THREE.Vector3.prototype = { + + constructor: THREE.Vector3, + + set: function ( x, y, z ) { + + this.x = x; + this.y = y; + this.z = z; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + + return this; + + }, + + multiply: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); + return this.multiplyVectors( v, w ); + + } + + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + + return this; + + }, + + multiplyVectors: function ( a, b ) { + + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; + + return this; + + }, + + applyEuler: function () { + + var quaternion; + + return function ( euler ) { + + if ( euler instanceof THREE.Euler === false ) { + + console.error( 'THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.' ); + + } + + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); + + this.applyQuaternion( quaternion.setFromEuler( euler ) ); + + return this; + + }; + + }(), + + applyAxisAngle: function () { + + var quaternion; + + return function ( axis, angle ) { + + if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); + + this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); + + return this; + + }; + + }(), + + applyMatrix3: function ( m ) { + + var x = this.x; + var y = this.y; + var z = this.z; + + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + + return this; + + }, + + applyMatrix4: function ( m ) { + + // input: THREE.Matrix4 affine matrix + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ]; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ]; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ]; + + return this; + + }, + + applyProjection: function ( m ) { + + // input: THREE.Matrix4 projection matrix + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + var d = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); // perspective divide + + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * d; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * d; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * d; + + return this; + + }, + + applyQuaternion: function ( q ) { + + var x = this.x; + var y = this.y; + var z = this.z; + + var qx = q.x; + var qy = q.y; + var qz = q.z; + var qw = q.w; + + // calculate quat * vector + + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = - qx * x - qy * y - qz * z; + + // calculate result * inverse quat + + this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; + this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; + this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; + + return this; + + }, + + transformDirection: function ( m ) { + + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction + + var x = this.x, y = this.y, z = this.z; + + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + + this.normalize(); + + return this; + + }, + + divide: function ( v ) { + + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + this.z = 0; + + } + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + if ( this.z > v.z ) { + + this.z = v.z; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + if ( this.z < v.z ) { + + this.z = v.z; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + if ( this.z < min.z ) { + + this.z = min.z; + + } else if ( this.z > max.z ) { + + this.z = max.z; + + } + + return this; + + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector3(); + max = new THREE.Vector3(); + + } + + min.set( minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + + }, + + lengthManhattan: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + + return this; + + }, + + cross: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); + return this.crossVectors( v, w ); + + } + + var x = this.x, y = this.y, z = this.z; + + this.x = y * v.z - z * v.y; + this.y = z * v.x - x * v.z; + this.z = x * v.y - y * v.x; + + return this; + + }, + + crossVectors: function ( a, b ) { + + var ax = a.x, ay = a.y, az = a.z; + var bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + + return this; + + }, + + projectOnVector: function () { + + var v1, dot; + + return function ( vector ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + v1.copy( vector ).normalize(); + + dot = this.dot( v1 ); + + return this.copy( v1 ).multiplyScalar( dot ); + + }; + + }(), + + projectOnPlane: function () { + + var v1; + + return function ( planeNormal ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + v1.copy( this ).projectOnVector( planeNormal ); + + return this.sub( v1 ); + + } + + }(), + + reflect: function () { + + // reflect incident vector off plane orthogonal to normal + // normal is assumed to have unit length + + var v1; + + return function ( normal ) { + + if ( v1 === undefined ) v1 = new THREE.Vector3(); + + return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + + } + + }(), + + angleTo: function ( v ) { + + var theta = this.dot( v ) / ( this.length() * v.length() ); + + // clamp, to handle numerical problems + + return Math.acos( THREE.Math.clamp( theta, - 1, 1 ) ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x; + var dy = this.y - v.y; + var dz = this.z - v.z; + + return dx * dx + dy * dy + dz * dz; + + }, + + setEulerFromRotationMatrix: function ( m, order ) { + + console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); + + }, + + setEulerFromQuaternion: function ( q, order ) { + + console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); + + }, + + getPositionFromMatrix: function ( m ) { + + console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); + + return this.setFromMatrixPosition( m ); + + }, + + getScaleFromMatrix: function ( m ) { + + console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); + + return this.setFromMatrixScale( m ); + }, + + getColumnFromMatrix: function ( index, matrix ) { + + console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); + + return this.setFromMatrixColumn( index, matrix ); + + }, + + setFromMatrixPosition: function ( m ) { + + this.x = m.elements[ 12 ]; + this.y = m.elements[ 13 ]; + this.z = m.elements[ 14 ]; + + return this; + + }, + + setFromMatrixScale: function ( m ) { + + var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[ 2 ] ).length(); + var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[ 6 ] ).length(); + var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; + + return this; + }, + + setFromMatrixColumn: function ( index, matrix ) { + + var offset = index * 4; + + var me = matrix.elements; + + this.x = me[ offset ]; + this.y = me[ offset + 1 ]; + this.z = me[ offset + 2 ]; + + return this; + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + this.z = array[ 2 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y, this.z ]; + + }, + + clone: function () { + + return new THREE.Vector3( this.x, this.y, this.z ); + + } + +}; + +// File:src/math/Vector4.js + +/** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Vector4 = function ( x, y, z, w ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + this.w = ( w !== undefined ) ? w : 1; + +}; + +THREE.Vector4.prototype = { + + constructor: THREE.Vector4, + + set: function ( x, y, z, w ) { + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setW: function ( w ) { + + this.w = w; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + case 3: this.w = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + case 3: return this.w; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + this.w += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; + + return this; + + }, + + applyMatrix4: function ( m ) { + + var x = this.x; + var y = this.y; + var z = this.z; + var w = this.w; + + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; + + return this; + + }, + + divideScalar: function ( scalar ) { + + if ( scalar !== 0 ) { + + var invScalar = 1 / scalar; + + this.x *= invScalar; + this.y *= invScalar; + this.z *= invScalar; + this.w *= invScalar; + + } else { + + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + + } + + return this; + + }, + + setAxisAngleFromQuaternion: function ( q ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + var s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } + + return this; + + }, + + setAxisAngleFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var angle, x, y, z, // variables for result + epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + + te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + if ( ( Math.abs( m12 - m21 ) < epsilon ) + && ( Math.abs( m13 - m31 ) < epsilon ) + && ( Math.abs( m23 - m32 ) < epsilon ) ) { + + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms + + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) + && ( Math.abs( m13 + m31 ) < epsilon2 ) + && ( Math.abs( m23 + m32 ) < epsilon2 ) + && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + + // this singularity is identity matrix so angle = 0 + + this.set( 1, 0, 0, 0 ); + + return this; // zero angle, arbitrary axis + + } + + // otherwise this singularity is angle = 180 + + angle = Math.PI; + + var xx = ( m11 + 1 ) / 2; + var yy = ( m22 + 1 ) / 2; + var zz = ( m33 + 1 ) / 2; + var xy = ( m12 + m21 ) / 4; + var xz = ( m13 + m31 ) / 4; + var yz = ( m23 + m32 ) / 4; + + if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term + + if ( xx < epsilon ) { + + x = 0; + y = 0.707106781; + z = 0.707106781; + + } else { + + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; + + } + + } else if ( yy > zz ) { // m22 is the largest diagonal term + + if ( yy < epsilon ) { + + x = 0.707106781; + y = 0; + z = 0.707106781; + + } else { + + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; + + } + + } else { // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + + return this; + + }, + + min: function ( v ) { + + if ( this.x > v.x ) { + + this.x = v.x; + + } + + if ( this.y > v.y ) { + + this.y = v.y; + + } + + if ( this.z > v.z ) { + + this.z = v.z; + + } + + if ( this.w > v.w ) { + + this.w = v.w; + + } + + return this; + + }, + + max: function ( v ) { + + if ( this.x < v.x ) { + + this.x = v.x; + + } + + if ( this.y < v.y ) { + + this.y = v.y; + + } + + if ( this.z < v.z ) { + + this.z = v.z; + + } + + if ( this.w < v.w ) { + + this.w = v.w; + + } + + return this; + + }, + + clamp: function ( min, max ) { + + // This function assumes min < max, if this assumption isn't true it will not operate correctly + + if ( this.x < min.x ) { + + this.x = min.x; + + } else if ( this.x > max.x ) { + + this.x = max.x; + + } + + if ( this.y < min.y ) { + + this.y = min.y; + + } else if ( this.y > max.y ) { + + this.y = max.y; + + } + + if ( this.z < min.z ) { + + this.z = min.z; + + } else if ( this.z > max.z ) { + + this.z = max.z; + + } + + if ( this.w < min.w ) { + + this.w = min.w; + + } else if ( this.w > max.w ) { + + this.w = max.w; + + } + + return this; + + }, + + clampScalar: ( function () { + + var min, max; + + return function ( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new THREE.Vector4(); + max = new THREE.Vector4(); + + } + + min.set( minVal, minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + } )(), + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + this.w = Math.round( this.w ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + this.w = - this.w; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + + }, + + lengthManhattan: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() ); + + }, + + setLength: function ( l ) { + + var oldLength = this.length(); + + if ( oldLength !== 0 && l !== oldLength ) { + + this.multiplyScalar( l / oldLength ); + + } + + return this; + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; + + return this; + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + + }, + + fromArray: function ( array ) { + + this.x = array[ 0 ]; + this.y = array[ 1 ]; + this.z = array[ 2 ]; + this.w = array[ 3 ]; + + return this; + + }, + + toArray: function () { + + return [ this.x, this.y, this.z, this.w ]; + + }, + + clone: function () { + + return new THREE.Vector4( this.x, this.y, this.z, this.w ); + + } + +}; + +// File:src/math/Euler.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Euler = function ( x, y, z, order ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._order = order || THREE.Euler.DefaultOrder; + +}; + +THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; + +THREE.Euler.DefaultOrder = 'XYZ'; + +THREE.Euler.prototype = { + + constructor: THREE.Euler, + + _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this.onChangeCallback(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this.onChangeCallback(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this.onChangeCallback(); + + }, + + get order () { + + return this._order; + + }, + + set order ( value ) { + + this._order = value; + this.onChangeCallback(); + + }, + + set: function ( x, y, z, order ) { + + this._x = x; + this._y = y; + this._z = z; + this._order = order || this._order; + + this.onChangeCallback(); + + return this; + + }, + + copy: function ( euler ) { + + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; + + this.onChangeCallback(); + + return this; + + }, + + setFromRotationMatrix: function ( m, order ) { + + var clamp = THREE.Math.clamp; + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var te = m.elements; + var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; + var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; + var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + order = order || this._order; + + if ( order === 'XYZ' ) { + + this._y = Math.asin( clamp( m13, - 1, 1 ) ); + + if ( Math.abs( m13 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); + + } else { + + this._x = Math.atan2( m32, m22 ); + this._z = 0; + + } + + } else if ( order === 'YXZ' ) { + + this._x = Math.asin( - clamp( m23, - 1, 1 ) ); + + if ( Math.abs( m23 ) < 0.99999 ) { + + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); + + } else { + + this._y = Math.atan2( - m31, m11 ); + this._z = 0; + + } + + } else if ( order === 'ZXY' ) { + + this._x = Math.asin( clamp( m32, - 1, 1 ) ); + + if ( Math.abs( m32 ) < 0.99999 ) { + + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); + + } else { + + this._y = 0; + this._z = Math.atan2( m21, m11 ); + + } + + } else if ( order === 'ZYX' ) { + + this._y = Math.asin( - clamp( m31, - 1, 1 ) ); + + if ( Math.abs( m31 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); + + } else { + + this._x = 0; + this._z = Math.atan2( - m12, m22 ); + + } + + } else if ( order === 'YZX' ) { + + this._z = Math.asin( clamp( m21, - 1, 1 ) ); + + if ( Math.abs( m21 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); + + } else { + + this._x = 0; + this._y = Math.atan2( m13, m33 ); + + } + + } else if ( order === 'XZY' ) { + + this._z = Math.asin( - clamp( m12, - 1, 1 ) ); + + if ( Math.abs( m12 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); + + } else { + + this._x = Math.atan2( - m23, m33 ); + this._y = 0; + + } + + } else { + + console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ) + + } + + this._order = order; + + this.onChangeCallback(); + + return this; + + }, + + setFromQuaternion: function ( q, order, update ) { + + var clamp = THREE.Math.clamp; + + // q is assumed to be normalized + + // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m + + var sqx = q.x * q.x; + var sqy = q.y * q.y; + var sqz = q.z * q.z; + var sqw = q.w * q.w; + + order = order || this._order; + + if ( order === 'XYZ' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) ); + this._y = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ), - 1, 1 ) ); + this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) ); + + } else if ( order === 'YXZ' ) { + + this._x = Math.asin( clamp( 2 * ( q.x * q.w - q.y * q.z ), - 1, 1 ) ); + this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw - sqx - sqy + sqz ) ); + this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw - sqx + sqy - sqz ) ); + + } else if ( order === 'ZXY' ) { + + this._x = Math.asin( clamp( 2 * ( q.x * q.w + q.y * q.z ), - 1, 1 ) ); + this._y = Math.atan2( 2 * ( q.y * q.w - q.z * q.x ), ( sqw - sqx - sqy + sqz ) ); + this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw - sqx + sqy - sqz ) ); + + } else if ( order === 'ZYX' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w + q.z * q.y ), ( sqw - sqx - sqy + sqz ) ); + this._y = Math.asin( clamp( 2 * ( q.y * q.w - q.x * q.z ), - 1, 1 ) ); + this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw + sqx - sqy - sqz ) ); + + } else if ( order === 'YZX' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w - q.z * q.y ), ( sqw - sqx + sqy - sqz ) ); + this._y = Math.atan2( 2 * ( q.y * q.w - q.x * q.z ), ( sqw + sqx - sqy - sqz ) ); + this._z = Math.asin( clamp( 2 * ( q.x * q.y + q.z * q.w ), - 1, 1 ) ); + + } else if ( order === 'XZY' ) { + + this._x = Math.atan2( 2 * ( q.x * q.w + q.y * q.z ), ( sqw - sqx + sqy - sqz ) ); + this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw + sqx - sqy - sqz ) ); + this._z = Math.asin( clamp( 2 * ( q.z * q.w - q.x * q.y ), - 1, 1 ) ); + + } else { + + console.warn( 'THREE.Euler: .setFromQuaternion() given unsupported order: ' + order ) + + } + + this._order = order; + + if ( update !== false ) this.onChangeCallback(); + + return this; + + }, + + reorder: function () { + + // WARNING: this discards revolution information -bhouston + + var q = new THREE.Quaternion(); + + return function ( newOrder ) { + + q.setFromEuler( this ); + this.setFromQuaternion( q, newOrder ); + + }; + + + }(), + + equals: function ( euler ) { + + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + + }, + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + + this.onChangeCallback(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._order ]; + + }, + + onChange: function ( callback ) { + + this.onChangeCallback = callback; + + return this; + + }, + + onChangeCallback: function () {}, + + clone: function () { + + return new THREE.Euler( this._x, this._y, this._z, this._order ); + + } + +}; + +// File:src/math/Line3.js + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Line3 = function ( start, end ) { + + this.start = ( start !== undefined ) ? start : new THREE.Vector3(); + this.end = ( end !== undefined ) ? end : new THREE.Vector3(); + +}; + +THREE.Line3.prototype = { + + constructor: THREE.Line3, + + set: function ( start, end ) { + + this.start.copy( start ); + this.end.copy( end ); + + return this; + + }, + + copy: function ( line ) { + + this.start.copy( line.start ); + this.end.copy( line.end ); + + return this; + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + + }, + + delta: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.end, this.start ); + + }, + + distanceSq: function () { + + return this.start.distanceToSquared( this.end ); + + }, + + distance: function () { + + return this.start.distanceTo( this.end ); + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + closestPointToPointParameter: function () { + + var startP = new THREE.Vector3(); + var startEnd = new THREE.Vector3(); + + return function ( point, clampToLine ) { + + startP.subVectors( point, this.start ); + startEnd.subVectors( this.end, this.start ); + + var startEnd2 = startEnd.dot( startEnd ); + var startEnd_startP = startEnd.dot( startP ); + + var t = startEnd_startP / startEnd2; + + if ( clampToLine ) { + + t = THREE.Math.clamp( t, 0, 1 ); + + } + + return t; + + }; + + }(), + + closestPointToPoint: function ( point, clampToLine, optionalTarget ) { + + var t = this.closestPointToPointParameter( point, clampToLine ); + + var result = optionalTarget || new THREE.Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + applyMatrix4: function ( matrix ) { + + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); + + return this; + + }, + + equals: function ( line ) { + + return line.start.equals( this.start ) && line.end.equals( this.end ); + + }, + + clone: function () { + + return new THREE.Line3().copy( this ); + + } + +}; + +// File:src/math/Box2.js + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Box2 = function ( min, max ) { + + this.min = ( min !== undefined ) ? min : new THREE.Vector2( Infinity, Infinity ); + this.max = ( max !== undefined ) ? max : new THREE.Vector2( - Infinity, - Infinity ); + +}; + +THREE.Box2.prototype = { + + constructor: THREE.Box2, + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + setFromPoints: function ( points ) { + + this.makeEmpty(); + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ) + + } + + return this; + + }, + + setFromCenterAndSize: function () { + + var v1 = new THREE.Vector2(); + + return function ( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = Infinity; + this.max.x = this.max.y = - Infinity; + + return this; + + }, + + empty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + size: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + }, + + containsPoint: function ( point ) { + + if ( point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y ) { + + return false; + + } + + return true; + + }, + + containsBox: function ( box ) { + + if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) && + ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) ) { + + return true; + + } + + return false; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new THREE.Vector2(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); + + }, + + isIntersectionBox: function ( box ) { + + // using 6 splitting planes to rule out intersections. + + if ( box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y ) { + + return false; + + } + + return true; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector2(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function () { + + var v1 = new THREE.Vector2(); + + return function ( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + }, + + clone: function () { + + return new THREE.Box2().copy( this ); + + } + +}; + +// File:src/math/Box3.js + +/** + * @author bhouston / http://exocortex.com + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Box3 = function ( min, max ) { + + this.min = ( min !== undefined ) ? min : new THREE.Vector3( Infinity, Infinity, Infinity ); + this.max = ( max !== undefined ) ? max : new THREE.Vector3( - Infinity, - Infinity, - Infinity ); + +}; + +THREE.Box3.prototype = { + + constructor: THREE.Box3, + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + setFromPoints: function ( points ) { + + this.makeEmpty(); + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ) + + } + + return this; + + }, + + setFromCenterAndSize: function () { + + var v1 = new THREE.Vector3(); + + return function ( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + setFromObject: function () { + + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and childrens', world transforms + + var v1 = new THREE.Vector3(); + + return function ( object ) { + + var scope = this; + + object.updateMatrixWorld( true ); + + this.makeEmpty(); + + object.traverse( function ( node ) { + + if ( node.geometry !== undefined && node.geometry.vertices !== undefined ) { + + var vertices = node.geometry.vertices; + + for ( var i = 0, il = vertices.length; i < il; i ++ ) { + + v1.copy( vertices[ i ] ); + + v1.applyMatrix4( node.matrixWorld ); + + scope.expandByPoint( v1 ); + + } + + } + + } ); + + return this; + + }; + + }(), + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = this.min.z = Infinity; + this.max.x = this.max.y = this.max.z = - Infinity; + + return this; + + }, + + empty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); + + }, + + center: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + size: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + + }, + + containsPoint: function ( point ) { + + if ( point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y || + point.z < this.min.z || point.z > this.max.z ) { + + return false; + + } + + return true; + + }, + + containsBox: function ( box ) { + + if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) && + ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) && + ( this.min.z <= box.min.z ) && ( box.max.z <= this.max.z ) ) { + + return true; + + } + + return false; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new THREE.Vector3(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ), + ( point.z - this.min.z ) / ( this.max.z - this.min.z ) + ); + + }, + + isIntersectionBox: function ( box ) { + + // using 6 splitting planes to rule out intersections. + + if ( box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y || + box.max.z < this.min.z || box.min.z > this.max.z ) { + + return false; + + } + + return true; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function () { + + var v1 = new THREE.Vector3(); + + return function ( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + getBoundingSphere: function () { + + var v1 = new THREE.Vector3(); + + return function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Sphere(); + + result.center = this.center(); + result.radius = this.size( v1 ).length() * 0.5; + + return result; + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + applyMatrix4: function () { + + var points = [ + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3(), + new THREE.Vector3() + ]; + + return function ( matrix ) { + + // NOTE: I am using a binary pattern to specify all 2^3 combinations below + points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 + points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 + points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 + points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 + points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 + points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 + points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 + points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 + + this.makeEmpty(); + this.setFromPoints( points ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + }, + + clone: function () { + + return new THREE.Box3().copy( this ); + + } + +}; + +// File:src/math/Matrix3.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://exocortex.com + */ + +THREE.Matrix3 = function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + this.elements = new Float32Array( 9 ); + + var te = this.elements; + + te[ 0 ] = ( n11 !== undefined ) ? n11 : 1; te[ 3 ] = n12 || 0; te[ 6 ] = n13 || 0; + te[ 1 ] = n21 || 0; te[ 4 ] = ( n22 !== undefined ) ? n22 : 1; te[ 7 ] = n23 || 0; + te[ 2 ] = n31 || 0; te[ 5 ] = n32 || 0; te[ 8 ] = ( n33 !== undefined ) ? n33 : 1; + +}; + +THREE.Matrix3.prototype = { + + constructor: THREE.Matrix3, + + set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + var te = this.elements; + + te[ 0 ] = n11; te[ 3 ] = n12; te[ 6 ] = n13; + te[ 1 ] = n21; te[ 4 ] = n22; te[ 7 ] = n23; + te[ 2 ] = n31; te[ 5 ] = n32; te[ 8 ] = n33; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ); + + return this; + + }, + + copy: function ( m ) { + + var me = m.elements; + + this.set( + + me[ 0 ], me[ 3 ], me[ 6 ], + me[ 1 ], me[ 4 ], me[ 7 ], + me[ 2 ], me[ 5 ], me[ 8 ] + + ); + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); + return vector.applyMatrix3( this ); + + }, + + multiplyVector3Array: function ( a ) { + + console.warn( 'THREE.Matrix3: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.' ); + return this.applyToVector3Array( a ); + + }, + + applyToVector3Array: function () { + + var v1 = new THREE.Vector3(); + + return function ( array, offset, length ) { + + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = array.length; + + for ( var i = 0, j = offset, il; i < length; i += 3, j += 3 ) { + + v1.x = array[ j ]; + v1.y = array[ j + 1 ]; + v1.z = array[ j + 2 ]; + + v1.applyMatrix3( this ); + + array[ j ] = v1.x; + array[ j + 1 ] = v1.y; + array[ j + 2 ] = v1.z; + + } + + return array; + + }; + + }(), + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + + return this; + + }, + + determinant: function () { + + var te = this.elements; + + var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + + }, + + getInverse: function ( matrix, throwOnInvertible ) { + + // input: THREE.Matrix4 + // ( based on http://code.google.com/p/webgl-mjs/ ) + + var me = matrix.elements; + var te = this.elements; + + te[ 0 ] = me[ 10 ] * me[ 5 ] - me[ 6 ] * me[ 9 ]; + te[ 1 ] = - me[ 10 ] * me[ 1 ] + me[ 2 ] * me[ 9 ]; + te[ 2 ] = me[ 6 ] * me[ 1 ] - me[ 2 ] * me[ 5 ]; + te[ 3 ] = - me[ 10 ] * me[ 4 ] + me[ 6 ] * me[ 8 ]; + te[ 4 ] = me[ 10 ] * me[ 0 ] - me[ 2 ] * me[ 8 ]; + te[ 5 ] = - me[ 6 ] * me[ 0 ] + me[ 2 ] * me[ 4 ]; + te[ 6 ] = me[ 9 ] * me[ 4 ] - me[ 5 ] * me[ 8 ]; + te[ 7 ] = - me[ 9 ] * me[ 0 ] + me[ 1 ] * me[ 8 ]; + te[ 8 ] = me[ 5 ] * me[ 0 ] - me[ 1 ] * me[ 4 ]; + + var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ]; + + // no inverse + + if ( det === 0 ) { + + var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0"; + + if ( throwOnInvertible || false ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + this.identity(); + + return this; + + } + + this.multiplyScalar( 1.0 / det ); + + return this; + + }, + + transpose: function () { + + var tmp, m = this.elements; + + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + + return this; + + }, + + flattenToArrayOffset: function ( array, offset ) { + + var te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; + + return array; + + }, + + getNormalMatrix: function ( m ) { + + // input: THREE.Matrix4 + + this.getInverse( m ).transpose(); + + return this; + + }, + + transposeIntoArray: function ( r ) { + + var m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; + + return this; + + }, + + fromArray: function ( array ) { + + this.elements.set( array ); + + return this; + + }, + + toArray: function () { + + var te = this.elements; + + return [ + te[ 0 ], te[ 1 ], te[ 2 ], + te[ 3 ], te[ 4 ], te[ 5 ], + te[ 6 ], te[ 7 ], te[ 8 ] + ]; + + }, + + clone: function () { + + var te = this.elements; + + return new THREE.Matrix3( + + te[ 0 ], te[ 3 ], te[ 6 ], + te[ 1 ], te[ 4 ], te[ 7 ], + te[ 2 ], te[ 5 ], te[ 8 ] + + ); + + } + +}; + +// File:src/math/Matrix4.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author jordi_ros / http://plattsoft.com + * @author D1plo1d / http://github.com/D1plo1d + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author timknip / http://www.floorplanner.com/ + * @author bhouston / http://exocortex.com + * @author WestLangley / http://github.com/WestLangley + */ + + +THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + this.elements = new Float32Array( 16 ); + + // TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix + // we should not support semi specification of Matrix4, it is just weird. + + var te = this.elements; + + te[ 0 ] = ( n11 !== undefined ) ? n11 : 1; te[ 4 ] = n12 || 0; te[ 8 ] = n13 || 0; te[ 12 ] = n14 || 0; + te[ 1 ] = n21 || 0; te[ 5 ] = ( n22 !== undefined ) ? n22 : 1; te[ 9 ] = n23 || 0; te[ 13 ] = n24 || 0; + te[ 2 ] = n31 || 0; te[ 6 ] = n32 || 0; te[ 10 ] = ( n33 !== undefined ) ? n33 : 1; te[ 14 ] = n34 || 0; + te[ 3 ] = n41 || 0; te[ 7 ] = n42 || 0; te[ 11 ] = n43 || 0; te[ 15 ] = ( n44 !== undefined ) ? n44 : 1; + +}; + +THREE.Matrix4.prototype = { + + constructor: THREE.Matrix4, + + set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + var te = this.elements; + + te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; + te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; + te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; + te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + copy: function ( m ) { + + this.elements.set( m.elements ); + + return this; + + }, + + extractPosition: function ( m ) { + + console.warn( 'THREEMatrix4: .extractPosition() has been renamed to .copyPosition().' ); + return this.copyPosition( m ); + + }, + + copyPosition: function ( m ) { + + var te = this.elements; + var me = m.elements; + + te[ 12 ] = me[ 12 ]; + te[ 13 ] = me[ 13 ]; + te[ 14 ] = me[ 14 ]; + + return this; + + }, + + extractRotation: function () { + + var v1 = new THREE.Vector3(); + + return function ( m ) { + + var te = this.elements; + var me = m.elements; + + var scaleX = 1 / v1.set( me[ 0 ], me[ 1 ], me[ 2 ] ).length(); + var scaleY = 1 / v1.set( me[ 4 ], me[ 5 ], me[ 6 ] ).length(); + var scaleZ = 1 / v1.set( me[ 8 ], me[ 9 ], me[ 10 ] ).length(); + + te[ 0 ] = me[ 0 ] * scaleX; + te[ 1 ] = me[ 1 ] * scaleX; + te[ 2 ] = me[ 2 ] * scaleX; + + te[ 4 ] = me[ 4 ] * scaleY; + te[ 5 ] = me[ 5 ] * scaleY; + te[ 6 ] = me[ 6 ] * scaleY; + + te[ 8 ] = me[ 8 ] * scaleZ; + te[ 9 ] = me[ 9 ] * scaleZ; + te[ 10 ] = me[ 10 ] * scaleZ; + + return this; + + }; + + }(), + + makeRotationFromEuler: function ( euler ) { + + if ( euler instanceof THREE.Euler === false ) { + + console.error( 'THREE.Matrix: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); + + } + + var te = this.elements; + + var x = euler.x, y = euler.y, z = euler.z; + var a = Math.cos( x ), b = Math.sin( x ); + var c = Math.cos( y ), d = Math.sin( y ); + var e = Math.cos( z ), f = Math.sin( z ); + + if ( euler.order === 'XYZ' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = - c * f; + te[ 8 ] = d; + + te[ 1 ] = af + be * d; + te[ 5 ] = ae - bf * d; + te[ 9 ] = - b * c; + + te[ 2 ] = bf - ae * d; + te[ 6 ] = be + af * d; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YXZ' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce + df * b; + te[ 4 ] = de * b - cf; + te[ 8 ] = a * d; + + te[ 1 ] = a * f; + te[ 5 ] = a * e; + te[ 9 ] = - b; + + te[ 2 ] = cf * b - de; + te[ 6 ] = df + ce * b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZXY' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce - df * b; + te[ 4 ] = - a * f; + te[ 8 ] = de + cf * b; + + te[ 1 ] = cf + de * b; + te[ 5 ] = a * e; + te[ 9 ] = df - ce * b; + + te[ 2 ] = - a * d; + te[ 6 ] = b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZYX' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = be * d - af; + te[ 8 ] = ae * d + bf; + + te[ 1 ] = c * f; + te[ 5 ] = bf * d + ae; + te[ 9 ] = af * d - be; + + te[ 2 ] = - d; + te[ 6 ] = b * c; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YZX' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = bd - ac * f; + te[ 8 ] = bc * f + ad; + + te[ 1 ] = f; + te[ 5 ] = a * e; + te[ 9 ] = - b * e; + + te[ 2 ] = - d * e; + te[ 6 ] = ad * f + bc; + te[ 10 ] = ac - bd * f; + + } else if ( euler.order === 'XZY' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = - f; + te[ 8 ] = d * e; + + te[ 1 ] = ac * f + bd; + te[ 5 ] = a * e; + te[ 9 ] = ad * f - bc; + + te[ 2 ] = bc * f - ad; + te[ 6 ] = b * e; + te[ 10 ] = bd * f + ac; + + } + + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; + + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + }, + + setRotationFromQuaternion: function ( q ) { + + console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' ); + + return this.makeRotationFromQuaternion( q ); + + }, + + makeRotationFromQuaternion: function ( q ) { + + var te = this.elements; + + var x = q.x, y = q.y, z = q.z, w = q.w; + var x2 = x + x, y2 = y + y, z2 = z + z; + var xx = x * x2, xy = x * y2, xz = x * z2; + var yy = y * y2, yz = y * z2, zz = z * z2; + var wx = w * x2, wy = w * y2, wz = w * z2; + + te[ 0 ] = 1 - ( yy + zz ); + te[ 4 ] = xy - wz; + te[ 8 ] = xz + wy; + + te[ 1 ] = xy + wz; + te[ 5 ] = 1 - ( xx + zz ); + te[ 9 ] = yz - wx; + + te[ 2 ] = xz - wy; + te[ 6 ] = yz + wx; + te[ 10 ] = 1 - ( xx + yy ); + + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; + + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + }, + + lookAt: function () { + + var x = new THREE.Vector3(); + var y = new THREE.Vector3(); + var z = new THREE.Vector3(); + + return function ( eye, target, up ) { + + var te = this.elements; + + z.subVectors( eye, target ).normalize(); + + if ( z.length() === 0 ) { + + z.z = 1; + + } + + x.crossVectors( up, z ).normalize(); + + if ( x.length() === 0 ) { + + z.x += 0.0001; + x.crossVectors( up, z ).normalize(); + + } + + y.crossVectors( z, x ); + + + te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x; + te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y; + te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z; + + return this; + + }; + + }(), + + multiply: function ( m, n ) { + + if ( n !== undefined ) { + + console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' ); + return this.multiplyMatrices( m, n ); + + } + + return this.multiplyMatrices( this, m ); + + }, + + multiplyMatrices: function ( a, b ) { + + var ae = a.elements; + var be = b.elements; + var te = this.elements; + + var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; + var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; + var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; + var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; + + var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; + var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; + var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; + var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; + + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; + te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; + te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; + te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; + + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; + te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; + te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; + te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; + + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; + te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; + te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; + te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; + + te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; + te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; + te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; + te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; + + return this; + + }, + + multiplyToArray: function ( a, b, r ) { + + var te = this.elements; + + this.multiplyMatrices( a, b ); + + r[ 0 ] = te[ 0 ]; r[ 1 ] = te[ 1 ]; r[ 2 ] = te[ 2 ]; r[ 3 ] = te[ 3 ]; + r[ 4 ] = te[ 4 ]; r[ 5 ] = te[ 5 ]; r[ 6 ] = te[ 6 ]; r[ 7 ] = te[ 7 ]; + r[ 8 ] = te[ 8 ]; r[ 9 ] = te[ 9 ]; r[ 10 ] = te[ 10 ]; r[ 11 ] = te[ 11 ]; + r[ 12 ] = te[ 12 ]; r[ 13 ] = te[ 13 ]; r[ 14 ] = te[ 14 ]; r[ 15 ] = te[ 15 ]; + + return this; + + }, + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; + te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; + te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; + te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' ); + return vector.applyProjection( this ); + + }, + + multiplyVector4: function ( vector ) { + + console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + + multiplyVector3Array: function ( a ) { + + console.warn( 'THREE.Matrix4: .multiplyVector3Array() has been renamed. Use matrix.applyToVector3Array( array ) instead.' ); + return this.applyToVector3Array( a ); + + }, + + applyToVector3Array: function () { + + var v1 = new THREE.Vector3(); + + return function ( array, offset, length ) { + + if ( offset === undefined ) offset = 0; + if ( length === undefined ) length = array.length; + + for ( var i = 0, j = offset, il; i < length; i += 3, j += 3 ) { + + v1.x = array[ j ]; + v1.y = array[ j + 1 ]; + v1.z = array[ j + 2 ]; + + v1.applyMatrix4( this ); + + array[ j ] = v1.x; + array[ j + 1 ] = v1.y; + array[ j + 2 ] = v1.z; + + } + + return array; + + }; + + }(), + + rotateAxis: function ( v ) { + + console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); + + v.transformDirection( this ); + + }, + + crossVector: function ( vector ) { + + console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + + determinant: function () { + + var te = this.elements; + + var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; + var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; + var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; + var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; + + //TODO: make this more efficient + //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) + + return ( + n41 * ( + + n14 * n23 * n32 + - n13 * n24 * n32 + - n14 * n22 * n33 + + n12 * n24 * n33 + + n13 * n22 * n34 + - n12 * n23 * n34 + ) + + n42 * ( + + n11 * n23 * n34 + - n11 * n24 * n33 + + n14 * n21 * n33 + - n13 * n21 * n34 + + n13 * n24 * n31 + - n14 * n23 * n31 + ) + + n43 * ( + + n11 * n24 * n32 + - n11 * n22 * n34 + - n14 * n21 * n32 + + n12 * n21 * n34 + + n14 * n22 * n31 + - n12 * n24 * n31 + ) + + n44 * ( + - n13 * n22 * n31 + - n11 * n23 * n32 + + n11 * n22 * n33 + + n13 * n21 * n32 + - n12 * n21 * n33 + + n12 * n23 * n31 + ) + + ); + + }, + + transpose: function () { + + var te = this.elements; + var tmp; + + tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; + tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; + tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; + + tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; + tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; + tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; + + return this; + + }, + + flattenToArrayOffset: function ( array, offset ) { + + var te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + array[ offset + 3 ] = te[ 3 ]; + + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + + array[ offset + 8 ] = te[ 8 ]; + array[ offset + 9 ] = te[ 9 ]; + array[ offset + 10 ] = te[ 10 ]; + array[ offset + 11 ] = te[ 11 ]; + + array[ offset + 12 ] = te[ 12 ]; + array[ offset + 13 ] = te[ 13 ]; + array[ offset + 14 ] = te[ 14 ]; + array[ offset + 15 ] = te[ 15 ]; + + return array; + + }, + + getPosition: function () { + + var v1 = new THREE.Vector3(); + + return function () { + + console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' ); + + var te = this.elements; + return v1.set( te[ 12 ], te[ 13 ], te[ 14 ] ); + + }; + + }(), + + setPosition: function ( v ) { + + var te = this.elements; + + te[ 12 ] = v.x; + te[ 13 ] = v.y; + te[ 14 ] = v.z; + + return this; + + }, + + getInverse: function ( m, throwOnInvertible ) { + + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + var te = this.elements; + var me = m.elements; + + var n11 = me[ 0 ], n12 = me[ 4 ], n13 = me[ 8 ], n14 = me[ 12 ]; + var n21 = me[ 1 ], n22 = me[ 5 ], n23 = me[ 9 ], n24 = me[ 13 ]; + var n31 = me[ 2 ], n32 = me[ 6 ], n33 = me[ 10 ], n34 = me[ 14 ]; + var n41 = me[ 3 ], n42 = me[ 7 ], n43 = me[ 11 ], n44 = me[ 15 ]; + + te[ 0 ] = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44; + te[ 4 ] = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44; + te[ 8 ] = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44; + te[ 12 ] = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; + te[ 1 ] = n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44; + te[ 5 ] = n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44; + te[ 9 ] = n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44; + te[ 13 ] = n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34; + te[ 2 ] = n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44; + te[ 6 ] = n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44; + te[ 10 ] = n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44; + te[ 14 ] = n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34; + te[ 3 ] = n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43; + te[ 7 ] = n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43; + te[ 11 ] = n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43; + te[ 15 ] = n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33; + + var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ]; + + if ( det == 0 ) { + + var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0"; + + if ( throwOnInvertible || false ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + this.identity(); + + return this; + } + + this.multiplyScalar( 1 / det ); + + return this; + + }, + + translate: function ( v ) { + + console.warn( 'THREE.Matrix4: .translate() has been removed.' ); + + }, + + rotateX: function ( angle ) { + + console.warn( 'THREE.Matrix4: .rotateX() has been removed.' ); + + }, + + rotateY: function ( angle ) { + + console.warn( 'THREE.Matrix4: .rotateY() has been removed.' ); + + }, + + rotateZ: function ( angle ) { + + console.warn( 'THREE.Matrix4: .rotateZ() has been removed.' ); + + }, + + rotateByAxis: function ( axis, angle ) { + + console.warn( 'THREE.Matrix4: .rotateByAxis() has been removed.' ); + + }, + + scale: function ( v ) { + + var te = this.elements; + var x = v.x, y = v.y, z = v.z; + + te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; + te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; + te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; + te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; + + return this; + + }, + + getMaxScaleOnAxis: function () { + + var te = this.elements; + + var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; + var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; + var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; + + return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) ); + + }, + + makeTranslation: function ( x, y, z ) { + + this.set( + + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationX: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + 1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationY: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, 0, s, 0, + 0, 1, 0, 0, + - s, 0, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationZ: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, - s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationAxis: function ( axis, angle ) { + + // Based on http://www.gamedev.net/reference/articles/article1199.asp + + var c = Math.cos( angle ); + var s = Math.sin( angle ); + var t = 1 - c; + var x = axis.x, y = axis.y, z = axis.z; + var tx = t * x, ty = t * y; + + this.set( + + tx * x + c, tx * y - s * z, tx * z + s * y, 0, + tx * y + s * z, ty * y + c, ty * z - s * x, 0, + tx * z - s * y, ty * z + s * x, t * z * z + c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeScale: function ( x, y, z ) { + + this.set( + + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + compose: function ( position, quaternion, scale ) { + + this.makeRotationFromQuaternion( quaternion ); + this.scale( scale ); + this.setPosition( position ); + + return this; + + }, + + decompose: function () { + + var vector = new THREE.Vector3(); + var matrix = new THREE.Matrix4(); + + return function ( position, quaternion, scale ) { + + var te = this.elements; + + var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); + var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); + var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); + + // if determine is negative, we need to invert one scale + var det = this.determinant(); + if ( det < 0 ) { + sx = - sx; + } + + position.x = te[ 12 ]; + position.y = te[ 13 ]; + position.z = te[ 14 ]; + + // scale the rotation part + + matrix.elements.set( this.elements ); // at this point matrix is incomplete so we can't use .copy() + + var invSX = 1 / sx; + var invSY = 1 / sy; + var invSZ = 1 / sz; + + matrix.elements[ 0 ] *= invSX; + matrix.elements[ 1 ] *= invSX; + matrix.elements[ 2 ] *= invSX; + + matrix.elements[ 4 ] *= invSY; + matrix.elements[ 5 ] *= invSY; + matrix.elements[ 6 ] *= invSY; + + matrix.elements[ 8 ] *= invSZ; + matrix.elements[ 9 ] *= invSZ; + matrix.elements[ 10 ] *= invSZ; + + quaternion.setFromRotationMatrix( matrix ); + + scale.x = sx; + scale.y = sy; + scale.z = sz; + + return this; + + }; + + }(), + + makeFrustum: function ( left, right, bottom, top, near, far ) { + + var te = this.elements; + var x = 2 * near / ( right - left ); + var y = 2 * near / ( top - bottom ); + + var a = ( right + left ) / ( right - left ); + var b = ( top + bottom ) / ( top - bottom ); + var c = - ( far + near ) / ( far - near ); + var d = - 2 * far * near / ( far - near ); + + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; + + return this; + + }, + + makePerspective: function ( fov, aspect, near, far ) { + + var ymax = near * Math.tan( THREE.Math.degToRad( fov * 0.5 ) ); + var ymin = - ymax; + var xmin = ymin * aspect; + var xmax = ymax * aspect; + + return this.makeFrustum( xmin, xmax, ymin, ymax, near, far ); + + }, + + makeOrthographic: function ( left, right, top, bottom, near, far ) { + + var te = this.elements; + var w = right - left; + var h = top - bottom; + var p = far - near; + + var x = ( right + left ) / w; + var y = ( top + bottom ) / h; + var z = ( far + near ) / p; + + te[ 0 ] = 2 / w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; + te[ 1 ] = 0; te[ 5 ] = 2 / h; te[ 9 ] = 0; te[ 13 ] = - y; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 / p; te[ 14 ] = - z; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; + + return this; + + }, + + fromArray: function ( array ) { + + this.elements.set( array ); + + return this; + + }, + + toArray: function () { + + var te = this.elements; + + return [ + te[ 0 ], te[ 1 ], te[ 2 ], te[ 3 ], + te[ 4 ], te[ 5 ], te[ 6 ], te[ 7 ], + te[ 8 ], te[ 9 ], te[ 10 ], te[ 11 ], + te[ 12 ], te[ 13 ], te[ 14 ], te[ 15 ] + ]; + + }, + + clone: function () { + + var te = this.elements; + + return new THREE.Matrix4( + + te[ 0 ], te[ 4 ], te[ 8 ], te[ 12 ], + te[ 1 ], te[ 5 ], te[ 9 ], te[ 13 ], + te[ 2 ], te[ 6 ], te[ 10 ], te[ 14 ], + te[ 3 ], te[ 7 ], te[ 11 ], te[ 15 ] + + ); + + } + +}; + +// File:src/math/Ray.js + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Ray = function ( origin, direction ) { + + this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3(); + this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3(); + +}; + +THREE.Ray.prototype = { + + constructor: THREE.Ray, + + set: function ( origin, direction ) { + + this.origin.copy( origin ); + this.direction.copy( direction ); + + return this; + + }, + + copy: function ( ray ) { + + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); + + return this; + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + return result.copy( this.direction ).multiplyScalar( t ).add( this.origin ); + + }, + + recast: function () { + + var v1 = new THREE.Vector3(); + + return function ( t ) { + + this.origin.copy( this.at( t, v1 ) ); + + return this; + + }; + + }(), + + closestPointToPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + result.subVectors( point, this.origin ); + var directionDistance = result.dot( this.direction ); + + if ( directionDistance < 0 ) { + + return result.copy( this.origin ); + + } + + return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + }, + + distanceToPoint: function () { + + var v1 = new THREE.Vector3(); + + return function ( point ) { + + var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction ); + + // point behind the ray + + if ( directionDistance < 0 ) { + + return this.origin.distanceTo( point ); + + } + + v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + return v1.distanceTo( point ); + + }; + + }(), + + distanceSqToSegment: function ( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { + + // from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp + // It returns the min distance between the ray and the segment + // defined by v0 and v1 + // It can also set two optional targets : + // - The closest point on the ray + // - The closest point on the segment + + var segCenter = v0.clone().add( v1 ).multiplyScalar( 0.5 ); + var segDir = v1.clone().sub( v0 ).normalize(); + var segExtent = v0.distanceTo( v1 ) * 0.5; + var diff = this.origin.clone().sub( segCenter ); + var a01 = - this.direction.dot( segDir ); + var b0 = diff.dot( this.direction ); + var b1 = - diff.dot( segDir ); + var c = diff.lengthSq(); + var det = Math.abs( 1 - a01 * a01 ); + var s0, s1, sqrDist, extDet; + + if ( det >= 0 ) { + + // The ray and segment are not parallel. + + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; + + if ( s0 >= 0 ) { + + if ( s1 >= - extDet ) { + + if ( s1 <= extDet ) { + + // region 0 + // Minimum at interior points of ray and segment. + + var invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + + } else { + + // region 1 + + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + // region 5 + + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + if ( s1 <= - extDet ) { + + // region 4 + + s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } else if ( s1 <= extDet ) { + + // region 3 + + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; + + } else { + + // region 2 + + s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } + + } else { + + // Ray and segment are parallel. + + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + if ( optionalPointOnRay ) { + + optionalPointOnRay.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) ); + + } + + if ( optionalPointOnSegment ) { + + optionalPointOnSegment.copy( segDir.clone().multiplyScalar( s1 ).add( segCenter ) ); + + } + + return sqrDist; + + }, + + isIntersectionSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) <= sphere.radius; + + }, + + intersectSphere: function () { + + // from http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-sphere-intersection/ + + var v1 = new THREE.Vector3(); + + return function ( sphere, optionalTarget ) { + + v1.subVectors( sphere.center, this.origin ); + + var tca = v1.dot( this.direction ); + + var d2 = v1.dot( v1 ) - tca * tca; + + var radius2 = sphere.radius * sphere.radius; + + if ( d2 > radius2 ) return null; + + var thc = Math.sqrt( radius2 - d2 ); + + // t0 = first intersect point - entrance on front of sphere + var t0 = tca - thc; + + // t1 = second intersect point - exit point on back of sphere + var t1 = tca + thc; + + // test to see if both t0 and t1 are behind the ray - if so, return null + if ( t0 < 0 && t1 < 0 ) return null; + + // test to see if t0 is behind the ray: + // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, + // in order to always return an intersect point that is in front of the ray. + if ( t0 < 0 ) return this.at( t1, optionalTarget ); + + // else t0 is in front of the ray, so return the first collision point scaled by t0 + return this.at( t0, optionalTarget ); + + } + + }(), + + isIntersectionPlane: function ( plane ) { + + // check if the ray lies on the plane first + + var distToPoint = plane.distanceToPoint( this.origin ); + + if ( distToPoint === 0 ) { + + return true; + + } + + var denominator = plane.normal.dot( this.direction ); + + if ( denominator * distToPoint < 0 ) { + + return true; + + } + + // ray origin is behind the plane (and is pointing behind it) + + return false; + + }, + + distanceToPlane: function ( plane ) { + + var denominator = plane.normal.dot( this.direction ); + if ( denominator == 0 ) { + + // line is coplanar, return origin + if ( plane.distanceToPoint( this.origin ) == 0 ) { + + return 0; + + } + + // Null is preferable to undefined since undefined means.... it is undefined + + return null; + + } + + var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; + + // Return if the ray never intersects the plane + + return t >= 0 ? t : null; + + }, + + intersectPlane: function ( plane, optionalTarget ) { + + var t = this.distanceToPlane( plane ); + + if ( t === null ) { + + return null; + } + + return this.at( t, optionalTarget ); + + }, + + isIntersectionBox: function () { + + var v = new THREE.Vector3(); + + return function ( box ) { + + return this.intersectBox( box, v ) !== null; + + }; + + }(), + + intersectBox: function ( box , optionalTarget ) { + + // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/ + + var tmin,tmax,tymin,tymax,tzmin,tzmax; + + var invdirx = 1 / this.direction.x, + invdiry = 1 / this.direction.y, + invdirz = 1 / this.direction.z; + + var origin = this.origin; + + if ( invdirx >= 0 ) { + + tmin = ( box.min.x - origin.x ) * invdirx; + tmax = ( box.max.x - origin.x ) * invdirx; + + } else { + + tmin = ( box.max.x - origin.x ) * invdirx; + tmax = ( box.min.x - origin.x ) * invdirx; + } + + if ( invdiry >= 0 ) { + + tymin = ( box.min.y - origin.y ) * invdiry; + tymax = ( box.max.y - origin.y ) * invdiry; + + } else { + + tymin = ( box.max.y - origin.y ) * invdiry; + tymax = ( box.min.y - origin.y ) * invdiry; + } + + if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; + + // These lines also handle the case where tmin or tmax is NaN + // (result of 0 * Infinity). x !== x returns true if x is NaN + + if ( tymin > tmin || tmin !== tmin ) tmin = tymin; + + if ( tymax < tmax || tmax !== tmax ) tmax = tymax; + + if ( invdirz >= 0 ) { + + tzmin = ( box.min.z - origin.z ) * invdirz; + tzmax = ( box.max.z - origin.z ) * invdirz; + + } else { + + tzmin = ( box.max.z - origin.z ) * invdirz; + tzmax = ( box.min.z - origin.z ) * invdirz; + } + + if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; + + if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; + + if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; + + //return point closest to the ray (positive side) + + if ( tmax < 0 ) return null; + + return this.at( tmin >= 0 ? tmin : tmax, optionalTarget ); + + }, + + intersectTriangle: function () { + + // Compute the offset origin, edges, and normal. + var diff = new THREE.Vector3(); + var edge1 = new THREE.Vector3(); + var edge2 = new THREE.Vector3(); + var normal = new THREE.Vector3(); + + return function ( a, b, c, backfaceCulling, optionalTarget ) { + + // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp + + edge1.subVectors( b, a ); + edge2.subVectors( c, a ); + normal.crossVectors( edge1, edge2 ); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + var DdN = this.direction.dot( normal ); + var sign; + + if ( DdN > 0 ) { + + if ( backfaceCulling ) return null; + sign = 1; + + } else if ( DdN < 0 ) { + + sign = - 1; + DdN = - DdN; + + } else { + + return null; + + } + + diff.subVectors( this.origin, a ); + var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) ); + + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { + + return null; + + } + + var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) ); + + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { + + return null; + + } + + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { + + return null; + + } + + // Line intersects triangle, check if ray does. + var QdN = - sign * diff.dot( normal ); + + // t < 0, no intersection + if ( QdN < 0 ) { + + return null; + + } + + // Ray intersects triangle. + return this.at( QdN / DdN, optionalTarget ); + + }; + + }(), + + applyMatrix4: function ( matrix4 ) { + + this.direction.add( this.origin ).applyMatrix4( matrix4 ); + this.origin.applyMatrix4( matrix4 ); + this.direction.sub( this.origin ); + this.direction.normalize(); + + return this; + }, + + equals: function ( ray ) { + + return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); + + }, + + clone: function () { + + return new THREE.Ray().copy( this ); + + } + +}; + +// File:src/math/Sphere.js + +/** + * @author bhouston / http://exocortex.com + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Sphere = function ( center, radius ) { + + this.center = ( center !== undefined ) ? center : new THREE.Vector3(); + this.radius = ( radius !== undefined ) ? radius : 0; + +}; + +THREE.Sphere.prototype = { + + constructor: THREE.Sphere, + + set: function ( center, radius ) { + + this.center.copy( center ); + this.radius = radius; + + return this; + }, + + setFromPoints: function () { + + var box = new THREE.Box3(); + + return function ( points, optionalCenter ) { + + var center = this.center; + + if ( optionalCenter !== undefined ) { + + center.copy( optionalCenter ); + + } else { + + box.setFromPoints( points ).center( center ); + + } + + var maxRadiusSq = 0; + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + + } + + this.radius = Math.sqrt( maxRadiusSq ); + + return this; + + }; + + }(), + + copy: function ( sphere ) { + + this.center.copy( sphere.center ); + this.radius = sphere.radius; + + return this; + + }, + + empty: function () { + + return ( this.radius <= 0 ); + + }, + + containsPoint: function ( point ) { + + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); + + }, + + distanceToPoint: function ( point ) { + + return ( point.distanceTo( this.center ) - this.radius ); + + }, + + intersectsSphere: function ( sphere ) { + + var radiusSum = this.radius + sphere.radius; + + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); + + }, + + clampPoint: function ( point, optionalTarget ) { + + var deltaLengthSq = this.center.distanceToSquared( point ); + + var result = optionalTarget || new THREE.Vector3(); + result.copy( point ); + + if ( deltaLengthSq > ( this.radius * this.radius ) ) { + + result.sub( this.center ).normalize(); + result.multiplyScalar( this.radius ).add( this.center ); + + } + + return result; + + }, + + getBoundingBox: function ( optionalTarget ) { + + var box = optionalTarget || new THREE.Box3(); + + box.set( this.center, this.center ); + box.expandByScalar( this.radius ); + + return box; + + }, + + applyMatrix4: function ( matrix ) { + + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); + + return this; + + }, + + translate: function ( offset ) { + + this.center.add( offset ); + + return this; + + }, + + equals: function ( sphere ) { + + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + + }, + + clone: function () { + + return new THREE.Sphere().copy( this ); + + } + +}; + +// File:src/math/Frustum.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author bhouston / http://exocortex.com + */ + +THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) { + + this.planes = [ + + ( p0 !== undefined ) ? p0 : new THREE.Plane(), + ( p1 !== undefined ) ? p1 : new THREE.Plane(), + ( p2 !== undefined ) ? p2 : new THREE.Plane(), + ( p3 !== undefined ) ? p3 : new THREE.Plane(), + ( p4 !== undefined ) ? p4 : new THREE.Plane(), + ( p5 !== undefined ) ? p5 : new THREE.Plane() + + ]; + +}; + +THREE.Frustum.prototype = { + + constructor: THREE.Frustum, + + set: function ( p0, p1, p2, p3, p4, p5 ) { + + var planes = this.planes; + + planes[ 0 ].copy( p0 ); + planes[ 1 ].copy( p1 ); + planes[ 2 ].copy( p2 ); + planes[ 3 ].copy( p3 ); + planes[ 4 ].copy( p4 ); + planes[ 5 ].copy( p5 ); + + return this; + + }, + + copy: function ( frustum ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + planes[ i ].copy( frustum.planes[ i ] ); + + } + + return this; + + }, + + setFromMatrix: function ( m ) { + + var planes = this.planes; + var me = m.elements; + var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; + var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; + var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; + var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + + return this; + + }, + + intersectsObject: function () { + + var sphere = new THREE.Sphere(); + + return function ( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + return this.intersectsSphere( sphere ); + + }; + + }(), + + intersectsSphere: function ( sphere ) { + + var planes = this.planes; + var center = sphere.center; + var negRadius = - sphere.radius; + + for ( var i = 0; i < 6; i ++ ) { + + var distance = planes[ i ].distanceToPoint( center ); + + if ( distance < negRadius ) { + + return false; + + } + + } + + return true; + + }, + + intersectsBox: function () { + + var p1 = new THREE.Vector3(), + p2 = new THREE.Vector3(); + + return function ( box ) { + + var planes = this.planes; + + for ( var i = 0; i < 6 ; i ++ ) { + + var plane = planes[ i ]; + + p1.x = plane.normal.x > 0 ? box.min.x : box.max.x; + p2.x = plane.normal.x > 0 ? box.max.x : box.min.x; + p1.y = plane.normal.y > 0 ? box.min.y : box.max.y; + p2.y = plane.normal.y > 0 ? box.max.y : box.min.y; + p1.z = plane.normal.z > 0 ? box.min.z : box.max.z; + p2.z = plane.normal.z > 0 ? box.max.z : box.min.z; + + var d1 = plane.distanceToPoint( p1 ); + var d2 = plane.distanceToPoint( p2 ); + + // if both outside plane, no intersection + + if ( d1 < 0 && d2 < 0 ) { + + return false; + + } + } + + return true; + }; + + }(), + + + containsPoint: function ( point ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } + + } + + return true; + + }, + + clone: function () { + + return new THREE.Frustum().copy( this ); + + } + +}; + +// File:src/math/Plane.js + +/** + * @author bhouston / http://exocortex.com + */ + +THREE.Plane = function ( normal, constant ) { + + this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3( 1, 0, 0 ); + this.constant = ( constant !== undefined ) ? constant : 0; + +}; + +THREE.Plane.prototype = { + + constructor: THREE.Plane, + + set: function ( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; + + return this; + + }, + + setComponents: function ( x, y, z, w ) { + + this.normal.set( x, y, z ); + this.constant = w; + + return this; + + }, + + setFromNormalAndCoplanarPoint: function ( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); // must be this.normal, not normal, as this.normal is normalized + + return this; + + }, + + setFromCoplanarPoints: function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( a, b, c ) { + + var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); + + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + + this.setFromNormalAndCoplanarPoint( normal, a ); + + return this; + + }; + + }(), + + + copy: function ( plane ) { + + this.normal.copy( plane.normal ); + this.constant = plane.constant; + + return this; + + }, + + normalize: function () { + + // Note: will lead to a divide by zero if the plane is invalid. + + var inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; + + return this; + + }, + + negate: function () { + + this.constant *= - 1; + this.normal.negate(); + + return this; + + }, + + distanceToPoint: function ( point ) { + + return this.normal.dot( point ) + this.constant; + + }, + + distanceToSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) - sphere.radius; + + }, + + projectPoint: function ( point, optionalTarget ) { + + return this.orthoPoint( point, optionalTarget ).sub( point ).negate(); + + }, + + orthoPoint: function ( point, optionalTarget ) { + + var perpendicularMagnitude = this.distanceToPoint( point ); + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude ); + + }, + + isIntersectionLine: function ( line ) { + + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + + var startSign = this.distanceToPoint( line.start ); + var endSign = this.distanceToPoint( line.end ); + + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + + }, + + intersectLine: function () { + + var v1 = new THREE.Vector3(); + + return function ( line, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + var direction = line.delta( v1 ); + + var denominator = this.normal.dot( direction ); + + if ( denominator == 0 ) { + + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) == 0 ) { + + return result.copy( line.start ); + + } + + // Unsure if this is the correct method to handle this case. + return undefined; + + } + + var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + + if ( t < 0 || t > 1 ) { + + return undefined; + + } + + return result.copy( direction ).multiplyScalar( t ).add( line.start ); + + }; + + }(), + + + coplanarPoint: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.copy( this.normal ).multiplyScalar( - this.constant ); + + }, + + applyMatrix4: function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var m1 = new THREE.Matrix3(); + + return function ( matrix, optionalNormalMatrix ) { + + // compute new normal based on theory here: + // http://www.songho.ca/opengl/gl_normaltransform.html + var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix ); + var newNormal = v1.copy( this.normal ).applyMatrix3( normalMatrix ); + + var newCoplanarPoint = this.coplanarPoint( v2 ); + newCoplanarPoint.applyMatrix4( matrix ); + + this.setFromNormalAndCoplanarPoint( newNormal, newCoplanarPoint ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.constant = this.constant - offset.dot( this.normal ); + + return this; + + }, + + equals: function ( plane ) { + + return plane.normal.equals( this.normal ) && ( plane.constant == this.constant ); + + }, + + clone: function () { + + return new THREE.Plane().copy( this ); + + } + +}; + +// File:src/math/Math.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Math = { + + generateUUID: function () { + + // http://www.broofa.com/Tools/Math.uuid.htm + + var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' ); + var uuid = new Array( 36 ); + var rnd = 0, r; + + return function () { + + for ( var i = 0; i < 36; i ++ ) { + + if ( i == 8 || i == 13 || i == 18 || i == 23 ) { + + uuid[ i ] = '-'; + + } else if ( i == 14 ) { + + uuid[ i ] = '4'; + + } else { + + if ( rnd <= 0x02 ) rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0; + r = rnd & 0xf; + rnd = rnd >> 4; + uuid[ i ] = chars[ ( i == 19 ) ? ( r & 0x3 ) | 0x8 : r ]; + + } + } + + return uuid.join( '' ); + + }; + + }(), + + // Clamp value to range + + clamp: function ( x, a, b ) { + + return ( x < a ) ? a : ( ( x > b ) ? b : x ); + + }, + + // Clamp value to range to range + + mapLinear: function ( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + + }, + + // http://en.wikipedia.org/wiki/Smoothstep + + smoothstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * ( 3 - 2 * x ); + + }, + + smootherstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + + }, + + // Random float from <0, 1> with 16 bits of randomness + // (standard Math.random() creates repetitive patterns when applied over larger space) + + random16: function () { + + return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; + + }, + + // Random integer from interval + + randInt: function ( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + + }, + + // Random float from interval + + randFloat: function ( low, high ) { + + return low + Math.random() * ( high - low ); + + }, + + // Random float from <-range/2, range/2> interval + + randFloatSpread: function ( range ) { + + return range * ( 0.5 - Math.random() ); + + }, + + sign: function ( x ) { + + return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : 0; + + }, + + degToRad: function () { + + var degreeToRadiansFactor = Math.PI / 180; + + return function ( degrees ) { + + return degrees * degreeToRadiansFactor; + + }; + + }(), + + radToDeg: function () { + + var radianToDegreesFactor = 180 / Math.PI; + + return function ( radians ) { + + return radians * radianToDegreesFactor; + + }; + + }(), + + isPowerOfTwo: function ( value ) { + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + } + +}; + +// File:src/math/Spline.js + +/** + * Spline from Tween.js, slightly optimized (and trashed) + * http://sole.github.com/tween.js/examples/05_spline.html + * + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Spline = function ( points ) { + + this.points = points; + + var c = [], v3 = { x: 0, y: 0, z: 0 }, + point, intPoint, weight, w2, w3, + pa, pb, pc, pd; + + this.initFromArray = function ( a ) { + + this.points = []; + + for ( var i = 0; i < a.length; i ++ ) { + + this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] }; + + } + + }; + + this.getPoint = function ( k ) { + + point = ( this.points.length - 1 ) * k; + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > this.points.length - 2 ? this.points.length - 1 : intPoint + 1; + c[ 3 ] = intPoint > this.points.length - 3 ? this.points.length - 1 : intPoint + 2; + + pa = this.points[ c[ 0 ] ]; + pb = this.points[ c[ 1 ] ]; + pc = this.points[ c[ 2 ] ]; + pd = this.points[ c[ 3 ] ]; + + w2 = weight * weight; + w3 = weight * w2; + + v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 ); + v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 ); + v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 ); + + return v3; + + }; + + this.getControlPointsArray = function () { + + var i, p, l = this.points.length, + coords = []; + + for ( i = 0; i < l; i ++ ) { + + p = this.points[ i ]; + coords[ i ] = [ p.x, p.y, p.z ]; + + } + + return coords; + + }; + + // approximate length by summing linear segments + + this.getLength = function ( nSubDivisions ) { + + var i, index, nSamples, position, + point = 0, intPoint = 0, oldIntPoint = 0, + oldPosition = new THREE.Vector3(), + tmpVec = new THREE.Vector3(), + chunkLengths = [], + totalLength = 0; + + // first point has 0 length + + chunkLengths[ 0 ] = 0; + + if ( ! nSubDivisions ) nSubDivisions = 100; + + nSamples = this.points.length * nSubDivisions; + + oldPosition.copy( this.points[ 0 ] ); + + for ( i = 1; i < nSamples; i ++ ) { + + index = i / nSamples; + + position = this.getPoint( index ); + tmpVec.copy( position ); + + totalLength += tmpVec.distanceTo( oldPosition ); + + oldPosition.copy( position ); + + point = ( this.points.length - 1 ) * index; + intPoint = Math.floor( point ); + + if ( intPoint != oldIntPoint ) { + + chunkLengths[ intPoint ] = totalLength; + oldIntPoint = intPoint; + + } + + } + + // last point ends with total length + + chunkLengths[ chunkLengths.length ] = totalLength; + + return { chunks: chunkLengths, total: totalLength }; + + }; + + this.reparametrizeByArcLength = function ( samplingCoef ) { + + var i, j, + index, indexCurrent, indexNext, + linearDistance, realDistance, + sampling, position, + newpoints = [], + tmpVec = new THREE.Vector3(), + sl = this.getLength(); + + newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() ); + + for ( i = 1; i < this.points.length; i ++ ) { + + //tmpVec.copy( this.points[ i - 1 ] ); + //linearDistance = tmpVec.distanceTo( this.points[ i ] ); + + realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ]; + + sampling = Math.ceil( samplingCoef * realDistance / sl.total ); + + indexCurrent = ( i - 1 ) / ( this.points.length - 1 ); + indexNext = i / ( this.points.length - 1 ); + + for ( j = 1; j < sampling - 1; j ++ ) { + + index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent ); + + position = this.getPoint( index ); + newpoints.push( tmpVec.copy( position ).clone() ); + + } + + newpoints.push( tmpVec.copy( this.points[ i ] ).clone() ); + + } + + this.points = newpoints; + + }; + + // Catmull-Rom + + function interpolate( p0, p1, p2, p3, t, t2, t3 ) { + + var v0 = ( p2 - p0 ) * 0.5, + v1 = ( p3 - p1 ) * 0.5; + + return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + }; + +}; + +// File:src/math/Triangle.js + +/** + * @author bhouston / http://exocortex.com + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Triangle = function ( a, b, c ) { + + this.a = ( a !== undefined ) ? a : new THREE.Vector3(); + this.b = ( b !== undefined ) ? b : new THREE.Vector3(); + this.c = ( c !== undefined ) ? c : new THREE.Vector3(); + +}; + +THREE.Triangle.normal = function () { + + var v0 = new THREE.Vector3(); + + return function ( a, b, c, optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + + result.subVectors( c, b ); + v0.subVectors( a, b ); + result.cross( v0 ); + + var resultLengthSq = result.lengthSq(); + if ( resultLengthSq > 0 ) { + + return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) ); + + } + + return result.set( 0, 0, 0 ); + + }; + +}(); + +// static/instance method to calculate barycoordinates +// based on: http://www.blackpawn.com/texts/pointinpoly/default.html +THREE.Triangle.barycoordFromPoint = function () { + + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( point, a, b, c, optionalTarget ) { + + v0.subVectors( c, a ); + v1.subVectors( b, a ); + v2.subVectors( point, a ); + + var dot00 = v0.dot( v0 ); + var dot01 = v0.dot( v1 ); + var dot02 = v0.dot( v2 ); + var dot11 = v1.dot( v1 ); + var dot12 = v1.dot( v2 ); + + var denom = ( dot00 * dot11 - dot01 * dot01 ); + + var result = optionalTarget || new THREE.Vector3(); + + // colinear or singular triangle + if ( denom == 0 ) { + // arbitrary location outside of triangle? + // not sure if this is the best idea, maybe should be returning undefined + return result.set( - 2, - 1, - 1 ); + } + + var invDenom = 1 / denom; + var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + // barycoordinates must always sum to 1 + return result.set( 1 - u - v, v, u ); + + }; + +}(); + +THREE.Triangle.containsPoint = function () { + + var v1 = new THREE.Vector3(); + + return function ( point, a, b, c ) { + + var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 ); + + return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); + + }; + +}(); + +THREE.Triangle.prototype = { + + constructor: THREE.Triangle, + + set: function ( a, b, c ) { + + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); + + return this; + + }, + + setFromPointsAndIndices: function ( points, i0, i1, i2 ) { + + this.a.copy( points[ i0 ] ); + this.b.copy( points[ i1 ] ); + this.c.copy( points[ i2 ] ); + + return this; + + }, + + copy: function ( triangle ) { + + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); + + return this; + + }, + + area: function () { + + var v0 = new THREE.Vector3(); + var v1 = new THREE.Vector3(); + + return function () { + + v0.subVectors( this.c, this.b ); + v1.subVectors( this.a, this.b ); + + return v0.cross( v1 ).length() * 0.5; + + }; + + }(), + + midpoint: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Vector3(); + return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); + + }, + + normal: function ( optionalTarget ) { + + return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget ); + + }, + + plane: function ( optionalTarget ) { + + var result = optionalTarget || new THREE.Plane(); + + return result.setFromCoplanarPoints( this.a, this.b, this.c ); + + }, + + barycoordFromPoint: function ( point, optionalTarget ) { + + return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget ); + + }, + + containsPoint: function ( point ) { + + return THREE.Triangle.containsPoint( point, this.a, this.b, this.c ); + + }, + + equals: function ( triangle ) { + + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); + + }, + + clone: function () { + + return new THREE.Triangle().copy( this ); + + } + +}; + +// File:src/core/Clock.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Clock = function ( autoStart ) { + + this.autoStart = ( autoStart !== undefined ) ? autoStart : true; + + this.startTime = 0; + this.oldTime = 0; + this.elapsedTime = 0; + + this.running = false; + +}; + +THREE.Clock.prototype = { + + constructor: THREE.Clock, + + start: function () { + + this.startTime = self.performance !== undefined && self.performance.now !== undefined + ? self.performance.now() + : Date.now(); + + this.oldTime = this.startTime; + this.running = true; + }, + + stop: function () { + + this.getElapsedTime(); + this.running = false; + + }, + + getElapsedTime: function () { + + this.getDelta(); + return this.elapsedTime; + + }, + + getDelta: function () { + + var diff = 0; + + if ( this.autoStart && ! this.running ) { + + this.start(); + + } + + if ( this.running ) { + + var newTime = self.performance !== undefined && self.performance.now !== undefined + ? self.performance.now() + : Date.now(); + + diff = 0.001 * ( newTime - this.oldTime ); + this.oldTime = newTime; + + this.elapsedTime += diff; + + } + + return diff; + + } + +}; + +// File:src/core/EventDispatcher.js + +/** + * https://github.com/mrdoob/eventdispatcher.js/ + */ + +THREE.EventDispatcher = function () {} + +THREE.EventDispatcher.prototype = { + + constructor: THREE.EventDispatcher, + + apply: function ( object ) { + + object.addEventListener = THREE.EventDispatcher.prototype.addEventListener; + object.hasEventListener = THREE.EventDispatcher.prototype.hasEventListener; + object.removeEventListener = THREE.EventDispatcher.prototype.removeEventListener; + object.dispatchEvent = THREE.EventDispatcher.prototype.dispatchEvent; + + }, + + addEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) this._listeners = {}; + + var listeners = this._listeners; + + if ( listeners[ type ] === undefined ) { + + listeners[ type ] = []; + + } + + if ( listeners[ type ].indexOf( listener ) === - 1 ) { + + listeners[ type ].push( listener ); + + } + + }, + + hasEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return false; + + var listeners = this._listeners; + + if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) { + + return true; + + } + + return false; + + }, + + removeEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ type ]; + + if ( listenerArray !== undefined ) { + + var index = listenerArray.indexOf( listener ); + + if ( index !== - 1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + }, + + dispatchEvent: function ( event ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + var array = []; + var length = listenerArray.length; + + for ( var i = 0; i < length; i ++ ) { + + array[ i ] = listenerArray[ i ]; + + } + + for ( var i = 0; i < length; i ++ ) { + + array[ i ].call( this, event ); + + } + + } + + } + +}; + +// File:src/core/Raycaster.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author bhouston / http://exocortex.com/ + * @author stephomi / http://stephaneginier.com/ + */ + +( function ( THREE ) { + + THREE.Raycaster = function ( origin, direction, near, far ) { + + this.ray = new THREE.Ray( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + this.near = near || 0; + this.far = far || Infinity; + + this.params = { + Sprite: {}, + Mesh: {}, + PointCloud: { threshold: 1 }, + LOD: {}, + Line: {} + }; + + }; + + var descSort = function ( a, b ) { + + return a.distance - b.distance; + + }; + + var intersectObject = function ( object, raycaster, intersects, recursive ) { + + object.raycast( raycaster, intersects ); + + if ( recursive === true ) { + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + intersectObject( children[ i ], raycaster, intersects, true ); + + } + + } + + }; + + // + + THREE.Raycaster.prototype = { + + constructor: THREE.Raycaster, + + precision: 0.0001, + linePrecision: 1, + + set: function ( origin, direction ) { + + this.ray.set( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + }, + + intersectObject: function ( object, recursive ) { + + var intersects = []; + + intersectObject( object, this, intersects, recursive ); + + intersects.sort( descSort ); + + return intersects; + + }, + + intersectObjects: function ( objects, recursive ) { + + var intersects = []; + + for ( var i = 0, l = objects.length; i < l; i ++ ) { + + intersectObject( objects[ i ], this, intersects, recursive ); + + } + + intersects.sort( descSort ); + + return intersects; + + } + + }; + +}( THREE ) ); + +// File:src/core/Object3D.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.Object3D = function () { + + this.id = THREE.Object3DIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.parent = undefined; + this.children = []; + + this.up = THREE.Object3D.DefaultUp.clone(); + + var scope = this; + + var position = new THREE.Vector3(); + var rotation = new THREE.Euler(); + var quaternion = new THREE.Quaternion(); + var scale = new THREE.Vector3( 1, 1, 1 ); + + rotation.onChange( function () { + quaternion.setFromEuler( rotation, false ); + } ); + + quaternion.onChange( function () { + rotation.setFromQuaternion( quaternion, undefined, false ); + } ); + + Object.defineProperties( this, { + position: { + enumerable: true, + value: position + }, + rotation: { + enumerable: true, + value: rotation + }, + quaternion: { + enumerable: true, + value: quaternion + }, + scale: { + enumerable: true, + value: scale + }, + } ); + + this.renderDepth = null; + + this.rotationAutoUpdate = true; + + this.matrix = new THREE.Matrix4(); + this.matrixWorld = new THREE.Matrix4(); + + this.matrixAutoUpdate = true; + this.matrixWorldNeedsUpdate = false; + + this.visible = true; + + this.castShadow = false; + this.receiveShadow = false; + + this.frustumCulled = true; + + this.userData = {}; + +}; + +THREE.Object3D.DefaultUp = new THREE.Vector3( 0, 1, 0 ); + +THREE.Object3D.prototype = { + + constructor: THREE.Object3D, + + get eulerOrder () { + + console.warn( 'THREE.Object3D: .eulerOrder has been moved to .rotation.order.' ); + + return this.rotation.order; + + }, + + set eulerOrder ( value ) { + + console.warn( 'THREE.Object3D: .eulerOrder has been moved to .rotation.order.' ); + + this.rotation.order = value; + + }, + + get useQuaternion () { + + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + + set useQuaternion ( value ) { + + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + + applyMatrix: function ( matrix ) { + + this.matrix.multiplyMatrices( matrix, this.matrix ); + + this.matrix.decompose( this.position, this.quaternion, this.scale ); + + }, + + setRotationFromAxisAngle: function ( axis, angle ) { + + // assumes axis is normalized + + this.quaternion.setFromAxisAngle( axis, angle ); + + }, + + setRotationFromEuler: function ( euler ) { + + this.quaternion.setFromEuler( euler, true ); + + }, + + setRotationFromMatrix: function ( m ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + this.quaternion.setFromRotationMatrix( m ); + + }, + + setRotationFromQuaternion: function ( q ) { + + // assumes q is normalized + + this.quaternion.copy( q ); + + }, + + rotateOnAxis: function () { + + // rotate object on axis in object space + // axis is assumed to be normalized + + var q1 = new THREE.Quaternion(); + + return function ( axis, angle ) { + + q1.setFromAxisAngle( axis, angle ); + + this.quaternion.multiply( q1 ); + + return this; + + } + + }(), + + rotateX: function () { + + var v1 = new THREE.Vector3( 1, 0, 0 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateY: function () { + + var v1 = new THREE.Vector3( 0, 1, 0 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateZ: function () { + + var v1 = new THREE.Vector3( 0, 0, 1 ); + + return function ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + translateOnAxis: function () { + + // translate object by distance along axis in object space + // axis is assumed to be normalized + + var v1 = new THREE.Vector3(); + + return function ( axis, distance ) { + + v1.copy( axis ).applyQuaternion( this.quaternion ); + + this.position.add( v1.multiplyScalar( distance ) ); + + return this; + + } + + }(), + + translate: function ( distance, axis ) { + + console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' ); + return this.translateOnAxis( axis, distance ); + + }, + + translateX: function () { + + var v1 = new THREE.Vector3( 1, 0, 0 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateY: function () { + + var v1 = new THREE.Vector3( 0, 1, 0 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateZ: function () { + + var v1 = new THREE.Vector3( 0, 0, 1 ); + + return function ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + localToWorld: function ( vector ) { + + return vector.applyMatrix4( this.matrixWorld ); + + }, + + worldToLocal: function () { + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) ); + + }; + + }(), + + lookAt: function () { + + // This routine does not support objects with rotated and/or translated parent(s) + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + m1.lookAt( vector, this.position, this.up ); + + this.quaternion.setFromRotationMatrix( m1 ); + + }; + + }(), + + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + }; + + if ( object === this ) { + + console.error( "THREE.Object3D.add:", object, "can't be added as a child of itself." ); + return this; + + } + + if ( object instanceof THREE.Object3D ) { + + if ( object.parent !== undefined ) { + + object.parent.remove( object ); + + } + + object.parent = this; + object.dispatchEvent( { type: 'added' } ); + + this.children.push( object ); + + // add to scene + + var scene = this; + + while ( scene.parent !== undefined ) { + + scene = scene.parent; + + } + + if ( scene !== undefined && scene instanceof THREE.Scene ) { + + scene.__addObject( object ); + + } + + } else { + + console.error( "THREE.Object3D.add:", object, "is not an instance of THREE.Object3D." ); + + } + + return this; + + }, + + remove: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i++ ) { + + this.remove( arguments[ i ] ); + + } + + }; + + var index = this.children.indexOf( object ); + + if ( index !== - 1 ) { + + object.parent = undefined; + object.dispatchEvent( { type: 'removed' } ); + + this.children.splice( index, 1 ); + + // remove from scene + + var scene = this; + + while ( scene.parent !== undefined ) { + + scene = scene.parent; + + } + + if ( scene !== undefined && scene instanceof THREE.Scene ) { + + scene.__removeObject( object ); + + } + + } + + }, + + raycast: function () {}, + + traverse: function ( callback ) { + + callback( this ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].traverse( callback ); + + } + + }, + + traverseVisible: function ( callback ) { + + if ( this.visible === false ) return; + + callback( this ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].traverseVisible( callback ); + + } + + }, + + getObjectById: function ( id, recursive ) { + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child.id === id ) { + + return child; + + } + + if ( recursive === true ) { + + child = child.getObjectById( id, recursive ); + + if ( child !== undefined ) { + + return child; + + } + + } + + } + + return undefined; + + }, + + getObjectByName: function ( name, recursive ) { + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child.name === name ) { + + return child; + + } + + if ( recursive === true ) { + + child = child.getObjectByName( name, recursive ); + + if ( child !== undefined ) { + + return child; + + } + + } + + } + + return undefined; + + }, + + getChildByName: function ( name, recursive ) { + + console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' ); + return this.getObjectByName( name, recursive ); + + }, + + updateMatrix: function () { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + + }, + + updateMatrixWorld: function ( force ) { + + if ( this.matrixAutoUpdate === true ) this.updateMatrix(); + + if ( this.matrixWorldNeedsUpdate === true || force === true ) { + + if ( this.parent === undefined ) { + + this.matrixWorld.copy( this.matrix ); + + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].updateMatrixWorld( force ); + + } + + }, + + clone: function ( object, recursive ) { + + if ( object === undefined ) object = new THREE.Object3D(); + if ( recursive === undefined ) recursive = true; + + object.name = this.name; + + object.up.copy( this.up ); + + object.position.copy( this.position ); + object.quaternion.copy( this.quaternion ); + object.scale.copy( this.scale ); + + object.renderDepth = this.renderDepth; + + object.rotationAutoUpdate = this.rotationAutoUpdate; + + object.matrix.copy( this.matrix ); + object.matrixWorld.copy( this.matrixWorld ); + + object.matrixAutoUpdate = this.matrixAutoUpdate; + object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate; + + object.visible = this.visible; + + object.castShadow = this.castShadow; + object.receiveShadow = this.receiveShadow; + + object.frustumCulled = this.frustumCulled; + + object.userData = JSON.parse( JSON.stringify( this.userData ) ); + + if ( recursive === true ) { + + for ( var i = 0; i < this.children.length; i ++ ) { + + var child = this.children[ i ]; + object.add( child.clone() ); + + } + + } + + return object; + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Object3D.prototype ); + +THREE.Object3DIdCount = 0; + +// File:src/core/Projector.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author julianwa / https://github.com/julianwa + */ + +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: [] }, + + _vA = new THREE.Vector3(), + _vB = new THREE.Vector3(), + _vC = new THREE.Vector3(), + + _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 ), + _points4 = new Array( 4 ), + + _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 ) { + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + + return vector.applyProjection( _viewProjectionMatrix ); + + }; + + this.unprojectVector = function () { + + var projectionMatrixInverse = new THREE.Matrix4(); + + return function ( vector, camera ) { + + projectionMatrixInverse.getInverse( camera.projectionMatrix ); + _viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, projectionMatrixInverse ); + + return vector.applyProjection( _viewProjectionMatrix ); + + }; + + }(); + + this.pickingRay = function ( vector, camera ) { + + // set two vectors with opposing z values + vector.z = - 1.0; + var end = new THREE.Vector3( vector.x, vector.y, 1.0 ); + + this.unprojectVector( vector, camera ); + this.unprojectVector( end, camera ); + + // find direction from vector to end + end.sub( vector ).normalize(); + + return new THREE.Raycaster( vector, end ); + + }; + + var RenderList = function () { + + var normals = []; + var uvs = []; + + var object = null; + var material = null; + + var normalMatrix = new THREE.Matrix3(); + + var setObject = function ( value ) { + + object = value; + material = object.material; + + normalMatrix.getNormalMatrix( object.matrixWorld ); + + normals.length = 0; + uvs.length = 0; + + }; + + var projectVertex = function ( 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; + + }; + + var pushVertex = function ( x, y, z ) { + + _vertex = getNextVertexInPool(); + _vertex.position.set( x, y, z ); + + projectVertex( _vertex ); + + }; + + var pushNormal = function ( x, y, z ) { + + normals.push( x, y, z ); + + }; + + var pushUv = function ( x, y ) { + + uvs.push( x, y ); + + }; + + var checkTriangleVisibility = function ( 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.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ); + + }; + + var checkBackfaceCulling = function ( 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; + + }; + + var pushLine = function ( a, b ) { + + var v1 = _vertexPool[ a ]; + var v2 = _vertexPool[ b ]; + + _line = getNextLineInPool(); + + _line.id = object.id; + _line.v1.copy( v1 ); + _line.v2.copy( v2 ); + _line.z = ( v1.positionScreen.z + v2.positionScreen.z ) / 2; + + _line.material = object.material; + + _renderData.elements.push( _line ); + + }; + + var pushTriangle = function ( a, b, c ) { + + 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; + + for ( var i = 0; i < 3; i ++ ) { + + var offset = arguments[ i ] * 3; + var normal = _face.vertexNormalsModel[ i ]; + + normal.set( normals[ offset ], normals[ offset + 1 ], normals[ offset + 2 ] ); + normal.applyMatrix3( normalMatrix ).normalize(); + + var offset2 = arguments[ i ] * 2; + + var uv = _face.uvs[ i ]; + uv.set( uvs[ offset2 ], uvs[ offset2 + 1 ] ); + + } + + _face.vertexNormalsLength = 3; + + _face.material = object.material; + + _renderData.elements.push( _face ); + + } + + }; + + return { + setObject: setObject, + projectVertex: projectVertex, + checkTriangleVisibility: checkTriangleVisibility, + checkBackfaceCulling: checkBackfaceCulling, + pushVertex: pushVertex, + pushNormal: pushNormal, + pushUv: pushUv, + pushLine: pushLine, + pushTriangle: pushTriangle + } + + }; + + var renderList = new RenderList(); + + 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 === undefined ) camera.updateMatrixWorld(); + + _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) ); + _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix ); + + _frustum.setFromMatrix( _viewProjectionMatrix ); + + // + + _objectCount = 0; + + _renderData.objects.length = 0; + _renderData.lights.length = 0; + + scene.traverseVisible( function ( object ) { + + if ( object instanceof THREE.Light ) { + + _renderData.lights.push( object ); + + } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Sprite ) { + + if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { + + _object = getNextObjectInPool(); + _object.id = object.id; + _object.object = object; + + if ( object.renderDepth !== null ) { + + _object.z = object.renderDepth; + + } else { + + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _viewProjectionMatrix ); + _object.z = _vector3.z; + + } + + _renderData.objects.push( _object ); + + } + + } + + } ); + + if ( sortObjects === true ) { + + _renderData.objects.sort( painterSort ); + + } + + // + + for ( var o = 0, ol = _renderData.objects.length; o < ol; o ++ ) { + + var object = _renderData.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 attributes = geometry.attributes; + var offsets = geometry.offsets; + + 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 ( attributes.index !== undefined ) { + + var indices = attributes.index.array; + + if ( offsets.length > 0 ) { + + for ( var o = 0; o < offsets.length; o ++ ) { + + var offset = offsets[ o ]; + var index = offset.index; + + for ( var i = offset.start, l = offset.start + offset.count; i < l; i += 3 ) { + + renderList.pushTriangle( indices[ i ] + index, indices[ i + 1 ] + index, indices[ i + 2 ] + index ); + + } + + } + + } else { + + for ( var i = 0, l = indices.length; i < l; i += 3 ) { + + renderList.pushTriangle( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); + + } + + } + + } else { + + for ( var i = 0, l = positions.length / 3; i < l; i += 3 ) { + + renderList.pushTriangle( i, i + 1, i + 2 ); + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + var vertices = geometry.vertices; + var faces = geometry.faces; + var faceVertexUvs = geometry.faceVertexUvs[ 0 ]; + + _normalMatrix.getNormalMatrix( _modelMatrix ); + + var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; + var objectMaterials = isFaceMaterial === true ? object.material : null; + + for ( var v = 0, vl = vertices.length; v < vl; v ++ ) { + + var vertex = vertices[ v ]; + renderList.pushVertex( vertex.x, vertex.y, vertex.z ); + + } + + for ( var f = 0, fl = faces.length; f < fl; f ++ ) { + + var face = faces[ f ]; + + var material = isFaceMaterial === true + ? objectMaterials.materials[ 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 ( material.morphTargets === true ) { + + var morphTargets = geometry.morphTargets; + var morphInfluences = object.morphTargetInfluences; + + var v1p = v1.position; + var v2p = v2.position; + var v3p = v3.position; + + _vA.set( 0, 0, 0 ); + _vB.set( 0, 0, 0 ); + _vC.set( 0, 0, 0 ); + + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { + + var influence = morphInfluences[ t ]; + + if ( influence === 0 ) continue; + + var targets = morphTargets[ t ].vertices; + + _vA.x += ( targets[ face.a ].x - v1p.x ) * influence; + _vA.y += ( targets[ face.a ].y - v1p.y ) * influence; + _vA.z += ( targets[ face.a ].z - v1p.z ) * influence; + + _vB.x += ( targets[ face.b ].x - v2p.x ) * influence; + _vB.y += ( targets[ face.b ].y - v2p.y ) * influence; + _vB.z += ( targets[ face.b ].z - v2p.z ) * influence; + + _vC.x += ( targets[ face.c ].x - v3p.x ) * influence; + _vC.y += ( targets[ face.c ].y - v3p.y ) * influence; + _vC.z += ( targets[ face.c ].z - v3p.z ) * influence; + + } + + v1.position.add( _vA ); + v2.position.add( _vB ); + v3.position.add( _vC ); + + renderList.projectVertex( v1 ); + renderList.projectVertex( v2 ); + renderList.projectVertex( v3 ); + + } + + 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; + + _renderData.elements.push( _face ); + + } + + } + + } else if ( object instanceof THREE.Line ) { + + 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.index !== undefined ) { + + var indices = attributes.index.array; + + for ( var i = 0, l = indices.length; i < l; i += 2 ) { + + renderList.pushLine( indices[ i ], indices[ i + 1 ] ); + + } + + } else { + + var step = object.type === THREE.LinePieces ? 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 ) { + + _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); + + var vertices = object.geometry.vertices; + + if ( vertices.length === 0 ) continue; + + v1 = getNextVertexInPool(); + v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix ); + + // Handle LineStrip and LinePieces + var step = object.type === THREE.LinePieces ? 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.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.Sprite ) { + + _vector4.set( _modelMatrix.elements[ 12 ], _modelMatrix.elements[ 13 ], _modelMatrix.elements[ 14 ], 1 ); + _vector4.applyMatrix4( _viewProjectionMatrix ); + + 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.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 ); + + } + + } + + } + + if ( sortElements === true ) _renderData.elements.sort( painterSort ); + + return _renderData; + + }; + + // 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.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; + + } + + } + + } + +}; + +// File:src/core/Face3.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) { + + this.a = a; + this.b = b; + this.c = c; + + this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3(); + this.vertexNormals = normal instanceof Array ? normal : []; + + this.color = color instanceof THREE.Color ? color : new THREE.Color(); + this.vertexColors = color instanceof Array ? color : []; + + this.vertexTangents = []; + + this.materialIndex = materialIndex !== undefined ? materialIndex : 0; + +}; + +THREE.Face3.prototype = { + + constructor: THREE.Face3, + + clone: function () { + + var face = new THREE.Face3( this.a, this.b, this.c ); + + face.normal.copy( this.normal ); + face.color.copy( this.color ); + + face.materialIndex = this.materialIndex; + + for ( var i = 0, il = this.vertexNormals.length; i < il; i ++ ) { + + face.vertexNormals[ i ] = this.vertexNormals[ i ].clone(); + + } + + for ( var i = 0, il = this.vertexColors.length; i < il; i ++ ) { + + face.vertexColors[ i ] = this.vertexColors[ i ].clone(); + + } + + for ( var i = 0, il = this.vertexTangents.length; i < il; i ++ ) { + + face.vertexTangents[ i ] = this.vertexTangents[ i ].clone(); + + } + + return face; + + } + +}; + +// File:src/core/Face4.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) { + + console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.' ) + return new THREE.Face3( a, b, c, normal, color, materialIndex ); + +}; + +// File:src/core/BufferAttribute.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BufferAttribute = function ( array, itemSize ) { + + this.array = array; + this.itemSize = itemSize; + +}; + +THREE.BufferAttribute.prototype = { + + constructor: THREE.BufferAttribute, + + get length () { + + return this.array.length; + + }, + + set: function ( value ) { + + this.array.set( value ); + + return this; + + }, + + setX: function ( index, x ) { + + this.array[ index * this.itemSize ] = x; + + return this; + + }, + + setY: function ( index, y ) { + + this.array[ index * this.itemSize + 1 ] = y; + + return this; + + }, + + setZ: function ( index, z ) { + + this.array[ index * this.itemSize + 2 ] = z; + + return this; + + }, + + setXY: function ( index, x, y ) { + + index *= this.itemSize; + + this.array[ index ] = x; + this.array[ index + 1 ] = y; + + return this; + + }, + + setXYZ: function ( index, x, y, z ) { + + index *= this.itemSize; + + this.array[ index ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + + return this; + + }, + + setXYZW: function ( index, x, y, z, w ) { + + index *= this.itemSize; + + this.array[ index ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; + + return this; + + } + +}; + +// + +THREE.Int8Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Int8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Uint8Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Uint8Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Uint8ClampedAttribute = function ( data, itemSize ) { + + console.warn( 'THREE.Uint8ClampedAttribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + + +}; + +THREE.Int16Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Int16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Uint16Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Uint16Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Int32Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Int32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Uint32Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Uint32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Float32Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Float32Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +THREE.Float64Attribute = function ( data, itemSize ) { + + console.warn( 'THREE.Float64Attribute has been removed. Use THREE.BufferAttribute( array, itemSize ) instead.' ); + return new THREE.BufferAttribute( data, itemSize ); + +}; + +// File:src/core/BufferGeometry.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.BufferGeometry = function () { + + this.id = THREE.GeometryIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.attributes = {}; + this.drawcalls = []; + this.offsets = this.drawcalls; // backwards compatibility + + this.boundingBox = null; + this.boundingSphere = null; + +}; + +THREE.BufferGeometry.prototype = { + + constructor: THREE.BufferGeometry, + + addAttribute: function ( name, attribute ) { + + if ( attribute instanceof THREE.BufferAttribute === false ) { + + console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' ); + + this.attributes[ name ] = { array: arguments[ 1 ], itemSize: arguments[ 2 ] }; + + return; + + } + + this.attributes[ name ] = attribute; + + }, + + getAttribute: function ( name ) { + + return this.attributes[ name ]; + + }, + + addDrawCall: function ( start, count, indexOffset ) { + + this.drawcalls.push( { + + start: start, + count: count, + index: indexOffset !== undefined ? indexOffset : 0 + + } ); + + }, + + applyMatrix: function ( matrix ) { + + var position = this.attributes.position; + + if ( position !== undefined ) { + + matrix.applyToVector3Array( position.array ); + position.needsUpdate = true; + + } + + var normal = this.attributes.normal; + + if ( normal !== undefined ) { + + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + normalMatrix.applyToVector3Array( normal.array ); + normal.needsUpdate = true; + + } + + }, + + fromGeometry: function ( geometry, settings ) { + + settings = settings || { 'vertexColors': THREE.NoColors }; + + var vertices = geometry.vertices; + var faces = geometry.faces; + var faceVertexUvs = geometry.faceVertexUvs; + var vertexColors = settings.vertexColors; + var hasFaceVertexUv = faceVertexUvs[ 0 ].length > 0; + var hasFaceVertexNormals = faces[ 0 ].vertexNormals.length == 3; + + var positions = new Float32Array( faces.length * 3 * 3 ); + this.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); + + var normals = new Float32Array( faces.length * 3 * 3 ); + this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); + + if ( vertexColors !== THREE.NoColors ) { + + var colors = new Float32Array( faces.length * 3 * 3 ); + this.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) ); + + } + + if ( hasFaceVertexUv === true ) { + + var uvs = new Float32Array( faces.length * 3 * 2 ); + this.addAttribute( 'uvs', new THREE.BufferAttribute( uvs, 2 ) ); + + } + + for ( var i = 0, i2 = 0, i3 = 0; i < faces.length; i ++, i2 += 6, i3 += 9 ) { + + var face = faces[ i ]; + + var a = vertices[ face.a ]; + var b = vertices[ face.b ]; + var c = vertices[ face.c ]; + + positions[ i3 ] = a.x; + positions[ i3 + 1 ] = a.y; + positions[ i3 + 2 ] = a.z; + + positions[ i3 + 3 ] = b.x; + positions[ i3 + 4 ] = b.y; + positions[ i3 + 5 ] = b.z; + + positions[ i3 + 6 ] = c.x; + positions[ i3 + 7 ] = c.y; + positions[ i3 + 8 ] = c.z; + + if ( hasFaceVertexNormals === true ) { + + var na = face.vertexNormals[ 0 ]; + var nb = face.vertexNormals[ 1 ]; + var nc = face.vertexNormals[ 2 ]; + + normals[ i3 ] = na.x; + normals[ i3 + 1 ] = na.y; + normals[ i3 + 2 ] = na.z; + + normals[ i3 + 3 ] = nb.x; + normals[ i3 + 4 ] = nb.y; + normals[ i3 + 5 ] = nb.z; + + normals[ i3 + 6 ] = nc.x; + normals[ i3 + 7 ] = nc.y; + normals[ i3 + 8 ] = nc.z; + + } else { + + var n = face.normal; + + normals[ i3 ] = n.x; + normals[ i3 + 1 ] = n.y; + normals[ i3 + 2 ] = n.z; + + normals[ i3 + 3 ] = n.x; + normals[ i3 + 4 ] = n.y; + normals[ i3 + 5 ] = n.z; + + normals[ i3 + 6 ] = n.x; + normals[ i3 + 7 ] = n.y; + normals[ i3 + 8 ] = n.z; + + } + + if ( vertexColors === THREE.FaceColors ) { + + var fc = face.color; + + colors[ i3 ] = fc.r; + colors[ i3 + 1 ] = fc.g; + colors[ i3 + 2 ] = fc.b; + + colors[ i3 + 3 ] = fc.r; + colors[ i3 + 4 ] = fc.g; + colors[ i3 + 5 ] = fc.b; + + colors[ i3 + 6 ] = fc.r; + colors[ i3 + 7 ] = fc.g; + colors[ i3 + 8 ] = fc.b; + + } else if ( vertexColors === THREE.VertexColors ) { + + var vca = face.vertexColors[ 0 ]; + var vcb = face.vertexColors[ 1 ]; + var vcc = face.vertexColors[ 2 ]; + + colors[ i3 ] = vca.r; + colors[ i3 + 1 ] = vca.g; + colors[ i3 + 2 ] = vca.b; + + colors[ i3 + 3 ] = vcb.r; + colors[ i3 + 4 ] = vcb.g; + colors[ i3 + 5 ] = vcb.b; + + colors[ i3 + 6 ] = vcc.r; + colors[ i3 + 7 ] = vcc.g; + colors[ i3 + 8 ] = vcc.b; + + } + + if ( hasFaceVertexUv === true ) { + + var uva = faceVertexUvs[ 0 ][ i ][ 0 ]; + var uvb = faceVertexUvs[ 0 ][ i ][ 1 ]; + var uvc = faceVertexUvs[ 0 ][ i ][ 2 ]; + + uvs[ i2 ] = uva.x; + uvs[ i2 + 1 ] = uva.y; + + uvs[ i2 + 2 ] = uvb.x; + uvs[ i2 + 3 ] = uvb.y; + + uvs[ i2 + 4 ] = uvc.x; + uvs[ i2 + 5 ] = uvc.y; + + } + + } + + this.computeBoundingSphere() + + return this; + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new THREE.Box3(); + + } + + var positions = this.attributes[ 'position' ].array; + + if ( positions ) { + + var bb = this.boundingBox; + + if ( positions.length >= 3 ) { + bb.min.x = bb.max.x = positions[ 0 ]; + bb.min.y = bb.max.y = positions[ 1 ]; + bb.min.z = bb.max.z = positions[ 2 ]; + } + + for ( var i = 3, il = positions.length; i < il; i += 3 ) { + + var x = positions[ i ]; + var y = positions[ i + 1 ]; + var z = positions[ i + 2 ]; + + // bounding box + + if ( x < bb.min.x ) { + + bb.min.x = x; + + } else if ( x > bb.max.x ) { + + bb.max.x = x; + + } + + if ( y < bb.min.y ) { + + bb.min.y = y; + + } else if ( y > bb.max.y ) { + + bb.max.y = y; + + } + + if ( z < bb.min.z ) { + + bb.min.z = z; + + } else if ( z > bb.max.z ) { + + bb.max.z = z; + + } + + } + + } + + if ( positions === undefined || positions.length === 0 ) { + + this.boundingBox.min.set( 0, 0, 0 ); + this.boundingBox.max.set( 0, 0, 0 ); + + } + + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.' ); + + } + + }, + + computeBoundingSphere: function () { + + var box = new THREE.Box3(); + var vector = new THREE.Vector3(); + + return function () { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new THREE.Sphere(); + + } + + var positions = this.attributes[ 'position' ].array; + + if ( positions ) { + + box.makeEmpty(); + + var center = this.boundingSphere.center; + + for ( var i = 0, il = positions.length; i < il; i += 3 ) { + + vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + box.expandByPoint( vector ); + + } + + box.center( center ); + + // hoping to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + + var maxRadiusSq = 0; + + for ( var i = 0, il = positions.length; i < il; i += 3 ) { + + vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.' ); + + } + + } + + } + + }(), + + computeFaceNormals: function () { + + // backwards compatibility + + }, + + computeVertexNormals: function () { + + if ( this.attributes[ 'position' ] ) { + + var i, il; + var j, jl; + + var nVertexElements = this.attributes[ 'position' ].array.length; + + if ( this.attributes[ 'normal' ] === undefined ) { + + this.attributes[ 'normal' ] = { + + itemSize: 3, + array: new Float32Array( nVertexElements ) + + }; + + } else { + + // reset existing normals to zero + + for ( i = 0, il = this.attributes[ 'normal' ].array.length; i < il; i ++ ) { + + this.attributes[ 'normal' ].array[ i ] = 0; + + } + + } + + var positions = this.attributes[ 'position' ].array; + var normals = this.attributes[ 'normal' ].array; + + var vA, vB, vC, x, y, z, + + pA = new THREE.Vector3(), + pB = new THREE.Vector3(), + pC = new THREE.Vector3(), + + cb = new THREE.Vector3(), + ab = new THREE.Vector3(); + + // indexed elements + + if ( this.attributes[ 'index' ] ) { + + var indices = this.attributes[ 'index' ].array; + + var offsets = ( this.offsets.length > 0 ? this.offsets : [ { start: 0, count: indices.length, index: 0 } ] ); + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + vA = index + indices[ i ]; + vB = index + indices[ i + 1 ]; + vC = index + indices[ i + 2 ]; + + x = positions[ vA * 3 ]; + y = positions[ vA * 3 + 1 ]; + z = positions[ vA * 3 + 2 ]; + pA.set( x, y, z ); + + x = positions[ vB * 3 ]; + y = positions[ vB * 3 + 1 ]; + z = positions[ vB * 3 + 2 ]; + pB.set( x, y, z ); + + x = positions[ vC * 3 ]; + y = positions[ vC * 3 + 1 ]; + z = positions[ vC * 3 + 2 ]; + pC.set( x, y, z ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ vA * 3 ] += cb.x; + normals[ vA * 3 + 1 ] += cb.y; + normals[ vA * 3 + 2 ] += cb.z; + + normals[ vB * 3 ] += cb.x; + normals[ vB * 3 + 1 ] += cb.y; + normals[ vB * 3 + 2 ] += cb.z; + + normals[ vC * 3 ] += cb.x; + normals[ vC * 3 + 1 ] += cb.y; + normals[ vC * 3 + 2 ] += cb.z; + + } + + } + + // non-indexed elements (unconnected triangle soup) + + } else { + + for ( i = 0, il = positions.length; i < il; i += 9 ) { + + x = positions[ i ]; + y = positions[ i + 1 ]; + z = positions[ i + 2 ]; + pA.set( x, y, z ); + + x = positions[ i + 3 ]; + y = positions[ i + 4 ]; + z = positions[ i + 5 ]; + pB.set( x, y, z ); + + x = positions[ i + 6 ]; + y = positions[ i + 7 ]; + z = positions[ i + 8 ]; + pC.set( x, y, z ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ i ] = cb.x; + normals[ i + 1 ] = cb.y; + normals[ i + 2 ] = cb.z; + + normals[ i + 3 ] = cb.x; + normals[ i + 4 ] = cb.y; + normals[ i + 5 ] = cb.z; + + normals[ i + 6 ] = cb.x; + normals[ i + 7 ] = cb.y; + normals[ i + 8 ] = cb.z; + + } + + } + + this.normalizeNormals(); + + this.normalsNeedUpdate = true; + + } + + }, + + computeTangents: function () { + + // based on http://www.terathon.com/code/tangent.html + // (per vertex tangents) + + if ( this.attributes[ 'index' ] === undefined || + this.attributes[ 'position' ] === undefined || + this.attributes[ 'normal' ] === undefined || + this.attributes[ 'uv' ] === undefined ) { + + console.warn( 'Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()' ); + return; + + } + + var indices = this.attributes[ 'index' ].array; + var positions = this.attributes[ 'position' ].array; + var normals = this.attributes[ 'normal' ].array; + var uvs = this.attributes[ 'uv' ].array; + + var nVertices = positions.length / 3; + + if ( this.attributes[ 'tangent' ] === undefined ) { + + var nTangentElements = 4 * nVertices; + + this.attributes[ 'tangent' ] = { + + itemSize: 4, + array: new Float32Array( nTangentElements ) + + }; + + } + + var tangents = this.attributes[ 'tangent' ].array; + + var tan1 = [], tan2 = []; + + for ( var k = 0; k < nVertices; k ++ ) { + + tan1[ k ] = new THREE.Vector3(); + tan2[ k ] = new THREE.Vector3(); + + } + + var xA, yA, zA, + xB, yB, zB, + xC, yC, zC, + + uA, vA, + uB, vB, + uC, vC, + + x1, x2, y1, y2, z1, z2, + s1, s2, t1, t2, r; + + var sdir = new THREE.Vector3(), tdir = new THREE.Vector3(); + + function handleTriangle( a, b, c ) { + + xA = positions[ a * 3 ]; + yA = positions[ a * 3 + 1 ]; + zA = positions[ a * 3 + 2 ]; + + xB = positions[ b * 3 ]; + yB = positions[ b * 3 + 1 ]; + zB = positions[ b * 3 + 2 ]; + + xC = positions[ c * 3 ]; + yC = positions[ c * 3 + 1 ]; + zC = positions[ c * 3 + 2 ]; + + uA = uvs[ a * 2 ]; + vA = uvs[ a * 2 + 1 ]; + + uB = uvs[ b * 2 ]; + vB = uvs[ b * 2 + 1 ]; + + uC = uvs[ c * 2 ]; + vC = uvs[ c * 2 + 1 ]; + + x1 = xB - xA; + x2 = xC - xA; + + y1 = yB - yA; + y2 = yC - yA; + + z1 = zB - zA; + z2 = zC - zA; + + s1 = uB - uA; + s2 = uC - uA; + + t1 = vB - vA; + t2 = vC - vA; + + r = 1.0 / ( s1 * t2 - s2 * t1 ); + + sdir.set( + ( t2 * x1 - t1 * x2 ) * r, + ( t2 * y1 - t1 * y2 ) * r, + ( t2 * z1 - t1 * z2 ) * r + ); + + tdir.set( + ( s1 * x2 - s2 * x1 ) * r, + ( s1 * y2 - s2 * y1 ) * r, + ( s1 * z2 - s2 * z1 ) * r + ); + + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); + + } + + var i, il; + var j, jl; + var iA, iB, iC; + + var offsets = this.offsets; + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + iA = index + indices[ i ]; + iB = index + indices[ i + 1 ]; + iC = index + indices[ i + 2 ]; + + handleTriangle( iA, iB, iC ); + + } + + } + + var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(); + var n = new THREE.Vector3(), n2 = new THREE.Vector3(); + var w, t, test; + + function handleVertex( v ) { + + n.x = normals[ v * 3 ]; + n.y = normals[ v * 3 + 1 ]; + n.z = normals[ v * 3 + 2 ]; + + n2.copy( n ); + + t = tan1[ v ]; + + // Gram-Schmidt orthogonalize + + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + + // Calculate handedness + + tmp2.crossVectors( n2, t ); + test = tmp2.dot( tan2[ v ] ); + w = ( test < 0.0 ) ? - 1.0 : 1.0; + + tangents[ v * 4 ] = tmp.x; + tangents[ v * 4 + 1 ] = tmp.y; + tangents[ v * 4 + 2 ] = tmp.z; + tangents[ v * 4 + 3 ] = w; + + } + + for ( j = 0, jl = offsets.length; j < jl; ++ j ) { + + var start = offsets[ j ].start; + var count = offsets[ j ].count; + var index = offsets[ j ].index; + + for ( i = start, il = start + count; i < il; i += 3 ) { + + iA = index + indices[ i ]; + iB = index + indices[ i + 1 ]; + iC = index + indices[ i + 2 ]; + + handleVertex( iA ); + handleVertex( iB ); + handleVertex( iC ); + + } + + } + + }, + + /* + computeOffsets + Compute the draw offset for large models by chunking the index buffer into chunks of 65k addressable vertices. + This method will effectively rewrite the index buffer and remap all attributes to match the new indices. + WARNING: This method will also expand the vertex count to prevent sprawled triangles across draw offsets. + indexBufferSize - Defaults to 65535, but allows for larger or smaller chunks. + */ + computeOffsets: function ( indexBufferSize ) { + + var size = indexBufferSize; + if ( indexBufferSize === undefined ) + size = 65535; //WebGL limits type of index buffer values to 16-bit. + + var s = Date.now(); + + var indices = this.attributes[ 'index' ].array; + var vertices = this.attributes[ 'position' ].array; + + var verticesCount = ( vertices.length / 3 ); + var facesCount = ( indices.length / 3 ); + + /* + console.log("Computing buffers in offsets of "+size+" -> indices:"+indices.length+" vertices:"+vertices.length); + console.log("Faces to process: "+(indices.length/3)); + console.log("Reordering "+verticesCount+" vertices."); + */ + + var sortedIndices = new Uint16Array( indices.length ); //16-bit buffers + var indexPtr = 0; + var vertexPtr = 0; + + var offsets = [ { start:0, count:0, index:0 } ]; + var offset = offsets[ 0 ]; + + var duplicatedVertices = 0; + var newVerticeMaps = 0; + var faceVertices = new Int32Array( 6 ); + var vertexMap = new Int32Array( vertices.length ); + var revVertexMap = new Int32Array( vertices.length ); + for ( var j = 0; j < vertices.length; j ++ ) { vertexMap[ j ] = - 1; revVertexMap[ j ] = - 1; } + + /* + Traverse every face and reorder vertices in the proper offsets of 65k. + We can have more than 65k entries in the index buffer per offset, but only reference 65k values. + */ + for ( var findex = 0; findex < facesCount; findex ++ ) { + newVerticeMaps = 0; + + for ( var vo = 0; vo < 3; vo ++ ) { + var vid = indices[ findex * 3 + vo ]; + if ( vertexMap[ vid ] == - 1 ) { + //Unmapped vertice + faceVertices[ vo * 2 ] = vid; + faceVertices[ vo * 2 + 1 ] = - 1; + newVerticeMaps ++; + } else if ( vertexMap[ vid ] < offset.index ) { + //Reused vertices from previous block (duplicate) + faceVertices[ vo * 2 ] = vid; + faceVertices[ vo * 2 + 1 ] = - 1; + duplicatedVertices ++; + } else { + //Reused vertice in the current block + faceVertices[ vo * 2 ] = vid; + faceVertices[ vo * 2 + 1 ] = vertexMap[ vid ]; + } + } + + var faceMax = vertexPtr + newVerticeMaps; + if ( faceMax > ( offset.index + size ) ) { + var new_offset = { start:indexPtr, count:0, index:vertexPtr }; + offsets.push( new_offset ); + offset = new_offset; + + //Re-evaluate reused vertices in light of new offset. + for ( var v = 0; v < 6; v += 2 ) { + var new_vid = faceVertices[ v + 1 ]; + if ( new_vid > - 1 && new_vid < offset.index ) + faceVertices[ v + 1 ] = - 1; + } + } + + //Reindex the face. + for ( var v = 0; v < 6; v += 2 ) { + var vid = faceVertices[ v ]; + var new_vid = faceVertices[ v + 1 ]; + + if ( new_vid === - 1 ) + new_vid = vertexPtr ++; + + vertexMap[ vid ] = new_vid; + revVertexMap[ new_vid ] = vid; + sortedIndices[ indexPtr ++ ] = new_vid - offset.index; //XXX overflows at 16bit + offset.count ++; + } + } + + /* Move all attribute values to map to the new computed indices , also expand the vertice stack to match our new vertexPtr. */ + this.reorderBuffers( sortedIndices, revVertexMap, vertexPtr ); + this.offsets = offsets; + + /* + var orderTime = Date.now(); + console.log("Reorder time: "+(orderTime-s)+"ms"); + console.log("Duplicated "+duplicatedVertices+" vertices."); + console.log("Compute Buffers time: "+(Date.now()-s)+"ms"); + console.log("Draw offsets: "+offsets.length); + */ + + return offsets; + }, + + merge: function () { + + console.log( 'BufferGeometry.merge(): TODO' ); + + }, + + normalizeNormals: function () { + + var normals = this.attributes[ 'normal' ].array; + + var x, y, z, n; + + for ( var i = 0, il = normals.length; i < il; i += 3 ) { + + x = normals[ i ]; + y = normals[ i + 1 ]; + z = normals[ i + 2 ]; + + n = 1.0 / Math.sqrt( x * x + y * y + z * z ); + + normals[ i ] *= n; + normals[ i + 1 ] *= n; + normals[ i + 2 ] *= n; + + } + + }, + + /* + reoderBuffers: + Reorder attributes based on a new indexBuffer and indexMap. + indexBuffer - Uint16Array of the new ordered indices. + indexMap - Int32Array where the position is the new vertex ID and the value the old vertex ID for each vertex. + vertexCount - Amount of total vertices considered in this reordering (in case you want to grow the vertice stack). + */ + reorderBuffers: function ( indexBuffer, indexMap, vertexCount ) { + + /* Create a copy of all attributes for reordering. */ + var sortedAttributes = {}; + var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ]; + for ( var attr in this.attributes ) { + if ( attr == 'index' ) + continue; + var sourceArray = this.attributes[ attr ].array; + for ( var i = 0, il = types.length; i < il; i ++ ) { + var type = types[ i ]; + if ( sourceArray instanceof type ) { + sortedAttributes[ attr ] = new type( this.attributes[ attr ].itemSize * vertexCount ); + break; + } + } + } + + /* Move attribute positions based on the new index map */ + for ( var new_vid = 0; new_vid < vertexCount; new_vid ++ ) { + var vid = indexMap[ new_vid ]; + for ( var attr in this.attributes ) { + if ( attr == 'index' ) + continue; + var attrArray = this.attributes[ attr ].array; + var attrSize = this.attributes[ attr ].itemSize; + var sortedAttr = sortedAttributes[ attr ]; + for ( var k = 0; k < attrSize; k ++ ) + sortedAttr[ new_vid * attrSize + k ] = attrArray[ vid * attrSize + k ]; + } + } + + /* Carry the new sorted buffers locally */ + this.attributes[ 'index' ].array = indexBuffer; + for ( var attr in this.attributes ) { + if ( attr == 'index' ) + continue; + this.attributes[ attr ].array = sortedAttributes[ attr ]; + this.attributes[ attr ].numItems = this.attributes[ attr ].itemSize * vertexCount; + } + }, + + clone: function () { + + var geometry = new THREE.BufferGeometry(); + + var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ]; + + for ( var attr in this.attributes ) { + + var sourceAttr = this.attributes[ attr ]; + var sourceArray = sourceAttr.array; + + var attribute = { + + itemSize: sourceAttr.itemSize, + array: null + + }; + + for ( var i = 0, il = types.length; i < il; i ++ ) { + + var type = types[ i ]; + + if ( sourceArray instanceof type ) { + + attribute.array = new type( sourceArray ); + break; + + } + + } + + geometry.attributes[ attr ] = attribute; + + } + + for ( var i = 0, il = this.offsets.length; i < il; i ++ ) { + + var offset = this.offsets[ i ]; + + geometry.offsets.push( { + + start: offset.start, + index: offset.index, + count: offset.count + + } ); + + } + + return geometry; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype ); + +// File:src/core/Geometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author kile / http://kile.stravaganza.org/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author bhouston / http://exocortex.com + */ + +THREE.Geometry = function () { + + this.id = THREE.GeometryIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.vertices = []; + this.colors = []; // one-to-one vertex colors, used in Points and Line + + this.faces = []; + + this.faceVertexUvs = [ [] ]; + + this.morphTargets = []; + this.morphColors = []; + this.morphNormals = []; + + this.skinWeights = []; + this.skinIndices = []; + + this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + this.hasTangents = false; + + this.dynamic = true; // the intermediate typed arrays will be deleted when set to false + + // update flags + + this.verticesNeedUpdate = false; + this.elementsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.tangentsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + + this.buffersNeedUpdate = false; + this.groupsNeedUpdate = false; + +}; + +THREE.Geometry.prototype = { + + constructor: THREE.Geometry, + + applyMatrix: function ( matrix ) { + + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { + + var vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); + + } + + for ( var i = 0, il = this.faces.length; i < il; i ++ ) { + + var face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); + + } + + } + + if ( this.boundingBox instanceof THREE.Box3 ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere instanceof THREE.Sphere ) { + + this.computeBoundingSphere(); + + } + + }, + + center: function () { + + this.computeBoundingBox(); + + var offset = new THREE.Vector3(); + + offset.addVectors( this.boundingBox.min, this.boundingBox.max ); + offset.multiplyScalar( - 0.5 ); + + this.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) ); + this.computeBoundingBox(); + + return offset; + + }, + + computeFaceNormals: function () { + + var cb = new THREE.Vector3(), ab = new THREE.Vector3(); + + for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) { + + var face = this.faces[ f ]; + + var vA = this.vertices[ face.a ]; + var vB = this.vertices[ face.b ]; + var vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + face.normal.copy( cb ); + + } + + }, + + computeVertexNormals: function ( areaWeighted ) { + + var v, vl, f, fl, face, vertices; + + vertices = new Array( this.vertices.length ); + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ] = new THREE.Vector3(); + + } + + if ( areaWeighted ) { + + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm + + var vA, vB, vC, vD; + var cb = new THREE.Vector3(), ab = new THREE.Vector3(), + db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3(); + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vA = this.vertices[ face.a ]; + vB = this.vertices[ face.b ]; + vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); + + } + + } else { + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); + + } + + } + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ].normalize(); + + } + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + face.vertexNormals[ 0 ] = vertices[ face.a ].clone(); + face.vertexNormals[ 1 ] = vertices[ face.b ].clone(); + face.vertexNormals[ 2 ] = vertices[ face.c ].clone(); + + } + + }, + + computeMorphNormals: function () { + + var i, il, f, fl, face; + + // save original normals + // - create temp variables on first access + // otherwise just copy (for faster repeated calls) + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + if ( ! face.__originalFaceNormal ) { + + face.__originalFaceNormal = face.normal.clone(); + + } else { + + face.__originalFaceNormal.copy( face.normal ); + + } + + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; + + for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) { + + if ( ! face.__originalVertexNormals[ i ] ) { + + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); + + } else { + + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); + + } + + } + + } + + // use temp geometry to compute face and vertex normals for each morph + + var tmpGeo = new THREE.Geometry(); + tmpGeo.faces = this.faces; + + for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) { + + // create on first access + + if ( ! this.morphNormals[ i ] ) { + + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; + + var dstNormalsFace = this.morphNormals[ i ].faceNormals; + var dstNormalsVertex = this.morphNormals[ i ].vertexNormals; + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + faceNormal = new THREE.Vector3(); + vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() }; + + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); + + } + + } + + var morphNormals = this.morphNormals[ i ]; + + // set vertices to morph target + + tmpGeo.vertices = this.morphTargets[ i ].vertices; + + // compute morph normals + + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); + + // store morph normals + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + faceNormal = morphNormals.faceNormals[ f ]; + vertexNormals = morphNormals.vertexNormals[ f ]; + + faceNormal.copy( face.normal ); + + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); + + } + + } + + // restore original normals + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; + + } + + }, + + computeTangents: function () { + + // based on http://www.terathon.com/code/tangent.html + // tangents go to vertices + + var f, fl, v, vl, i, il, vertexIndex, + face, uv, vA, vB, vC, uvA, uvB, uvC, + x1, x2, y1, y2, z1, z2, + s1, s2, t1, t2, r, t, test, + tan1 = [], tan2 = [], + sdir = new THREE.Vector3(), tdir = new THREE.Vector3(), + tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(), + n = new THREE.Vector3(), w; + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + tan1[ v ] = new THREE.Vector3(); + tan2[ v ] = new THREE.Vector3(); + + } + + function handleTriangle( context, a, b, c, ua, ub, uc ) { + + vA = context.vertices[ a ]; + vB = context.vertices[ b ]; + vC = context.vertices[ c ]; + + uvA = uv[ ua ]; + uvB = uv[ ub ]; + uvC = uv[ uc ]; + + x1 = vB.x - vA.x; + x2 = vC.x - vA.x; + y1 = vB.y - vA.y; + y2 = vC.y - vA.y; + z1 = vB.z - vA.z; + z2 = vC.z - vA.z; + + s1 = uvB.x - uvA.x; + s2 = uvC.x - uvA.x; + t1 = uvB.y - uvA.y; + t2 = uvC.y - uvA.y; + + r = 1.0 / ( s1 * t2 - s2 * t1 ); + sdir.set( ( t2 * x1 - t1 * x2 ) * r, + ( t2 * y1 - t1 * y2 ) * r, + ( t2 * z1 - t1 * z2 ) * r ); + tdir.set( ( s1 * x2 - s2 * x1 ) * r, + ( s1 * y2 - s2 * y1 ) * r, + ( s1 * z2 - s2 * z1 ) * r ); + + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); + + } + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + uv = this.faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents + + handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 ); + + } + + var faceIndex = [ 'a', 'b', 'c', 'd' ]; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + for ( i = 0; i < Math.min( face.vertexNormals.length, 3 ); i ++ ) { + + n.copy( face.vertexNormals[ i ] ); + + vertexIndex = face[ faceIndex[ i ] ]; + + t = tan1[ vertexIndex ]; + + // Gram-Schmidt orthogonalize + + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + + // Calculate handedness + + tmp2.crossVectors( face.vertexNormals[ i ], t ); + test = tmp2.dot( tan2[ vertexIndex ] ); + w = ( test < 0.0 ) ? - 1.0 : 1.0; + + face.vertexTangents[ i ] = new THREE.Vector4( tmp.x, tmp.y, tmp.z, w ); + + } + + } + + this.hasTangents = true; + + }, + + computeLineDistances: function () { + + var d = 0; + var vertices = this.vertices; + + for ( var i = 0, il = vertices.length; i < il; i ++ ) { + + if ( i > 0 ) { + + d += vertices[ i ].distanceTo( vertices[ i - 1 ] ); + + } + + this.lineDistances[ i ] = d; + + } + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new THREE.Box3(); + + } + + this.boundingBox.setFromPoints( this.vertices ); + + }, + + computeBoundingSphere: function () { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new THREE.Sphere(); + + } + + this.boundingSphere.setFromPoints( this.vertices ); + + }, + + merge: function ( geometry, matrix, materialIndexOffset ) { + + if ( geometry instanceof THREE.Geometry === false ) { + + console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); + return; + + } + + var normalMatrix, + vertexOffset = this.vertices.length, + uvPosition = this.faceVertexUvs[ 0 ].length, + vertices1 = this.vertices, + vertices2 = geometry.vertices, + faces1 = this.faces, + faces2 = geometry.faces, + uvs1 = this.faceVertexUvs[ 0 ], + uvs2 = geometry.faceVertexUvs[ 0 ]; + + if ( materialIndexOffset === undefined ) materialIndexOffset = 0; + + if ( matrix !== undefined ) { + + normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + } + + // vertices + + for ( var i = 0, il = vertices2.length; i < il; i ++ ) { + + var vertex = vertices2[ i ]; + + var vertexCopy = vertex.clone(); + + if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); + + vertices1.push( vertexCopy ); + + } + + // faces + + for ( i = 0, il = faces2.length; i < il; i ++ ) { + + var face = faces2[ i ], faceCopy, normal, color, + faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; + + faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); + + if ( normalMatrix !== undefined ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix !== undefined ) { + + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + faceCopy.vertexNormals.push( normal ); + + } + + faceCopy.color.copy( face.color ); + + for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { + + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); + + } + + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; + + faces1.push( faceCopy ); + + } + + // uvs + + for ( i = 0, il = uvs2.length; i < il; i ++ ) { + + var uv = uvs2[ i ], uvCopy = []; + + if ( uv === undefined ) { + + continue; + + } + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); + + } + + uvs1.push( uvCopy ); + + } + + }, + + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ + + mergeVertices: function () { + + var verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique) + var unique = [], changes = []; + + var v, key; + var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001 + var precision = Math.pow( 10, precisionPoints ); + var i,il, face; + var indices, k, j, jl, u; + + for ( i = 0, il = this.vertices.length; i < il; i ++ ) { + + v = this.vertices[ i ]; + key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); + + if ( verticesMap[ key ] === undefined ) { + + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; + + } else { + + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; + + } + + }; + + + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + var faceIndicesToRemove = []; + + for ( i = 0, il = this.faces.length; i < il; i ++ ) { + + face = this.faces[ i ]; + + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; + + indices = [ face.a, face.b, face.c ]; + + var dupIndex = - 1; + + // if any duplicate vertices are found in a Face3 + // we have to remove the face as nothing can be saved + for ( var n = 0; n < 3; n ++ ) { + if ( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) { + + dupIndex = n; + faceIndicesToRemove.push( i ); + break; + + } + } + + } + + for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { + var idx = faceIndicesToRemove[ i ]; + + this.faces.splice( idx, 1 ); + + for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { + + this.faceVertexUvs[ j ].splice( idx, 1 ); + + } + + } + + // Use unique set of vertices + + var diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; + + }, + + // Geometry splitting + + makeGroups: ( function () { + + var geometryGroupCounter = 0; + + return function ( usesFaceMaterial, maxVerticesInGroup ) { + + var f, fl, face, materialIndex, + groupHash, hash_map = {},geometryGroup; + + var numMorphTargets = this.morphTargets.length; + var numMorphNormals = this.morphNormals.length; + + this.geometryGroups = {}; + this.geometryGroupsList = []; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + materialIndex = usesFaceMaterial ? face.materialIndex : 0; + + if ( ! ( materialIndex in hash_map ) ) { + + hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 }; + + } + + groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; + + if ( ! ( groupHash in this.geometryGroups ) ) { + + geometryGroup = { 'id': geometryGroupCounter++, 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + this.geometryGroups[ groupHash ] = geometryGroup; + this.geometryGroupsList.push(geometryGroup); + } + + if ( this.geometryGroups[ groupHash ].vertices + 3 > maxVerticesInGroup ) { + + hash_map[ materialIndex ].counter += 1; + groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; + + if ( ! ( groupHash in this.geometryGroups ) ) { + + geometryGroup = { 'id': geometryGroupCounter++, 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + this.geometryGroups[ groupHash ] = geometryGroup; + this.geometryGroupsList.push(geometryGroup); + + } + + } + + this.geometryGroups[ groupHash ].faces3.push( f ); + this.geometryGroups[ groupHash ].vertices += 3; + + } + + }; + + } )(), + + clone: function () { + + var geometry = new THREE.Geometry(); + + var vertices = this.vertices; + + for ( var i = 0, il = vertices.length; i < il; i ++ ) { + + geometry.vertices.push( vertices[ i ].clone() ); + + } + + var faces = this.faces; + + for ( var i = 0, il = faces.length; i < il; i ++ ) { + + geometry.faces.push( faces[ i ].clone() ); + + } + + var uvs = this.faceVertexUvs[ 0 ]; + + for ( var i = 0, il = uvs.length; i < il; i ++ ) { + + var uv = uvs[ i ], uvCopy = []; + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); + + } + + geometry.faceVertexUvs[ 0 ].push( uvCopy ); + + } + + return geometry; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Geometry.prototype ); + +THREE.GeometryIdCount = 0; + +// File:src/cameras/Camera.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.Camera = function () { + + THREE.Object3D.call( this ); + + this.matrixWorldInverse = new THREE.Matrix4(); + this.projectionMatrix = new THREE.Matrix4(); + +}; + +THREE.Camera.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Camera.prototype.lookAt = function () { + + // This routine does not support cameras with rotated and/or translated parent(s) + + var m1 = new THREE.Matrix4(); + + return function ( vector ) { + + m1.lookAt( this.position, vector, this.up ); + + this.quaternion.setFromRotationMatrix( m1 ); + + }; + +}(); + +THREE.Camera.prototype.clone = function ( camera ) { + + if ( camera === undefined ) camera = new THREE.Camera(); + + THREE.Object3D.prototype.clone.call( this, camera ); + + camera.matrixWorldInverse.copy( this.matrixWorldInverse ); + camera.projectionMatrix.copy( this.projectionMatrix ); + + return camera; +}; + +// File:src/cameras/CubeCamera.js + +/** + * Camera for rendering cube maps + * - renders scene into axis-aligned cube + * + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.CubeCamera = function ( near, far, cubeResolution ) { + + THREE.Object3D.call( this ); + + var fov = 90, aspect = 1; + + var cameraPX = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPX.up.set( 0, - 1, 0 ); + cameraPX.lookAt( new THREE.Vector3( 1, 0, 0 ) ); + this.add( cameraPX ); + + var cameraNX = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNX.up.set( 0, - 1, 0 ); + cameraNX.lookAt( new THREE.Vector3( - 1, 0, 0 ) ); + this.add( cameraNX ); + + var cameraPY = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( new THREE.Vector3( 0, 1, 0 ) ); + this.add( cameraPY ); + + var cameraNY = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNY.up.set( 0, 0, - 1 ); + cameraNY.lookAt( new THREE.Vector3( 0, - 1, 0 ) ); + this.add( cameraNY ); + + var cameraPZ = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.up.set( 0, - 1, 0 ); + cameraPZ.lookAt( new THREE.Vector3( 0, 0, 1 ) ); + this.add( cameraPZ ); + + var cameraNZ = new THREE.PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.up.set( 0, - 1, 0 ); + cameraNZ.lookAt( new THREE.Vector3( 0, 0, - 1 ) ); + this.add( cameraNZ ); + + this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } ); + + this.updateCubeMap = function ( renderer, scene ) { + + var renderTarget = this.renderTarget; + var generateMipmaps = renderTarget.generateMipmaps; + + renderTarget.generateMipmaps = false; + + renderTarget.activeCubeFace = 0; + renderer.render( scene, cameraPX, renderTarget ); + + renderTarget.activeCubeFace = 1; + renderer.render( scene, cameraNX, renderTarget ); + + renderTarget.activeCubeFace = 2; + renderer.render( scene, cameraPY, renderTarget ); + + renderTarget.activeCubeFace = 3; + renderer.render( scene, cameraNY, renderTarget ); + + renderTarget.activeCubeFace = 4; + renderer.render( scene, cameraPZ, renderTarget ); + + renderTarget.generateMipmaps = generateMipmaps; + + renderTarget.activeCubeFace = 5; + renderer.render( scene, cameraNZ, renderTarget ); + + }; + +}; + +THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype ); + +// File:src/cameras/OrthographicCamera.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) { + + THREE.Camera.call( this ); + + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; + + this.near = ( near !== undefined ) ? near : 0.1; + this.far = ( far !== undefined ) ? far : 2000; + + this.updateProjectionMatrix(); + +}; + +THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype ); + +THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () { + + this.projectionMatrix.makeOrthographic( this.left, this.right, this.top, this.bottom, this.near, this.far ); + +}; + +THREE.OrthographicCamera.prototype.clone = function () { + + var camera = new THREE.OrthographicCamera(); + + THREE.Camera.prototype.clone.call( this, camera ); + + camera.left = this.left; + camera.right = this.right; + camera.top = this.top; + camera.bottom = this.bottom; + + camera.near = this.near; + camera.far = this.far; + + return camera; +}; + +// File:src/cameras/PerspectiveCamera.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author greggman / http://games.greggman.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + +THREE.PerspectiveCamera = function ( fov, aspect, near, far ) { + + THREE.Camera.call( this ); + + this.fov = fov !== undefined ? fov : 50; + this.aspect = aspect !== undefined ? aspect : 1; + this.near = near !== undefined ? near : 0.1; + this.far = far !== undefined ? far : 2000; + + this.updateProjectionMatrix(); + +}; + +THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype ); + + +/** + * Uses Focal Length (in mm) to estimate and set FOV + * 35mm (fullframe) camera is used if frame size is not specified; + * Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html + */ + +THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight ) { + + if ( frameHeight === undefined ) frameHeight = 24; + + this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) ); + this.updateProjectionMatrix(); + +} + + +/** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + * + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + * + * then for each monitor you would call it like this + * + * var w = 1920; + * var h = 1080; + * var fullWidth = w * 3; + * var fullHeight = h * 2; + * + * --A-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * --B-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * --C-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * --D-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * --E-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * --F-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * + * Note there is no reason monitors have to be the same size or in a grid. + */ + +THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) { + + this.fullWidth = fullWidth; + this.fullHeight = fullHeight; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + + this.updateProjectionMatrix(); + +}; + + +THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () { + + if ( this.fullWidth ) { + + var aspect = this.fullWidth / this.fullHeight; + var top = Math.tan( THREE.Math.degToRad( this.fov * 0.5 ) ) * this.near; + var bottom = - top; + var left = aspect * bottom; + var right = aspect * top; + var width = Math.abs( right - left ); + var height = Math.abs( top - bottom ); + + this.projectionMatrix.makeFrustum( + left + this.x * width / this.fullWidth, + left + ( this.x + this.width ) * width / this.fullWidth, + top - ( this.y + this.height ) * height / this.fullHeight, + top - this.y * height / this.fullHeight, + this.near, + this.far + ); + + } else { + + this.projectionMatrix.makePerspective( this.fov, this.aspect, this.near, this.far ); + + } + +}; + +THREE.PerspectiveCamera.prototype.clone = function () { + + var camera = new THREE.PerspectiveCamera(); + + THREE.Camera.prototype.clone.call( this, camera ); + + camera.fov = this.fov; + camera.aspect = this.aspect; + camera.near = this.near; + camera.far = this.far; + + return camera; +}; + +// File:src/lights/Light.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Light = function ( color ) { + + THREE.Object3D.call( this ); + + this.color = new THREE.Color( color ); + +}; + +THREE.Light.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Light.prototype.clone = function ( light ) { + + if ( light === undefined ) light = new THREE.Light(); + + THREE.Object3D.prototype.clone.call( this, light ); + + light.color.copy( this.color ); + + return light; + +}; + +// File:src/lights/AmbientLight.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.AmbientLight = function ( color ) { + + THREE.Light.call( this, color ); + +}; + +THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.AmbientLight.prototype.clone = function () { + + var light = new THREE.AmbientLight(); + + THREE.Light.prototype.clone.call( this, light ); + + return light; + +}; + +// File:src/lights/AreaLight.js + +/** + * @author MPanknin / http://www.redplant.de/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.AreaLight = function ( color, intensity ) { + + THREE.Light.call( this, color ); + + this.normal = new THREE.Vector3( 0, - 1, 0 ); + this.right = new THREE.Vector3( 1, 0, 0 ); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + + this.width = 1.0; + this.height = 1.0; + + this.constantAttenuation = 1.5; + this.linearAttenuation = 0.5; + this.quadraticAttenuation = 0.1; + +}; + +THREE.AreaLight.prototype = Object.create( THREE.Light.prototype ); + + +// File:src/lights/DirectionalLight.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DirectionalLight = function ( color, intensity ) { + + THREE.Light.call( this, color ); + + this.position.set( 0, 1, 0 ); + this.target = new THREE.Object3D(); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + + this.castShadow = false; + this.onlyShadow = false; + + // + + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; + + this.shadowCameraLeft = - 500; + this.shadowCameraRight = 500; + this.shadowCameraTop = 500; + this.shadowCameraBottom = - 500; + + this.shadowCameraVisible = false; + + this.shadowBias = 0; + this.shadowDarkness = 0.5; + + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; + + // + + this.shadowCascade = false; + + this.shadowCascadeOffset = new THREE.Vector3( 0, 0, - 1000 ); + this.shadowCascadeCount = 2; + + this.shadowCascadeBias = [ 0, 0, 0 ]; + this.shadowCascadeWidth = [ 512, 512, 512 ]; + this.shadowCascadeHeight = [ 512, 512, 512 ]; + + this.shadowCascadeNearZ = [ - 1.000, 0.990, 0.998 ]; + this.shadowCascadeFarZ = [ 0.990, 0.998, 1.000 ]; + + this.shadowCascadeArray = []; + + // + + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; + +}; + +THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.DirectionalLight.prototype.clone = function () { + + var light = new THREE.DirectionalLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.target = this.target.clone(); + + light.intensity = this.intensity; + + light.castShadow = this.castShadow; + light.onlyShadow = this.onlyShadow; + + // + + light.shadowCameraNear = this.shadowCameraNear; + light.shadowCameraFar = this.shadowCameraFar; + + light.shadowCameraLeft = this.shadowCameraLeft; + light.shadowCameraRight = this.shadowCameraRight; + light.shadowCameraTop = this.shadowCameraTop; + light.shadowCameraBottom = this.shadowCameraBottom; + + light.shadowCameraVisible = this.shadowCameraVisible; + + light.shadowBias = this.shadowBias; + light.shadowDarkness = this.shadowDarkness; + + light.shadowMapWidth = this.shadowMapWidth; + light.shadowMapHeight = this.shadowMapHeight; + + // + + light.shadowCascade = this.shadowCascade; + + light.shadowCascadeOffset.copy( this.shadowCascadeOffset ); + light.shadowCascadeCount = this.shadowCascadeCount; + + light.shadowCascadeBias = this.shadowCascadeBias.slice( 0 ); + light.shadowCascadeWidth = this.shadowCascadeWidth.slice( 0 ); + light.shadowCascadeHeight = this.shadowCascadeHeight.slice( 0 ); + + light.shadowCascadeNearZ = this.shadowCascadeNearZ.slice( 0 ); + light.shadowCascadeFarZ = this.shadowCascadeFarZ.slice( 0 ); + + return light; + +}; + +// File:src/lights/HemisphereLight.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.HemisphereLight = function ( skyColor, groundColor, intensity ) { + + THREE.Light.call( this, skyColor ); + + this.position.set( 0, 100, 0 ); + + this.groundColor = new THREE.Color( groundColor ); + this.intensity = ( intensity !== undefined ) ? intensity : 1; + +}; + +THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.HemisphereLight.prototype.clone = function () { + + var light = new THREE.HemisphereLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.groundColor.copy( this.groundColor ); + light.intensity = this.intensity; + + return light; + +}; + +// File:src/lights/PointLight.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.PointLight = function ( color, intensity, distance ) { + + THREE.Light.call( this, color ); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.distance = ( distance !== undefined ) ? distance : 0; + +}; + +THREE.PointLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.PointLight.prototype.clone = function () { + + var light = new THREE.PointLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.intensity = this.intensity; + light.distance = this.distance; + + return light; + +}; + +// File:src/lights/SpotLight.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SpotLight = function ( color, intensity, distance, angle, exponent ) { + + THREE.Light.call( this, color ); + + this.position.set( 0, 1, 0 ); + this.target = new THREE.Object3D(); + + this.intensity = ( intensity !== undefined ) ? intensity : 1; + this.distance = ( distance !== undefined ) ? distance : 0; + this.angle = ( angle !== undefined ) ? angle : Math.PI / 3; + this.exponent = ( exponent !== undefined ) ? exponent : 10; + + this.castShadow = false; + this.onlyShadow = false; + + // + + this.shadowCameraNear = 50; + this.shadowCameraFar = 5000; + this.shadowCameraFov = 50; + + this.shadowCameraVisible = false; + + this.shadowBias = 0; + this.shadowDarkness = 0.5; + + this.shadowMapWidth = 512; + this.shadowMapHeight = 512; + + // + + this.shadowMap = null; + this.shadowMapSize = null; + this.shadowCamera = null; + this.shadowMatrix = null; + +}; + +THREE.SpotLight.prototype = Object.create( THREE.Light.prototype ); + +THREE.SpotLight.prototype.clone = function () { + + var light = new THREE.SpotLight(); + + THREE.Light.prototype.clone.call( this, light ); + + light.target = this.target.clone(); + + light.intensity = this.intensity; + light.distance = this.distance; + light.angle = this.angle; + light.exponent = this.exponent; + + light.castShadow = this.castShadow; + light.onlyShadow = this.onlyShadow; + + // + + light.shadowCameraNear = this.shadowCameraNear; + light.shadowCameraFar = this.shadowCameraFar; + light.shadowCameraFov = this.shadowCameraFov; + + light.shadowCameraVisible = this.shadowCameraVisible; + + light.shadowBias = this.shadowBias; + light.shadowDarkness = this.shadowDarkness; + + light.shadowMapWidth = this.shadowMapWidth; + light.shadowMapHeight = this.shadowMapHeight; + + return light; + +}; + +// File:src/loaders/Cache.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Cache = function () { + + this.files = {}; + +}; + +THREE.Cache.prototype = { + + constructor: THREE.Cache, + + add: function ( key, file ) { + + // console.log( 'THREE.Cache', 'Adding key:', key ); + + this.files[ key ] = file; + + }, + + get: function ( key ) { + + // console.log( 'THREE.Cache', 'Checking key:', key ); + + return this.files[ key ]; + + }, + + remove: function ( key ) { + + delete this.files[ key ]; + + }, + + clear: function () { + + this.files = {} + + } + +}; + +// File:src/loaders/Loader.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Loader = function ( showStatus ) { + + this.showStatus = showStatus; + this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null; + + this.imageLoader = new THREE.ImageLoader(); + + this.onLoadStart = function () {}; + this.onLoadProgress = function () {}; + this.onLoadComplete = function () {}; + +}; + +THREE.Loader.prototype = { + + constructor: THREE.Loader, + + crossOrigin: undefined, + + addStatusElement: function () { + + var e = document.createElement( 'div' ); + + e.style.position = 'absolute'; + e.style.right = '0px'; + e.style.top = '0px'; + e.style.fontSize = '0.8em'; + e.style.textAlign = 'left'; + e.style.background = 'rgba(0,0,0,0.25)'; + e.style.color = '#fff'; + e.style.width = '120px'; + e.style.padding = '0.5em 0.5em 0.5em 0.5em'; + e.style.zIndex = 1000; + + e.innerHTML = 'Loading ...'; + + return e; + + }, + + updateProgress: function ( progress ) { + + var message = 'Loaded '; + + if ( progress.total ) { + + message += ( 100 * progress.loaded / progress.total ).toFixed( 0 ) + '%'; + + + } else { + + message += ( progress.loaded / 1024 ).toFixed( 2 ) + ' KB'; + + } + + this.statusDomElement.innerHTML = message; + + }, + + extractUrlBase: function ( url ) { + + var parts = url.split( '/' ); + + if ( parts.length === 1 ) return './'; + + parts.pop(); + + return parts.join( '/' ) + '/'; + + }, + + initMaterials: function ( materials, texturePath ) { + + var array = []; + + for ( var i = 0; i < materials.length; ++ i ) { + + array[ i ] = this.createMaterial( materials[ i ], texturePath ); + + } + + return array; + + }, + + needsTangents: function ( materials ) { + + for ( var i = 0, il = materials.length; i < il; i ++ ) { + + var m = materials[ i ]; + + if ( m instanceof THREE.ShaderMaterial ) return true; + + } + + return false; + + }, + + createMaterial: function ( m, texturePath ) { + + var scope = this; + + function nearest_pow2( n ) { + + var l = Math.log( n ) / Math.LN2; + return Math.pow( 2, Math.round( l ) ); + + } + + function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) { + + var fullPath = texturePath + sourceFile; + + var texture; + + var loader = THREE.Loader.Handlers.get( fullPath ); + + if ( loader !== null ) { + + texture = loader.load( fullPath ); + + } else { + + texture = new THREE.Texture(); + + loader = scope.imageLoader; + loader.crossOrigin = scope.crossOrigin; + loader.load( fullPath, function ( image ) { + + if ( THREE.Math.isPowerOfTwo( image.width ) === false || + THREE.Math.isPowerOfTwo( image.height ) === false ) { + + var width = nearest_pow2( image.width ); + var height = nearest_pow2( image.height ); + + var canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, width, height ); + + texture.image = canvas; + + } else { + + texture.image = image; + + } + + texture.needsUpdate = true; + + } ); + + } + + texture.sourceFile = sourceFile; + + if ( repeat ) { + + texture.repeat.set( repeat[ 0 ], repeat[ 1 ] ); + + if ( repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping; + if ( repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping; + + } + + if ( offset ) { + + texture.offset.set( offset[ 0 ], offset[ 1 ] ); + + } + + if ( wrap ) { + + var wrapMap = { + 'repeat': THREE.RepeatWrapping, + 'mirror': THREE.MirroredRepeatWrapping + } + + if ( wrapMap[ wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ wrap[ 0 ] ]; + if ( wrapMap[ wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ wrap[ 1 ] ]; + + } + + if ( anisotropy ) { + + texture.anisotropy = anisotropy; + + } + + where[ name ] = texture; + + } + + function rgb2hex( rgb ) { + + return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255; + + } + + // defaults + + var mtype = 'MeshLambertMaterial'; + var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false }; + + // parameters from model file + + if ( m.shading ) { + + var shading = m.shading.toLowerCase(); + + if ( shading === 'phong' ) mtype = 'MeshPhongMaterial'; + else if ( shading === 'basic' ) mtype = 'MeshBasicMaterial'; + + } + + if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) { + + mpars.blending = THREE[ m.blending ]; + + } + + if ( m.transparent !== undefined || m.opacity < 1.0 ) { + + mpars.transparent = m.transparent; + + } + + if ( m.depthTest !== undefined ) { + + mpars.depthTest = m.depthTest; + + } + + if ( m.depthWrite !== undefined ) { + + mpars.depthWrite = m.depthWrite; + + } + + if ( m.visible !== undefined ) { + + mpars.visible = m.visible; + + } + + if ( m.flipSided !== undefined ) { + + mpars.side = THREE.BackSide; + + } + + if ( m.doubleSided !== undefined ) { + + mpars.side = THREE.DoubleSide; + + } + + if ( m.wireframe !== undefined ) { + + mpars.wireframe = m.wireframe; + + } + + if ( m.vertexColors !== undefined ) { + + if ( m.vertexColors === 'face' ) { + + mpars.vertexColors = THREE.FaceColors; + + } else if ( m.vertexColors ) { + + mpars.vertexColors = THREE.VertexColors; + + } + + } + + // colors + + if ( m.colorDiffuse ) { + + mpars.color = rgb2hex( m.colorDiffuse ); + + } else if ( m.DbgColor ) { + + mpars.color = m.DbgColor; + + } + + if ( m.colorSpecular ) { + + mpars.specular = rgb2hex( m.colorSpecular ); + + } + + if ( m.colorAmbient ) { + + mpars.ambient = rgb2hex( m.colorAmbient ); + + } + + if ( m.colorEmissive ) { + + mpars.emissive = rgb2hex( m.colorEmissive ); + + } + + // modifiers + + if ( m.transparency ) { + + mpars.opacity = m.transparency; + + } + + if ( m.specularCoef ) { + + mpars.shininess = m.specularCoef; + + } + + // textures + + if ( m.mapDiffuse && texturePath ) { + + create_texture( mpars, 'map', m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy ); + + } + + if ( m.mapLight && texturePath ) { + + create_texture( mpars, 'lightMap', m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy ); + + } + + if ( m.mapBump && texturePath ) { + + create_texture( mpars, 'bumpMap', m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy ); + + } + + if ( m.mapNormal && texturePath ) { + + create_texture( mpars, 'normalMap', m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy ); + + } + + if ( m.mapSpecular && texturePath ) { + + create_texture( mpars, 'specularMap', m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy ); + + } + + if ( m.mapAlpha && texturePath ) { + + create_texture( mpars, 'alphaMap', m.mapAlpha, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy ); + + } + + // + + if ( m.mapBumpScale ) { + + mpars.bumpScale = m.mapBumpScale; + + } + + // special case for normal mapped material + + if ( m.mapNormal ) { + + var shader = THREE.ShaderLib[ 'normalmap' ]; + var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + uniforms[ 'tNormal' ].value = mpars.normalMap; + + if ( m.mapNormalFactor ) { + + uniforms[ 'uNormalScale' ].value.set( m.mapNormalFactor, m.mapNormalFactor ); + + } + + if ( mpars.map ) { + + uniforms[ 'tDiffuse' ].value = mpars.map; + uniforms[ 'enableDiffuse' ].value = true; + + } + + if ( mpars.specularMap ) { + + uniforms[ 'tSpecular' ].value = mpars.specularMap; + uniforms[ 'enableSpecular' ].value = true; + + } + + if ( mpars.lightMap ) { + + uniforms[ 'tAO' ].value = mpars.lightMap; + uniforms[ 'enableAO' ].value = true; + + } + + // for the moment don't handle displacement texture + + uniforms[ 'diffuse' ].value.setHex( mpars.color ); + uniforms[ 'specular' ].value.setHex( mpars.specular ); + uniforms[ 'ambient' ].value.setHex( mpars.ambient ); + + uniforms[ 'shininess' ].value = mpars.shininess; + + if ( mpars.opacity !== undefined ) { + + uniforms[ 'opacity' ].value = mpars.opacity; + + } + + var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true }; + var material = new THREE.ShaderMaterial( parameters ); + + if ( mpars.transparent ) { + + material.transparent = true; + + } + + } else { + + var material = new THREE[ mtype ]( mpars ); + + } + + if ( m.DbgName !== undefined ) material.name = m.DbgName; + + return material; + + } + +}; + +THREE.Loader.Handlers = { + + handlers: [], + + add: function ( regex, loader ) { + + this.handlers.push( regex, loader ); + + }, + + get: function ( file ) { + + for ( var i = 0, l = this.handlers.length; i < l; i += 2 ) { + + var regex = this.handlers[ i ]; + var loader = this.handlers[ i + 1 ]; + + if ( regex.test( file ) ) { + + return loader; + + } + + } + + return null; + + } + +}; + +// File:src/loaders/XHRLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.XHRLoader = function ( manager ) { + + this.cache = new THREE.Cache(); + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.XHRLoader.prototype = { + + constructor: THREE.XHRLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var cached = scope.cache.get( url ); + + if ( cached !== undefined ) { + + if ( onLoad ) onLoad( cached ); + return; + + } + + var request = new XMLHttpRequest(); + request.open( 'GET', url, true ); + + request.addEventListener( 'load', function ( event ) { + + scope.cache.add( url, this.response ); + + if ( onLoad ) onLoad( this.response ); + + scope.manager.itemEnd( url ); + + }, false ); + + if ( onProgress !== undefined ) { + + request.addEventListener( 'progress', function ( event ) { + + onProgress( event ); + + }, false ); + + } + + if ( onError !== undefined ) { + + request.addEventListener( 'error', function ( event ) { + + onError( event ); + + }, false ); + + } + + if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin; + if ( this.responseType !== undefined ) request.responseType = this.responseType; + + request.send( null ); + + scope.manager.itemStart( url ); + + }, + + setResponseType: function ( value ) { + + this.responseType = value; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +// File:src/loaders/ImageLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ImageLoader = function ( manager ) { + + this.cache = new THREE.Cache(); + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ImageLoader.prototype = { + + constructor: THREE.ImageLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var cached = scope.cache.get( url ); + + if ( cached !== undefined ) { + + onLoad( cached ); + return; + + } + + var image = document.createElement( 'img' ); + + if ( onLoad !== undefined ) { + + image.addEventListener( 'load', function ( event ) { + + scope.cache.add( url, this ); + + onLoad( this ); + scope.manager.itemEnd( url ); + + }, false ); + + } + + if ( onProgress !== undefined ) { + + image.addEventListener( 'progress', function ( event ) { + + onProgress( event ); + + }, false ); + + } + + if ( onError !== undefined ) { + + image.addEventListener( 'error', function ( event ) { + + onError( event ); + + }, false ); + + } + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + image.src = url; + + scope.manager.itemStart( url ); + + return image; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +} + +// File:src/loaders/JSONLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.JSONLoader = function ( showStatus ) { + + THREE.Loader.call( this, showStatus ); + + this.withCredentials = false; + +}; + +THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype ); + +THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) { + + var scope = this; + + // todo: unify load API to for easier SceneLoader use + + texturePath = texturePath && ( typeof texturePath === 'string' ) ? texturePath : this.extractUrlBase( url ); + + this.onLoadStart(); + this.loadAjaxJSON( this, url, callback, texturePath ); + +}; + +THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) { + + var xhr = new XMLHttpRequest(); + + var length = 0; + + xhr.onreadystatechange = function () { + + if ( xhr.readyState === xhr.DONE ) { + + if ( xhr.status === 200 || xhr.status === 0 ) { + + if ( xhr.responseText ) { + + var json = JSON.parse( xhr.responseText ); + + if ( json.metadata !== undefined && json.metadata.type === 'scene' ) { + + console.error( 'THREE.JSONLoader: "' + url + '" seems to be a Scene. Use THREE.SceneLoader instead.' ); + return; + + } + + var result = context.parse( json, texturePath ); + callback( result.geometry, result.materials ); + + } else { + + console.error( 'THREE.JSONLoader: "' + url + '" seems to be unreachable or the file is empty.' ); + + } + + // in context of more complex asset initialization + // do not block on single failed file + // maybe should go even one more level up + + context.onLoadComplete(); + + } else { + + console.error( 'THREE.JSONLoader: Couldn\'t load "' + url + '" (' + xhr.status + ')' ); + + } + + } else if ( xhr.readyState === xhr.LOADING ) { + + if ( callbackProgress ) { + + if ( length === 0 ) { + + length = xhr.getResponseHeader( 'Content-Length' ); + + } + + callbackProgress( { total: length, loaded: xhr.responseText.length } ); + + } + + } else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) { + + if ( callbackProgress !== undefined ) { + + length = xhr.getResponseHeader( 'Content-Length' ); + + } + + } + + }; + + xhr.open( 'GET', url, true ); + xhr.withCredentials = this.withCredentials; + xhr.send( null ); + +}; + +THREE.JSONLoader.prototype.parse = function ( json, texturePath ) { + + var scope = this, + geometry = new THREE.Geometry(), + scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0; + + parseModel( scale ); + + parseSkin(); + parseMorphing( scale ); + + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + function parseModel( scale ) { + + function isBitSet( value, position ) { + + return value & ( 1 << position ); + + } + + var i, j, fi, + + offset, zLength, + + colorIndex, normalIndex, uvIndex, materialIndex, + + type, + isQuad, + hasMaterial, + hasFaceVertexUv, + hasFaceNormal, hasFaceVertexNormal, + hasFaceColor, hasFaceVertexColor, + + vertex, face, faceA, faceB, color, hex, normal, + + uvLayer, uv, u, v, + + faces = json.faces, + vertices = json.vertices, + normals = json.normals, + colors = json.colors, + + nUvLayers = 0; + + if ( json.uvs !== undefined ) { + + // disregard empty arrays + + for ( i = 0; i < json.uvs.length; i ++ ) { + + if ( json.uvs[ i ].length ) nUvLayers ++; + + } + + for ( i = 0; i < nUvLayers; i ++ ) { + + geometry.faceVertexUvs[ i ] = []; + + } + + } + + offset = 0; + zLength = vertices.length; + + while ( offset < zLength ) { + + vertex = new THREE.Vector3(); + + vertex.x = vertices[ offset ++ ] * scale; + vertex.y = vertices[ offset ++ ] * scale; + vertex.z = vertices[ offset ++ ] * scale; + + geometry.vertices.push( vertex ); + + } + + offset = 0; + zLength = faces.length; + + while ( offset < zLength ) { + + type = faces[ offset ++ ]; + + + isQuad = isBitSet( type, 0 ); + hasMaterial = isBitSet( type, 1 ); + hasFaceVertexUv = isBitSet( type, 3 ); + hasFaceNormal = isBitSet( type, 4 ); + hasFaceVertexNormal = isBitSet( type, 5 ); + hasFaceColor = isBitSet( type, 6 ); + hasFaceVertexColor = isBitSet( type, 7 ); + + // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor); + + if ( isQuad ) { + + faceA = new THREE.Face3(); + faceA.a = faces[ offset ]; + faceA.b = faces[ offset + 1 ]; + faceA.c = faces[ offset + 3 ]; + + faceB = new THREE.Face3(); + faceB.a = faces[ offset + 1 ]; + faceB.b = faces[ offset + 2 ]; + faceB.c = faces[ offset + 3 ]; + + offset += 4; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + faceA.materialIndex = materialIndex; + faceB.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i ++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + geometry.faceVertexUvs[ i ][ fi + 1 ] = [] + + for ( j = 0; j < 4; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new THREE.Vector2( u, v ); + + if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv ); + if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + faceA.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + faceB.normal.copy( faceA.normal ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 4; i ++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new THREE.Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + + if ( i !== 2 ) faceA.vertexNormals.push( normal ); + if ( i !== 0 ) faceB.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + faceA.color.setHex( hex ); + faceB.color.setHex( hex ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 4; i ++ ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) ); + if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) ); + + } + + } + + geometry.faces.push( faceA ); + geometry.faces.push( faceB ); + + } else { + + face = new THREE.Face3(); + face.a = faces[ offset ++ ]; + face.b = faces[ offset ++ ]; + face.c = faces[ offset ++ ]; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + face.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i ++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + + for ( j = 0; j < 3; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new THREE.Vector2( u, v ); + + geometry.faceVertexUvs[ i ][ fi ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + face.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 3; i ++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new THREE.Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + face.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + face.color.setHex( colors[ colorIndex ] ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 3; i ++ ) { + + colorIndex = faces[ offset ++ ]; + face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) ); + + } + + } + + geometry.faces.push( face ); + + } + + } + + }; + + function parseSkin() { + var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2; + + if ( json.skinWeights ) { + + for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) { + + var x = json.skinWeights[ i ]; + var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0; + var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0; + var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0; + + geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) ); + + } + + } + + if ( json.skinIndices ) { + + for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) { + + var a = json.skinIndices[ i ]; + var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0; + var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0; + var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0; + + geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) ); + + } + + } + + geometry.bones = json.bones; + + if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) { + + console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' + + geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' ); + + } + + + // could change this to json.animations[0] or remove completely + + geometry.animation = json.animation; + geometry.animations = json.animations; + + }; + + function parseMorphing( scale ) { + + if ( json.morphTargets !== undefined ) { + + var i, l, v, vl, dstVertices, srcVertices; + + for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) { + + geometry.morphTargets[ i ] = {}; + geometry.morphTargets[ i ].name = json.morphTargets[ i ].name; + geometry.morphTargets[ i ].vertices = []; + + dstVertices = geometry.morphTargets[ i ].vertices; + srcVertices = json.morphTargets [ i ].vertices; + + for ( v = 0, vl = srcVertices.length; v < vl; v += 3 ) { + + var vertex = new THREE.Vector3(); + vertex.x = srcVertices[ v ] * scale; + vertex.y = srcVertices[ v + 1 ] * scale; + vertex.z = srcVertices[ v + 2 ] * scale; + + dstVertices.push( vertex ); + + } + + } + + } + + if ( json.morphColors !== undefined ) { + + var i, l, c, cl, dstColors, srcColors, color; + + for ( i = 0, l = json.morphColors.length; i < l; i ++ ) { + + geometry.morphColors[ i ] = {}; + geometry.morphColors[ i ].name = json.morphColors[ i ].name; + geometry.morphColors[ i ].colors = []; + + dstColors = geometry.morphColors[ i ].colors; + srcColors = json.morphColors [ i ].colors; + + for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) { + + color = new THREE.Color( 0xffaa00 ); + color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] ); + dstColors.push( color ); + + } + + } + + } + + }; + + if ( json.materials === undefined || json.materials.length === 0 ) { + + return { geometry: geometry }; + + } else { + + var materials = this.initMaterials( json.materials, texturePath ); + + if ( this.needsTangents( materials ) ) { + + geometry.computeTangents(); + + } + + return { geometry: geometry, materials: materials }; + + } + +}; + +// File:src/loaders/LoadingManager.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.LoadingManager = function ( onLoad, onProgress, onError ) { + + var scope = this; + + var loaded = 0, total = 0; + + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + total ++; + + }; + + this.itemEnd = function ( url ) { + + loaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, loaded, total ); + + } + + if ( loaded === total && scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + }; + +}; + +THREE.DefaultLoadingManager = new THREE.LoadingManager(); + +// File:src/loaders/BufferGeometryLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BufferGeometryLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.BufferGeometryLoader.prototype = { + + constructor: THREE.BufferGeometryLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.BufferGeometry(); + + var attributes = json.attributes; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + + geometry.attributes[ key ] = { + itemSize: attribute.itemSize, + array: new self[ attribute.type ]( attribute.array ) + } + + } + + var offsets = json.offsets; + + if ( offsets !== undefined ) { + + geometry.offsets = JSON.parse( JSON.stringify( offsets ) ); + + } + + var boundingSphere = json.boundingSphere; + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new THREE.Sphere( + new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ), + boundingSphere.radius + ); + + } + + return geometry; + + } + +}; + +// File:src/loaders/MaterialLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MaterialLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.MaterialLoader.prototype = { + + constructor: THREE.MaterialLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader(); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var material = new THREE[ json.type ]; + + if ( json.color !== undefined ) material.color.setHex( json.color ); + if ( json.ambient !== undefined ) material.ambient.setHex( json.ambient ); + if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive ); + if ( json.specular !== undefined ) material.specular.setHex( json.specular ); + if ( json.shininess !== undefined ) material.shininess = json.shininess; + if ( json.uniforms !== undefined ) material.uniforms = json.uniforms; + if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; + if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; + if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; + if ( json.blending !== undefined ) material.blending = json.blending; + if ( json.side !== undefined ) material.side = json.side; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + + if ( json.materials !== undefined ) { + + for ( var i = 0, l = json.materials.length; i < l; i ++ ) { + + material.materials.push( this.parse( json.materials[ i ] ) ); + + } + + } + + return material; + + } + +}; + +// File:src/loaders/ObjectLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ObjectLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ObjectLoader.prototype = { + + constructor: THREE.ObjectLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.XHRLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometries = this.parseGeometries( json.geometries ); + var materials = this.parseMaterials( json.materials ); + var object = this.parseObject( json.object, geometries, materials ); + + return object; + + }, + + parseGeometries: function ( json ) { + + var geometries = {}; + + if ( json !== undefined ) { + + var geometryLoader = new THREE.JSONLoader(); + var bufferGeometryLoader = new THREE.BufferGeometryLoader(); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var geometry; + var data = json[ i ]; + + switch ( data.type ) { + + case 'PlaneGeometry': + + geometry = new THREE.PlaneGeometry( + data.width, + data.height, + data.widthSegments, + data.heightSegments + ); + + break; + + case 'BoxGeometry': + case 'CubeGeometry': // backwards compatible + + geometry = new THREE.BoxGeometry( + data.width, + data.height, + data.depth, + data.widthSegments, + data.heightSegments, + data.depthSegments + ); + + break; + + case 'CircleGeometry': + + geometry = new THREE.CircleGeometry( + data.radius, + data.segments + ); + + break; + + case 'CylinderGeometry': + + geometry = new THREE.CylinderGeometry( + data.radiusTop, + data.radiusBottom, + data.height, + data.radialSegments, + data.heightSegments, + data.openEnded + ); + + break; + + case 'SphereGeometry': + + geometry = new THREE.SphereGeometry( + data.radius, + data.widthSegments, + data.heightSegments, + data.phiStart, + data.phiLength, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'IcosahedronGeometry': + + geometry = new THREE.IcosahedronGeometry( + data.radius, + data.detail + ); + + break; + + case 'TorusGeometry': + + geometry = new THREE.TorusGeometry( + data.radius, + data.tube, + data.radialSegments, + data.tubularSegments, + data.arc + ); + + break; + + case 'TorusKnotGeometry': + + geometry = new THREE.TorusKnotGeometry( + data.radius, + data.tube, + data.radialSegments, + data.tubularSegments, + data.p, + data.q, + data.heightScale + ); + + break; + + case 'BufferGeometry': + + geometry = bufferGeometryLoader.parse( data.data ); + + break; + + case 'Geometry': + + geometry = geometryLoader.parse( data.data ).geometry; + + break; + + } + + geometry.uuid = data.uuid; + + if ( data.name !== undefined ) geometry.name = data.name; + + geometries[ data.uuid ] = geometry; + + } + + } + + return geometries; + + }, + + parseMaterials: function ( json ) { + + var materials = {}; + + if ( json !== undefined ) { + + var loader = new THREE.MaterialLoader(); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var data = json[ i ]; + var material = loader.parse( data ); + + material.uuid = data.uuid; + + if ( data.name !== undefined ) material.name = data.name; + + materials[ data.uuid ] = material; + + } + + } + + return materials; + + }, + + parseObject: function () { + + var matrix = new THREE.Matrix4(); + + return function ( data, geometries, materials ) { + + var object; + + switch ( data.type ) { + + case 'Scene': + + object = new THREE.Scene(); + + break; + + case 'PerspectiveCamera': + + object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + + break; + + case 'OrthographicCamera': + + object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + + break; + + case 'AmbientLight': + + object = new THREE.AmbientLight( data.color ); + + break; + + case 'DirectionalLight': + + object = new THREE.DirectionalLight( data.color, data.intensity ); + + break; + + case 'PointLight': + + object = new THREE.PointLight( data.color, data.intensity, data.distance ); + + break; + + case 'SpotLight': + + object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent ); + + break; + + case 'HemisphereLight': + + object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity ); + + break; + + case 'Mesh': + + var geometry = geometries[ data.geometry ]; + var material = materials[ data.material ]; + + if ( geometry === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined geometry ' + data.geometry ); + + } + + if ( material === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined material ' + data.material ); + + } + + object = new THREE.Mesh( geometry, material ); + + break; + + case 'Sprite': + + var material = materials[ data.material ]; + + if ( material === undefined ) { + + console.error( 'THREE.ObjectLoader: Undefined material ' + data.material ); + + } + + object = new THREE.Sprite( material ); + + break; + + default: + + object = new THREE.Object3D(); + + } + + object.uuid = data.uuid; + + if ( data.name !== undefined ) object.name = data.name; + if ( data.matrix !== undefined ) { + + matrix.fromArray( data.matrix ); + matrix.decompose( object.position, object.quaternion, object.scale ); + + } else { + + if ( data.position !== undefined ) object.position.fromArray( data.position ); + if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); + if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + + } + + if ( data.visible !== undefined ) object.visible = data.visible; + if ( data.userData !== undefined ) object.userData = data.userData; + + if ( data.children !== undefined ) { + + for ( var child in data.children ) { + + object.add( this.parseObject( data.children[ child ], geometries, materials ) ); + + } + + } + + return object; + + } + + }() + +}; + +// File:src/loaders/TextureLoader.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.TextureLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.TextureLoader.prototype = { + + constructor: THREE.TextureLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.ImageLoader( scope.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.load( url, function ( image ) { + + var texture = new THREE.Texture( image ); + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +// File:src/materials/Material.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Material = function () { + + this.id = THREE.MaterialIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.side = THREE.FrontSide; + + this.opacity = 1; + this.transparent = false; + + this.blending = THREE.NormalBlending; + + this.blendSrc = THREE.SrcAlphaFactor; + this.blendDst = THREE.OneMinusSrcAlphaFactor; + this.blendEquation = THREE.AddEquation; + + this.depthTest = true; + this.depthWrite = true; + + this.polygonOffset = false; + this.polygonOffsetFactor = 0; + this.polygonOffsetUnits = 0; + + this.alphaTest = 0; + + this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer + + this.visible = true; + + this.needsUpdate = true; + +}; + +THREE.Material.prototype = { + + constructor: THREE.Material, + + setValues: function ( values ) { + + if ( values === undefined ) return; + + for ( var key in values ) { + + var newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( "THREE.Material: '" + key + "' parameter is undefined." ); + continue; + + } + + if ( key in this ) { + + var currentValue = this[ key ]; + + if ( currentValue instanceof THREE.Color ) { + + currentValue.set( newValue ); + + } else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) { + + currentValue.copy( newValue ); + + } else if ( key == 'overdraw' ) { + + // ensure overdraw is backwards-compatable with legacy boolean type + this[ key ] = Number( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } + + } + + }, + + clone: function ( material ) { + + if ( material === undefined ) material = new THREE.Material(); + + material.name = this.name; + + material.side = this.side; + + material.opacity = this.opacity; + material.transparent = this.transparent; + + material.blending = this.blending; + + material.blendSrc = this.blendSrc; + material.blendDst = this.blendDst; + material.blendEquation = this.blendEquation; + + material.depthTest = this.depthTest; + material.depthWrite = this.depthWrite; + + material.polygonOffset = this.polygonOffset; + material.polygonOffsetFactor = this.polygonOffsetFactor; + material.polygonOffsetUnits = this.polygonOffsetUnits; + + material.alphaTest = this.alphaTest; + + material.overdraw = this.overdraw; + + material.visible = this.visible; + + return material; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Material.prototype ); + +THREE.MaterialIdCount = 0; + +// File:src/materials/LineBasicMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * linewidth: , + * linecap: "round", + * linejoin: "round", + * + * vertexColors: + * + * fog: + * } + */ + +THREE.LineBasicMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.linewidth = 1; + this.linecap = 'round'; + this.linejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.LineBasicMaterial.prototype.clone = function () { + + var material = new THREE.LineBasicMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.linewidth = this.linewidth; + material.linecap = this.linecap; + material.linejoin = this.linejoin; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +// File:src/materials/LineDashedMaterial.js + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * linewidth: , + * + * scale: , + * dashSize: , + * gapSize: , + * + * vertexColors: + * + * fog: + * } + */ + +THREE.LineDashedMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.linewidth = 1; + + this.scale = 1; + this.dashSize = 3; + this.gapSize = 1; + + this.vertexColors = false; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.LineDashedMaterial.prototype.clone = function () { + + var material = new THREE.LineDashedMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.linewidth = this.linewidth; + + material.scale = this.scale; + material.dashSize = this.dashSize; + material.gapSize = this.gapSize; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +// File:src/materials/MeshBasicMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * + * fog: + * } + */ + +THREE.MeshBasicMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // emissive + + this.map = null; + + this.lightMap = null; + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + + this.setValues( parameters ); + +}; + +THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshBasicMaterial.prototype.clone = function () { + + var material = new THREE.MeshBasicMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.specularMap = this.specularMap; + + material.alphaMap = this.alphaMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + + return material; + +}; + +// File:src/materials/MeshLambertMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * ambient: , + * emissive: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.MeshLambertMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // diffuse + this.ambient = new THREE.Color( 0xffffff ); + this.emissive = new THREE.Color( 0x000000 ); + + this.wrapAround = false; + this.wrapRGB = new THREE.Vector3( 1, 1, 1 ); + + this.map = null; + + this.lightMap = null; + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + +}; + +THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshLambertMaterial.prototype.clone = function () { + + var material = new THREE.MeshLambertMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.ambient.copy( this.ambient ); + material.emissive.copy( this.emissive ); + + material.wrapAround = this.wrapAround; + material.wrapRGB.copy( this.wrapRGB ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.specularMap = this.specularMap; + + material.alphaMap = this.alphaMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +// File:src/materials/MeshPhongMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * ambient: , + * emissive: , + * specular: , + * shininess: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * + * bumpMap: new THREE.Texture( ), + * bumpScale: , + * + * normalMap: new THREE.Texture( ), + * normalScale: , + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.MeshPhongMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); // diffuse + this.ambient = new THREE.Color( 0xffffff ); + this.emissive = new THREE.Color( 0x000000 ); + this.specular = new THREE.Color( 0x111111 ); + this.shininess = 30; + + this.metal = false; + + this.wrapAround = false; + this.wrapRGB = new THREE.Vector3( 1, 1, 1 ); + + this.map = null; + + this.lightMap = null; + + this.bumpMap = null; + this.bumpScale = 1; + + this.normalMap = null; + this.normalScale = new THREE.Vector2( 1, 1 ); + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = THREE.MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.fog = true; + + this.shading = THREE.SmoothShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.vertexColors = THREE.NoColors; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + +}; + +THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshPhongMaterial.prototype.clone = function () { + + var material = new THREE.MeshPhongMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.ambient.copy( this.ambient ); + material.emissive.copy( this.emissive ); + material.specular.copy( this.specular ); + material.shininess = this.shininess; + + material.metal = this.metal; + + material.wrapAround = this.wrapAround; + material.wrapRGB.copy( this.wrapRGB ); + + material.map = this.map; + + material.lightMap = this.lightMap; + + material.bumpMap = this.bumpMap; + material.bumpScale = this.bumpScale; + + material.normalMap = this.normalMap; + material.normalScale.copy( this.normalScale ); + + material.specularMap = this.specularMap; + + material.alphaMap = this.alphaMap; + + material.envMap = this.envMap; + material.combine = this.combine; + material.reflectivity = this.reflectivity; + material.refractionRatio = this.refractionRatio; + + material.fog = this.fog; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + material.wireframeLinecap = this.wireframeLinecap; + material.wireframeLinejoin = this.wireframeLinejoin; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +// File:src/materials/MeshDepthMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * opacity: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: + * } + */ + +THREE.MeshDepthMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.morphTargets = false; + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.setValues( parameters ); + +}; + +THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshDepthMaterial.prototype.clone = function () { + + var material = new THREE.MeshDepthMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + return material; + +}; + +// File:src/materials/MeshNormalMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * + * parameters = { + * opacity: , + * + * shading: THREE.FlatShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: + * } + */ + +THREE.MeshNormalMaterial = function ( parameters ) { + + THREE.Material.call( this, parameters ); + + this.shading = THREE.FlatShading; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.morphTargets = false; + + this.setValues( parameters ); + +}; + +THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.MeshNormalMaterial.prototype.clone = function () { + + var material = new THREE.MeshNormalMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + return material; + +}; + +// File:src/materials/MeshFaceMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.MeshFaceMaterial = function ( materials ) { + + this.materials = materials instanceof Array ? materials : []; + +}; + +THREE.MeshFaceMaterial.prototype.clone = function () { + + var material = new THREE.MeshFaceMaterial(); + + for ( var i = 0; i < this.materials.length; i ++ ) { + + material.materials.push( this.materials[ i ].clone() ); + + } + + return material; + +}; + +// File:src/materials/PointCloudMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * size: , + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * vertexColors: , + * + * fog: + * } + */ + +THREE.PointCloudMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.map = null; + + this.size = 1; + this.sizeAttenuation = true; + + this.vertexColors = THREE.NoColors; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.PointCloudMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.PointCloudMaterial.prototype.clone = function () { + + var material = new THREE.PointCloudMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + + material.map = this.map; + + material.size = this.size; + material.sizeAttenuation = this.sizeAttenuation; + + material.vertexColors = this.vertexColors; + + material.fog = this.fog; + + return material; + +}; + +// backwards compatibility + +THREE.ParticleBasicMaterial = function ( parameters ) { + + console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointCloudMaterial.' ); + return new THREE.PointCloudMaterial( parameters ); + +}; + +THREE.ParticleSystemMaterial = function ( parameters ) { + + console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointCloudMaterial.' ); + return new THREE.PointCloudMaterial( parameters ); + +}; + +// File:src/materials/ShaderMaterial.js + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * defines: { "label" : "value" }, + * uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } }, + * + * fragmentShader: , + * vertexShader: , + * + * shading: THREE.SmoothShading, + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * lights: , + * + * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors, + * + * skinning: , + * morphTargets: , + * morphNormals: , + * + * fog: + * } + */ + +THREE.ShaderMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.defines = {}; + this.uniforms = {}; + this.attributes = null; + + this.vertexShader = 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}'; + this.fragmentShader = 'void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}'; + + this.shading = THREE.SmoothShading; + + this.linewidth = 1; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.fog = false; // set to use scene fog + + this.lights = false; // set to use scene lights + + this.vertexColors = THREE.NoColors; // set to use "color" attribute stream + + this.skinning = false; // set to use skinning attribute streams + + this.morphTargets = false; // set to use morph targets + this.morphNormals = false; // set to use morph normals + + // When rendered geometry doesn't include these attributes but the material does, + // use these default values in WebGL. This avoids errors when buffer data is missing. + this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv2': [ 0, 0 ] + }; + + this.index0AttributeName = undefined; + + this.setValues( parameters ); + +}; + +THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.ShaderMaterial.prototype.clone = function () { + + var material = new THREE.ShaderMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.fragmentShader = this.fragmentShader; + material.vertexShader = this.vertexShader; + + material.uniforms = THREE.UniformsUtils.clone( this.uniforms ); + + material.attributes = this.attributes; + material.defines = this.defines; + + material.shading = this.shading; + + material.wireframe = this.wireframe; + material.wireframeLinewidth = this.wireframeLinewidth; + + material.fog = this.fog; + + material.lights = this.lights; + + material.vertexColors = this.vertexColors; + + material.skinning = this.skinning; + + material.morphTargets = this.morphTargets; + material.morphNormals = this.morphNormals; + + return material; + +}; + +// File:src/materials/RawShaderMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RawShaderMaterial = function ( parameters ) { + + THREE.ShaderMaterial.call( this, parameters ); + +}; + +THREE.RawShaderMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype ); + +THREE.RawShaderMaterial.prototype.clone = function () { + + var material = new THREE.RawShaderMaterial(); + + THREE.ShaderMaterial.prototype.clone.call( this, material ); + + return material; + +}; + +// File:src/materials/SpriteMaterial.js + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * blending: THREE.NormalBlending, + * depthTest: , + * depthWrite: , + * + * uvOffset: new THREE.Vector2(), + * uvScale: new THREE.Vector2(), + * + * fog: + * } + */ + +THREE.SpriteMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + // defaults + + this.color = new THREE.Color( 0xffffff ); + this.map = null; + + this.rotation = 0; + + this.fog = false; + + // set parameters + + this.setValues( parameters ); + +}; + +THREE.SpriteMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.SpriteMaterial.prototype.clone = function () { + + var material = new THREE.SpriteMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.map = this.map; + + material.rotation = this.rotation; + + material.fog = this.fog; + + return material; + +}; + +// File:src/materials/SpriteCanvasMaterial.js + +/** + * @author mrdoob / http://mrdoob.com/ + * + * parameters = { + * color: , + * program: , + * opacity: , + * blending: THREE.NormalBlending + * } + */ + +THREE.SpriteCanvasMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + this.program = function ( context, color ) {}; + + this.setValues( parameters ); + +}; + +THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.SpriteCanvasMaterial.prototype.clone = function () { + + var material = new THREE.SpriteCanvasMaterial(); + + THREE.Material.prototype.clone.call( this, material ); + + material.color.copy( this.color ); + material.program = this.program; + + return material; + +}; + +// backwards compatibility + +THREE.ParticleCanvasMaterial = THREE.SpriteCanvasMaterial; + +// File:src/textures/Texture.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + */ + +THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + this.id = THREE.TextureIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + this.image = image !== undefined ? image : THREE.Texture.DEFAULT_IMAGE; + this.mipmaps = []; + + this.mapping = mapping !== undefined ? mapping : THREE.Texture.DEFAULT_MAPPING; + + this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping; + this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping; + + this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter; + this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter; + + this.anisotropy = anisotropy !== undefined ? anisotropy : 1; + + this.format = format !== undefined ? format : THREE.RGBAFormat; + this.type = type !== undefined ? type : THREE.UnsignedByteType; + + this.offset = new THREE.Vector2( 0, 0 ); + this.repeat = new THREE.Vector2( 1, 1 ); + + this.generateMipmaps = true; + this.premultiplyAlpha = false; + this.flipY = true; + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + + this._needsUpdate = false; + this.onUpdate = null; + +}; + +THREE.Texture.DEFAULT_IMAGE = undefined; +THREE.Texture.DEFAULT_MAPPING = new THREE.UVMapping(); + +THREE.Texture.prototype = { + + constructor: THREE.Texture, + + get needsUpdate () { + + return this._needsUpdate; + + }, + + set needsUpdate ( value ) { + + if ( value === true ) this.update(); + + this._needsUpdate = value; + + }, + + clone: function ( texture ) { + + if ( texture === undefined ) texture = new THREE.Texture(); + + texture.image = this.image; + texture.mipmaps = this.mipmaps.slice( 0 ); + + texture.mapping = this.mapping; + + texture.wrapS = this.wrapS; + texture.wrapT = this.wrapT; + + texture.magFilter = this.magFilter; + texture.minFilter = this.minFilter; + + texture.anisotropy = this.anisotropy; + + texture.format = this.format; + texture.type = this.type; + + texture.offset.copy( this.offset ); + texture.repeat.copy( this.repeat ); + + texture.generateMipmaps = this.generateMipmaps; + texture.premultiplyAlpha = this.premultiplyAlpha; + texture.flipY = this.flipY; + texture.unpackAlignment = this.unpackAlignment; + + return texture; + + }, + + update: function () { + + this.dispatchEvent( { type: 'update' } ); + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype ); + +THREE.TextureIdCount = 0; + +// File:src/textures/CubeTexture.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CubeTexture = function ( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + THREE.Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.images = images; + +}; + +THREE.CubeTexture.prototype = Object.create( THREE.Texture.prototype ); + +THREE.CubeTexture.clone = function ( texture ) { + + if ( texture === undefined ) texture = new THREE.CubeTexture(); + + THREE.Texture.prototype.clone.call( this, texture ); + + texture.images = this.images; + + return texture; + +}; + +// File:src/textures/CompressedTexture.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.CompressedTexture = function ( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) { + + THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.image = { width: width, height: height }; + this.mipmaps = mipmaps; + + this.generateMipmaps = false; // WebGL currently can't generate mipmaps for compressed textures, they must be embedded in DDS file + +}; + +THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype ); + +THREE.CompressedTexture.prototype.clone = function () { + + var texture = new THREE.CompressedTexture(); + + THREE.Texture.prototype.clone.call( this, texture ); + + return texture; + +}; + +// File:src/textures/DataTexture.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) { + + THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.image = { data: data, width: width, height: height }; + +}; + +THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype ); + +THREE.DataTexture.prototype.clone = function () { + + var texture = new THREE.DataTexture(); + + THREE.Texture.prototype.clone.call( this, texture ); + + return texture; + +}; + +// File:src/objects/PointCloud.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.PointCloud = function ( geometry, material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.PointCloudMaterial( { color: Math.random() * 0xffffff } ); + + this.sortParticles = false; + +}; + +THREE.PointCloud.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.PointCloud.prototype.raycast = ( function () { + + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); + + return function ( raycaster, intersects ) { + + var object = this; + var geometry = object.geometry; + var threshold = raycaster.params.PointCloud.threshold; + + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + if ( geometry.boundingBox !== null ) { + + if ( ray.isIntersectionBox( geometry.boundingBox ) === false ) { + + return; + + } + + } + + var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + var position = new THREE.Vector3(); + + var testPoint = function ( point, index ) { + + var rayPointDistance = ray.distanceToPoint( point ); + + if ( rayPointDistance < localThreshold ) { + + var intersectPoint = ray.closestPointToPoint( point ); + intersectPoint.applyMatrix4( object.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectPoint ); + + intersects.push( { + + distance: distance, + distanceToRay: rayPointDistance, + point: intersectPoint.clone(), + index: index, + face: null, + object: object + + } ); + + } + + }; + + if ( geometry instanceof THREE.BufferGeometry ) { + + var attributes = geometry.attributes; + var positions = attributes.position.array; + + if ( attributes.index !== undefined ) { + + var indices = attributes.index.array; + var offsets = geometry.offsets; + + if ( offsets.length === 0 ) { + + var offset = { + start: 0, + count: indices.length, + index: 0 + }; + + offsets = [ offset ]; + + } + + for ( var oi = 0, ol = offsets.length; oi < ol; ++oi ) { + + var start = offsets[ oi ].start; + var count = offsets[ oi ].count; + var index = offsets[ oi ].index; + + for ( var i = start, il = start + count; i < il; i ++ ) { + + var a = index + indices[ i ]; + + position.set( + positions[ a * 3 ], + positions[ a * 3 + 1 ], + positions[ a * 3 + 2 ] + ); + + testPoint( position, a ); + + } + + } + + } else { + + var pointCount = positions.length / 3; + + for ( var i = 0; i < pointCount; i ++ ) { + + position.set( + positions[ 3 * i ], + positions[ 3 * i + 1 ], + positions[ 3 * i + 2 ] + ); + + testPoint( position, i ); + + } + + } + + } else { + + var vertices = this.geometry.vertices; + + for ( var i = 0; i < vertices.length; i ++ ) { + + testPoint( vertices[ i ], i ); + + } + + } + + }; + +}() ); + +THREE.PointCloud.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.PointCloud( this.geometry, this.material ); + + object.sortParticles = this.sortParticles; + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +// Backwards compatibility + +THREE.ParticleSystem = function ( geometry, material ) { + + console.warn( 'THREE.ParticleSystem has been renamed to THREE.PointCloud.' ); + return new THREE.PointCloud( geometry, material ); + +}; + +// File:src/objects/Line.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Line = function ( geometry, material, type ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } ); + + this.type = ( type !== undefined ) ? type : THREE.LineStrip; + +}; + +THREE.LineStrip = 0; +THREE.LinePieces = 1; + +THREE.Line.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Line.prototype.raycast = ( function () { + + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); + var sphere = new THREE.Sphere(); + + return function ( raycaster, intersects ) { + + var precision = raycaster.linePrecision; + var precisionSq = precision * precision; + + var geometry = this.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + // Checking boundingSphere distance to ray + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( this.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return; + + } + + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + /* if ( geometry instanceof THREE.BufferGeometry ) { + + } else */ if ( geometry instanceof THREE.Geometry ) { + + var vertices = geometry.vertices; + var nbVertices = vertices.length; + var interSegment = new THREE.Vector3(); + var interRay = new THREE.Vector3(); + var step = this.type === THREE.LineStrip ? 1 : 2; + + for ( var i = 0; i < nbVertices - 1; i = i + step ) { + + var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + var distance = ray.origin.distanceTo( interRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + face: null, + faceIndex: null, + object: this + + } ); + + } + + } + + }; + +}() ); + +THREE.Line.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Line( this.geometry, this.material, this.type ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +// File:src/objects/Mesh.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author jonobr1 / http://jonobr1.com/ + */ + +THREE.Mesh = function ( geometry, material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } ); + + this.updateMorphTargets(); + +}; + +THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Mesh.prototype.updateMorphTargets = function () { + + if ( this.geometry.morphTargets !== undefined && this.geometry.morphTargets.length > 0 ) { + + this.morphTargetBase = - 1; + this.morphTargetForcedOrder = []; + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) { + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m; + + } + + } + +}; + +THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) { + + if ( this.morphTargetDictionary[ name ] !== undefined ) { + + return this.morphTargetDictionary[ name ]; + + } + + console.log( 'THREE.Mesh.getMorphTargetIndexByName: morph target ' + name + ' does not exist. Returning 0.' ); + + return 0; + +}; + + +THREE.Mesh.prototype.raycast = ( function () { + + var inverseMatrix = new THREE.Matrix4(); + var ray = new THREE.Ray(); + var sphere = new THREE.Sphere(); + + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); + + return function ( raycaster, intersects ) { + + var geometry = this.geometry; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( this.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return; + + } + + // Check boundingBox before continuing + + inverseMatrix.getInverse( this.matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + if ( geometry.boundingBox !== null ) { + + if ( ray.isIntersectionBox( geometry.boundingBox ) === false ) { + + return; + + } + + } + + if ( geometry instanceof THREE.BufferGeometry ) { + + var material = this.material; + + if ( material === undefined ) return; + + var attributes = geometry.attributes; + + var a, b, c; + var precision = raycaster.precision; + + if ( attributes.index !== undefined ) { + + var indices = attributes.index.array; + var positions = attributes.position.array; + var offsets = geometry.offsets; + + if ( offsets.length === 0 ) { + + offsets = [ { start: 0, count: indices.length, index: 0 } ]; + + } + + for ( var oi = 0, ol = offsets.length; oi < ol; ++oi ) { + + var start = offsets[ oi ].start; + var count = offsets[ oi ].count; + var index = offsets[ oi ].index; + + for ( var i = start, il = start + count; i < il; i += 3 ) { + + a = index + indices[ i ]; + b = index + indices[ i + 1 ]; + c = index + indices[ i + 2 ]; + + vA.set( + positions[ a * 3 ], + positions[ a * 3 + 1 ], + positions[ a * 3 + 2 ] + ); + vB.set( + positions[ b * 3 ], + positions[ b * 3 + 1 ], + positions[ b * 3 + 2 ] + ); + vC.set( + positions[ c * 3 ], + positions[ c * 3 + 1 ], + positions[ c * 3 + 2 ] + ); + + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = ray.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = ray.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( this.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + indices: [ a, b, c ], + face: null, + faceIndex: null, + object: this + + } ); + + } + + } + + } else { + + var positions = attributes.position.array; + + for ( var i = 0, j = 0, il = positions.length; i < il; i += 3, j += 9 ) { + + a = i; + b = i + 1; + c = i + 2; + + vA.set( + positions[ j ], + positions[ j + 1 ], + positions[ j + 2 ] + ); + vB.set( + positions[ j + 3 ], + positions[ j + 4 ], + positions[ j + 5 ] + ); + vC.set( + positions[ j + 6 ], + positions[ j + 7 ], + positions[ j + 8 ] + ); + + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = ray.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = ray.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( this.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + indices: [ a, b, c ], + face: null, + faceIndex: null, + object: this + + } ); + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + var isFaceMaterial = this.material instanceof THREE.MeshFaceMaterial; + var objectMaterials = isFaceMaterial === true ? this.material.materials : null; + + var a, b, c, d; + var precision = raycaster.precision; + + var vertices = geometry.vertices; + + for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) { + + var face = geometry.faces[ f ]; + + var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : this.material; + + if ( material === undefined ) continue; + + a = vertices[ face.a ]; + b = vertices[ face.b ]; + c = vertices[ face.c ]; + + if ( material.morphTargets === true ) { + + var morphTargets = geometry.morphTargets; + var morphInfluences = this.morphTargetInfluences; + + vA.set( 0, 0, 0 ); + vB.set( 0, 0, 0 ); + vC.set( 0, 0, 0 ); + + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { + + var influence = morphInfluences[ t ]; + + if ( influence === 0 ) continue; + + var targets = morphTargets[ t ].vertices; + + vA.x += ( targets[ face.a ].x - a.x ) * influence; + vA.y += ( targets[ face.a ].y - a.y ) * influence; + vA.z += ( targets[ face.a ].z - a.z ) * influence; + + vB.x += ( targets[ face.b ].x - b.x ) * influence; + vB.y += ( targets[ face.b ].y - b.y ) * influence; + vB.z += ( targets[ face.b ].z - b.z ) * influence; + + vC.x += ( targets[ face.c ].x - c.x ) * influence; + vC.y += ( targets[ face.c ].y - c.y ) * influence; + vC.z += ( targets[ face.c ].z - c.z ) * influence; + + } + + vA.add( a ); + vB.add( b ); + vC.add( c ); + + a = vA; + b = vB; + c = vC; + + } + + if ( material.side === THREE.BackSide ) { + + var intersectionPoint = ray.intersectTriangle( c, b, a, true ); + + } else { + + var intersectionPoint = ray.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( this.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPoint ); + + if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + point: intersectionPoint, + face: face, + faceIndex: f, + object: this + + } ); + + } + + } + + }; + +}() ); + +THREE.Mesh.prototype.clone = function ( object, recursive ) { + + if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material ); + + THREE.Object3D.prototype.clone.call( this, object, recursive ); + + return object; + +}; + +// File:src/objects/Bone.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com + */ + +THREE.Bone = function ( belongsToSkin ) { + + THREE.Object3D.call( this ); + + this.skin = belongsToSkin; + + this.accumulatedRotWeight = 0; + this.accumulatedPosWeight = 0; + this.accumulatedSclWeight = 0; + +}; + +THREE.Bone.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Bone.prototype.updateMatrixWorld = function ( force ) { + + THREE.Object3D.prototype.updateMatrixWorld.call( this, force ); + + // Reset weights to be re-accumulated in the next frame + + this.accumulatedRotWeight = 0; + this.accumulatedPosWeight = 0; + this.accumulatedSclWeight = 0; + +}; + + +// File:src/objects/Skeleton.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author michael guerrero / http://realitymeltdown.com + * @author ikerr / http://verold.com + */ + +THREE.Skeleton = function ( bones, boneInverses, useVertexTexture ) { + + this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; + + this.identityMatrix = new THREE.Matrix4(); + + // copy the bone array + + bones = bones || []; + + this.bones = bones.slice( 0 ); + + // create a bone texture or an array of floats + + if ( this.useVertexTexture ) { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones (8 * 8 / 4) + // 16x16 pixel texture max 64 bones (16 * 16 / 4) + // 32x32 pixel texture max 256 bones (32 * 32 / 4) + // 64x64 pixel texture max 1024 bones (64 * 64 / 4) + + var size; + + if ( this.bones.length > 256 ) + size = 64; + else if ( this.bones.length > 64 ) + size = 32; + else if ( this.bones.length > 16 ) + size = 16; + else + size = 8; + + this.boneTextureWidth = size; + this.boneTextureHeight = size; + + this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel + this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType ); + this.boneTexture.minFilter = THREE.NearestFilter; + this.boneTexture.magFilter = THREE.NearestFilter; + this.boneTexture.generateMipmaps = false; + this.boneTexture.flipY = false; + + } else { + + this.boneMatrices = new Float32Array( 16 * this.bones.length ); + + } + + // use the supplied bone inverses or calculate the inverses + + if ( boneInverses === undefined ) { + + this.calculateInverses(); + + } else { + + if ( this.bones.length === boneInverses.length ) { + + this.boneInverses = boneInverses.slice( 0 ); + + } else { + + console.warn( 'THREE.Skeleton bonInverses is the wrong length.' ); + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + this.boneInverses.push( new THREE.Matrix4() ); + + } + + } + + } + +}; + +THREE.Skeleton.prototype.calculateInverses = function () { + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + var inverse = new THREE.Matrix4(); + + if ( this.bones[ b ] ) { + + inverse.getInverse( this.bones[ b ].matrixWorld ); + + } + + this.boneInverses.push( inverse ); + + } + +}; + +THREE.Skeleton.prototype.pose = function () { + + var bone; + + // recover the bind-time world matrices + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + bone = this.bones[ b ]; + + if ( bone ) { + + bone.matrixWorld.getInverse( this.boneInverses[ b ] ); + + } + + } + + // compute the local matrices, positions, rotations and scales + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + bone = this.bones[ b ]; + + if ( bone ) { + + if ( bone.parent ) { + + bone.matrix.getInverse( bone.parent.matrixWorld ); + bone.matrix.multiply( bone.matrixWorld ); + + } + else { + + bone.matrix.copy( bone.matrixWorld ); + + } + + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + + } + + } + +}; + +THREE.Skeleton.prototype.update = function () { + + var offsetMatrix = new THREE.Matrix4(); + + // flatten bone matrices to array + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + // compute the offset between the current and the original transform + + var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix; + + offsetMatrix.multiplyMatrices( matrix, this.boneInverses[ b ] ); + offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); + + } + + if ( this.useVertexTexture ) { + + this.boneTexture.needsUpdate = true; + + } + +}; + + +// File:src/objects/SkinnedMesh.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com + */ + +THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { + + THREE.Mesh.call( this, geometry, material ); + + this.bindMode = "attached"; + this.bindMatrix = new THREE.Matrix4(); + this.bindMatrixInverse = new THREE.Matrix4(); + + // init bones + + // TODO: remove bone creation as there is no reason (other than + // convenience) for THREE.SkinnedMesh to do this. + + var bones = []; + + if ( this.geometry && this.geometry.bones !== undefined ) { + + var bone, gbone, p, q, s; + + for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++b ) { + + gbone = this.geometry.bones[ b ]; + + p = gbone.pos; + q = gbone.rotq; + s = gbone.scl; + + bone = new THREE.Bone( this ); + bones.push( bone ); + + bone.name = gbone.name; + bone.position.set( p[ 0 ], p[ 1 ], p[ 2 ] ); + bone.quaternion.set( q[ 0 ], q[ 1 ], q[ 2 ], q[ 3 ] ); + + if ( s !== undefined ) { + + bone.scale.set( s[ 0 ], s[ 1 ], s[ 2 ] ); + + } else { + + bone.scale.set( 1, 1, 1 ); + + } + + } + + for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++b ) { + + gbone = this.geometry.bones[ b ]; + + if ( gbone.parent !== - 1 ) { + + bones[ gbone.parent ].add( bones[ b ] ); + + } else { + + this.add( bones[ b ] ); + + } + + } + + } + + this.normalizeSkinWeights(); + + this.updateMatrixWorld( true ); + this.bind( new THREE.Skeleton( bones, undefined, useVertexTexture ) ); + +}; + + +THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.SkinnedMesh.prototype.bind = function( skeleton, bindMatrix ) { + + this.skeleton = skeleton; + + if ( bindMatrix === undefined ) { + + this.updateMatrixWorld( true ); + + bindMatrix = this.matrixWorld; + + } + + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.getInverse( bindMatrix ); + +}; + +THREE.SkinnedMesh.prototype.pose = function () { + + this.skeleton.pose(); + +}; + +THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () { + + if ( this.geometry instanceof THREE.Geometry ) { + + for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) { + + var sw = this.geometry.skinWeights[ i ]; + + var scale = 1.0 / sw.lengthManhattan(); + + if ( scale !== Infinity ) { + + sw.multiplyScalar( scale ); + + } else { + + sw.set( 1 ); // this will be normalized by the shader anyway + + } + + } + + } else { + + // skinning weights assumed to be normalized for THREE.BufferGeometry + + } + +}; + +THREE.SkinnedMesh.prototype.updateMatrixWorld = function( force ) { + + THREE.Mesh.prototype.updateMatrixWorld.call( this, true ); + + if ( this.bindMode === "attached" ) { + + this.bindMatrixInverse.getInverse( this.matrixWorld ); + + } else if ( this.bindMode === "detached" ) { + + this.bindMatrixInverse.getInverse( this.bindMatrix ); + + } else { + + console.warn( 'THREE.SkinnedMesh unreckognized bindMode: ' + this.bindMode ); + + } + +}; + +THREE.SkinnedMesh.prototype.clone = function( object ) { + + if ( object === undefined ) { + + object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture ); + + } + + THREE.Mesh.prototype.clone.call( this, object ); + + return object; + +}; + + +// File:src/objects/MorphAnimMesh.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.MorphAnimMesh = function ( geometry, material ) { + + THREE.Mesh.call( this, geometry, material ); + + // API + + this.duration = 1000; // milliseconds + this.mirroredLoop = false; + this.time = 0; + + // internals + + this.lastKeyframe = 0; + this.currentKeyframe = 0; + + this.direction = 1; + this.directionBackwards = false; + + this.setFrameRange( 0, this.geometry.morphTargets.length - 1 ); + +}; + +THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) { + + this.startKeyframe = start; + this.endKeyframe = end; + + this.length = this.endKeyframe - this.startKeyframe + 1; + +}; + +THREE.MorphAnimMesh.prototype.setDirectionForward = function () { + + this.direction = 1; + this.directionBackwards = false; + +}; + +THREE.MorphAnimMesh.prototype.setDirectionBackward = function () { + + this.direction = - 1; + this.directionBackwards = true; + +}; + +THREE.MorphAnimMesh.prototype.parseAnimations = function () { + + var geometry = this.geometry; + + if ( ! geometry.animations ) geometry.animations = {}; + + var firstAnimation, animations = geometry.animations; + + var pattern = /([a-z]+)_?(\d+)/; + + for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) { + + var morph = geometry.morphTargets[ i ]; + var parts = morph.name.match( pattern ); + + if ( parts && parts.length > 1 ) { + + var label = parts[ 1 ]; + var num = parts[ 2 ]; + + if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: - Infinity }; + + var animation = animations[ label ]; + + if ( i < animation.start ) animation.start = i; + if ( i > animation.end ) animation.end = i; + + if ( ! firstAnimation ) firstAnimation = label; + + } + + } + + geometry.firstAnimation = firstAnimation; + +}; + +THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) { + + if ( ! this.geometry.animations ) this.geometry.animations = {}; + + this.geometry.animations[ label ] = { start: start, end: end }; + +}; + +THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) { + + var animation = this.geometry.animations[ label ]; + + if ( animation ) { + + this.setFrameRange( animation.start, animation.end ); + this.duration = 1000 * ( ( animation.end - animation.start ) / fps ); + this.time = 0; + + } else { + + console.warn( 'animation[' + label + '] undefined' ); + + } + +}; + +THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) { + + var frameTime = this.duration / this.length; + + this.time += this.direction * delta; + + if ( this.mirroredLoop ) { + + if ( this.time > this.duration || this.time < 0 ) { + + this.direction *= - 1; + + if ( this.time > this.duration ) { + + this.time = this.duration; + this.directionBackwards = true; + + } + + if ( this.time < 0 ) { + + this.time = 0; + this.directionBackwards = false; + + } + + } + + } else { + + this.time = this.time % this.duration; + + if ( this.time < 0 ) this.time += this.duration; + + } + + var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 ); + + if ( keyframe !== this.currentKeyframe ) { + + this.morphTargetInfluences[ this.lastKeyframe ] = 0; + this.morphTargetInfluences[ this.currentKeyframe ] = 1; + + this.morphTargetInfluences[ keyframe ] = 0; + + this.lastKeyframe = this.currentKeyframe; + this.currentKeyframe = keyframe; + + } + + var mix = ( this.time % frameTime ) / frameTime; + + if ( this.directionBackwards ) { + + mix = 1 - mix; + + } + + this.morphTargetInfluences[ this.currentKeyframe ] = mix; + this.morphTargetInfluences[ this.lastKeyframe ] = 1 - mix; + +}; + +THREE.MorphAnimMesh.prototype.interpolateTargets = function ( a, b, t ) { + + var influences = this.morphTargetInfluences; + + for ( var i = 0, l = influences.length; i < l; i ++ ) { + + influences[ i ] = 0; + + } + + if ( a > -1 ) influences[ a ] = 1 - t; + if ( b > -1 ) influences[ b ] = t; + +}; + +THREE.MorphAnimMesh.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material ); + + object.duration = this.duration; + object.mirroredLoop = this.mirroredLoop; + object.time = this.time; + + object.lastKeyframe = this.lastKeyframe; + object.currentKeyframe = this.currentKeyframe; + + object.direction = this.direction; + object.directionBackwards = this.directionBackwards; + + THREE.Mesh.prototype.clone.call( this, object ); + + return object; + +}; + +// File:src/objects/LOD.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.LOD = function () { + + THREE.Object3D.call( this ); + + this.objects = []; + +}; + + +THREE.LOD.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.LOD.prototype.addLevel = function ( object, distance ) { + + if ( distance === undefined ) distance = 0; + + distance = Math.abs( distance ); + + for ( var l = 0; l < this.objects.length; l ++ ) { + + if ( distance < this.objects[ l ].distance ) { + + break; + + } + + } + + this.objects.splice( l, 0, { distance: distance, object: object } ); + this.add( object ); + +}; + +THREE.LOD.prototype.getObjectForDistance = function ( distance ) { + + for ( var i = 1, l = this.objects.length; i < l; i ++ ) { + + if ( distance < this.objects[ i ].distance ) { + + break; + + } + + } + + return this.objects[ i - 1 ].object; + +}; + +THREE.LOD.prototype.raycast = ( function () { + + var matrixPosition = new THREE.Vector3(); + + return function ( raycaster, intersects ) { + + matrixPosition.setFromMatrixPosition( this.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( matrixPosition ); + + this.getObjectForDistance( distance ).raycast( raycaster, intersects ); + + }; + +}() ); + +THREE.LOD.prototype.update = function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + + return function ( camera ) { + + if ( this.objects.length > 1 ) { + + v1.setFromMatrixPosition( camera.matrixWorld ); + v2.setFromMatrixPosition( this.matrixWorld ); + + var distance = v1.distanceTo( v2 ); + + this.objects[ 0 ].object.visible = true; + + for ( var i = 1, l = this.objects.length; i < l; i ++ ) { + + if ( distance >= this.objects[ i ].distance ) { + + this.objects[ i - 1 ].object.visible = false; + this.objects[ i ].object.visible = true; + + } else { + + break; + + } + + } + + for ( ; i < l; i ++ ) { + + this.objects[ i ].object.visible = false; + + } + + } + + }; + +}(); + +THREE.LOD.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.LOD(); + + THREE.Object3D.prototype.clone.call( this, object ); + + for ( var i = 0, l = this.objects.length; i < l; i ++ ) { + var x = this.objects[ i ].object.clone(); + x.visible = i === 0; + object.addLevel( x, this.objects[ i ].distance ); + } + + return object; + +}; + +// File:src/objects/Sprite.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Sprite = ( function () { + + var vertices = new Float32Array( [ - 0.5, - 0.5, 0, 0.5, - 0.5, 0, 0.5, 0.5, 0 ] ); + + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + + return function ( material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry; + this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial(); + + }; + +} )(); + +THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Sprite.prototype.raycast = ( function () { + + var matrixPosition = new THREE.Vector3(); + + return function ( raycaster, intersects ) { + + matrixPosition.setFromMatrixPosition( this.matrixWorld ); + + var distance = raycaster.ray.distanceToPoint( matrixPosition ); + + if ( distance > this.scale.x ) { + + return; + + } + + intersects.push( { + + distance: distance, + point: this.position, + face: null, + object: this + + } ); + + }; + +}() ); + +THREE.Sprite.prototype.updateMatrix = function () { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + +}; + +THREE.Sprite.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Sprite( this.material ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +// Backwards compatibility + +THREE.Particle = THREE.Sprite; + +// File:src/scenes/Scene.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Scene = function () { + + THREE.Object3D.call( this ); + + this.fog = null; + this.overrideMaterial = null; + + this.autoUpdate = true; // checked by the renderer + this.matrixAutoUpdate = false; + + this.__lights = []; + + this.__objectsAdded = []; + this.__objectsRemoved = []; + +}; + +THREE.Scene.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Scene.prototype.__addObject = function ( object ) { + + if ( object instanceof THREE.Light ) { + + if ( this.__lights.indexOf( object ) === - 1 ) { + + this.__lights.push( object ); + + } + + if ( object.target && object.target.parent === undefined ) { + + this.add( object.target ); + + } + + } else if ( ! ( object instanceof THREE.Camera || object instanceof THREE.Bone ) ) { + + this.__objectsAdded.push( object ); + + // check if previously removed + + var i = this.__objectsRemoved.indexOf( object ); + + if ( i !== - 1 ) { + + this.__objectsRemoved.splice( i, 1 ); + + } + + } + + this.dispatchEvent( { type: 'objectAdded', object: object } ); + object.dispatchEvent( { type: 'addedToScene', scene: this } ); + + for ( var c = 0; c < object.children.length; c ++ ) { + + this.__addObject( object.children[ c ] ); + + } + +}; + +THREE.Scene.prototype.__removeObject = function ( object ) { + + if ( object instanceof THREE.Light ) { + + var i = this.__lights.indexOf( object ); + + if ( i !== - 1 ) { + + this.__lights.splice( i, 1 ); + + } + + if ( object.shadowCascadeArray ) { + + for ( var x = 0; x < object.shadowCascadeArray.length; x ++ ) { + + this.__removeObject( object.shadowCascadeArray[ x ] ); + + } + + } + + } else if ( ! ( object instanceof THREE.Camera ) ) { + + this.__objectsRemoved.push( object ); + + // check if previously added + + var i = this.__objectsAdded.indexOf( object ); + + if ( i !== - 1 ) { + + this.__objectsAdded.splice( i, 1 ); + + } + + } + + this.dispatchEvent( { type: 'objectRemoved', object: object } ); + object.dispatchEvent( { type: 'removedFromScene', scene: this } ); + + for ( var c = 0; c < object.children.length; c ++ ) { + + this.__removeObject( object.children[ c ] ); + + } + +}; + +THREE.Scene.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Scene(); + + THREE.Object3D.prototype.clone.call( this, object ); + + if ( this.fog !== null ) object.fog = this.fog.clone(); + if ( this.overrideMaterial !== null ) object.overrideMaterial = this.overrideMaterial.clone(); + + object.autoUpdate = this.autoUpdate; + object.matrixAutoUpdate = this.matrixAutoUpdate; + + return object; + +}; + +// File:src/scenes/Fog.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Fog = function ( color, near, far ) { + + this.name = ''; + + this.color = new THREE.Color( color ); + + this.near = ( near !== undefined ) ? near : 1; + this.far = ( far !== undefined ) ? far : 1000; + +}; + +THREE.Fog.prototype.clone = function () { + + return new THREE.Fog( this.color.getHex(), this.near, this.far ); + +}; + +// File:src/scenes/FogExp2.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.FogExp2 = function ( color, density ) { + + this.name = ''; + + this.color = new THREE.Color( color ); + this.density = ( density !== undefined ) ? density : 0.00025; + +}; + +THREE.FogExp2.prototype.clone = function () { + + return new THREE.FogExp2( this.color.getHex(), this.density ); + +}; + +// File:src/renderers/CanvasRenderer.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CanvasRenderer = function ( parameters ) { + + console.log( 'THREE.CanvasRenderer', THREE.REVISION ); + + var smoothstep = THREE.Math.smoothstep; + + 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, + + _context = _canvas.getContext( '2d', { + alpha: parameters.alpha === true + } ), + + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0, + + _contextGlobalAlpha = 1, + _contextGlobalCompositeOperation = 0, + _contextStrokeStyle = null, + _contextFillStyle = null, + _contextLineWidth = null, + _contextLineCap = null, + _contextLineJoin = null, + _contextLineDash = [], + + _camera, + + _v1, _v2, _v3, _v4, + _v5 = new THREE.RenderableVertex(), + _v6 = new THREE.RenderableVertex(), + + _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, + _v4x, _v4y, _v5x, _v5y, _v6x, _v6y, + + _color = new THREE.Color(), + _color1 = new THREE.Color(), + _color2 = new THREE.Color(), + _color3 = new THREE.Color(), + _color4 = new THREE.Color(), + + _diffuseColor = new THREE.Color(), + _emissiveColor = new THREE.Color(), + + _lightColor = new THREE.Color(), + + _patterns = {}, + + _image, _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(); + + // dash+gap fallbacks for Firefox and everything else + + if ( _context.setLineDash === undefined ) { + + _context.setLineDash = function () {} + + } + + this.domElement = _canvas; + + this.devicePixelRatio = parameters.devicePixelRatio !== undefined + ? parameters.devicePixelRatio + : self.devicePixelRatio !== undefined + ? self.devicePixelRatio + : 1; + + this.autoClear = true; + this.sortObjects = true; + this.sortElements = true; + + this.info = { + + render: { + + vertices: 0, + faces: 0 + + } + + } + + // WebGLRenderer compatibility + + this.supportsVertexTextures = function () {}; + this.setFaceCulling = function () {}; + + this.setSize = function ( width, height, updateStyle ) { + + _canvasWidth = width * this.devicePixelRatio; + _canvasHeight = height * this.devicePixelRatio; + + _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 * this.devicePixelRatio; + _viewportY = y * this.devicePixelRatio; + + _viewportWidth = width * this.devicePixelRatio; + _viewportHeight = height * this.devicePixelRatio; + + }; + + this.setScissor = function () {}; + this.enableScissorTest = 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.empty() === false ) { + + _clearBox.intersect( _clipBox ); + _clearBox.expandByScalar( 2 ); + + _clearBox.min.x = _clearBox.min.x + _canvasWidthHalf; + _clearBox.min.y = - _clearBox.min.y + _canvasHeightHalf; + _clearBox.max.x = _clearBox.max.x + _canvasWidthHalf; + _clearBox.max.y = - _clearBox.max.y + _canvasHeightHalf; + + if ( _clearAlpha < 1 ) { + + _context.clearRect( + _clearBox.min.x | 0, + _clearBox.min.y | 0, + ( _clearBox.max.x - _clearBox.min.x ) | 0, + ( _clearBox.max.y - _clearBox.min.y ) | 0 + ); + + } + + if ( _clearAlpha > 0 ) { + + setBlending( THREE.NormalBlending ); + setOpacity( 1 ); + + 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.min.y | 0, + ( _clearBox.max.x - _clearBox.min.x ) | 0, + ( _clearBox.max.y - _clearBox.min.y ) | 0 + ); + + } + + _clearBox.makeEmpty(); + + } + + }; + + // compatibility + + this.clearColor = function () {}; + this.clearDepth = function () {}; + this.clearStencil = function () {}; + + this.render = function ( scene, camera ) { + + if ( camera instanceof THREE.Camera === false ) { + + console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' ); + return; + + } + + 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; + _camera = camera; + + _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.isIntersectionBox( _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.isIntersectionBox( _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 instanceof THREE.AmbientLight ) { + + _ambientLight.add( lightColor ); + + } else if ( light instanceof THREE.DirectionalLight ) { + + // for sprites + + _directionalLights.add( lightColor ); + + } else if ( light instanceof THREE.PointLight ) { + + // 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 instanceof THREE.DirectionalLight ) { + + 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 instanceof THREE.PointLight ) { + + 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 = 0.5 * 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 instanceof THREE.SpriteMaterial ) { + + var texture = material.map; + + if ( texture !== null && texture.image !== undefined ) { + + if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { + + if ( texture.image.width > 0 ) { + + textureToPattern( texture ); + + } + + texture.addEventListener( 'update', onTextureUpdate ); + + } + + var pattern = _patterns[ texture.id ]; + + if ( pattern !== undefined ) { + + setFillStyle( pattern ); + + } else { + + setFillStyle( 'rgba( 0, 0, 0, 1 )' ); + + } + + // + + 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 instanceof THREE.SpriteCanvasMaterial ) { + + 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(); + + } + + /* 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 instanceof THREE.LineBasicMaterial ) { + + 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 ); + + } + + } + + _context.stroke(); + _elemBox.expandByScalar( material.linewidth * 2 ); + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + setLineWidth( material.linewidth ); + setLineCap( material.linecap ); + setLineJoin( material.linejoin ); + setStrokeStyle( material.color.getStyle() ); + setLineDash( [ material.dashSize, material.gapSize ] ); + + _context.stroke(); + + _elemBox.expandByScalar( material.linewidth * 2 ); + + 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 instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && 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 instanceof THREE.MeshBasicMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.MeshPhongMaterial ) { + + if ( material.map !== null ) { + + if ( material.map.mapping instanceof 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 instanceof 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 if ( material.envMap.mapping instanceof THREE.SphericalRefractionMapping ) { + + _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 instanceof THREE.MeshDepthMaterial ) { + + _color.r = _color.g = _color.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _camera.near, _camera.far ); + + material.wireframe === true + ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin ) + : fillPath( _color ); + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + _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 onTextureUpdate ( event ) { + + textureToPattern( event.target ); + + } + + function textureToPattern( texture ) { + + if ( texture instanceof THREE.CompressedTexture ) return; + + var repeatX = texture.wrapS === THREE.RepeatWrapping; + var repeatY = texture.wrapT === THREE.RepeatWrapping; + + var image = texture.image; + + var canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + var context = canvas.getContext( '2d' ); + context.setTransform( 1, 0, 0, - 1, 0, image.height ); + context.drawImage( image, 0, 0 ); + + _patterns[ texture.id ] = _context.createPattern( + canvas, repeatX === true && repeatY === true + ? 'repeat' + : repeatX === true && repeatY === false + ? 'repeat-x' + : repeatX === false && repeatY === true + ? 'repeat-y' + : 'no-repeat' + ); + + } + + function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) { + + if ( texture instanceof THREE.DataTexture ) return; + + if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) { + + if ( texture.image !== undefined && texture.image.width > 0 ) { + + textureToPattern( texture ); + + } + + texture.addEventListener( 'update', onTextureUpdate ); + + } + + var pattern = _patterns[ texture.id ]; + + if ( pattern !== undefined ) { + + setFillStyle( pattern ); + + } 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'; + + } + + _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; + + } + + } + +}; + +// File:src/renderers/shaders/ShaderChunk.js + +THREE.ShaderChunk = {}; + +// File:src/renderers/shaders/ShaderChunk/alphatest_fragment.glsl + +THREE.ShaderChunk[ 'alphatest_fragment'] = "#ifdef ALPHATEST\n\n if ( gl_FragColor.a < ALPHATEST ) discard;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/lights_lambert_vertex.glsl + +THREE.ShaderChunk[ 'lights_lambert_vertex'] = "vLightFront = vec3( 0.0 );\n\n#ifdef DOUBLE_SIDED\n\n vLightBack = vec3( 0.0 );\n\n#endif\n\ntransformedNormal = normalize( transformedNormal );\n\n#if MAX_DIR_LIGHTS > 0\n\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\n vec3 dirVector = normalize( lDirection.xyz );\n\n float dotProduct = dot( transformedNormal, dirVector );\n vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );\n\n #ifdef DOUBLE_SIDED\n\n vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n\n #ifdef WRAP_AROUND\n\n vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n\n #endif\n\n #endif\n\n #ifdef WRAP_AROUND\n\n vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\n directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );\n\n #ifdef DOUBLE_SIDED\n\n directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );\n\n #endif\n\n #endif\n\n vLightFront += directionalLightColor[ i ] * directionalLightWeighting;\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;\n\n #endif\n\n}\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\n vec3 lVector = lPosition.xyz - mvPosition.xyz;\n\n float lDistance = 1.0;\n if ( pointLightDistance[ i ] > 0.0 )\n lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\n\n lVector = normalize( lVector );\n float dotProduct = dot( transformedNormal, lVector );\n\n vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );\n\n #ifdef DOUBLE_SIDED\n\n vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n\n #ifdef WRAP_AROUND\n\n vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n\n #endif\n\n #endif\n\n #ifdef WRAP_AROUND\n\n vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\n pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );\n\n #ifdef DOUBLE_SIDED\n\n pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );\n\n #endif\n\n #endif\n\n vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;\n\n #endif\n\n }\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\n vec3 lVector = lPosition.xyz - mvPosition.xyz;\n\n float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );\n\n if ( spotEffect > spotLightAngleCos[ i ] ) {\n\n spotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\n\n float lDistance = 1.0;\n if ( spotLightDistance[ i ] > 0.0 )\n lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\n\n lVector = normalize( lVector );\n\n float dotProduct = dot( transformedNormal, lVector );\n vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );\n\n #ifdef DOUBLE_SIDED\n\n vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n\n #ifdef WRAP_AROUND\n\n vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n\n #endif\n\n #endif\n\n #ifdef WRAP_AROUND\n\n vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\n spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );\n\n #ifdef DOUBLE_SIDED\n\n spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );\n\n #endif\n\n #endif\n\n vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;\n\n #endif\n\n }\n\n }\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\n vec3 lVector = normalize( lDirection.xyz );\n\n float dotProduct = dot( transformedNormal, lVector );\n\n float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;\n\n vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n #ifdef DOUBLE_SIDED\n\n vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n\n #endif\n\n }\n\n#endif\n\nvLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;\n\n#ifdef DOUBLE_SIDED\n\n vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/map_particle_pars_fragment.glsl + +THREE.ShaderChunk[ 'map_particle_pars_fragment'] = "#ifdef USE_MAP\n\n uniform sampler2D map;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/default_vertex.glsl + +THREE.ShaderChunk[ 'default_vertex'] = "vec4 mvPosition;\n\n#ifdef USE_SKINNING\n\n mvPosition = modelViewMatrix * skinned;\n\n#endif\n\n#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )\n\n mvPosition = modelViewMatrix * vec4( morphed, 1.0 );\n\n#endif\n\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )\n\n mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\n#endif\n\ngl_Position = projectionMatrix * mvPosition;"; + +// File:src/renderers/shaders/ShaderChunk/map_pars_fragment.glsl + +THREE.ShaderChunk[ 'map_pars_fragment'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n varying vec2 vUv;\n\n#endif\n\n#ifdef USE_MAP\n\n uniform sampler2D map;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/skinnormal_vertex.glsl + +THREE.ShaderChunk[ 'skinnormal_vertex'] = "#ifdef USE_SKINNING\n\n mat4 skinMatrix = mat4( 0.0 );\n skinMatrix += skinWeight.x * boneMatX;\n skinMatrix += skinWeight.y * boneMatY;\n skinMatrix += skinWeight.z * boneMatZ;\n skinMatrix += skinWeight.w * boneMatW;\n skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\n #ifdef USE_MORPHNORMALS\n\n vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\n\n #else\n\n vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\n\n #endif\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/logdepthbuf_pars_vertex.glsl + +THREE.ShaderChunk[ 'logdepthbuf_pars_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n varying float vFragDepth;\n\n #endif\n\n uniform float logDepthBufFC;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lightmap_pars_vertex.glsl + +THREE.ShaderChunk[ 'lightmap_pars_vertex'] = "#ifdef USE_LIGHTMAP\n\n varying vec2 vUv2;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl + +THREE.ShaderChunk[ 'lights_phong_fragment'] = "vec3 normal = normalize( vNormal );\nvec3 viewPosition = normalize( vViewPosition );\n\n#ifdef DOUBLE_SIDED\n\n normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n\n#endif\n\n#ifdef USE_NORMALMAP\n\n normal = perturbNormal2Arb( -vViewPosition, normal );\n\n#elif defined( USE_BUMPMAP )\n\n normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n vec3 pointDiffuse = vec3( 0.0 );\n vec3 pointSpecular = vec3( 0.0 );\n\n for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n\n vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\n vec3 lVector = lPosition.xyz + vViewPosition.xyz;\n\n float lDistance = 1.0;\n if ( pointLightDistance[ i ] > 0.0 )\n lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\n\n lVector = normalize( lVector );\n\n // diffuse\n\n float dotProduct = dot( normal, lVector );\n\n #ifdef WRAP_AROUND\n\n float pointDiffuseWeightFull = max( dotProduct, 0.0 );\n float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\n\n vec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n\n #else\n\n float pointDiffuseWeight = max( dotProduct, 0.0 );\n\n #endif\n\n pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;\n\n // specular\n\n vec3 pointHalfVector = normalize( lVector + viewPosition );\n float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\n float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );\n\n float specularNormalization = ( shininess + 2.0 ) / 8.0;\n\n vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, pointHalfVector ), 0.0 ), 5.0 );\n pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;\n\n }\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n vec3 spotDiffuse = vec3( 0.0 );\n vec3 spotSpecular = vec3( 0.0 );\n\n for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n\n vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\n vec3 lVector = lPosition.xyz + vViewPosition.xyz;\n\n float lDistance = 1.0;\n if ( spotLightDistance[ i ] > 0.0 )\n lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\n\n lVector = normalize( lVector );\n\n float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\n\n if ( spotEffect > spotLightAngleCos[ i ] ) {\n\n spotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );\n\n // diffuse\n\n float dotProduct = dot( normal, lVector );\n\n #ifdef WRAP_AROUND\n\n float spotDiffuseWeightFull = max( dotProduct, 0.0 );\n float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\n\n vec3 spotDiffuseWeight = mix( vec3( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n\n #else\n\n float spotDiffuseWeight = max( dotProduct, 0.0 );\n\n #endif\n\n spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;\n\n // specular\n\n vec3 spotHalfVector = normalize( lVector + viewPosition );\n float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\n float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );\n\n float specularNormalization = ( shininess + 2.0 ) / 8.0;\n\n vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, spotHalfVector ), 0.0 ), 5.0 );\n spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;\n\n }\n\n }\n\n#endif\n\n#if MAX_DIR_LIGHTS > 0\n\n vec3 dirDiffuse = vec3( 0.0 );\n vec3 dirSpecular = vec3( 0.0 );\n\n for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\n\n vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\n vec3 dirVector = normalize( lDirection.xyz );\n\n // diffuse\n\n float dotProduct = dot( normal, dirVector );\n\n #ifdef WRAP_AROUND\n\n float dirDiffuseWeightFull = max( dotProduct, 0.0 );\n float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\n\n vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );\n\n #else\n\n float dirDiffuseWeight = max( dotProduct, 0.0 );\n\n #endif\n\n dirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;\n\n // specular\n\n vec3 dirHalfVector = normalize( dirVector + viewPosition );\n float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\n float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );\n\n /*\n // fresnel term from skin shader\n const float F0 = 0.128;\n\n float base = 1.0 - dot( viewPosition, dirHalfVector );\n float exponential = pow( base, 5.0 );\n\n float fresnel = exponential + F0 * ( 1.0 - exponential );\n */\n\n /*\n // fresnel term from fresnel shader\n const float mFresnelBias = 0.08;\n const float mFresnelScale = 0.3;\n const float mFresnelPower = 5.0;\n\n float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );\n */\n\n float specularNormalization = ( shininess + 2.0 ) / 8.0;\n\n // dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;\n\n vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );\n dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n\n\n }\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n vec3 hemiDiffuse = vec3( 0.0 );\n vec3 hemiSpecular = vec3( 0.0 );\n\n for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\n\n vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\n vec3 lVector = normalize( lDirection.xyz );\n\n // diffuse\n\n float dotProduct = dot( normal, lVector );\n float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\n\n vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n\n hemiDiffuse += diffuse * hemiColor;\n\n // specular (sky light)\n\n vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\n float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\n float hemiSpecularWeightSky = specularStrength * max( pow( max( hemiDotNormalHalfSky, 0.0 ), shininess ), 0.0 );\n\n // specular (ground light)\n\n vec3 lVectorGround = -lVector;\n\n vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\n float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\n float hemiSpecularWeightGround = specularStrength * max( pow( max( hemiDotNormalHalfGround, 0.0 ), shininess ), 0.0 );\n\n float dotProductGround = dot( normal, lVectorGround );\n\n float specularNormalization = ( shininess + 2.0 ) / 8.0;\n\n vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );\n vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );\n hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n\n }\n\n#endif\n\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n\n#if MAX_DIR_LIGHTS > 0\n\n totalDiffuse += dirDiffuse;\n totalSpecular += dirSpecular;\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n totalDiffuse += hemiDiffuse;\n totalSpecular += hemiSpecular;\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n totalDiffuse += pointDiffuse;\n totalSpecular += pointSpecular;\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n totalDiffuse += spotDiffuse;\n totalSpecular += spotSpecular;\n\n#endif\n\n#ifdef METAL\n\n gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );\n\n#else\n\n gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/fog_pars_fragment.glsl + +THREE.ShaderChunk[ 'fog_pars_fragment'] = "#ifdef USE_FOG\n\n uniform vec3 fogColor;\n\n #ifdef FOG_EXP2\n\n uniform float fogDensity;\n\n #else\n\n uniform float fogNear;\n uniform float fogFar;\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl + +THREE.ShaderChunk[ 'morphnormal_vertex'] = "#ifdef USE_MORPHNORMALS\n\n vec3 morphedNormal = vec3( 0.0 );\n\n morphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n morphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n morphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n morphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n\n morphedNormal += normal;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'envmap_pars_fragment'] = "#ifdef USE_ENVMAP\n\n uniform float reflectivity;\n uniform samplerCube envMap;\n uniform float flipEnvMap;\n uniform int combine;\n\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\n uniform bool useRefract;\n uniform float refractionRatio;\n\n #else\n\n varying vec3 vReflect;\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/logdepthbuf_fragment.glsl + +THREE.ShaderChunk[ 'logdepthbuf_fragment'] = "#if defined(USE_LOGDEPTHBUF) && defined(USE_LOGDEPTHBUF_EXT)\n\n gl_FragDepthEXT = log2(vFragDepth) * logDepthBufFC * 0.5;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'normalmap_pars_fragment'] = "#ifdef USE_NORMALMAP\n\n uniform sampler2D normalMap;\n uniform vec2 normalScale;\n\n // Per-Pixel Tangent Space Normal Mapping\n // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html\n\n vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\n vec3 q0 = dFdx( eye_pos.xyz );\n vec3 q1 = dFdy( eye_pos.xyz );\n vec2 st0 = dFdx( vUv.st );\n vec2 st1 = dFdy( vUv.st );\n\n vec3 S = normalize( q0 * st1.t - q1 * st0.t );\n vec3 T = normalize( -q0 * st1.s + q1 * st0.s );\n vec3 N = normalize( surf_norm );\n\n vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n mapN.xy = normalScale * mapN.xy;\n mat3 tsn = mat3( S, T, N );\n return normalize( tsn * mapN );\n\n }\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/lights_phong_pars_vertex.glsl + +THREE.ShaderChunk[ 'lights_phong_pars_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n varying vec3 vWorldPosition;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/lightmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'lightmap_pars_fragment'] = "#ifdef USE_LIGHTMAP\n\n varying vec2 vUv2;\n uniform sampler2D lightMap;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl + +THREE.ShaderChunk[ 'shadowmap_vertex'] = "#ifdef USE_SHADOWMAP\n\n for( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n\n }\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lights_phong_vertex.glsl + +THREE.ShaderChunk[ 'lights_phong_vertex'] = "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n vWorldPosition = worldPosition.xyz;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/map_fragment.glsl + +THREE.ShaderChunk[ 'map_fragment'] = "#ifdef USE_MAP\n\n vec4 texelColor = texture2D( map, vUv );\n\n #ifdef GAMMA_INPUT\n\n texelColor.xyz *= texelColor.xyz;\n\n #endif\n\n gl_FragColor = gl_FragColor * texelColor;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lightmap_vertex.glsl + +THREE.ShaderChunk[ 'lightmap_vertex'] = "#ifdef USE_LIGHTMAP\n\n vUv2 = uv2;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/map_particle_fragment.glsl + +THREE.ShaderChunk[ 'map_particle_fragment'] = "#ifdef USE_MAP\n\n gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/color_pars_fragment.glsl + +THREE.ShaderChunk[ 'color_pars_fragment'] = "#ifdef USE_COLOR\n\n varying vec3 vColor;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/color_vertex.glsl + +THREE.ShaderChunk[ 'color_vertex'] = "#ifdef USE_COLOR\n\n #ifdef GAMMA_INPUT\n\n vColor = color * color;\n\n #else\n\n vColor = color;\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/skinning_vertex.glsl + +THREE.ShaderChunk[ 'skinning_vertex'] = "#ifdef USE_SKINNING\n\n #ifdef USE_MORPHTARGETS\n\n vec4 skinVertex = bindMatrix * vec4( morphed, 1.0 );\n\n #else\n\n vec4 skinVertex = bindMatrix * vec4( position, 1.0 );\n\n #endif\n\n vec4 skinned = vec4( 0.0 );\n skinned += boneMatX * skinVertex * skinWeight.x;\n skinned += boneMatY * skinVertex * skinWeight.y;\n skinned += boneMatZ * skinVertex * skinWeight.z;\n skinned += boneMatW * skinVertex * skinWeight.w;\n skinned = bindMatrixInverse * skinned;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl + +THREE.ShaderChunk[ 'envmap_pars_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )\n\n varying vec3 vReflect;\n\n uniform float refractionRatio;\n uniform bool useRefract;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/linear_to_gamma_fragment.glsl + +THREE.ShaderChunk[ 'linear_to_gamma_fragment'] = "#ifdef GAMMA_OUTPUT\n\n gl_FragColor.xyz = sqrt( gl_FragColor.xyz );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/color_pars_vertex.glsl + +THREE.ShaderChunk[ 'color_pars_vertex'] = "#ifdef USE_COLOR\n\n varying vec3 vColor;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lights_lambert_pars_vertex.glsl + +THREE.ShaderChunk[ 'lights_lambert_pars_vertex'] = "uniform vec3 ambient;\nuniform vec3 diffuse;\nuniform vec3 emissive;\n\nuniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n uniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n\n#endif\n\n#ifdef WRAP_AROUND\n\n uniform vec3 wrapRGB;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/map_pars_vertex.glsl + +THREE.ShaderChunk[ 'map_pars_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n varying vec2 vUv;\n uniform vec4 offsetRepeat;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/envmap_fragment.glsl + +THREE.ShaderChunk[ 'envmap_fragment'] = "#ifdef USE_ENVMAP\n\n vec3 reflectVec;\n\n #if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\n vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\n // http://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations\n // Transforming Normal Vectors with the Inverse Transformation\n\n vec3 worldNormal = normalize( vec3( vec4( normal, 0.0 ) * viewMatrix ) );\n\n if ( useRefract ) {\n\n reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\n } else { \n\n reflectVec = reflect( cameraToVertex, worldNormal );\n\n }\n\n #else\n\n reflectVec = vReflect;\n\n #endif\n\n #ifdef DOUBLE_SIDED\n\n float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\n #else\n\n vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\n #endif\n\n #ifdef GAMMA_INPUT\n\n cubeColor.xyz *= cubeColor.xyz;\n\n #endif\n\n if ( combine == 1 ) {\n\n gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );\n\n } else if ( combine == 2 ) {\n\n gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;\n\n } else {\n\n gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );\n\n }\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/specularmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'specularmap_pars_fragment'] = "#ifdef USE_SPECULARMAP\n\n uniform sampler2D specularMap;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/logdepthbuf_vertex.glsl + +THREE.ShaderChunk[ 'logdepthbuf_vertex'] = "#ifdef USE_LOGDEPTHBUF\n\n gl_Position.z = log2(max(1e-6, gl_Position.w + 1.0)) * logDepthBufFC;\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n vFragDepth = 1.0 + gl_Position.w;\n\n#else\n\n gl_Position.z = (gl_Position.z - 1.0) * gl_Position.w;\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl + +THREE.ShaderChunk[ 'morphtarget_pars_vertex'] = "#ifdef USE_MORPHTARGETS\n\n #ifndef USE_MORPHNORMALS\n\n uniform float morphTargetInfluences[ 8 ];\n\n #else\n\n uniform float morphTargetInfluences[ 4 ];\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/specularmap_fragment.glsl + +THREE.ShaderChunk[ 'specularmap_fragment'] = "float specularStrength;\n\n#ifdef USE_SPECULARMAP\n\n vec4 texelSpecular = texture2D( specularMap, vUv );\n specularStrength = texelSpecular.r;\n\n#else\n\n specularStrength = 1.0;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/fog_fragment.glsl + +THREE.ShaderChunk[ 'fog_fragment'] = "#ifdef USE_FOG\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n float depth = gl_FragDepthEXT / gl_FragCoord.w;\n\n #else\n\n float depth = gl_FragCoord.z / gl_FragCoord.w;\n\n #endif\n\n #ifdef FOG_EXP2\n\n const float LOG2 = 1.442695;\n float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\n fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n\n #else\n\n float fogFactor = smoothstep( fogNear, fogFar, depth );\n\n #endif\n \n gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'bumpmap_pars_fragment'] = "#ifdef USE_BUMPMAP\n\n uniform sampler2D bumpMap;\n uniform float bumpScale;\n\n // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen\n // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html\n\n // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)\n\n vec2 dHdxy_fwd() {\n\n vec2 dSTdx = dFdx( vUv );\n vec2 dSTdy = dFdy( vUv );\n\n float Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\n return vec2( dBx, dBy );\n\n }\n\n vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\n vec3 vSigmaX = dFdx( surf_pos );\n vec3 vSigmaY = dFdy( surf_pos );\n vec3 vN = surf_norm; // normalized\n\n vec3 R1 = cross( vSigmaY, vN );\n vec3 R2 = cross( vN, vSigmaX );\n\n float fDet = dot( vSigmaX, R1 );\n\n vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n return normalize( abs( fDet ) * surf_norm - vGrad );\n\n }\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl + +THREE.ShaderChunk[ 'defaultnormal_vertex'] = "vec3 objectNormal;\n\n#ifdef USE_SKINNING\n\n objectNormal = skinnedNormal.xyz;\n\n#endif\n\n#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )\n\n objectNormal = morphedNormal;\n\n#endif\n\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )\n\n objectNormal = normal;\n\n#endif\n\n#ifdef FLIP_SIDED\n\n objectNormal = -objectNormal;\n\n#endif\n\nvec3 transformedNormal = normalMatrix * objectNormal;"; + +// File:src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl + +THREE.ShaderChunk[ 'lights_phong_pars_fragment'] = "uniform vec3 ambientLightColor;\n\n#if MAX_DIR_LIGHTS > 0\n\n uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\n uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n\n#endif\n\n#if MAX_HEMI_LIGHTS > 0\n\n uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\n uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n\n#endif\n\n#if MAX_POINT_LIGHTS > 0\n\n uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n\n uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\n uniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0\n\n uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\n uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\n uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\n uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n\n uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n\n#endif\n\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP ) || defined( USE_ENVMAP )\n\n varying vec3 vWorldPosition;\n\n#endif\n\n#ifdef WRAP_AROUND\n\n uniform vec3 wrapRGB;\n\n#endif\n\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;"; + +// File:src/renderers/shaders/ShaderChunk/skinbase_vertex.glsl + +THREE.ShaderChunk[ 'skinbase_vertex'] = "#ifdef USE_SKINNING\n\n mat4 boneMatX = getBoneMatrix( skinIndex.x );\n mat4 boneMatY = getBoneMatrix( skinIndex.y );\n mat4 boneMatZ = getBoneMatrix( skinIndex.z );\n mat4 boneMatW = getBoneMatrix( skinIndex.w );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/map_vertex.glsl + +THREE.ShaderChunk[ 'map_vertex'] = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP )\n\n vUv = uv * offsetRepeat.zw + offsetRepeat.xy;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl + +THREE.ShaderChunk[ 'lightmap_fragment'] = "#ifdef USE_LIGHTMAP\n\n gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl + +THREE.ShaderChunk[ 'shadowmap_pars_vertex'] = "#ifdef USE_SHADOWMAP\n\n varying vec4 vShadowCoord[ MAX_SHADOWS ];\n uniform mat4 shadowMatrix[ MAX_SHADOWS ];\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/color_fragment.glsl + +THREE.ShaderChunk[ 'color_fragment'] = "#ifdef USE_COLOR\n\n gl_FragColor = gl_FragColor * vec4( vColor, 1.0 );\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl + +THREE.ShaderChunk[ 'morphtarget_vertex'] = "#ifdef USE_MORPHTARGETS\n\n vec3 morphed = vec3( 0.0 );\n morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\n #ifndef USE_MORPHNORMALS\n\n morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\n #endif\n\n morphed += position;\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/envmap_vertex.glsl + +THREE.ShaderChunk[ 'envmap_vertex'] = "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )\n\n vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;\n worldNormal = normalize( worldNormal );\n\n vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\n if ( useRefract ) {\n\n vReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\n } else {\n\n vReflect = reflect( cameraToVertex, worldNormal );\n\n }\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/shadowmap_fragment.glsl + +THREE.ShaderChunk[ 'shadowmap_fragment'] = "#ifdef USE_SHADOWMAP\n\n #ifdef SHADOWMAP_DEBUG\n\n vec3 frustumColors[3];\n frustumColors[0] = vec3( 1.0, 0.5, 0.0 );\n frustumColors[1] = vec3( 0.0, 1.0, 0.8 );\n frustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n\n #endif\n\n #ifdef SHADOWMAP_CASCADE\n\n int inFrustumCount = 0;\n\n #endif\n\n float fDepth;\n vec3 shadowColor = vec3( 1.0 );\n\n for( int i = 0; i < MAX_SHADOWS; i ++ ) {\n\n vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\n\n // if ( something && something ) breaks ATI OpenGL shader compiler\n // if ( all( something, something ) ) using this instead\n\n bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n bool inFrustum = all( inFrustumVec );\n\n // don't shadow pixels outside of light frustum\n // use just first frustum (for cascades)\n // don't shadow pixels behind far plane of light frustum\n\n #ifdef SHADOWMAP_CASCADE\n\n inFrustumCount += int( inFrustum );\n bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\n\n #else\n\n bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\n #endif\n\n bool frustumTest = all( frustumTestVec );\n\n if ( frustumTest ) {\n\n shadowCoord.z += shadowBias[ i ];\n\n #if defined( SHADOWMAP_TYPE_PCF )\n\n // Percentage-close filtering\n // (9 pixel kernel)\n // http://fabiensanglard.net/shadowmappingPCF/\n\n float shadow = 0.0;\n\n /*\n // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL\n // must enroll loop manually\n\n for ( float y = -1.25; y <= 1.25; y += 1.25 )\n for ( float x = -1.25; x <= 1.25; x += 1.25 ) {\n\n vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );\n\n // doesn't seem to produce any noticeable visual difference compared to simple texture2D lookup\n //vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );\n\n float fDepth = unpackDepth( rgbaDepth );\n\n if ( fDepth < shadowCoord.z )\n shadow += 1.0;\n\n }\n\n shadow /= 9.0;\n\n */\n\n const float shadowDelta = 1.0 / 9.0;\n\n float xPixelOffset = 1.0 / shadowMapSize[ i ].x;\n float yPixelOffset = 1.0 / shadowMapSize[ i ].y;\n\n float dx0 = -1.25 * xPixelOffset;\n float dy0 = -1.25 * yPixelOffset;\n float dx1 = 1.25 * xPixelOffset;\n float dy1 = 1.25 * yPixelOffset;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n if ( fDepth < shadowCoord.z ) shadow += shadowDelta;\n\n shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n\n #elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\n // Percentage-close filtering\n // (9 pixel kernel)\n // http://fabiensanglard.net/shadowmappingPCF/\n\n float shadow = 0.0;\n\n float xPixelOffset = 1.0 / shadowMapSize[ i ].x;\n float yPixelOffset = 1.0 / shadowMapSize[ i ].y;\n\n float dx0 = -1.0 * xPixelOffset;\n float dy0 = -1.0 * yPixelOffset;\n float dx1 = 1.0 * xPixelOffset;\n float dy1 = 1.0 * yPixelOffset;\n\n mat3 shadowKernel;\n mat3 depthKernel;\n\n depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\n depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\n depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\n depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\n depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\n depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\n depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\n depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\n depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\n\n vec3 shadowZ = vec3( shadowCoord.z );\n shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\n shadowKernel[0] *= vec3(0.25);\n\n shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\n shadowKernel[1] *= vec3(0.25);\n\n shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\n shadowKernel[2] *= vec3(0.25);\n\n vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\n\n shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\n shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\n\n vec4 shadowValues;\n shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\n shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\n shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\n shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\n\n shadow = dot( shadowValues, vec4( 1.0 ) );\n\n shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n\n #else\n\n vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\n float fDepth = unpackDepth( rgbaDepth );\n\n if ( fDepth < shadowCoord.z )\n\n // spot with multiple shadows is darker\n\n shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\n\n // spot with multiple shadows has the same color as single shadow spot\n\n // shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );\n\n #endif\n\n }\n\n\n #ifdef SHADOWMAP_DEBUG\n\n #ifdef SHADOWMAP_CASCADE\n\n if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];\n\n #else\n\n if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];\n\n #endif\n\n #endif\n\n }\n\n #ifdef GAMMA_OUTPUT\n\n shadowColor *= shadowColor;\n\n #endif\n\n gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl + +THREE.ShaderChunk[ 'worldpos_vertex'] = "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )\n\n #ifdef USE_SKINNING\n\n vec4 worldPosition = modelMatrix * skinned;\n\n #endif\n\n #if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\n\n vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );\n\n #endif\n\n #if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\n\n vec4 worldPosition = modelMatrix * vec4( position, 1.0 );\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl + +THREE.ShaderChunk[ 'shadowmap_pars_fragment'] = "#ifdef USE_SHADOWMAP\n\n uniform sampler2D shadowMap[ MAX_SHADOWS ];\n uniform vec2 shadowMapSize[ MAX_SHADOWS ];\n\n uniform float shadowDarkness[ MAX_SHADOWS ];\n uniform float shadowBias[ MAX_SHADOWS ];\n\n varying vec4 vShadowCoord[ MAX_SHADOWS ];\n\n float unpackDepth( const in vec4 rgba_depth ) {\n\n const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\n float depth = dot( rgba_depth, bit_shift );\n return depth;\n\n }\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/skinning_pars_vertex.glsl + +THREE.ShaderChunk[ 'skinning_pars_vertex'] = "#ifdef USE_SKINNING\n\n uniform mat4 bindMatrix;\n uniform mat4 bindMatrixInverse;\n\n #ifdef BONE_TEXTURE\n\n uniform sampler2D boneTexture;\n uniform int boneTextureWidth;\n uniform int boneTextureHeight;\n\n mat4 getBoneMatrix( const in float i ) {\n\n float j = i * 4.0;\n float x = mod( j, float( boneTextureWidth ) );\n float y = floor( j / float( boneTextureWidth ) );\n\n float dx = 1.0 / float( boneTextureWidth );\n float dy = 1.0 / float( boneTextureHeight );\n\n y = dy * ( y + 0.5 );\n\n vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\n mat4 bone = mat4( v1, v2, v3, v4 );\n\n return bone;\n\n }\n\n #else\n\n uniform mat4 boneGlobalMatrices[ MAX_BONES ];\n\n mat4 getBoneMatrix( const in float i ) {\n\n mat4 bone = boneGlobalMatrices[ int(i) ];\n return bone;\n\n }\n\n #endif\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/logdepthbuf_pars_fragment.glsl + +THREE.ShaderChunk[ 'logdepthbuf_pars_fragment'] = "#ifdef USE_LOGDEPTHBUF\n\n uniform float logDepthBufFC;\n\n #ifdef USE_LOGDEPTHBUF_EXT\n\n #extension GL_EXT_frag_depth : enable\n varying float vFragDepth;\n\n #endif\n\n#endif"; + +// File:src/renderers/shaders/ShaderChunk/alphamap_fragment.glsl + +THREE.ShaderChunk[ 'alphamap_fragment'] = "#ifdef USE_ALPHAMAP\n\n gl_FragColor.a *= texture2D( alphaMap, vUv ).g;\n\n#endif\n"; + +// File:src/renderers/shaders/ShaderChunk/alphamap_pars_fragment.glsl + +THREE.ShaderChunk[ 'alphamap_pars_fragment'] = "#ifdef USE_ALPHAMAP\n\n uniform sampler2D alphaMap;\n\n#endif\n"; + +// File:src/renderers/shaders/UniformsUtils.js + +/** + * Uniform Utilities + */ + +THREE.UniformsUtils = { + + merge: function ( uniforms ) { + + var u, p, tmp, merged = {}; + + for ( u = 0; u < uniforms.length; u ++ ) { + + tmp = this.clone( uniforms[ u ] ); + + for ( p in tmp ) { + + merged[ p ] = tmp[ p ]; + + } + + } + + return merged; + + }, + + clone: function ( uniforms_src ) { + + var u, p, parameter, parameter_src, uniforms_dst = {}; + + for ( u in uniforms_src ) { + + uniforms_dst[ u ] = {}; + + for ( p in uniforms_src[ u ] ) { + + parameter_src = uniforms_src[ u ][ p ]; + + if ( parameter_src instanceof THREE.Color || + parameter_src instanceof THREE.Vector2 || + parameter_src instanceof THREE.Vector3 || + parameter_src instanceof THREE.Vector4 || + parameter_src instanceof THREE.Matrix4 || + parameter_src instanceof THREE.Texture ) { + + uniforms_dst[ u ][ p ] = parameter_src.clone(); + + } else if ( parameter_src instanceof Array ) { + + uniforms_dst[ u ][ p ] = parameter_src.slice(); + + } else { + + uniforms_dst[ u ][ p ] = parameter_src; + + } + + } + + } + + return uniforms_dst; + + } + +}; + +// File:src/renderers/shaders/UniformsLib.js + +/** + * Uniforms library for shared webgl shaders + */ + +THREE.UniformsLib = { + + common: { + + "diffuse" : { type: "c", value: new THREE.Color( 0xeeeeee ) }, + "opacity" : { type: "f", value: 1.0 }, + + "map" : { type: "t", value: null }, + "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) }, + + "lightMap" : { type: "t", value: null }, + "specularMap" : { type: "t", value: null }, + "alphaMap" : { type: "t", value: null }, + + "envMap" : { type: "t", value: null }, + "flipEnvMap" : { type: "f", value: - 1 }, + "useRefract" : { type: "i", value: 0 }, + "reflectivity" : { type: "f", value: 1.0 }, + "refractionRatio" : { type: "f", value: 0.98 }, + "combine" : { type: "i", value: 0 }, + + "morphTargetInfluences" : { type: "f", value: 0 } + + }, + + bump: { + + "bumpMap" : { type: "t", value: null }, + "bumpScale" : { type: "f", value: 1 } + + }, + + normalmap: { + + "normalMap" : { type: "t", value: null }, + "normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) } + }, + + fog : { + + "fogDensity" : { type: "f", value: 0.00025 }, + "fogNear" : { type: "f", value: 1 }, + "fogFar" : { type: "f", value: 2000 }, + "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) } + + }, + + lights: { + + "ambientLightColor" : { type: "fv", value: [] }, + + "directionalLightDirection" : { type: "fv", value: [] }, + "directionalLightColor" : { type: "fv", value: [] }, + + "hemisphereLightDirection" : { type: "fv", value: [] }, + "hemisphereLightSkyColor" : { type: "fv", value: [] }, + "hemisphereLightGroundColor" : { type: "fv", value: [] }, + + "pointLightColor" : { type: "fv", value: [] }, + "pointLightPosition" : { type: "fv", value: [] }, + "pointLightDistance" : { type: "fv1", value: [] }, + + "spotLightColor" : { type: "fv", value: [] }, + "spotLightPosition" : { type: "fv", value: [] }, + "spotLightDirection" : { type: "fv", value: [] }, + "spotLightDistance" : { type: "fv1", value: [] }, + "spotLightAngleCos" : { type: "fv1", value: [] }, + "spotLightExponent" : { type: "fv1", value: [] } + + }, + + particle: { + + "psColor" : { type: "c", value: new THREE.Color( 0xeeeeee ) }, + "opacity" : { type: "f", value: 1.0 }, + "size" : { type: "f", value: 1.0 }, + "scale" : { type: "f", value: 1.0 }, + "map" : { type: "t", value: null }, + + "fogDensity" : { type: "f", value: 0.00025 }, + "fogNear" : { type: "f", value: 1 }, + "fogFar" : { type: "f", value: 2000 }, + "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) } + + }, + + shadowmap: { + + "shadowMap": { type: "tv", value: [] }, + "shadowMapSize": { type: "v2v", value: [] }, + + "shadowBias" : { type: "fv1", value: [] }, + "shadowDarkness": { type: "fv1", value: [] }, + + "shadowMatrix" : { type: "m4v", value: [] } + + } + +}; + +// File:src/renderers/shaders/ShaderLib.js + +/** + * Webgl Shader Library for three.js + * + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + */ + + +THREE.ShaderLib = { + + 'basic': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "shadowmap" ] + + ] ), + + vertexShader: [ + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + + " #ifdef USE_ENVMAP", + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + " #endif", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = vec4( diffuse, opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'lambert': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) }, + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) }, + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + } + + ] ), + + vertexShader: [ + + "#define LAMBERT", + + "varying vec3 vLightFront;", + + "#ifdef DOUBLE_SIDED", + + " varying vec3 vLightBack;", + + "#endif", + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "lights_lambert_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_lambert_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + + "varying vec3 vLightFront;", + + "#ifdef DOUBLE_SIDED", + + " varying vec3 vLightBack;", + + "#endif", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = vec4( vec3( 1.0 ), opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + + " #ifdef DOUBLE_SIDED", + + //"float isFront = float( gl_FrontFacing );", + //"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;", + + " if ( gl_FrontFacing )", + " gl_FragColor.xyz *= vLightFront;", + " else", + " gl_FragColor.xyz *= vLightBack;", + + " #else", + + " gl_FragColor.xyz *= vLightFront;", + + " #endif", + + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'phong': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "bump" ], + THREE.UniformsLib[ "normalmap" ], + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) }, + "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) }, + "specular" : { type: "c", value: new THREE.Color( 0x111111 ) }, + "shininess": { type: "f", value: 30 }, + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + } + + ] ), + + vertexShader: [ + + "#define PHONG", + + "varying vec3 vViewPosition;", + "varying vec3 vNormal;", + + THREE.ShaderChunk[ "map_pars_vertex" ], + THREE.ShaderChunk[ "lightmap_pars_vertex" ], + THREE.ShaderChunk[ "envmap_pars_vertex" ], + THREE.ShaderChunk[ "lights_phong_pars_vertex" ], + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "map_vertex" ], + THREE.ShaderChunk[ "lightmap_vertex" ], + THREE.ShaderChunk[ "color_vertex" ], + + THREE.ShaderChunk[ "morphnormal_vertex" ], + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + THREE.ShaderChunk[ "defaultnormal_vertex" ], + + " vNormal = normalize( transformedNormal );", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + " vViewPosition = -mvPosition.xyz;", + + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "envmap_vertex" ], + THREE.ShaderChunk[ "lights_phong_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + "uniform vec3 ambient;", + "uniform vec3 emissive;", + "uniform vec3 specular;", + "uniform float shininess;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_pars_fragment" ], + THREE.ShaderChunk[ "alphamap_pars_fragment" ], + THREE.ShaderChunk[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "lights_phong_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "bumpmap_pars_fragment" ], + THREE.ShaderChunk[ "normalmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = vec4( vec3( 1.0 ), opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_fragment" ], + THREE.ShaderChunk[ "alphamap_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "specularmap_fragment" ], + + THREE.ShaderChunk[ "lights_phong_fragment" ], + + THREE.ShaderChunk[ "lightmap_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "envmap_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'particle_basic': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "particle" ], + THREE.UniformsLib[ "shadowmap" ] + + ] ), + + vertexShader: [ + + "uniform float size;", + "uniform float scale;", + + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "color_vertex" ], + + " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + + " #ifdef USE_SIZEATTENUATION", + " gl_PointSize = size * ( scale / length( mvPosition.xyz ) );", + " #else", + " gl_PointSize = size;", + " #endif", + + " gl_Position = projectionMatrix * mvPosition;", + + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + THREE.ShaderChunk[ "worldpos_vertex" ], + THREE.ShaderChunk[ "shadowmap_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 psColor;", + "uniform float opacity;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "map_particle_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = vec4( psColor, opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "map_particle_fragment" ], + THREE.ShaderChunk[ "alphatest_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "shadowmap_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'dashed': { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "common" ], + THREE.UniformsLib[ "fog" ], + + { + "scale" : { type: "f", value: 1 }, + "dashSize" : { type: "f", value: 1 }, + "totalSize": { type: "f", value: 2 } + } + + ] ), + + vertexShader: [ + + "uniform float scale;", + "attribute float lineDistance;", + + "varying float vLineDistance;", + + THREE.ShaderChunk[ "color_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "color_vertex" ], + + " vLineDistance = scale * lineDistance;", + + " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + " gl_Position = projectionMatrix * mvPosition;", + + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform vec3 diffuse;", + "uniform float opacity;", + + "uniform float dashSize;", + "uniform float totalSize;", + + "varying float vLineDistance;", + + THREE.ShaderChunk[ "color_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " if ( mod( vLineDistance, totalSize ) > dashSize ) {", + + " discard;", + + " }", + + " gl_FragColor = vec4( diffuse, opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + THREE.ShaderChunk[ "color_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n") + + }, + + 'depth': { + + uniforms: { + + "mNear": { type: "f", value: 1.0 }, + "mFar" : { type: "f", value: 2000.0 }, + "opacity" : { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float mNear;", + "uniform float mFar;", + "uniform float opacity;", + + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + + " #ifdef USE_LOGDEPTHBUF_EXT", + + " float depth = gl_FragDepthEXT / gl_FragCoord.w;", + + " #else", + + " float depth = gl_FragCoord.z / gl_FragCoord.w;", + + " #endif", + + " float color = 1.0 - smoothstep( mNear, mFar, depth );", + " gl_FragColor = vec4( vec3( color ), opacity );", + + "}" + + ].join("\n") + + }, + + 'normal': { + + uniforms: { + + "opacity" : { type: "f", value: 1.0 } + + }, + + vertexShader: [ + + "varying vec3 vNormal;", + + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + " vNormal = normalize( normalMatrix * normal );", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + "varying vec3 vNormal;", + + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + + "}" + + ].join("\n") + + }, + + /* ------------------------------------------------------------------------- + // Normal map shader + // - Blinn-Phong + // - normal + diffuse + specular + AO + displacement + reflection + shadow maps + // - point and directional lights (use with "lights: true" material option) + ------------------------------------------------------------------------- */ + + 'normalmap' : { + + uniforms: THREE.UniformsUtils.merge( [ + + THREE.UniformsLib[ "fog" ], + THREE.UniformsLib[ "lights" ], + THREE.UniformsLib[ "shadowmap" ], + + { + + "enableAO" : { type: "i", value: 0 }, + "enableDiffuse" : { type: "i", value: 0 }, + "enableSpecular" : { type: "i", value: 0 }, + "enableReflection" : { type: "i", value: 0 }, + "enableDisplacement": { type: "i", value: 0 }, + + "tDisplacement": { type: "t", value: null }, // must go first as this is vertex texture + "tDiffuse" : { type: "t", value: null }, + "tCube" : { type: "t", value: null }, + "tNormal" : { type: "t", value: null }, + "tSpecular" : { type: "t", value: null }, + "tAO" : { type: "t", value: null }, + + "uNormalScale": { type: "v2", value: new THREE.Vector2( 1, 1 ) }, + + "uDisplacementBias": { type: "f", value: 0.0 }, + "uDisplacementScale": { type: "f", value: 1.0 }, + + "diffuse": { type: "c", value: new THREE.Color( 0xffffff ) }, + "specular": { type: "c", value: new THREE.Color( 0x111111 ) }, + "ambient": { type: "c", value: new THREE.Color( 0xffffff ) }, + "shininess": { type: "f", value: 30 }, + "opacity": { type: "f", value: 1 }, + + "useRefract": { type: "i", value: 0 }, + "refractionRatio": { type: "f", value: 0.98 }, + "reflectivity": { type: "f", value: 0.5 }, + + "uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) }, + "uRepeat" : { type: "v2", value: new THREE.Vector2( 1, 1 ) }, + + "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) } + + } + + ] ), + + fragmentShader: [ + + "uniform vec3 ambient;", + "uniform vec3 diffuse;", + "uniform vec3 specular;", + "uniform float shininess;", + "uniform float opacity;", + + "uniform bool enableDiffuse;", + "uniform bool enableSpecular;", + "uniform bool enableAO;", + "uniform bool enableReflection;", + + "uniform sampler2D tDiffuse;", + "uniform sampler2D tNormal;", + "uniform sampler2D tSpecular;", + "uniform sampler2D tAO;", + + "uniform samplerCube tCube;", + + "uniform vec2 uNormalScale;", + + "uniform bool useRefract;", + "uniform float refractionRatio;", + "uniform float reflectivity;", + + "varying vec3 vTangent;", + "varying vec3 vBinormal;", + "varying vec3 vNormal;", + "varying vec2 vUv;", + + "uniform vec3 ambientLightColor;", + + "#if MAX_DIR_LIGHTS > 0", + + " uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];", + " uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + " uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];", + " uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];", + " uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + " uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];", + " uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];", + " uniform float pointLightDistance[ MAX_POINT_LIGHTS ];", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + " uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];", + " uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];", + " uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];", + " uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];", + " uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];", + " uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];", + + "#endif", + + "#ifdef WRAP_AROUND", + + " uniform vec3 wrapRGB;", + + "#endif", + + "varying vec3 vWorldPosition;", + "varying vec3 vViewPosition;", + + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + + " gl_FragColor = vec4( vec3( 1.0 ), opacity );", + + " vec3 specularTex = vec3( 1.0 );", + + " vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;", + " normalTex.xy *= uNormalScale;", + " normalTex = normalize( normalTex );", + + " if( enableDiffuse ) {", + + " #ifdef GAMMA_INPUT", + + " vec4 texelColor = texture2D( tDiffuse, vUv );", + " texelColor.xyz *= texelColor.xyz;", + + " gl_FragColor = gl_FragColor * texelColor;", + + " #else", + + " gl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );", + + " #endif", + + " }", + + " if( enableAO ) {", + + " #ifdef GAMMA_INPUT", + + " vec4 aoColor = texture2D( tAO, vUv );", + " aoColor.xyz *= aoColor.xyz;", + + " gl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;", + + " #else", + + " gl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;", + + " #endif", + + " }", + + THREE.ShaderChunk[ "alphatest_fragment" ], + + " if( enableSpecular )", + " specularTex = texture2D( tSpecular, vUv ).xyz;", + + " mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );", + " vec3 finalNormal = tsb * normalTex;", + + " #ifdef FLIP_SIDED", + + " finalNormal = -finalNormal;", + + " #endif", + + " vec3 normal = normalize( finalNormal );", + " vec3 viewPosition = normalize( vViewPosition );", + + // point lights + + " #if MAX_POINT_LIGHTS > 0", + + " vec3 pointDiffuse = vec3( 0.0 );", + " vec3 pointSpecular = vec3( 0.0 );", + + " for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {", + + " vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );", + " vec3 pointVector = lPosition.xyz + vViewPosition.xyz;", + + " float pointDistance = 1.0;", + " if ( pointLightDistance[ i ] > 0.0 )", + " pointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );", + + " pointVector = normalize( pointVector );", + + // diffuse + + " #ifdef WRAP_AROUND", + + " float pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );", + " float pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );", + + " vec3 pointDiffuseWeight = mix( vec3( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );", + + " #else", + + " float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );", + + " #endif", + + " pointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;", + + // specular + + " vec3 pointHalfVector = normalize( pointVector + viewPosition );", + " float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );", + " float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );", + + " float specularNormalization = ( shininess + 2.0 ) / 8.0;", + + " vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( pointVector, pointHalfVector ), 0.0 ), 5.0 );", + " pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;", + + " }", + + " #endif", + + // spot lights + + " #if MAX_SPOT_LIGHTS > 0", + + " vec3 spotDiffuse = vec3( 0.0 );", + " vec3 spotSpecular = vec3( 0.0 );", + + " for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {", + + " vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );", + " vec3 spotVector = lPosition.xyz + vViewPosition.xyz;", + + " float spotDistance = 1.0;", + " if ( spotLightDistance[ i ] > 0.0 )", + " spotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );", + + " spotVector = normalize( spotVector );", + + " float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );", + + " if ( spotEffect > spotLightAngleCos[ i ] ) {", + + " spotEffect = max( pow( max( spotEffect, 0.0 ), spotLightExponent[ i ] ), 0.0 );", + + // diffuse + + " #ifdef WRAP_AROUND", + + " float spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );", + " float spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );", + + " vec3 spotDiffuseWeight = mix( vec3( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );", + + " #else", + + " float spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );", + + " #endif", + + " spotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;", + + // specular + + " vec3 spotHalfVector = normalize( spotVector + viewPosition );", + " float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );", + " float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );", + + " float specularNormalization = ( shininess + 2.0 ) / 8.0;", + + " vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( spotVector, spotHalfVector ), 0.0 ), 5.0 );", + " spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;", + + " }", + + " }", + + " #endif", + + // directional lights + + " #if MAX_DIR_LIGHTS > 0", + + " vec3 dirDiffuse = vec3( 0.0 );", + " vec3 dirSpecular = vec3( 0.0 );", + + " for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {", + + " vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", + " vec3 dirVector = normalize( lDirection.xyz );", + + // diffuse + + " #ifdef WRAP_AROUND", + + " float directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );", + " float directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );", + + " vec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );", + + " #else", + + " float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );", + + " #endif", + + " dirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;", + + // specular + + " vec3 dirHalfVector = normalize( dirVector + viewPosition );", + " float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );", + " float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );", + + " float specularNormalization = ( shininess + 2.0 ) / 8.0;", + + " vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );", + " dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;", + + " }", + + " #endif", + + // hemisphere lights + + " #if MAX_HEMI_LIGHTS > 0", + + " vec3 hemiDiffuse = vec3( 0.0 );", + " vec3 hemiSpecular = vec3( 0.0 );" , + + " for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {", + + " vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );", + " vec3 lVector = normalize( lDirection.xyz );", + + // diffuse + + " float dotProduct = dot( normal, lVector );", + " float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;", + + " vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );", + + " hemiDiffuse += diffuse * hemiColor;", + + // specular (sky light) + + + " vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );", + " float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;", + " float hemiSpecularWeightSky = specularTex.r * max( pow( max( hemiDotNormalHalfSky, 0.0 ), shininess ), 0.0 );", + + // specular (ground light) + + " vec3 lVectorGround = -lVector;", + + " vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );", + " float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;", + " float hemiSpecularWeightGround = specularTex.r * max( pow( max( hemiDotNormalHalfGround, 0.0 ), shininess ), 0.0 );", + + " float dotProductGround = dot( normal, lVectorGround );", + + " float specularNormalization = ( shininess + 2.0 ) / 8.0;", + + " vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );", + " vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );", + " hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );", + + " }", + + " #endif", + + // all lights contribution summation + + " vec3 totalDiffuse = vec3( 0.0 );", + " vec3 totalSpecular = vec3( 0.0 );", + + " #if MAX_DIR_LIGHTS > 0", + + " totalDiffuse += dirDiffuse;", + " totalSpecular += dirSpecular;", + + " #endif", + + " #if MAX_HEMI_LIGHTS > 0", + + " totalDiffuse += hemiDiffuse;", + " totalSpecular += hemiSpecular;", + + " #endif", + + " #if MAX_POINT_LIGHTS > 0", + + " totalDiffuse += pointDiffuse;", + " totalSpecular += pointSpecular;", + + " #endif", + + " #if MAX_SPOT_LIGHTS > 0", + + " totalDiffuse += spotDiffuse;", + " totalSpecular += spotSpecular;", + + " #endif", + + " #ifdef METAL", + + " gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );", + + " #else", + + " gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;", + + " #endif", + + " if ( enableReflection ) {", + + " vec3 vReflect;", + " vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );", + + " if ( useRefract ) {", + + " vReflect = refract( cameraToVertex, normal, refractionRatio );", + + " } else {", + + " vReflect = reflect( cameraToVertex, normal );", + + " }", + + " vec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );", + + " #ifdef GAMMA_INPUT", + + " cubeColor.xyz *= cubeColor.xyz;", + + " #endif", + + " gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );", + + " }", + + THREE.ShaderChunk[ "shadowmap_fragment" ], + THREE.ShaderChunk[ "linear_to_gamma_fragment" ], + THREE.ShaderChunk[ "fog_fragment" ], + + "}" + + ].join("\n"), + + vertexShader: [ + + "attribute vec4 tangent;", + + "uniform vec2 uOffset;", + "uniform vec2 uRepeat;", + + "uniform bool enableDisplacement;", + + "#ifdef VERTEX_TEXTURES", + + " uniform sampler2D tDisplacement;", + " uniform float uDisplacementScale;", + " uniform float uDisplacementBias;", + + "#endif", + + "varying vec3 vTangent;", + "varying vec3 vBinormal;", + "varying vec3 vNormal;", + "varying vec2 vUv;", + + "varying vec3 vWorldPosition;", + "varying vec3 vViewPosition;", + + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "shadowmap_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "skinnormal_vertex" ], + + // normal, tangent and binormal vectors + + " #ifdef USE_SKINNING", + + " vNormal = normalize( normalMatrix * skinnedNormal.xyz );", + + " vec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );", + " vTangent = normalize( normalMatrix * skinnedTangent.xyz );", + + " #else", + + " vNormal = normalize( normalMatrix * normal );", + " vTangent = normalize( normalMatrix * tangent.xyz );", + + " #endif", + + " vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );", + + " vUv = uv * uRepeat + uOffset;", + + // displacement mapping + + " vec3 displacedPosition;", + + " #ifdef VERTEX_TEXTURES", + + " if ( enableDisplacement ) {", + + " vec3 dv = texture2D( tDisplacement, uv ).xyz;", + " float df = uDisplacementScale * dv.x + uDisplacementBias;", + " displacedPosition = position + normalize( normal ) * df;", + + " } else {", + + " #ifdef USE_SKINNING", + + " vec4 skinVertex = bindMatrix * vec4( position, 1.0 );", + + " vec4 skinned = vec4( 0.0 );", + " skinned += boneMatX * skinVertex * skinWeight.x;", + " skinned += boneMatY * skinVertex * skinWeight.y;", + " skinned += boneMatZ * skinVertex * skinWeight.z;", + " skinned += boneMatW * skinVertex * skinWeight.w;", + " skinned = bindMatrixInverse * skinned;", + + " displacedPosition = skinned.xyz;", + + " #else", + + " displacedPosition = position;", + + " #endif", + + " }", + + " #else", + + " #ifdef USE_SKINNING", + + " vec4 skinVertex = bindMatrix * vec4( position, 1.0 );", + + " vec4 skinned = vec4( 0.0 );", + " skinned += boneMatX * skinVertex * skinWeight.x;", + " skinned += boneMatY * skinVertex * skinWeight.y;", + " skinned += boneMatZ * skinVertex * skinWeight.z;", + " skinned += boneMatW * skinVertex * skinWeight.w;", + " skinned = bindMatrixInverse * skinned;", + + " displacedPosition = skinned.xyz;", + + " #else", + + " displacedPosition = position;", + + " #endif", + + " #endif", + + // + + " vec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );", + " vec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );", + + " gl_Position = projectionMatrix * mvPosition;", + + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + // + + " vWorldPosition = worldPosition.xyz;", + " vViewPosition = -mvPosition.xyz;", + + // shadows + + " #ifdef USE_SHADOWMAP", + + " for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + " vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;", + + " }", + + " #endif", + + "}" + + ].join("\n") + + }, + + /* ------------------------------------------------------------------------- + // Cube map shader + ------------------------------------------------------------------------- */ + + 'cube': { + + uniforms: { "tCube": { type: "t", value: null }, + "tFlip": { type: "f", value: - 1 } }, + + vertexShader: [ + + "varying vec3 vWorldPosition;", + + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + " vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", + " vWorldPosition = worldPosition.xyz;", + + " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform samplerCube tCube;", + "uniform float tFlip;", + + "varying vec3 vWorldPosition;", + + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "void main() {", + + " gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + + "}" + + ].join("\n") + + }, + + /* Depth encoding into RGBA texture + * + * based on SpiderGL shadow map example + * http://spidergl.org/example.php?id=6 + * + * originally from + * http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/page__whichpage__1%25EF%25BF%25BD + * + * see also + * http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/ + */ + + 'depthRGBA': { + + uniforms: {}, + + vertexShader: [ + + THREE.ShaderChunk[ "morphtarget_pars_vertex" ], + THREE.ShaderChunk[ "skinning_pars_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + THREE.ShaderChunk[ "logdepthbuf_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ], + + "vec4 pack_depth( const in float depth ) {", + + " const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );", + " const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );", + " vec4 res = mod( depth * bit_shift * vec4( 255 ), vec4( 256 ) ) / vec4( 255 );", // " vec4 res = fract( depth * bit_shift );", + " res -= res.xxyz * bit_mask;", + " return res;", + + "}", + + "void main() {", + + THREE.ShaderChunk[ "logdepthbuf_fragment" ], + + " #ifdef USE_LOGDEPTHBUF_EXT", + + " gl_FragData[ 0 ] = pack_depth( gl_FragDepthEXT );", + + " #else", + + " gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );", + + " #endif", + + //"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z / gl_FragCoord.w );", + //"float z = ( ( gl_FragCoord.z / gl_FragCoord.w ) - 3.0 ) / ( 4000.0 - 3.0 );", + //"gl_FragData[ 0 ] = pack_depth( z );", + //"gl_FragData[ 0 ] = vec4( z, z, z, 1.0 );", + + "}" + + ].join("\n") + + } + +}; + +// File:src/renderers/WebGLRenderer.js + +/** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + */ + +THREE.WebGLRenderer = function ( parameters ) { + + console.log( 'THREE.WebGLRenderer', THREE.REVISION ); + + parameters = parameters || {}; + + var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ), + _context = parameters.context !== undefined ? parameters.context : null, + + _precision = parameters.precision !== undefined ? parameters.precision : 'highp', + + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, + _depth = parameters.depth !== undefined ? parameters.depth : true, + _stencil = parameters.stencil !== undefined ? parameters.stencil : true, + _antialias = parameters.antialias !== undefined ? parameters.antialias : false, + _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, + _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, + _logarithmicDepthBuffer = parameters.logarithmicDepthBuffer !== undefined ? parameters.logarithmicDepthBuffer : false, + + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0; + + var opaqueObjects = []; + var transparentObjects = []; + + // public properties + + this.domElement = _canvas; + this.context = null; + this.devicePixelRatio = parameters.devicePixelRatio !== undefined + ? parameters.devicePixelRatio + : self.devicePixelRatio !== undefined + ? self.devicePixelRatio + : 1; + + // clearing + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + // scene graph + + this.sortObjects = true; + + // physically based shading + + this.gammaInput = false; + this.gammaOutput = false; + + // shadow map + + this.shadowMapEnabled = false; + this.shadowMapAutoUpdate = true; + this.shadowMapType = THREE.PCFShadowMap; + this.shadowMapCullFace = THREE.CullFaceFront; + this.shadowMapDebug = false; + this.shadowMapCascade = false; + + // morphs + + this.maxMorphTargets = 8; + this.maxMorphNormals = 4; + + // flags + + this.autoScaleCubemaps = true; + + // custom render plugins + + this.renderPluginsPre = []; + this.renderPluginsPost = []; + + // info + + this.info = { + + memory: { + + programs: 0, + geometries: 0, + textures: 0 + + }, + + render: { + + calls: 0, + vertices: 0, + faces: 0, + points: 0 + + } + + }; + + // internal properties + + var _this = this, + + _programs = [], + + // internal state cache + + _currentProgram = null, + _currentFramebuffer = null, + _currentMaterialId = - 1, + _currentGeometryGroupHash = null, + _currentCamera = null, + + _usedTextureUnits = 0, + + // GL state cache + + _oldDoubleSided = - 1, + _oldFlipSided = - 1, + + _oldBlending = - 1, + + _oldBlendEquation = - 1, + _oldBlendSrc = - 1, + _oldBlendDst = - 1, + + _oldDepthTest = - 1, + _oldDepthWrite = - 1, + + _oldPolygonOffset = null, + _oldPolygonOffsetFactor = null, + _oldPolygonOffsetUnits = null, + + _oldLineWidth = null, + + _viewportX = 0, + _viewportY = 0, + _viewportWidth = _canvas.width, + _viewportHeight = _canvas.height, + _currentWidth = 0, + _currentHeight = 0, + + _newAttributes = new Uint8Array( 16 ), + _enabledAttributes = new Uint8Array( 16 ), + + // frustum + + _frustum = new THREE.Frustum(), + + // camera matrices cache + + _projScreenMatrix = new THREE.Matrix4(), + _projScreenMatrixPS = new THREE.Matrix4(), + + _vector3 = new THREE.Vector3(), + + // light arrays cache + + _direction = new THREE.Vector3(), + + _lightsNeedUpdate = true, + + _lights = { + + ambient: [ 0, 0, 0 ], + directional: { length: 0, colors:[], positions: [] }, + point: { length: 0, colors: [], positions: [], distances: [] }, + spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [] }, + hemi: { length: 0, skyColors: [], groundColors: [], positions: [] } + + }; + + // initialize + + var _gl; + + var _glExtensionTextureFloat; + var _glExtensionTextureFloatLinear; + var _glExtensionStandardDerivatives; + var _glExtensionTextureFilterAnisotropic; + var _glExtensionCompressedTextureS3TC; + var _glExtensionElementIndexUint; + var _glExtensionFragDepth; + + + initGL(); + + setDefaultGLState(); + + this.context = _gl; + + // GPU capabilities + + var _maxTextures = _gl.getParameter( _gl.MAX_TEXTURE_IMAGE_UNITS ); + var _maxVertexTextures = _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + var _maxTextureSize = _gl.getParameter( _gl.MAX_TEXTURE_SIZE ); + var _maxCubemapSize = _gl.getParameter( _gl.MAX_CUBE_MAP_TEXTURE_SIZE ); + + var _maxAnisotropy = _glExtensionTextureFilterAnisotropic ? _gl.getParameter( _glExtensionTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT ) : 0; + + var _supportsVertexTextures = ( _maxVertexTextures > 0 ); + var _supportsBoneTextures = _supportsVertexTextures && _glExtensionTextureFloat; + + var _compressedTextureFormats = _glExtensionCompressedTextureS3TC ? _gl.getParameter( _gl.COMPRESSED_TEXTURE_FORMATS ) : []; + + // + + var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_FLOAT ); + var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT ); + var _vertexShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_FLOAT ); + + var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT ); + var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT ); + var _fragmentShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_FLOAT ); + + // clamp precision to maximum available + + var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0; + var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0; + + if ( _precision === 'highp' && ! highpAvailable ) { + + if ( mediumpAvailable ) { + + _precision = 'mediump'; + console.warn( 'THREE.WebGLRenderer: highp not supported, using mediump.' ); + + } else { + + _precision = 'lowp'; + console.warn( 'THREE.WebGLRenderer: highp and mediump not supported, using lowp.' ); + + } + + } + + if ( _precision === 'mediump' && ! mediumpAvailable ) { + + _precision = 'lowp'; + console.warn( 'THREE.WebGLRenderer: mediump not supported, using lowp.' ); + + } + + // API + + this.getContext = function () { + + return _gl; + + }; + + this.supportsVertexTextures = function () { + + return _supportsVertexTextures; + + }; + + this.supportsFloatTextures = function () { + + return _glExtensionTextureFloat; + + }; + + this.supportsStandardDerivatives = function () { + + return _glExtensionStandardDerivatives; + + }; + + this.supportsCompressedTextureS3TC = function () { + + return _glExtensionCompressedTextureS3TC; + + }; + + this.getMaxAnisotropy = function () { + + return _maxAnisotropy; + + }; + + this.getPrecision = function () { + + return _precision; + + }; + + this.setSize = function ( width, height, updateStyle ) { + + _canvas.width = width * this.devicePixelRatio; + _canvas.height = height * this.devicePixelRatio; + + if ( updateStyle !== false ) { + + _canvas.style.width = width + 'px'; + _canvas.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + }; + + this.setViewport = function ( x, y, width, height ) { + + _viewportX = x * this.devicePixelRatio; + _viewportY = y * this.devicePixelRatio; + + _viewportWidth = width * this.devicePixelRatio; + _viewportHeight = height * this.devicePixelRatio; + + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); + + }; + + this.setScissor = function ( x, y, width, height ) { + + _gl.scissor( + x * this.devicePixelRatio, + y * this.devicePixelRatio, + width * this.devicePixelRatio, + height * this.devicePixelRatio + ); + + }; + + this.enableScissorTest = function ( enable ) { + + enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST ); + + }; + + // Clearing + + this.setClearColor = function ( color, alpha ) { + + _clearColor.set( color ); + _clearAlpha = alpha !== undefined ? alpha : 1; + + _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); + + }; + + this.setClearColorHex = function ( hex, alpha ) { + + console.warn( 'THREE.WebGLRenderer: .setClearColorHex() is being removed. Use .setClearColor() instead.' ); + this.setClearColor( hex, alpha ); + + }; + + this.getClearColor = function () { + + return _clearColor; + + }; + + this.getClearAlpha = function () { + + return _clearAlpha; + + }; + + this.clear = function ( color, depth, stencil ) { + + var bits = 0; + + if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT; + if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT; + if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT; + + _gl.clear( bits ); + + }; + + this.clearColor = function () { + + _gl.clear( _gl.COLOR_BUFFER_BIT ); + + }; + + this.clearDepth = function () { + + _gl.clear( _gl.DEPTH_BUFFER_BIT ); + + }; + + this.clearStencil = function () { + + _gl.clear( _gl.STENCIL_BUFFER_BIT ); + + }; + + this.clearTarget = function ( renderTarget, color, depth, stencil ) { + + this.setRenderTarget( renderTarget ); + this.clear( color, depth, stencil ); + + }; + + // Plugins + + this.addPostPlugin = function ( plugin ) { + + plugin.init( this ); + this.renderPluginsPost.push( plugin ); + + }; + + this.addPrePlugin = function ( plugin ) { + + plugin.init( this ); + this.renderPluginsPre.push( plugin ); + + }; + + // Rendering + + this.updateShadowMap = function ( scene, camera ) { + + _currentProgram = null; + _oldBlending = - 1; + _oldDepthTest = - 1; + _oldDepthWrite = - 1; + _currentGeometryGroupHash = - 1; + _currentMaterialId = - 1; + _lightsNeedUpdate = true; + _oldDoubleSided = - 1; + _oldFlipSided = - 1; + + initObjects( scene ); + + this.shadowMapPlugin.update( scene, camera ); + + }; + + // Internal functions + + // Buffer allocation + + function createParticleBuffers ( geometry ) { + + geometry.__webglVertexBuffer = _gl.createBuffer(); + geometry.__webglColorBuffer = _gl.createBuffer(); + + _this.info.memory.geometries ++; + + }; + + function createLineBuffers ( geometry ) { + + geometry.__webglVertexBuffer = _gl.createBuffer(); + geometry.__webglColorBuffer = _gl.createBuffer(); + geometry.__webglLineDistanceBuffer = _gl.createBuffer(); + + _this.info.memory.geometries ++; + + }; + + function createMeshBuffers ( geometryGroup ) { + + geometryGroup.__webglVertexBuffer = _gl.createBuffer(); + geometryGroup.__webglNormalBuffer = _gl.createBuffer(); + geometryGroup.__webglTangentBuffer = _gl.createBuffer(); + geometryGroup.__webglColorBuffer = _gl.createBuffer(); + geometryGroup.__webglUVBuffer = _gl.createBuffer(); + geometryGroup.__webglUV2Buffer = _gl.createBuffer(); + + geometryGroup.__webglSkinIndicesBuffer = _gl.createBuffer(); + geometryGroup.__webglSkinWeightsBuffer = _gl.createBuffer(); + + geometryGroup.__webglFaceBuffer = _gl.createBuffer(); + geometryGroup.__webglLineBuffer = _gl.createBuffer(); + + var m, ml; + + if ( geometryGroup.numMorphTargets ) { + + geometryGroup.__webglMorphTargetsBuffers = []; + + for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + geometryGroup.__webglMorphTargetsBuffers.push( _gl.createBuffer() ); + + } + + } + + if ( geometryGroup.numMorphNormals ) { + + geometryGroup.__webglMorphNormalsBuffers = []; + + for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + geometryGroup.__webglMorphNormalsBuffers.push( _gl.createBuffer() ); + + } + + } + + _this.info.memory.geometries ++; + + }; + + // Events + + var onGeometryDispose = function ( event ) { + + var geometry = event.target; + + geometry.removeEventListener( 'dispose', onGeometryDispose ); + + deallocateGeometry( geometry ); + + }; + + var onTextureDispose = function ( event ) { + + var texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + deallocateTexture( texture ); + + _this.info.memory.textures --; + + + }; + + var onRenderTargetDispose = function ( event ) { + + var renderTarget = event.target; + + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); + + deallocateRenderTarget( renderTarget ); + + _this.info.memory.textures --; + + }; + + var onMaterialDispose = function ( event ) { + + var material = event.target; + + material.removeEventListener( 'dispose', onMaterialDispose ); + + deallocateMaterial( material ); + + }; + + // Buffer deallocation + + var deleteBuffers = function ( geometry ) { + + if ( geometry.__webglVertexBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglVertexBuffer ); + if ( geometry.__webglNormalBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglNormalBuffer ); + if ( geometry.__webglTangentBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglTangentBuffer ); + if ( geometry.__webglColorBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglColorBuffer ); + if ( geometry.__webglUVBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglUVBuffer ); + if ( geometry.__webglUV2Buffer !== undefined ) _gl.deleteBuffer( geometry.__webglUV2Buffer ); + + if ( geometry.__webglSkinIndicesBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinIndicesBuffer ); + if ( geometry.__webglSkinWeightsBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinWeightsBuffer ); + + if ( geometry.__webglFaceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglFaceBuffer ); + if ( geometry.__webglLineBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineBuffer ); + + if ( geometry.__webglLineDistanceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineDistanceBuffer ); + // custom attributes + + if ( geometry.__webglCustomAttributesList !== undefined ) { + + for ( var id in geometry.__webglCustomAttributesList ) { + + _gl.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer ); + + } + + } + + _this.info.memory.geometries --; + + }; + + var deallocateGeometry = function ( geometry ) { + + geometry.__webglInit = undefined; + + if ( geometry instanceof THREE.BufferGeometry ) { + + var attributes = geometry.attributes; + + for ( var key in attributes ) { + + if ( attributes[ key ].buffer !== undefined ) { + + _gl.deleteBuffer( attributes[ key ].buffer ); + + } + + } + + _this.info.memory.geometries --; + + } else { + + if ( geometry.geometryGroups !== undefined ) { + + for ( var i = 0,l = geometry.geometryGroupsList.length; i 0 ) { + + geometryGroup.__uvArray = new Float32Array( nvertices * 2 ); + + } + + if ( geometry.faceVertexUvs.length > 1 ) { + + geometryGroup.__uv2Array = new Float32Array( nvertices * 2 ); + + } + + } + + if ( object.geometry.skinWeights.length && object.geometry.skinIndices.length ) { + + geometryGroup.__skinIndexArray = new Float32Array( nvertices * 4 ); + geometryGroup.__skinWeightArray = new Float32Array( nvertices * 4 ); + + } + + var UintArray = _glExtensionElementIndexUint !== null && ntris > 21845 ? Uint32Array : Uint16Array; // 65535 / 3 + + geometryGroup.__typeArray = UintArray; + geometryGroup.__faceArray = new UintArray( ntris * 3 ); + geometryGroup.__lineArray = new UintArray( nlines * 2 ); + + var m, ml; + + if ( geometryGroup.numMorphTargets ) { + + geometryGroup.__morphTargetsArrays = []; + + for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + geometryGroup.__morphTargetsArrays.push( new Float32Array( nvertices * 3 ) ); + + } + + } + + if ( geometryGroup.numMorphNormals ) { + + geometryGroup.__morphNormalsArrays = []; + + for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + geometryGroup.__morphNormalsArrays.push( new Float32Array( nvertices * 3 ) ); + + } + + } + + geometryGroup.__webglFaceCount = ntris * 3; + geometryGroup.__webglLineCount = nlines * 2; + + + // custom attributes + + if ( material.attributes ) { + + if ( geometryGroup.__webglCustomAttributesList === undefined ) { + + geometryGroup.__webglCustomAttributesList = []; + + } + + for ( var a in material.attributes ) { + + // Do a shallow copy of the attribute object so different geometryGroup chunks use different + // attribute buffers which are correctly indexed in the setMeshBuffers function + + var originalAttribute = material.attributes[ a ]; + + var attribute = {}; + + for ( var property in originalAttribute ) { + + attribute[ property ] = originalAttribute[ property ]; + + } + + if ( ! attribute.__webglInitialized || attribute.createUniqueBuffers ) { + + attribute.__webglInitialized = true; + + var size = 1; // "f" and "i" + + if ( attribute.type === 'v2' ) size = 2; + else if ( attribute.type === 'v3' ) size = 3; + else if ( attribute.type === 'v4' ) size = 4; + else if ( attribute.type === 'c' ) size = 3; + + attribute.size = size; + + attribute.array = new Float32Array( nvertices * size ); + + attribute.buffer = _gl.createBuffer(); + attribute.buffer.belongsToAttribute = a; + + originalAttribute.needsUpdate = true; + attribute.__original = originalAttribute; + + } + + geometryGroup.__webglCustomAttributesList.push( attribute ); + + } + + } + + geometryGroup.__inittedArrays = true; + + }; + + function getBufferMaterial( object, geometryGroup ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ geometryGroup.materialIndex ] + : object.material; + + }; + + function materialNeedsSmoothNormals ( material ) { + + return material && material.shading !== undefined && material.shading === THREE.SmoothShading; + + }; + + function bufferGuessNormalType ( material ) { + + // only MeshBasicMaterial and MeshDepthMaterial don't need normals + + if ( ( material instanceof THREE.MeshBasicMaterial && ! material.envMap ) || material instanceof THREE.MeshDepthMaterial ) { + + return false; + + } + + if ( materialNeedsSmoothNormals( material ) ) { + + return THREE.SmoothShading; + + } else { + + return THREE.FlatShading; + + } + + }; + + function bufferGuessVertexColorType( material ) { + + if ( material.vertexColors ) { + + return material.vertexColors; + + } + + return false; + + }; + + function bufferGuessUVType( material ) { + + // material must use some texture to require uvs + + if ( material.map || + material.lightMap || + material.bumpMap || + material.normalMap || + material.specularMap || + material.alphaMap || + material instanceof THREE.ShaderMaterial ) { + + return true; + + } + + return false; + + }; + + // + + function initDirectBuffers( geometry ) { + + for ( var name in geometry.attributes ) { + + var bufferType = ( name === 'index' ) ? _gl.ELEMENT_ARRAY_BUFFER : _gl.ARRAY_BUFFER; + + var attribute = geometry.attributes[ name ]; + attribute.buffer = _gl.createBuffer(); + + _gl.bindBuffer( bufferType, attribute.buffer ); + _gl.bufferData( bufferType, attribute.array, _gl.STATIC_DRAW ); + + } + + } + + // Buffer setting + + function setParticleBuffers ( geometry, hint, object ) { + + var v, c, vertex, offset, index, color, + + vertices = geometry.vertices, + vl = vertices.length, + + colors = geometry.colors, + cl = colors.length, + + vertexArray = geometry.__vertexArray, + colorArray = geometry.__colorArray, + + sortArray = geometry.__sortArray, + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyElements = geometry.elementsNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + + customAttributes = geometry.__webglCustomAttributesList, + i, il, + a, ca, cal, value, + customAttribute; + + if ( object.sortParticles ) { + + _projScreenMatrixPS.copy( _projScreenMatrix ); + _projScreenMatrixPS.multiply( object.matrixWorld ); + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + _vector3.copy( vertex ); + _vector3.applyProjection( _projScreenMatrixPS ); + + sortArray[ v ] = [ _vector3.z, v ]; + + } + + sortArray.sort( numericalSort ); + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ sortArray[ v ][ 1 ] ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + for ( c = 0; c < cl; c ++ ) { + + offset = c * 3; + + color = colors[ sortArray[ c ][ 1 ] ]; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( ! ( customAttribute.boundTo === undefined || customAttribute.boundTo === 'vertices' ) ) continue; + + offset = 0; + + cal = customAttribute.value.length; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + customAttribute.array[ ca ] = customAttribute.value[ index ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === 'c' ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + index = sortArray[ ca ][ 1 ]; + + value = customAttribute.value[ index ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + } + + } + + } else { + + if ( dirtyVertices ) { + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + } + + if ( dirtyColors ) { + + for ( c = 0; c < cl; c ++ ) { + + color = colors[ c ]; + + offset = c * 3; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate && + ( customAttribute.boundTo === undefined || + customAttribute.boundTo === 'vertices' ) ) { + + cal = customAttribute.value.length; + + offset = 0; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + customAttribute.array[ ca ] = customAttribute.value[ ca ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === 'c' ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + } + + } + + } + + } + + if ( dirtyVertices || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyColors || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate || object.sortParticles ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + } + + } + + function setLineBuffers ( geometry, hint ) { + + var v, c, d, vertex, offset, color, + + vertices = geometry.vertices, + colors = geometry.colors, + lineDistances = geometry.lineDistances, + + vl = vertices.length, + cl = colors.length, + dl = lineDistances.length, + + vertexArray = geometry.__vertexArray, + colorArray = geometry.__colorArray, + lineDistanceArray = geometry.__lineDistanceArray, + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + dirtyLineDistances = geometry.lineDistancesNeedUpdate, + + customAttributes = geometry.__webglCustomAttributesList, + + i, il, + a, ca, cal, value, + customAttribute; + + if ( dirtyVertices ) { + + for ( v = 0; v < vl; v ++ ) { + + vertex = vertices[ v ]; + + offset = v * 3; + + vertexArray[ offset ] = vertex.x; + vertexArray[ offset + 1 ] = vertex.y; + vertexArray[ offset + 2 ] = vertex.z; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyColors ) { + + for ( c = 0; c < cl; c ++ ) { + + color = colors[ c ]; + + offset = c * 3; + + colorArray[ offset ] = color.r; + colorArray[ offset + 1 ] = color.g; + colorArray[ offset + 2 ] = color.b; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + if ( dirtyLineDistances ) { + + for ( d = 0; d < dl; d ++ ) { + + lineDistanceArray[ d ] = lineDistances[ d ]; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, lineDistanceArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( customAttribute.needsUpdate && + ( customAttribute.boundTo === undefined || + customAttribute.boundTo === 'vertices' ) ) { + + offset = 0; + + cal = customAttribute.value.length; + + if ( customAttribute.size === 1 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + customAttribute.array[ ca ] = customAttribute.value[ ca ]; + + } + + } else if ( customAttribute.size === 2 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + + offset += 2; + + } + + } else if ( customAttribute.size === 3 ) { + + if ( customAttribute.type === 'c' ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.r; + customAttribute.array[ offset + 1 ] = value.g; + customAttribute.array[ offset + 2 ] = value.b; + + offset += 3; + + } + + } else { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + + offset += 3; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + for ( ca = 0; ca < cal; ca ++ ) { + + value = customAttribute.value[ ca ]; + + customAttribute.array[ offset ] = value.x; + customAttribute.array[ offset + 1 ] = value.y; + customAttribute.array[ offset + 2 ] = value.z; + customAttribute.array[ offset + 3 ] = value.w; + + offset += 4; + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + } + + } + + function setMeshBuffers( geometryGroup, object, hint, dispose, material ) { + + if ( ! geometryGroup.__inittedArrays ) { + + return; + + } + + var normalType = bufferGuessNormalType( material ), + vertexColorType = bufferGuessVertexColorType( material ), + uvType = bufferGuessUVType( material ), + + needsSmoothNormals = ( normalType === THREE.SmoothShading ); + + var f, fl, fi, face, + vertexNormals, faceNormal, normal, + vertexColors, faceColor, + vertexTangents, + uv, uv2, v1, v2, v3, v4, t1, t2, t3, t4, n1, n2, n3, n4, + c1, c2, c3, + sw1, sw2, sw3, sw4, + si1, si2, si3, si4, + sa1, sa2, sa3, sa4, + sb1, sb2, sb3, sb4, + m, ml, i, il, + vn, uvi, uv2i, + vk, vkl, vka, + nka, chf, faceVertexNormals, + a, + + vertexIndex = 0, + + offset = 0, + offset_uv = 0, + offset_uv2 = 0, + offset_face = 0, + offset_normal = 0, + offset_tangent = 0, + offset_line = 0, + offset_color = 0, + offset_skin = 0, + offset_morphTarget = 0, + offset_custom = 0, + offset_customSrc = 0, + + value, + + vertexArray = geometryGroup.__vertexArray, + uvArray = geometryGroup.__uvArray, + uv2Array = geometryGroup.__uv2Array, + normalArray = geometryGroup.__normalArray, + tangentArray = geometryGroup.__tangentArray, + colorArray = geometryGroup.__colorArray, + + skinIndexArray = geometryGroup.__skinIndexArray, + skinWeightArray = geometryGroup.__skinWeightArray, + + morphTargetsArrays = geometryGroup.__morphTargetsArrays, + morphNormalsArrays = geometryGroup.__morphNormalsArrays, + + customAttributes = geometryGroup.__webglCustomAttributesList, + customAttribute, + + faceArray = geometryGroup.__faceArray, + lineArray = geometryGroup.__lineArray, + + geometry = object.geometry, // this is shared for all chunks + + dirtyVertices = geometry.verticesNeedUpdate, + dirtyElements = geometry.elementsNeedUpdate, + dirtyUvs = geometry.uvsNeedUpdate, + dirtyNormals = geometry.normalsNeedUpdate, + dirtyTangents = geometry.tangentsNeedUpdate, + dirtyColors = geometry.colorsNeedUpdate, + dirtyMorphTargets = geometry.morphTargetsNeedUpdate, + + vertices = geometry.vertices, + chunk_faces3 = geometryGroup.faces3, + obj_faces = geometry.faces, + + obj_uvs = geometry.faceVertexUvs[ 0 ], + obj_uvs2 = geometry.faceVertexUvs[ 1 ], + + obj_colors = geometry.colors, + + obj_skinIndices = geometry.skinIndices, + obj_skinWeights = geometry.skinWeights, + + morphTargets = geometry.morphTargets, + morphNormals = geometry.morphNormals; + + if ( dirtyVertices ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = vertices[ face.a ]; + v2 = vertices[ face.b ]; + v3 = vertices[ face.c ]; + + vertexArray[ offset ] = v1.x; + vertexArray[ offset + 1 ] = v1.y; + vertexArray[ offset + 2 ] = v1.z; + + vertexArray[ offset + 3 ] = v2.x; + vertexArray[ offset + 4 ] = v2.y; + vertexArray[ offset + 5 ] = v2.z; + + vertexArray[ offset + 6 ] = v3.x; + vertexArray[ offset + 7 ] = v3.y; + vertexArray[ offset + 8 ] = v3.z; + + offset += 9; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint ); + + } + + if ( dirtyMorphTargets ) { + + for ( vk = 0, vkl = morphTargets.length; vk < vkl; vk ++ ) { + + offset_morphTarget = 0; + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + chf = chunk_faces3[ f ]; + face = obj_faces[ chf ]; + + // morph positions + + v1 = morphTargets[ vk ].vertices[ face.a ]; + v2 = morphTargets[ vk ].vertices[ face.b ]; + v3 = morphTargets[ vk ].vertices[ face.c ]; + + vka = morphTargetsArrays[ vk ]; + + vka[ offset_morphTarget ] = v1.x; + vka[ offset_morphTarget + 1 ] = v1.y; + vka[ offset_morphTarget + 2 ] = v1.z; + + vka[ offset_morphTarget + 3 ] = v2.x; + vka[ offset_morphTarget + 4 ] = v2.y; + vka[ offset_morphTarget + 5 ] = v2.z; + + vka[ offset_morphTarget + 6 ] = v3.x; + vka[ offset_morphTarget + 7 ] = v3.y; + vka[ offset_morphTarget + 8 ] = v3.z; + + // morph normals + + if ( material.morphNormals ) { + + if ( needsSmoothNormals ) { + + faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ]; + + n1 = faceVertexNormals.a; + n2 = faceVertexNormals.b; + n3 = faceVertexNormals.c; + + } else { + + n1 = morphNormals[ vk ].faceNormals[ chf ]; + n2 = n1; + n3 = n1; + + } + + nka = morphNormalsArrays[ vk ]; + + nka[ offset_morphTarget ] = n1.x; + nka[ offset_morphTarget + 1 ] = n1.y; + nka[ offset_morphTarget + 2 ] = n1.z; + + nka[ offset_morphTarget + 3 ] = n2.x; + nka[ offset_morphTarget + 4 ] = n2.y; + nka[ offset_morphTarget + 5 ] = n2.z; + + nka[ offset_morphTarget + 6 ] = n3.x; + nka[ offset_morphTarget + 7 ] = n3.y; + nka[ offset_morphTarget + 8 ] = n3.z; + + } + + // + + offset_morphTarget += 9; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ vk ] ); + _gl.bufferData( _gl.ARRAY_BUFFER, morphTargetsArrays[ vk ], hint ); + + if ( material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ vk ] ); + _gl.bufferData( _gl.ARRAY_BUFFER, morphNormalsArrays[ vk ], hint ); + + } + + } + + } + + if ( obj_skinWeights.length ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + // weights + + sw1 = obj_skinWeights[ face.a ]; + sw2 = obj_skinWeights[ face.b ]; + sw3 = obj_skinWeights[ face.c ]; + + skinWeightArray[ offset_skin ] = sw1.x; + skinWeightArray[ offset_skin + 1 ] = sw1.y; + skinWeightArray[ offset_skin + 2 ] = sw1.z; + skinWeightArray[ offset_skin + 3 ] = sw1.w; + + skinWeightArray[ offset_skin + 4 ] = sw2.x; + skinWeightArray[ offset_skin + 5 ] = sw2.y; + skinWeightArray[ offset_skin + 6 ] = sw2.z; + skinWeightArray[ offset_skin + 7 ] = sw2.w; + + skinWeightArray[ offset_skin + 8 ] = sw3.x; + skinWeightArray[ offset_skin + 9 ] = sw3.y; + skinWeightArray[ offset_skin + 10 ] = sw3.z; + skinWeightArray[ offset_skin + 11 ] = sw3.w; + + // indices + + si1 = obj_skinIndices[ face.a ]; + si2 = obj_skinIndices[ face.b ]; + si3 = obj_skinIndices[ face.c ]; + + skinIndexArray[ offset_skin ] = si1.x; + skinIndexArray[ offset_skin + 1 ] = si1.y; + skinIndexArray[ offset_skin + 2 ] = si1.z; + skinIndexArray[ offset_skin + 3 ] = si1.w; + + skinIndexArray[ offset_skin + 4 ] = si2.x; + skinIndexArray[ offset_skin + 5 ] = si2.y; + skinIndexArray[ offset_skin + 6 ] = si2.z; + skinIndexArray[ offset_skin + 7 ] = si2.w; + + skinIndexArray[ offset_skin + 8 ] = si3.x; + skinIndexArray[ offset_skin + 9 ] = si3.y; + skinIndexArray[ offset_skin + 10 ] = si3.z; + skinIndexArray[ offset_skin + 11 ] = si3.w; + + offset_skin += 12; + + } + + if ( offset_skin > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, skinIndexArray, hint ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, skinWeightArray, hint ); + + } + + } + + if ( dirtyColors && vertexColorType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexColors = face.vertexColors; + faceColor = face.color; + + if ( vertexColors.length === 3 && vertexColorType === THREE.VertexColors ) { + + c1 = vertexColors[ 0 ]; + c2 = vertexColors[ 1 ]; + c3 = vertexColors[ 2 ]; + + } else { + + c1 = faceColor; + c2 = faceColor; + c3 = faceColor; + + } + + colorArray[ offset_color ] = c1.r; + colorArray[ offset_color + 1 ] = c1.g; + colorArray[ offset_color + 2 ] = c1.b; + + colorArray[ offset_color + 3 ] = c2.r; + colorArray[ offset_color + 4 ] = c2.g; + colorArray[ offset_color + 5 ] = c2.b; + + colorArray[ offset_color + 6 ] = c3.r; + colorArray[ offset_color + 7 ] = c3.g; + colorArray[ offset_color + 8 ] = c3.b; + + offset_color += 9; + + } + + if ( offset_color > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint ); + + } + + } + + if ( dirtyTangents && geometry.hasTangents ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexTangents = face.vertexTangents; + + t1 = vertexTangents[ 0 ]; + t2 = vertexTangents[ 1 ]; + t3 = vertexTangents[ 2 ]; + + tangentArray[ offset_tangent ] = t1.x; + tangentArray[ offset_tangent + 1 ] = t1.y; + tangentArray[ offset_tangent + 2 ] = t1.z; + tangentArray[ offset_tangent + 3 ] = t1.w; + + tangentArray[ offset_tangent + 4 ] = t2.x; + tangentArray[ offset_tangent + 5 ] = t2.y; + tangentArray[ offset_tangent + 6 ] = t2.z; + tangentArray[ offset_tangent + 7 ] = t2.w; + + tangentArray[ offset_tangent + 8 ] = t3.x; + tangentArray[ offset_tangent + 9 ] = t3.y; + tangentArray[ offset_tangent + 10 ] = t3.z; + tangentArray[ offset_tangent + 11 ] = t3.w; + + offset_tangent += 12; + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, tangentArray, hint ); + + } + + if ( dirtyNormals && normalType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + vertexNormals = face.vertexNormals; + faceNormal = face.normal; + + if ( vertexNormals.length === 3 && needsSmoothNormals ) { + + for ( i = 0; i < 3; i ++ ) { + + vn = vertexNormals[ i ]; + + normalArray[ offset_normal ] = vn.x; + normalArray[ offset_normal + 1 ] = vn.y; + normalArray[ offset_normal + 2 ] = vn.z; + + offset_normal += 3; + + } + + } else { + + for ( i = 0; i < 3; i ++ ) { + + normalArray[ offset_normal ] = faceNormal.x; + normalArray[ offset_normal + 1 ] = faceNormal.y; + normalArray[ offset_normal + 2 ] = faceNormal.z; + + offset_normal += 3; + + } + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint ); + + } + + if ( dirtyUvs && obj_uvs && uvType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + fi = chunk_faces3[ f ]; + + uv = obj_uvs[ fi ]; + + if ( uv === undefined ) continue; + + for ( i = 0; i < 3; i ++ ) { + + uvi = uv[ i ]; + + uvArray[ offset_uv ] = uvi.x; + uvArray[ offset_uv + 1 ] = uvi.y; + + offset_uv += 2; + + } + + } + + if ( offset_uv > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, uvArray, hint ); + + } + + } + + if ( dirtyUvs && obj_uvs2 && uvType ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + fi = chunk_faces3[ f ]; + + uv2 = obj_uvs2[ fi ]; + + if ( uv2 === undefined ) continue; + + for ( i = 0; i < 3; i ++ ) { + + uv2i = uv2[ i ]; + + uv2Array[ offset_uv2 ] = uv2i.x; + uv2Array[ offset_uv2 + 1 ] = uv2i.y; + + offset_uv2 += 2; + + } + + } + + if ( offset_uv2 > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, uv2Array, hint ); + + } + + } + + if ( dirtyElements ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + faceArray[ offset_face ] = vertexIndex; + faceArray[ offset_face + 1 ] = vertexIndex + 1; + faceArray[ offset_face + 2 ] = vertexIndex + 2; + + offset_face += 3; + + lineArray[ offset_line ] = vertexIndex; + lineArray[ offset_line + 1 ] = vertexIndex + 1; + + lineArray[ offset_line + 2 ] = vertexIndex; + lineArray[ offset_line + 3 ] = vertexIndex + 2; + + lineArray[ offset_line + 4 ] = vertexIndex + 1; + lineArray[ offset_line + 5 ] = vertexIndex + 2; + + offset_line += 6; + + vertexIndex += 3; + + } + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faceArray, hint ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, lineArray, hint ); + + } + + if ( customAttributes ) { + + for ( i = 0, il = customAttributes.length; i < il; i ++ ) { + + customAttribute = customAttributes[ i ]; + + if ( ! customAttribute.__original.needsUpdate ) continue; + + offset_custom = 0; + offset_customSrc = 0; + + if ( customAttribute.size === 1 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === 'vertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + customAttribute.array[ offset_custom ] = customAttribute.value[ face.a ]; + customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ]; + customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ]; + + offset_custom += 3; + + } + + } else if ( customAttribute.boundTo === 'faces' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + customAttribute.array[ offset_custom ] = value; + customAttribute.array[ offset_custom + 1 ] = value; + customAttribute.array[ offset_custom + 2 ] = value; + + offset_custom += 3; + + } + + } + + } else if ( customAttribute.size === 2 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === 'vertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + + customAttribute.array[ offset_custom + 2 ] = v2.x; + customAttribute.array[ offset_custom + 3 ] = v2.y; + + customAttribute.array[ offset_custom + 4 ] = v3.x; + customAttribute.array[ offset_custom + 5 ] = v3.y; + + offset_custom += 6; + + } + + } else if ( customAttribute.boundTo === 'faces' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + + customAttribute.array[ offset_custom + 2 ] = v2.x; + customAttribute.array[ offset_custom + 3 ] = v2.y; + + customAttribute.array[ offset_custom + 4 ] = v3.x; + customAttribute.array[ offset_custom + 5 ] = v3.y; + + offset_custom += 6; + + } + + } + + } else if ( customAttribute.size === 3 ) { + + var pp; + + if ( customAttribute.type === 'c' ) { + + pp = [ 'r', 'g', 'b' ]; + + } else { + + pp = [ 'x', 'y', 'z' ]; + + } + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === 'vertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } else if ( customAttribute.boundTo === 'faces' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } else if ( customAttribute.boundTo === 'faceVertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value[ 0 ]; + v2 = value[ 1 ]; + v3 = value[ 2 ]; + + customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ]; + + customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ]; + customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ]; + customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ]; + + offset_custom += 9; + + } + + } + + } else if ( customAttribute.size === 4 ) { + + if ( customAttribute.boundTo === undefined || customAttribute.boundTo === 'vertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + face = obj_faces[ chunk_faces3[ f ] ]; + + v1 = customAttribute.value[ face.a ]; + v2 = customAttribute.value[ face.b ]; + v3 = customAttribute.value[ face.c ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } else if ( customAttribute.boundTo === 'faces' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value; + v2 = value; + v3 = value; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } else if ( customAttribute.boundTo === 'faceVertices' ) { + + for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) { + + value = customAttribute.value[ chunk_faces3[ f ] ]; + + v1 = value[ 0 ]; + v2 = value[ 1 ]; + v3 = value[ 2 ]; + + customAttribute.array[ offset_custom ] = v1.x; + customAttribute.array[ offset_custom + 1 ] = v1.y; + customAttribute.array[ offset_custom + 2 ] = v1.z; + customAttribute.array[ offset_custom + 3 ] = v1.w; + + customAttribute.array[ offset_custom + 4 ] = v2.x; + customAttribute.array[ offset_custom + 5 ] = v2.y; + customAttribute.array[ offset_custom + 6 ] = v2.z; + customAttribute.array[ offset_custom + 7 ] = v2.w; + + customAttribute.array[ offset_custom + 8 ] = v3.x; + customAttribute.array[ offset_custom + 9 ] = v3.y; + customAttribute.array[ offset_custom + 10 ] = v3.z; + customAttribute.array[ offset_custom + 11 ] = v3.w; + + offset_custom += 12; + + } + + } + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint ); + + } + + } + + if ( dispose ) { + + delete geometryGroup.__inittedArrays; + delete geometryGroup.__colorArray; + delete geometryGroup.__normalArray; + delete geometryGroup.__tangentArray; + delete geometryGroup.__uvArray; + delete geometryGroup.__uv2Array; + delete geometryGroup.__faceArray; + delete geometryGroup.__vertexArray; + delete geometryGroup.__lineArray; + delete geometryGroup.__skinIndexArray; + delete geometryGroup.__skinWeightArray; + + } + + }; + + function setDirectBuffers( geometry, hint ) { + + var attributes = geometry.attributes; + + var attributeName, attributeItem; + + for ( attributeName in attributes ) { + + attributeItem = attributes[ attributeName ]; + + if ( attributeItem.needsUpdate ) { + + if ( attributeName === 'index' ) { + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.buffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.array, hint ); + + } else { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, attributeItem.array, hint ); + + } + + attributeItem.needsUpdate = false; + + } + + } + + } + + // Buffer rendering + + this.renderBufferImmediate = function ( object, program, material ) { + + initAttributes(); + + if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = _gl.createBuffer(); + if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = _gl.createBuffer(); + if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = _gl.createBuffer(); + if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = _gl.createBuffer(); + + if ( object.hasPositions ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglVertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); + enableAttribute( program.attributes.position ); + _gl.vertexAttribPointer( program.attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer ); + + if ( material.shading === THREE.FlatShading ) { + + var nx, ny, nz, + nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz, + normalArray, + i, il = object.count * 3; + + for ( i = 0; i < il; i += 9 ) { + + normalArray = object.normalArray; + + nax = normalArray[ i ]; + nay = normalArray[ i + 1 ]; + naz = normalArray[ i + 2 ]; + + nbx = normalArray[ i + 3 ]; + nby = normalArray[ i + 4 ]; + nbz = normalArray[ i + 5 ]; + + ncx = normalArray[ i + 6 ]; + ncy = normalArray[ i + 7 ]; + ncz = normalArray[ i + 8 ]; + + nx = ( nax + nbx + ncx ) / 3; + ny = ( nay + nby + ncy ) / 3; + nz = ( naz + nbz + ncz ) / 3; + + normalArray[ i ] = nx; + normalArray[ i + 1 ] = ny; + normalArray[ i + 2 ] = nz; + + normalArray[ i + 3 ] = nx; + normalArray[ i + 4 ] = ny; + normalArray[ i + 5 ] = nz; + + normalArray[ i + 6 ] = nx; + normalArray[ i + 7 ] = ny; + normalArray[ i + 8 ] = nz; + + } + + } + + _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); + enableAttribute( program.attributes.normal ); + _gl.vertexAttribPointer( program.attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasUvs && material.map ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglUvBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); + enableAttribute( program.attributes.uv ); + _gl.vertexAttribPointer( program.attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasColors && material.vertexColors !== THREE.NoColors ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglColorBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); + enableAttribute( program.attributes.color ); + _gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } + + disableUnusedAttributes(); + + _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); + + object.count = 0; + + }; + + function setupVertexAttributes( material, programAttributes, geometryAttributes, startIndex ) { + + for ( var attributeName in programAttributes ) { + + var attributePointer = programAttributes[ attributeName ]; + var attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + var attributeSize = attributeItem.itemSize; + + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32 + + } else if ( material.defaultAttributeValues ) { + + if ( material.defaultAttributeValues[ attributeName ].length === 2 ) { + + _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) { + + _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] ); + + } + + } + + } + + } + + disableUnusedAttributes(); + + } + + this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) { + + if ( material.visible === false ) return; + + var linewidth, a, attribute; + var attributeItem, attributeName, attributePointer, attributeSize; + + var program = setProgram( camera, lights, fog, material, object ); + + var programAttributes = program.attributes; + var geometryAttributes = geometry.attributes; + + var updateBuffers = false, + wireframeBit = material.wireframe ? 1 : 0, + geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; + + if ( geometryHash !== _currentGeometryGroupHash ) { + + _currentGeometryGroupHash = geometryHash; + updateBuffers = true; + + } + + if ( updateBuffers ) { + + initAttributes(); + + } + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + var index = geometryAttributes[ 'index' ]; + + if ( index ) { + + // indexed triangles + + var type, size; + + if ( index.array instanceof Uint32Array ) { + + type = _gl.UNSIGNED_INT; + size = 4; + + } else { + + type = _gl.UNSIGNED_SHORT; + size = 2; + + } + + var offsets = geometry.offsets; + + if ( offsets.length === 0 ) { + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, 0 ); + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + _gl.drawElements( _gl.TRIANGLES, index.array.length, type, 0 ); + + _this.info.render.calls ++; + _this.info.render.vertices += index.array.length; // not really true, here vertices can be shared + _this.info.render.faces += index.array.length / 3; + + } else { + + // if there is more than 1 chunk + // must set attribute pointers to use new offsets for each chunk + // even if geometry and materials didn't change + + updateBuffers = true; + + for ( var i = 0, il = offsets.length; i < il; i ++ ) { + + var startIndex = offsets[ i ].index; + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, startIndex ); + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed triangles + + _gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, type, offsets[ i ].start * size ); + + _this.info.render.calls ++; + _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared + _this.info.render.faces += offsets[ i ].count / 3; + + } + + } + + } else { + + // non-indexed triangles + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, 0 ); + + } + + var position = geometry.attributes[ 'position' ]; + + // render non-indexed triangles + + _gl.drawArrays( _gl.TRIANGLES, 0, position.array.length / 3 ); + + _this.info.render.calls ++; + _this.info.render.vertices += position.array.length / 3; + _this.info.render.faces += position.array.length / 9; + + } + + } else if ( object instanceof THREE.PointCloud ) { + + // render particles + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, 0 ); + + } + + var position = geometryAttributes[ 'position' ]; + + // render particles + + _gl.drawArrays( _gl.POINTS, 0, position.array.length / 3 ); + + _this.info.render.calls ++; + _this.info.render.points += position.array.length / 3; + + } else if ( object instanceof THREE.Line ) { + + var mode = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + var index = geometryAttributes[ 'index' ]; + + if ( index ) { + + // indexed lines + + var type, size; + + if ( index.array instanceof Uint32Array ) { + + type = _gl.UNSIGNED_INT; + size = 4; + + } else { + + type = _gl.UNSIGNED_SHORT; + size = 2; + + } + + var offsets = geometry.offsets; + + if ( offsets.length === 0 ) { + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, 0 ); + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + _gl.drawElements( mode, index.array.length, type, 0 ); // 2 bytes per Uint16Array + + _this.info.render.calls ++; + _this.info.render.vertices += index.array.length; // not really true, here vertices can be shared + + } else { + + // if there is more than 1 chunk + // must set attribute pointers to use new offsets for each chunk + // even if geometry and materials didn't change + + if ( offsets.length > 1 ) updateBuffers = true; + + for ( var i = 0, il = offsets.length; i < il; i ++ ) { + + var startIndex = offsets[ i ].index; + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, startIndex ); + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed lines + + _gl.drawElements( mode, offsets[ i ].count, type, offsets[ i ].start * size ); // 2 bytes per Uint16Array + + _this.info.render.calls ++; + _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared + + } + + } + + } else { + + // non-indexed lines + + if ( updateBuffers ) { + + setupVertexAttributes( material, programAttributes, geometryAttributes, 0 ); + + } + + var position = geometryAttributes[ 'position' ]; + + _gl.drawArrays( mode, 0, position.array.length / 3 ); + + _this.info.render.calls ++; + _this.info.render.points += position.array.length / 3; + + } + + } + + }; + + this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) { + + if ( material.visible === false ) return; + + var linewidth, a, attribute, i, il; + + var program = setProgram( camera, lights, fog, material, object ); + + var attributes = program.attributes; + + var updateBuffers = false, + wireframeBit = material.wireframe ? 1 : 0, + geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; + + if ( geometryGroupHash !== _currentGeometryGroupHash ) { + + _currentGeometryGroupHash = geometryGroupHash; + updateBuffers = true; + + } + + if ( updateBuffers ) { + + initAttributes(); + + } + + // vertices + + if ( ! material.morphTargets && attributes.position >= 0 ) { + + if ( updateBuffers ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + } else { + + if ( object.morphTargetBase ) { + + setupMorphTargets( material, geometryGroup, object ); + + } + + } + + + if ( updateBuffers ) { + + // custom attributes + + // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers + + if ( geometryGroup.__webglCustomAttributesList ) { + + for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) { + + attribute = geometryGroup.__webglCustomAttributesList[ i ]; + + if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, attribute.buffer ); + enableAttribute( attributes[ attribute.buffer.belongsToAttribute ] ); + _gl.vertexAttribPointer( attributes[ attribute.buffer.belongsToAttribute ], attribute.size, _gl.FLOAT, false, 0, 0 ); + + } + + } + + } + + + // colors + + if ( attributes.color >= 0 ) { + + if ( object.geometry.colors.length > 0 || object.geometry.faces.length > 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer ); + enableAttribute( attributes.color ); + _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib3fv( attributes.color, material.defaultAttributeValues.color ); + + } + + } + + // normals + + if ( attributes.normal >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer ); + enableAttribute( attributes.normal ); + _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); + + } + + // tangents + + if ( attributes.tangent >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer ); + enableAttribute( attributes.tangent ); + _gl.vertexAttribPointer( attributes.tangent, 4, _gl.FLOAT, false, 0, 0 ); + + } + + // uvs + + if ( attributes.uv >= 0 ) { + + if ( object.geometry.faceVertexUvs[ 0 ] ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer ); + enableAttribute( attributes.uv ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib2fv( attributes.uv, material.defaultAttributeValues.uv ); + + } + + } + + if ( attributes.uv2 >= 0 ) { + + if ( object.geometry.faceVertexUvs[ 1 ] ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer ); + enableAttribute( attributes.uv2 ); + _gl.vertexAttribPointer( attributes.uv2, 2, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues ) { + + + _gl.vertexAttrib2fv( attributes.uv2, material.defaultAttributeValues.uv2 ); + + } + + } + + if ( material.skinning && + attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer ); + enableAttribute( attributes.skinIndex ); + _gl.vertexAttribPointer( attributes.skinIndex, 4, _gl.FLOAT, false, 0, 0 ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer ); + enableAttribute( attributes.skinWeight ); + _gl.vertexAttribPointer( attributes.skinWeight, 4, _gl.FLOAT, false, 0, 0 ); + + } + + // line distances + + if ( attributes.lineDistance >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglLineDistanceBuffer ); + enableAttribute( attributes.lineDistance ); + _gl.vertexAttribPointer( attributes.lineDistance, 1, _gl.FLOAT, false, 0, 0 ); + + } + + } + + disableUnusedAttributes(); + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + var type = geometryGroup.__typeArray === Uint32Array ? _gl.UNSIGNED_INT : _gl.UNSIGNED_SHORT; + + // wireframe + + if ( material.wireframe ) { + + setLineWidth( material.wireframeLinewidth ); + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer ); + _gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, type, 0 ); + + // triangles + + } else { + + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer ); + _gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, type, 0 ); + + } + + _this.info.render.calls ++; + _this.info.render.vertices += geometryGroup.__webglFaceCount; + _this.info.render.faces += geometryGroup.__webglFaceCount / 3; + + // render lines + + } else if ( object instanceof THREE.Line ) { + + var mode = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + _gl.drawArrays( mode, 0, geometryGroup.__webglLineCount ); + + _this.info.render.calls ++; + + // render particles + + } else if ( object instanceof THREE.PointCloud ) { + + _gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount ); + + _this.info.render.calls ++; + _this.info.render.points += geometryGroup.__webglParticleCount; + + } + + }; + + function initAttributes() { + + for ( var i = 0, l = _newAttributes.length; i < l; i ++ ) { + + _newAttributes[ i ] = 0; + + } + + } + + function enableAttribute( attribute ) { + + _newAttributes[ attribute ] = 1; + + if ( _enabledAttributes[ attribute ] === 0 ) { + + _gl.enableVertexAttribArray( attribute ); + _enabledAttributes[ attribute ] = 1; + + } + + } + + function disableUnusedAttributes() { + + for ( var i = 0, l = _enabledAttributes.length; i < l; i ++ ) { + + if ( _enabledAttributes[ i ] !== _newAttributes[ i ] ) { + + _gl.disableVertexAttribArray( i ); + _enabledAttributes[ i ] = 0; + + } + + } + + } + + function setupMorphTargets ( material, geometryGroup, object ) { + + // set base + + var attributes = material.program.attributes; + + if ( object.morphTargetBase !== - 1 && attributes.position >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ] ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } else if ( attributes.position >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer ); + enableAttribute( attributes.position ); + _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.morphTargetForcedOrder.length ) { + + // set forced order + + var m = 0; + var order = object.morphTargetForcedOrder; + var influences = object.morphTargetInfluences; + + while ( m < material.numSupportedMorphTargets && m < order.length ) { + + if ( attributes[ 'morphTarget' + m ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ] ); + enableAttribute( attributes[ 'morphTarget' + m ] ); + _gl.vertexAttribPointer( attributes[ 'morphTarget' + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( attributes[ 'morphNormal' + m ] >= 0 && material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ] ); + enableAttribute( attributes[ 'morphNormal' + m ] ); + _gl.vertexAttribPointer( attributes[ 'morphNormal' + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ]; + + m ++; + } + + } else { + + // find the most influencing + + var influence, activeInfluenceIndices = []; + var influences = object.morphTargetInfluences; + var i, il = influences.length; + + for ( i = 0; i < il; i ++ ) { + + influence = influences[ i ]; + + if ( influence > 0 ) { + + activeInfluenceIndices.push( [ influence, i ] ); + + } + + } + + if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) { + + activeInfluenceIndices.sort( numericalSort ); + activeInfluenceIndices.length = material.numSupportedMorphTargets; + + } else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) { + + activeInfluenceIndices.sort( numericalSort ); + + } else if ( activeInfluenceIndices.length === 0 ) { + + activeInfluenceIndices.push( [ 0, 0 ] ); + + }; + + var influenceIndex, m = 0; + + while ( m < material.numSupportedMorphTargets ) { + + if ( activeInfluenceIndices[ m ] ) { + + influenceIndex = activeInfluenceIndices[ m ][ 1 ]; + + if ( attributes[ 'morphTarget' + m ] >= 0 ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ] ); + enableAttribute( attributes[ 'morphTarget' + m ] ); + _gl.vertexAttribPointer( attributes[ 'morphTarget' + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( attributes[ 'morphNormal' + m ] >= 0 && material.morphNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ] ); + enableAttribute( attributes[ 'morphNormal' + m ] ); + _gl.vertexAttribPointer( attributes[ 'morphNormal' + m ], 3, _gl.FLOAT, false, 0, 0 ); + + + } + + object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ]; + + } else { + + /* + _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + if ( material.morphNormals ) { + + _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); + + } + */ + + object.__webglMorphTargetInfluences[ m ] = 0; + + } + + m ++; + + } + + } + + // load updated influences uniform + + if ( material.program.uniforms.morphTargetInfluences !== null ) { + + _gl.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences ); + + } + + }; + + // Sorting + + function painterSortStable ( a, b ) { + + if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return a.id - b.id; + + } + + }; + + function reversePainterSortStable ( a, b ) { + + if ( a.z !== b.z ) { + + return a.z - b.z; + + } else { + + return a.id - b.id; + + } + + }; + + function numericalSort ( a, b ) { + + return b[ 0 ] - a[ 0 ]; + + }; + + + // Rendering + + this.render = function ( scene, camera, renderTarget, forceClear ) { + + if ( camera instanceof THREE.Camera === false ) { + + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; + + } + + var i, il, + + webglObject, object, + renderList, + + lights = scene.__lights, + fog = scene.fog; + + // reset caching for this frame + + _currentMaterialId = - 1; + _currentCamera = null; + _lightsNeedUpdate = true; + + // update scene graph + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + if ( camera.parent === undefined ) camera.updateMatrixWorld(); + + // update Skeleton objects + function updateSkeletons( object ) { + + if ( object instanceof THREE.SkinnedMesh ) { + + object.skeleton.update(); + + } + + for ( var i = 0, l = object.children.length; i < l; i ++ ) { + + updateSkeletons( object.children[ i ] ); + + } + + } + + updateSkeletons( scene ); + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + initObjects( scene ); + + opaqueObjects.length = 0; + transparentObjects.length = 0; + + projectObject( scene, scene, camera ); + + if ( _this.sortObjects === true ) { + + opaqueObjects.sort( painterSortStable ); + transparentObjects.sort( reversePainterSortStable ); + + } + + // custom render plugins (pre pass) + + renderPlugins( this.renderPluginsPre, scene, camera ); + + // + + _this.info.render.calls = 0; + _this.info.render.vertices = 0; + _this.info.render.faces = 0; + _this.info.render.points = 0; + + this.setRenderTarget( renderTarget ); + + if ( this.autoClear || forceClear ) { + + this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil ); + + } + + // set matrices for regular objects (frustum culled) + + + + + // set matrices for immediate objects + + renderList = scene.__webglObjectsImmediate; + + for ( i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + if ( object.visible ) { + + setupMatrices( object, camera ); + + unrollImmediateBufferMaterial( webglObject ); + + } + + } + + if ( scene.overrideMaterial ) { + + var material = scene.overrideMaterial; + + this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + this.setDepthTest( material.depthTest ); + this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + renderObjects( opaqueObjects, camera, lights, fog, true, material ); + renderObjects( transparentObjects, camera, lights, fog, true, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, '', camera, lights, fog, false, material ); + + } else { + + var material = null; + + // opaque pass (front-to-back order) + + this.setBlending( THREE.NoBlending ); + + renderObjects( opaqueObjects, camera, lights, fog, false, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, 'opaque', camera, lights, fog, false, material ); + + // transparent pass (back-to-front order) + + renderObjects( transparentObjects, camera, lights, fog, true, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, 'transparent', camera, lights, fog, true, material ); + + } + + // custom render plugins (post pass) + + renderPlugins( this.renderPluginsPost, scene, camera ); + + + // Generate mipmap if we're using any kind of mipmap filtering + + if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) { + + updateRenderTargetMipmap( renderTarget ); + + } + + // Ensure depth buffer writing is enabled so it can be cleared on next render + + this.setDepthTest( true ); + this.setDepthWrite( true ); + + // _gl.finish(); + + }; + + function projectObject(scene, object,camera){ + + if ( object.visible === false ) return; + + var webglObjects = scene.__webglObjects[ object.id ]; + + if ( webglObjects && ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) ) { + + updateObject( scene, object ); + + for ( var i = 0, l = webglObjects.length; i < l; i ++ ) { + + var webglObject = webglObjects[i]; + + unrollBufferMaterial( webglObject ); + + webglObject.render = true; + + if ( _this.sortObjects === true ) { + + if ( object.renderDepth !== null ) { + + webglObject.z = object.renderDepth; + + } else { + + _vector3.setFromMatrixPosition( object.matrixWorld ); + _vector3.applyProjection( _projScreenMatrix ); + + webglObject.z = _vector3.z; + + } + + } + + } + + } + + for ( var i = 0, l = object.children.length; i < l; i ++ ) { + + projectObject( scene, object.children[ i ], camera ); + + } + + } + + function renderPlugins( plugins, scene, camera ) { + + if ( plugins.length === 0 ) return; + + for ( var i = 0, il = plugins.length; i < il; i ++ ) { + + // reset state for plugin (to start from clean slate) + + _currentProgram = null; + _currentCamera = null; + + _oldBlending = - 1; + _oldDepthTest = - 1; + _oldDepthWrite = - 1; + _oldDoubleSided = - 1; + _oldFlipSided = - 1; + _currentGeometryGroupHash = - 1; + _currentMaterialId = - 1; + + _lightsNeedUpdate = true; + + plugins[ i ].render( scene, camera, _currentWidth, _currentHeight ); + + // reset state after plugin (anything could have changed) + + _currentProgram = null; + _currentCamera = null; + + _oldBlending = - 1; + _oldDepthTest = - 1; + _oldDepthWrite = - 1; + _oldDoubleSided = - 1; + _oldFlipSided = - 1; + _currentGeometryGroupHash = - 1; + _currentMaterialId = - 1; + + _lightsNeedUpdate = true; + + } + + }; + + function renderObjects( renderList, camera, lights, fog, useBlending, overrideMaterial ) { + + var webglObject, object, buffer, material; + + for ( var i = renderList.length - 1; i !== - 1; i -- ) { + + webglObject = renderList[ i ]; + + object = webglObject.object; + buffer = webglObject.buffer; + + setupMatrices( object, camera ); + + if ( overrideMaterial ) { + + material = overrideMaterial; + + } else { + + material = webglObject.material; + + if ( ! material ) continue; + + if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + + _this.setDepthTest( material.depthTest ); + _this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + } + + _this.setMaterialFaces( material ); + + if ( buffer instanceof THREE.BufferGeometry ) { + + _this.renderBufferDirect( camera, lights, fog, material, buffer, object ); + + } else { + + _this.renderBuffer( camera, lights, fog, material, buffer, object ); + + } + + } + + }; + + function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) { + + var webglObject, object, material, program; + + for ( var i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + if ( object.visible ) { + + if ( overrideMaterial ) { + + material = overrideMaterial; + + } else { + + material = webglObject[ materialType ]; + + if ( ! material ) continue; + + if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + + _this.setDepthTest( material.depthTest ); + _this.setDepthWrite( material.depthWrite ); + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + } + + _this.renderImmediateObject( camera, lights, fog, material, object ); + + } + + } + + }; + + this.renderImmediateObject = function ( camera, lights, fog, material, object ) { + + var program = setProgram( camera, lights, fog, material, object ); + + _currentGeometryGroupHash = - 1; + + _this.setMaterialFaces( material ); + + if ( object.immediateRenderCallback ) { + + object.immediateRenderCallback( program, _gl, _frustum ); + + } else { + + object.render( function ( object ) { _this.renderBufferImmediate( object, program, material ); } ); + + } + + }; + + function unrollImmediateBufferMaterial ( globject ) { + + var object = globject.object, + material = object.material; + + if ( material.transparent ) { + + globject.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + }; + + function unrollBufferMaterial ( globject ) { + + var object = globject.object; + var buffer = globject.buffer; + + var geometry = object.geometry; + var material = object.material; + + if ( material instanceof THREE.MeshFaceMaterial ) { + + var materialIndex = geometry instanceof THREE.BufferGeometry ? 0 : buffer.materialIndex; + + material = material.materials[ materialIndex ]; + + if ( material.transparent ) { + + globject.material = material; + transparentObjects.push( globject ); + + } else { + + globject.material = material; + opaqueObjects.push( globject ); + + } + + } else { + + if ( material ) { + + if ( material.transparent ) { + + globject.material = material; + transparentObjects.push( globject ); + + } else { + + globject.material = material; + opaqueObjects.push( globject ); + + } + + } + + } + + }; + + // Objects refresh + + var initObjects = function ( scene ) { + + if ( ! scene.__webglObjects ) { + + scene.__webglObjects = {}; + scene.__webglObjectsImmediate = []; + + } + + while ( scene.__objectsAdded.length ) { + + addObject( scene.__objectsAdded[ 0 ], scene ); + scene.__objectsAdded.splice( 0, 1 ); + + } + + while ( scene.__objectsRemoved.length ) { + + removeObject( scene.__objectsRemoved[ 0 ], scene ); + scene.__objectsRemoved.splice( 0, 1 ); + + } + + }; + + // Objects adding + + function addObject( object, scene ) { + + var g, geometry, geometryGroup; + + if ( object.__webglInit === undefined ) { + + object.__webglInit = true; + + object._modelViewMatrix = new THREE.Matrix4(); + object._normalMatrix = new THREE.Matrix3(); + + } + + geometry = object.geometry; + + if ( geometry === undefined ) { + + // ImmediateRenderObject + + } else if ( geometry.__webglInit === undefined ) { + + geometry.__webglInit = true; + geometry.addEventListener( 'dispose', onGeometryDispose ); + + if ( geometry instanceof THREE.BufferGeometry ) { + + initDirectBuffers( geometry ); + + } else if ( object instanceof THREE.Mesh ) { + + if ( object.__webglActive !== undefined ) { + + removeObject( object, scene ); + + } + + initGeometryGroups(scene, object, geometry); + + } else if ( object instanceof THREE.Line ) { + + if ( ! geometry.__webglVertexBuffer ) { + + createLineBuffers( geometry ); + initLineBuffers( geometry, object ); + + geometry.verticesNeedUpdate = true; + geometry.colorsNeedUpdate = true; + geometry.lineDistancesNeedUpdate = true; + + } + + } else if ( object instanceof THREE.PointCloud ) { + + if ( ! geometry.__webglVertexBuffer ) { + + createParticleBuffers( geometry ); + initParticleBuffers( geometry, object ); + + geometry.verticesNeedUpdate = true; + geometry.colorsNeedUpdate = true; + + } + + } + + } + + if ( object.__webglActive === undefined) { + + if ( object instanceof THREE.Mesh ) { + + geometry = object.geometry; + + if ( geometry instanceof THREE.BufferGeometry ) { + + addBuffer( scene.__webglObjects, geometry, object ); + + } else if ( geometry instanceof THREE.Geometry ) { + + for ( var i = 0,l = geometry.geometryGroupsList.length; i= 0; o -- ) { + + if ( objlist[ o ].object === object ) { + + objlist.splice( o, 1 ); + + } + + } + + }; + + // Materials + + this.initMaterial = function ( material, lights, fog, object ) { + + material.addEventListener( 'dispose', onMaterialDispose ); + + var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID; + + if ( material instanceof THREE.MeshDepthMaterial ) { + + shaderID = 'depth'; + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + shaderID = 'normal'; + + } else if ( material instanceof THREE.MeshBasicMaterial ) { + + shaderID = 'basic'; + + } else if ( material instanceof THREE.MeshLambertMaterial ) { + + shaderID = 'lambert'; + + } else if ( material instanceof THREE.MeshPhongMaterial ) { + + shaderID = 'phong'; + + } else if ( material instanceof THREE.LineBasicMaterial ) { + + shaderID = 'basic'; + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + shaderID = 'dashed'; + + } else if ( material instanceof THREE.PointCloudMaterial ) { + + shaderID = 'particle_basic'; + + } + + if ( shaderID ) { + + var shader = THREE.ShaderLib[ shaderID ]; + + material.__webglShader = { + uniforms: THREE.UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + } + + } else { + + material.__webglShader = { + uniforms: material.uniforms, + vertexShader: material.vertexShader, + fragmentShader: material.fragmentShader + } + + } + + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) + + maxLightCount = allocateLights( lights ); + + maxShadows = allocateShadows( lights ); + + maxBones = allocateBones( object ); + + parameters = { + + precision: _precision, + supportsVertexTextures: _supportsVertexTextures, + + map: !! material.map, + envMap: !! material.envMap, + lightMap: !! material.lightMap, + bumpMap: !! material.bumpMap, + normalMap: !! material.normalMap, + specularMap: !! material.specularMap, + alphaMap: !! material.alphaMap, + + vertexColors: material.vertexColors, + + fog: fog, + useFog: material.fog, + fogExp: fog instanceof THREE.FogExp2, + + sizeAttenuation: material.sizeAttenuation, + logarithmicDepthBuffer: _logarithmicDepthBuffer, + + skinning: material.skinning, + maxBones: maxBones, + useVertexTexture: _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture, + + morphTargets: material.morphTargets, + morphNormals: material.morphNormals, + maxMorphTargets: this.maxMorphTargets, + maxMorphNormals: this.maxMorphNormals, + + maxDirLights: maxLightCount.directional, + maxPointLights: maxLightCount.point, + maxSpotLights: maxLightCount.spot, + maxHemiLights: maxLightCount.hemi, + + maxShadows: maxShadows, + shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow && maxShadows > 0, + shadowMapType: this.shadowMapType, + shadowMapDebug: this.shadowMapDebug, + shadowMapCascade: this.shadowMapCascade, + + alphaTest: material.alphaTest, + metal: material.metal, + wrapAround: material.wrapAround, + doubleSided: material.side === THREE.DoubleSide, + flipSided: material.side === THREE.BackSide + + }; + + // Generate code + + var chunks = []; + + if ( shaderID ) { + + chunks.push( shaderID ); + + } else { + + chunks.push( material.fragmentShader ); + chunks.push( material.vertexShader ); + + } + + for ( var d in material.defines ) { + + chunks.push( d ); + chunks.push( material.defines[ d ] ); + + } + + for ( var p in parameters ) { + + chunks.push( p ); + chunks.push( parameters[ p ] ); + + } + + var code = chunks.join(); + + var program; + + // Check if code has been already compiled + + for ( var p = 0, pl = _programs.length; p < pl; p ++ ) { + + var programInfo = _programs[ p ]; + + if ( programInfo.code === code ) { + + program = programInfo; + program.usedTimes ++; + + break; + + } + + } + + if ( program === undefined ) { + + program = new THREE.WebGLProgram( this, code, material, parameters ); + _programs.push( program ); + + _this.info.memory.programs = _programs.length; + + } + + material.program = program; + + var attributes = material.program.attributes; + + if ( material.morphTargets ) { + + material.numSupportedMorphTargets = 0; + + var id, base = 'morphTarget'; + + for ( i = 0; i < this.maxMorphTargets; i ++ ) { + + id = base + i; + + if ( attributes[ id ] >= 0 ) { + + material.numSupportedMorphTargets ++; + + } + + } + + } + + if ( material.morphNormals ) { + + material.numSupportedMorphNormals = 0; + + var id, base = 'morphNormal'; + + for ( i = 0; i < this.maxMorphNormals; i ++ ) { + + id = base + i; + + if ( attributes[ id ] >= 0 ) { + + material.numSupportedMorphNormals ++; + + } + + } + + } + + material.uniformsList = []; + + for ( u in material.__webglShader.uniforms ) { + + var location = material.program.uniforms[ u ]; + + if ( location ) { + material.uniformsList.push( [ material.__webglShader.uniforms[ u ], location ] ); + } + + } + + }; + + function setProgram( camera, lights, fog, material, object ) { + + _usedTextureUnits = 0; + + if ( material.needsUpdate ) { + + if ( material.program ) deallocateMaterial( material ); + + _this.initMaterial( material, lights, fog, object ); + material.needsUpdate = false; + + } + + if ( material.morphTargets ) { + + if ( ! object.__webglMorphTargetInfluences ) { + + object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets ); + + } + + } + + var refreshProgram = false; + var refreshMaterial = false; + var refreshLights = false; + + var program = material.program, + p_uniforms = program.uniforms, + m_uniforms = material.__webglShader.uniforms; + + if ( program.id !== _currentProgram ) { + + _gl.useProgram( program.program ); + _currentProgram = program.id; + + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; + + } + + if ( material.id !== _currentMaterialId ) { + + if ( _currentMaterialId === -1 ) refreshLights = true; + _currentMaterialId = material.id; + + refreshMaterial = true; + + } + + if ( refreshProgram || camera !== _currentCamera ) { + + _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + if ( _logarithmicDepthBuffer ) { + + _gl.uniform1f( p_uniforms.logDepthBufFC, 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); + + } + + + if ( camera !== _currentCamera ) _currentCamera = camera; + + // load material specific uniforms + // (shader material also gets them for the sake of genericity) + + if ( material instanceof THREE.ShaderMaterial || + material instanceof THREE.MeshPhongMaterial || + material.envMap ) { + + if ( p_uniforms.cameraPosition !== null ) { + + _vector3.setFromMatrixPosition( camera.matrixWorld ); + _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z ); + + } + + } + + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.ShaderMaterial || + material.skinning ) { + + if ( p_uniforms.viewMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements ); + + } + + } + + } + + // skinning uniforms must be set even if material didn't change + // auto-setting of texture unit for bone texture must go before other textures + // not sure why, but otherwise weird things happen + + if ( material.skinning ) { + + if ( object.bindMatrix && p_uniforms.bindMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.bindMatrix, false, object.bindMatrix.elements ); + + } + + if ( object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements ); + + } + + if ( _supportsBoneTextures && object.skeleton && object.skeleton.useVertexTexture ) { + + if ( p_uniforms.boneTexture !== null ) { + + var textureUnit = getTextureUnit(); + + _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); + _this.setTexture( object.skeleton.boneTexture, textureUnit ); + + } + + if ( p_uniforms.boneTextureWidth !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth ); + + } + + if ( p_uniforms.boneTextureHeight !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight ); + + } + + } else if ( object.skeleton && object.skeleton.boneMatrices ) { + + if ( p_uniforms.boneGlobalMatrices !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices ); + + } + + } + + } + + if ( refreshMaterial ) { + + // refresh uniforms common to several materials + + if ( fog && material.fog ) { + + refreshUniformsFog( m_uniforms, fog ); + + } + + if ( material instanceof THREE.MeshPhongMaterial || + material instanceof THREE.MeshLambertMaterial || + material.lights ) { + + if ( _lightsNeedUpdate ) { + + refreshLights = true; + setupLights( lights ); + _lightsNeedUpdate = false; + } + + if ( refreshLights ) { + refreshUniformsLights( m_uniforms, _lights ); + markUniformsLightsNeedsUpdate( m_uniforms, true ); + } else { + markUniformsLightsNeedsUpdate( m_uniforms, false ); + } + + } + + if ( material instanceof THREE.MeshBasicMaterial || + material instanceof THREE.MeshLambertMaterial || + material instanceof THREE.MeshPhongMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + + } + + // refresh single material specific uniforms + + if ( material instanceof THREE.LineBasicMaterial ) { + + refreshUniformsLine( m_uniforms, material ); + + } else if ( material instanceof THREE.LineDashedMaterial ) { + + refreshUniformsLine( m_uniforms, material ); + refreshUniformsDash( m_uniforms, material ); + + } else if ( material instanceof THREE.PointCloudMaterial ) { + + refreshUniformsParticle( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshPhongMaterial ) { + + refreshUniformsPhong( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshLambertMaterial ) { + + refreshUniformsLambert( m_uniforms, material ); + + } else if ( material instanceof THREE.MeshDepthMaterial ) { + + m_uniforms.mNear.value = camera.near; + m_uniforms.mFar.value = camera.far; + m_uniforms.opacity.value = material.opacity; + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + m_uniforms.opacity.value = material.opacity; + + } + + if ( object.receiveShadow && ! material._shadowPass ) { + + refreshUniformsShadow( m_uniforms, lights ); + + } + + // load common uniforms + + loadUniformsGeneric( material.uniformsList ); + + } + + loadUniformsMatrices( p_uniforms, object ); + + if ( p_uniforms.modelMatrix !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements ); + + } + + return program; + + }; + + // Uniforms (refresh uniforms objects) + + function refreshUniformsCommon ( uniforms, material ) { + + uniforms.opacity.value = material.opacity; + + if ( _this.gammaInput ) { + + uniforms.diffuse.value.copyGammaToLinear( material.color ); + + } else { + + uniforms.diffuse.value = material.color; + + } + + uniforms.map.value = material.map; + uniforms.lightMap.value = material.lightMap; + uniforms.specularMap.value = material.specularMap; + uniforms.alphaMap.value = material.alphaMap; + + if ( material.bumpMap ) { + + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; + + } + + if ( material.normalMap ) { + + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); + + } + + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + // 5. alpha map + + var uvScaleMap; + + if ( material.map ) { + + uvScaleMap = material.map; + + } else if ( material.specularMap ) { + + uvScaleMap = material.specularMap; + + } else if ( material.normalMap ) { + + uvScaleMap = material.normalMap; + + } else if ( material.bumpMap ) { + + uvScaleMap = material.bumpMap; + + } else if ( material.alphaMap ) { + + uvScaleMap = material.alphaMap; + + } + + if ( uvScaleMap !== undefined ) { + + var offset = uvScaleMap.offset; + var repeat = uvScaleMap.repeat; + + uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); + + } + + uniforms.envMap.value = material.envMap; + uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : - 1; + + if ( _this.gammaInput ) { + + //uniforms.reflectivity.value = material.reflectivity * material.reflectivity; + uniforms.reflectivity.value = material.reflectivity; + + } else { + + uniforms.reflectivity.value = material.reflectivity; + + } + + uniforms.refractionRatio.value = material.refractionRatio; + uniforms.combine.value = material.combine; + uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping; + + }; + + function refreshUniformsLine ( uniforms, material ) { + + uniforms.diffuse.value = material.color; + uniforms.opacity.value = material.opacity; + + }; + + function refreshUniformsDash ( uniforms, material ) { + + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; + + }; + + function refreshUniformsParticle ( uniforms, material ) { + + uniforms.psColor.value = material.color; + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size; + uniforms.scale.value = _canvas.height / 2.0; // TODO: Cache this. + + uniforms.map.value = material.map; + + }; + + function refreshUniformsFog ( uniforms, fog ) { + + uniforms.fogColor.value = fog.color; + + if ( fog instanceof THREE.Fog ) { + + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; + + } else if ( fog instanceof THREE.FogExp2 ) { + + uniforms.fogDensity.value = fog.density; + + } + + }; + + function refreshUniformsPhong ( uniforms, material ) { + + uniforms.shininess.value = material.shininess; + + if ( _this.gammaInput ) { + + uniforms.ambient.value.copyGammaToLinear( material.ambient ); + uniforms.emissive.value.copyGammaToLinear( material.emissive ); + uniforms.specular.value.copyGammaToLinear( material.specular ); + + } else { + + uniforms.ambient.value = material.ambient; + uniforms.emissive.value = material.emissive; + uniforms.specular.value = material.specular; + + } + + if ( material.wrapAround ) { + + uniforms.wrapRGB.value.copy( material.wrapRGB ); + + } + + }; + + function refreshUniformsLambert ( uniforms, material ) { + + if ( _this.gammaInput ) { + + uniforms.ambient.value.copyGammaToLinear( material.ambient ); + uniforms.emissive.value.copyGammaToLinear( material.emissive ); + + } else { + + uniforms.ambient.value = material.ambient; + uniforms.emissive.value = material.emissive; + + } + + if ( material.wrapAround ) { + + uniforms.wrapRGB.value.copy( material.wrapRGB ); + + } + + }; + + function refreshUniformsLights ( uniforms, lights ) { + + uniforms.ambientLightColor.value = lights.ambient; + + uniforms.directionalLightColor.value = lights.directional.colors; + uniforms.directionalLightDirection.value = lights.directional.positions; + + uniforms.pointLightColor.value = lights.point.colors; + uniforms.pointLightPosition.value = lights.point.positions; + uniforms.pointLightDistance.value = lights.point.distances; + + uniforms.spotLightColor.value = lights.spot.colors; + uniforms.spotLightPosition.value = lights.spot.positions; + uniforms.spotLightDistance.value = lights.spot.distances; + uniforms.spotLightDirection.value = lights.spot.directions; + uniforms.spotLightAngleCos.value = lights.spot.anglesCos; + uniforms.spotLightExponent.value = lights.spot.exponents; + + uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors; + uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors; + uniforms.hemisphereLightDirection.value = lights.hemi.positions; + + }; + + // If uniforms are marked as clean, they don't need to be loaded to the GPU. + + function markUniformsLightsNeedsUpdate ( uniforms, boolean ) { + + uniforms.ambientLightColor.needsUpdate = boolean; + + uniforms.directionalLightColor.needsUpdate = boolean; + uniforms.directionalLightDirection.needsUpdate = boolean; + + uniforms.pointLightColor.needsUpdate = boolean; + uniforms.pointLightPosition.needsUpdate = boolean; + uniforms.pointLightDistance.needsUpdate = boolean; + + uniforms.spotLightColor.needsUpdate = boolean; + uniforms.spotLightPosition.needsUpdate = boolean; + uniforms.spotLightDistance.needsUpdate = boolean; + uniforms.spotLightDirection.needsUpdate = boolean; + uniforms.spotLightAngleCos.needsUpdate = boolean; + uniforms.spotLightExponent.needsUpdate = boolean; + + uniforms.hemisphereLightSkyColor.needsUpdate = boolean; + uniforms.hemisphereLightGroundColor.needsUpdate = boolean; + uniforms.hemisphereLightDirection.needsUpdate = boolean; + + }; + + function refreshUniformsShadow ( uniforms, lights ) { + + if ( uniforms.shadowMatrix ) { + + var j = 0; + + for ( var i = 0, il = lights.length; i < il; i ++ ) { + + var light = lights[ i ]; + + if ( ! light.castShadow ) continue; + + if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) { + + uniforms.shadowMap.value[ j ] = light.shadowMap; + uniforms.shadowMapSize.value[ j ] = light.shadowMapSize; + + uniforms.shadowMatrix.value[ j ] = light.shadowMatrix; + + uniforms.shadowDarkness.value[ j ] = light.shadowDarkness; + uniforms.shadowBias.value[ j ] = light.shadowBias; + + j ++; + + } + + } + + } + + }; + + // Uniforms (load to GPU) + + function loadUniformsMatrices ( uniforms, object ) { + + _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object._modelViewMatrix.elements ); + + if ( uniforms.normalMatrix ) { + + _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object._normalMatrix.elements ); + + } + + }; + + function getTextureUnit() { + + var textureUnit = _usedTextureUnits; + + if ( textureUnit >= _maxTextures ) { + + console.warn( 'WebGLRenderer: trying to use ' + textureUnit + ' texture units while this GPU supports only ' + _maxTextures ); + + } + + _usedTextureUnits += 1; + + return textureUnit; + + }; + + function loadUniformsGeneric ( uniforms ) { + + var texture, textureUnit, offset; + + for ( var j = 0, jl = uniforms.length; j < jl; j ++ ) { + + var uniform = uniforms[ j ][ 0 ]; + + // needsUpdate property is not added to all uniforms. + if ( uniform.needsUpdate === false ) continue; + + var type = uniform.type; + var value = uniform.value; + var location = uniforms[ j ][ 1 ]; + + switch ( type ) { + + case '1i': + _gl.uniform1i( location, value ); + break; + + case '1f': + _gl.uniform1f( location, value ); + break; + + case '2f': + _gl.uniform2f( location, value[ 0 ], value[ 1 ] ); + break; + + case '3f': + _gl.uniform3f( location, value[ 0 ], value[ 1 ], value[ 2 ] ); + break; + + case '4f': + _gl.uniform4f( location, value[ 0 ], value[ 1 ], value[ 2 ], value[ 3 ] ); + break; + + case '1iv': + _gl.uniform1iv( location, value ); + break; + + case '3iv': + _gl.uniform3iv( location, value ); + break; + + case '1fv': + _gl.uniform1fv( location, value ); + break; + + case '2fv': + _gl.uniform2fv( location, value ); + break; + + case '3fv': + _gl.uniform3fv( location, value ); + break; + + case '4fv': + _gl.uniform4fv( location, value ); + break; + + case 'Matrix3fv': + _gl.uniformMatrix3fv( location, false, value ); + break; + + case 'Matrix4fv': + _gl.uniformMatrix4fv( location, false, value ); + break; + + // + + case 'i': + + // single integer + _gl.uniform1i( location, value ); + + break; + + case 'f': + + // single float + _gl.uniform1f( location, value ); + + break; + + case 'v2': + + // single THREE.Vector2 + _gl.uniform2f( location, value.x, value.y ); + + break; + + case 'v3': + + // single THREE.Vector3 + _gl.uniform3f( location, value.x, value.y, value.z ); + + break; + + case 'v4': + + // single THREE.Vector4 + _gl.uniform4f( location, value.x, value.y, value.z, value.w ); + + break; + + case 'c': + + // single THREE.Color + _gl.uniform3f( location, value.r, value.g, value.b ); + + break; + + case 'iv1': + + // flat array of integers (JS or typed array) + _gl.uniform1iv( location, value ); + + break; + + case 'iv': + + // flat array of integers with 3 x N size (JS or typed array) + _gl.uniform3iv( location, value ); + + break; + + case 'fv1': + + // flat array of floats (JS or typed array) + _gl.uniform1fv( location, value ); + + break; + + case 'fv': + + // flat array of floats with 3 x N size (JS or typed array) + _gl.uniform3fv( location, value ); + + break; + + case 'v2v': + + // array of THREE.Vector2 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 2 * value.length ); + + } + + for ( var i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 2; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + + } + + _gl.uniform2fv( location, uniform._array ); + + break; + + case 'v3v': + + // array of THREE.Vector3 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 3 * value.length ); + + } + + for ( var i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 3; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + uniform._array[ offset + 2 ] = value[ i ].z; + + } + + _gl.uniform3fv( location, uniform._array ); + + break; + + case 'v4v': + + // array of THREE.Vector4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 4 * value.length ); + + } + + for ( var i = 0, il = value.length; i < il; i ++ ) { + + offset = i * 4; + + uniform._array[ offset ] = value[ i ].x; + uniform._array[ offset + 1 ] = value[ i ].y; + uniform._array[ offset + 2 ] = value[ i ].z; + uniform._array[ offset + 3 ] = value[ i ].w; + + } + + _gl.uniform4fv( location, uniform._array ); + + break; + + case 'm3': + + // single THREE.Matrix3 + _gl.uniformMatrix3fv( location, false, value.elements ); + + break; + + case 'm3v': + + // array of THREE.Matrix3 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 9 * value.length ); + + } + + for ( var i = 0, il = value.length; i < il; i ++ ) { + + value[ i ].flattenToArrayOffset( uniform._array, i * 9 ); + + } + + _gl.uniformMatrix3fv( location, false, uniform._array ); + + break; + + case 'm4': + + // single THREE.Matrix4 + _gl.uniformMatrix4fv( location, false, value.elements ); + + break; + + case 'm4v': + + // array of THREE.Matrix4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 16 * value.length ); + + } + + for ( var i = 0, il = value.length; i < il; i ++ ) { + + value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); + + } + + _gl.uniformMatrix4fv( location, false, uniform._array ); + + break; + + case 't': + + // single THREE.Texture (2d or cube) + + texture = value; + textureUnit = getTextureUnit(); + + _gl.uniform1i( location, textureUnit ); + + if ( ! texture ) continue; + + if ( texture instanceof THREE.CubeTexture || + ( texture.image instanceof Array && texture.image.length === 6 ) ) { // CompressedTexture can have Array in image :/ + + setCubeTexture( texture, textureUnit ); + + } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { + + setCubeTextureDynamic( texture, textureUnit ); + + } else { + + _this.setTexture( texture, textureUnit ); + + } + + break; + + case 'tv': + + // array of THREE.Texture (2d) + + if ( uniform._array === undefined ) { + + uniform._array = []; + + } + + for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { + + uniform._array[ i ] = getTextureUnit(); + + } + + _gl.uniform1iv( location, uniform._array ); + + for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { + + texture = uniform.value[ i ]; + textureUnit = uniform._array[ i ]; + + if ( ! texture ) continue; + + _this.setTexture( texture, textureUnit ); + + } + + break; + + default: + + console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type ); + + } + + } + + }; + + function setupMatrices ( object, camera ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object._normalMatrix.getNormalMatrix( object._modelViewMatrix ); + + }; + + // + + function setColorGamma( array, offset, color, intensitySq ) { + + array[ offset ] = color.r * color.r * intensitySq; + array[ offset + 1 ] = color.g * color.g * intensitySq; + array[ offset + 2 ] = color.b * color.b * intensitySq; + + }; + + function setColorLinear( array, offset, color, intensity ) { + + array[ offset ] = color.r * intensity; + array[ offset + 1 ] = color.g * intensity; + array[ offset + 2 ] = color.b * intensity; + + }; + + function setupLights ( lights ) { + + var l, ll, light, n, + r = 0, g = 0, b = 0, + color, skyColor, groundColor, + intensity, intensitySq, + position, + distance, + + zlights = _lights, + + dirColors = zlights.directional.colors, + dirPositions = zlights.directional.positions, + + pointColors = zlights.point.colors, + pointPositions = zlights.point.positions, + pointDistances = zlights.point.distances, + + spotColors = zlights.spot.colors, + spotPositions = zlights.spot.positions, + spotDistances = zlights.spot.distances, + spotDirections = zlights.spot.directions, + spotAnglesCos = zlights.spot.anglesCos, + spotExponents = zlights.spot.exponents, + + hemiSkyColors = zlights.hemi.skyColors, + hemiGroundColors = zlights.hemi.groundColors, + hemiPositions = zlights.hemi.positions, + + dirLength = 0, + pointLength = 0, + spotLength = 0, + hemiLength = 0, + + dirCount = 0, + pointCount = 0, + spotCount = 0, + hemiCount = 0, + + dirOffset = 0, + pointOffset = 0, + spotOffset = 0, + hemiOffset = 0; + + for ( l = 0, ll = lights.length; l < ll; l ++ ) { + + light = lights[ l ]; + + if ( light.onlyShadow ) continue; + + color = light.color; + intensity = light.intensity; + distance = light.distance; + + if ( light instanceof THREE.AmbientLight ) { + + if ( ! light.visible ) continue; + + if ( _this.gammaInput ) { + + r += color.r * color.r; + g += color.g * color.g; + b += color.b * color.b; + + } else { + + r += color.r; + g += color.g; + b += color.b; + + } + + } else if ( light instanceof THREE.DirectionalLight ) { + + dirCount += 1; + + if ( ! light.visible ) continue; + + _direction.setFromMatrixPosition( light.matrixWorld ); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.normalize(); + + dirOffset = dirLength * 3; + + dirPositions[ dirOffset ] = _direction.x; + dirPositions[ dirOffset + 1 ] = _direction.y; + dirPositions[ dirOffset + 2 ] = _direction.z; + + if ( _this.gammaInput ) { + + setColorGamma( dirColors, dirOffset, color, intensity * intensity ); + + } else { + + setColorLinear( dirColors, dirOffset, color, intensity ); + + } + + dirLength += 1; + + } else if ( light instanceof THREE.PointLight ) { + + pointCount += 1; + + if ( ! light.visible ) continue; + + pointOffset = pointLength * 3; + + if ( _this.gammaInput ) { + + setColorGamma( pointColors, pointOffset, color, intensity * intensity ); + + } else { + + setColorLinear( pointColors, pointOffset, color, intensity ); + + } + + _vector3.setFromMatrixPosition( light.matrixWorld ); + + pointPositions[ pointOffset ] = _vector3.x; + pointPositions[ pointOffset + 1 ] = _vector3.y; + pointPositions[ pointOffset + 2 ] = _vector3.z; + + pointDistances[ pointLength ] = distance; + + pointLength += 1; + + } else if ( light instanceof THREE.SpotLight ) { + + spotCount += 1; + + if ( ! light.visible ) continue; + + spotOffset = spotLength * 3; + + if ( _this.gammaInput ) { + + setColorGamma( spotColors, spotOffset, color, intensity * intensity ); + + } else { + + setColorLinear( spotColors, spotOffset, color, intensity ); + + } + + _vector3.setFromMatrixPosition( light.matrixWorld ); + + spotPositions[ spotOffset ] = _vector3.x; + spotPositions[ spotOffset + 1 ] = _vector3.y; + spotPositions[ spotOffset + 2 ] = _vector3.z; + + spotDistances[ spotLength ] = distance; + + _direction.copy( _vector3 ); + _vector3.setFromMatrixPosition( light.target.matrixWorld ); + _direction.sub( _vector3 ); + _direction.normalize(); + + spotDirections[ spotOffset ] = _direction.x; + spotDirections[ spotOffset + 1 ] = _direction.y; + spotDirections[ spotOffset + 2 ] = _direction.z; + + spotAnglesCos[ spotLength ] = Math.cos( light.angle ); + spotExponents[ spotLength ] = light.exponent; + + spotLength += 1; + + } else if ( light instanceof THREE.HemisphereLight ) { + + hemiCount += 1; + + if ( ! light.visible ) continue; + + _direction.setFromMatrixPosition( light.matrixWorld ); + _direction.normalize(); + + hemiOffset = hemiLength * 3; + + hemiPositions[ hemiOffset ] = _direction.x; + hemiPositions[ hemiOffset + 1 ] = _direction.y; + hemiPositions[ hemiOffset + 2 ] = _direction.z; + + skyColor = light.color; + groundColor = light.groundColor; + + if ( _this.gammaInput ) { + + intensitySq = intensity * intensity; + + setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq ); + setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq ); + + } else { + + setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity ); + setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity ); + + } + + hemiLength += 1; + + } + + } + + // null eventual remains from removed lights + // (this is to avoid if in shader) + + for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0; + for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0; + for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0; + for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0; + for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0; + + zlights.directional.length = dirLength; + zlights.point.length = pointLength; + zlights.spot.length = spotLength; + zlights.hemi.length = hemiLength; + + zlights.ambient[ 0 ] = r; + zlights.ambient[ 1 ] = g; + zlights.ambient[ 2 ] = b; + + }; + + // GL state setting + + this.setFaceCulling = function ( cullFace, frontFaceDirection ) { + + if ( cullFace === THREE.CullFaceNone ) { + + _gl.disable( _gl.CULL_FACE ); + + } else { + + if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) { + + _gl.frontFace( _gl.CW ); + + } else { + + _gl.frontFace( _gl.CCW ); + + } + + if ( cullFace === THREE.CullFaceBack ) { + + _gl.cullFace( _gl.BACK ); + + } else if ( cullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.FRONT ); + + } else { + + _gl.cullFace( _gl.FRONT_AND_BACK ); + + } + + _gl.enable( _gl.CULL_FACE ); + + } + + }; + + this.setMaterialFaces = function ( material ) { + + var doubleSided = material.side === THREE.DoubleSide; + var flipSided = material.side === THREE.BackSide; + + if ( _oldDoubleSided !== doubleSided ) { + + if ( doubleSided ) { + + _gl.disable( _gl.CULL_FACE ); + + } else { + + _gl.enable( _gl.CULL_FACE ); + + } + + _oldDoubleSided = doubleSided; + + } + + if ( _oldFlipSided !== flipSided ) { + + if ( flipSided ) { + + _gl.frontFace( _gl.CW ); + + } else { + + _gl.frontFace( _gl.CCW ); + + } + + _oldFlipSided = flipSided; + + } + + }; + + this.setDepthTest = function ( depthTest ) { + + if ( _oldDepthTest !== depthTest ) { + + if ( depthTest ) { + + _gl.enable( _gl.DEPTH_TEST ); + + } else { + + _gl.disable( _gl.DEPTH_TEST ); + + } + + _oldDepthTest = depthTest; + + } + + }; + + this.setDepthWrite = function ( depthWrite ) { + + if ( _oldDepthWrite !== depthWrite ) { + + _gl.depthMask( depthWrite ); + _oldDepthWrite = depthWrite; + + } + + }; + + function setLineWidth ( width ) { + + if ( width !== _oldLineWidth ) { + + _gl.lineWidth( width ); + + _oldLineWidth = width; + + } + + }; + + function setPolygonOffset ( polygonoffset, factor, units ) { + + if ( _oldPolygonOffset !== polygonoffset ) { + + if ( polygonoffset ) { + + _gl.enable( _gl.POLYGON_OFFSET_FILL ); + + } else { + + _gl.disable( _gl.POLYGON_OFFSET_FILL ); + + } + + _oldPolygonOffset = polygonoffset; + + } + + if ( polygonoffset && ( _oldPolygonOffsetFactor !== factor || _oldPolygonOffsetUnits !== units ) ) { + + _gl.polygonOffset( factor, units ); + + _oldPolygonOffsetFactor = factor; + _oldPolygonOffsetUnits = units; + + } + + }; + + this.setBlending = function ( blending, blendEquation, blendSrc, blendDst ) { + + if ( blending !== _oldBlending ) { + + if ( blending === THREE.NoBlending ) { + + _gl.disable( _gl.BLEND ); + + } else if ( blending === THREE.AdditiveBlending ) { + + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE ); + + } else if ( blending === THREE.SubtractiveBlending ) { + + // TODO: Find blendFuncSeparate() combination + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.ZERO, _gl.ONE_MINUS_SRC_COLOR ); + + } else if ( blending === THREE.MultiplyBlending ) { + + // TODO: Find blendFuncSeparate() combination + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.ZERO, _gl.SRC_COLOR ); + + } else if ( blending === THREE.CustomBlending ) { + + _gl.enable( _gl.BLEND ); + + } else { + + _gl.enable( _gl.BLEND ); + _gl.blendEquationSeparate( _gl.FUNC_ADD, _gl.FUNC_ADD ); + _gl.blendFuncSeparate( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA ); + + } + + _oldBlending = blending; + + } + + if ( blending === THREE.CustomBlending ) { + + if ( blendEquation !== _oldBlendEquation ) { + + _gl.blendEquation( paramThreeToGL( blendEquation ) ); + + _oldBlendEquation = blendEquation; + + } + + if ( blendSrc !== _oldBlendSrc || blendDst !== _oldBlendDst ) { + + _gl.blendFunc( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ) ); + + _oldBlendSrc = blendSrc; + _oldBlendDst = blendDst; + + } + + } else { + + _oldBlendEquation = null; + _oldBlendSrc = null; + _oldBlendDst = null; + + } + + }; + + // Textures + + function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) { + + if ( isImagePowerOfTwo ) { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) ); + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) ); + + } else { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); + + } + + if ( _glExtensionTextureFilterAnisotropic && texture.type !== THREE.FloatType ) { + + if ( texture.anisotropy > 1 || texture.__oldAnisotropy ) { + + _gl.texParameterf( textureType, _glExtensionTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _maxAnisotropy ) ); + texture.__oldAnisotropy = texture.anisotropy; + + } + + } + + }; + + this.setTexture = function ( texture, slot ) { + + if ( texture.needsUpdate ) { + + if ( ! texture.__webglInit ) { + + texture.__webglInit = true; + + texture.addEventListener( 'dispose', onTextureDispose ); + + texture.__webglTexture = _gl.createTexture(); + + _this.info.memory.textures ++; + + } + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + + var image = texture.image, + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); + + setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo ); + + var mipmap, mipmaps = texture.mipmaps; + + if ( texture instanceof THREE.DataTexture ) { + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isImagePowerOfTwo ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + + } + + texture.generateMipmaps = false; + + } else { + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); + + } + + } else if ( texture instanceof THREE.CompressedTexture ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + if ( texture.format !== THREE.RGBAFormat ) { + _gl.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + } else { + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + } + + } + + } else { // regular Texture (image, video, canvas) + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isImagePowerOfTwo ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); + + } + + texture.generateMipmaps = false; + + } else { + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image ); + + } + + } + + if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); + + texture.needsUpdate = false; + + if ( texture.onUpdate ) texture.onUpdate(); + + } else { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture ); + + } + + }; + + function clampToMaxSize ( image, maxSize ) { + + if ( image.width <= maxSize && image.height <= maxSize ) { + + return image; + + } + + // Warning: Scaling through the canvas will only work with images that use + // premultiplied alpha. + + var maxDimension = Math.max( image.width, image.height ); + var newWidth = Math.floor( image.width * maxSize / maxDimension ); + var newHeight = Math.floor( image.height * maxSize / maxDimension ); + + var canvas = document.createElement( 'canvas' ); + canvas.width = newWidth; + canvas.height = newHeight; + + var ctx = canvas.getContext( '2d' ); + ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight ); + + return canvas; + + } + + function setCubeTexture ( texture, slot ) { + + if ( texture.image.length === 6 ) { + + if ( texture.needsUpdate ) { + + if ( ! texture.image.__webglTextureCube ) { + + texture.addEventListener( 'dispose', onTextureDispose ); + + texture.image.__webglTextureCube = _gl.createTexture(); + + _this.info.memory.textures ++; + + } + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + + var isCompressed = texture instanceof THREE.CompressedTexture; + + var cubeImage = []; + + for ( var i = 0; i < 6; i ++ ) { + + if ( _this.autoScaleCubemaps && ! isCompressed ) { + + cubeImage[ i ] = clampToMaxSize( texture.image[ i ], _maxCubemapSize ); + + } else { + + cubeImage[ i ] = texture.image[ i ]; + + } + + } + + var image = cubeImage[ 0 ], + isImagePowerOfTwo = THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ), + glFormat = paramThreeToGL( texture.format ), + glType = paramThreeToGL( texture.type ); + + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo ); + + for ( var i = 0; i < 6; i ++ ) { + + if ( ! isCompressed ) { + + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); + + } else { + + var mipmap, mipmaps = cubeImage[ i ].mipmaps; + + for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { + + mipmap = mipmaps[ j ]; + if ( texture.format !== THREE.RGBAFormat ) { + + _gl.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + + } else { + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + } + + } + } + } + + if ( texture.generateMipmaps && isImagePowerOfTwo ) { + + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + + } + + texture.needsUpdate = false; + + if ( texture.onUpdate ) texture.onUpdate(); + + } else { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube ); + + } + + } + + }; + + function setCubeTextureDynamic ( texture, slot ) { + + _gl.activeTexture( _gl.TEXTURE0 + slot ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.__webglTexture ); + + }; + + // Render targets + + function setupFrameBuffer ( framebuffer, renderTarget, textureTarget ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureTarget, renderTarget.__webglTexture, 0 ); + + }; + + function setupRenderBuffer ( renderbuffer, renderTarget ) { + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + /* For some reason this is not working. Defaulting to RGBA4. + } else if ( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + */ + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + } else { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); + + } + + }; + + this.setRenderTarget = function ( renderTarget ) { + + var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); + + if ( renderTarget && ! renderTarget.__webglFramebuffer ) { + + if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true; + if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true; + + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); + + renderTarget.__webglTexture = _gl.createTexture(); + + _this.info.memory.textures ++; + + // Setup texture, create render and frame buffers + + var isTargetPowerOfTwo = THREE.Math.isPowerOfTwo( renderTarget.width ) && THREE.Math.isPowerOfTwo( renderTarget.height ), + glFormat = paramThreeToGL( renderTarget.format ), + glType = paramThreeToGL( renderTarget.type ); + + if ( isCube ) { + + renderTarget.__webglFramebuffer = []; + renderTarget.__webglRenderbuffer = []; + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget, isTargetPowerOfTwo ); + + for ( var i = 0; i < 6; i ++ ) { + + renderTarget.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + renderTarget.__webglRenderbuffer[ i ] = _gl.createRenderbuffer(); + + _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + + setupFrameBuffer( renderTarget.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); + setupRenderBuffer( renderTarget.__webglRenderbuffer[ i ], renderTarget ); + + } + + if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + + } else { + + renderTarget.__webglFramebuffer = _gl.createFramebuffer(); + + if ( renderTarget.shareDepthFrom ) { + + renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer; + + } else { + + renderTarget.__webglRenderbuffer = _gl.createRenderbuffer(); + + } + + _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, renderTarget, isTargetPowerOfTwo ); + + _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + + setupFrameBuffer( renderTarget.__webglFramebuffer, renderTarget, _gl.TEXTURE_2D ); + + if ( renderTarget.shareDepthFrom ) { + + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer ); + + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer ); + + } + + } else { + + setupRenderBuffer( renderTarget.__webglRenderbuffer, renderTarget ); + + } + + if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); + + } + + // Release everything + + if ( isCube ) { + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); + + } else { + + _gl.bindTexture( _gl.TEXTURE_2D, null ); + + } + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); + + } + + var framebuffer, width, height, vx, vy; + + if ( renderTarget ) { + + if ( isCube ) { + + framebuffer = renderTarget.__webglFramebuffer[ renderTarget.activeCubeFace ]; + + } else { + + framebuffer = renderTarget.__webglFramebuffer; + + } + + width = renderTarget.width; + height = renderTarget.height; + + vx = 0; + vy = 0; + + } else { + + framebuffer = null; + + width = _viewportWidth; + height = _viewportHeight; + + vx = _viewportX; + vy = _viewportY; + + } + + if ( framebuffer !== _currentFramebuffer ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.viewport( vx, vy, width, height ); + + _currentFramebuffer = framebuffer; + + } + + _currentWidth = width; + _currentHeight = height; + + }; + + function updateRenderTargetMipmap ( renderTarget ) { + + if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { + + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture ); + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); + + } else { + + _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture ); + _gl.generateMipmap( _gl.TEXTURE_2D ); + _gl.bindTexture( _gl.TEXTURE_2D, null ); + + } + + }; + + // Fallback filters for non-power-of-2 textures + + function filterFallback ( f ) { + + if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) { + + return _gl.NEAREST; + + } + + return _gl.LINEAR; + + }; + + // Map three.js constants to WebGL constants + + function paramThreeToGL ( p ) { + + if ( p === THREE.RepeatWrapping ) return _gl.REPEAT; + if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE; + if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT; + + if ( p === THREE.NearestFilter ) return _gl.NEAREST; + if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST; + if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR; + + if ( p === THREE.LinearFilter ) return _gl.LINEAR; + if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST; + if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR; + + if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE; + if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5; + + if ( p === THREE.ByteType ) return _gl.BYTE; + if ( p === THREE.ShortType ) return _gl.SHORT; + if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT; + if ( p === THREE.IntType ) return _gl.INT; + if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT; + if ( p === THREE.FloatType ) return _gl.FLOAT; + + if ( p === THREE.AlphaFormat ) return _gl.ALPHA; + if ( p === THREE.RGBFormat ) return _gl.RGB; + if ( p === THREE.RGBAFormat ) return _gl.RGBA; + if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE; + if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA; + + if ( p === THREE.AddEquation ) return _gl.FUNC_ADD; + if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT; + if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT; + + if ( p === THREE.ZeroFactor ) return _gl.ZERO; + if ( p === THREE.OneFactor ) return _gl.ONE; + if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR; + if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR; + if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA; + if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA; + if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA; + if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA; + + if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR; + if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR; + if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE; + + if ( _glExtensionCompressedTextureS3TC !== undefined ) { + + if ( p === THREE.RGB_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === THREE.RGBA_S3TC_DXT3_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === THREE.RGBA_S3TC_DXT5_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT; + + } + + return 0; + + }; + + // Allocations + + function allocateBones ( object ) { + + if ( _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture ) { + + return 1024; + + } else { + + // default for when object is not specified + // ( for example when prebuilding shader + // to be used with multiple objects ) + // + // - leave some extra space for other uniforms + // - limit here is ANGLE's 254 max uniform vectors + // (up to 54 should be safe) + + var nVertexUniforms = _gl.getParameter( _gl.MAX_VERTEX_UNIFORM_VECTORS ); + var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); + + var maxBones = nVertexMatrices; + + if ( object !== undefined && object instanceof THREE.SkinnedMesh ) { + + maxBones = Math.min( object.skeleton.bones.length, maxBones ); + + if ( maxBones < object.skeleton.bones.length ) { + + console.warn( 'WebGLRenderer: too many bones - ' + object.skeleton.bones.length + ', this GPU supports just ' + maxBones + ' (try OpenGL instead of ANGLE)' ); + + } + + } + + return maxBones; + + } + + }; + + function allocateLights( lights ) { + + var dirLights = 0; + var pointLights = 0; + var spotLights = 0; + var hemiLights = 0; + + for ( var l = 0, ll = lights.length; l < ll; l ++ ) { + + var light = lights[ l ]; + + if ( light.onlyShadow || light.visible === false ) continue; + + if ( light instanceof THREE.DirectionalLight ) dirLights ++; + if ( light instanceof THREE.PointLight ) pointLights ++; + if ( light instanceof THREE.SpotLight ) spotLights ++; + if ( light instanceof THREE.HemisphereLight ) hemiLights ++; + + } + + return { 'directional': dirLights, 'point': pointLights, 'spot': spotLights, 'hemi': hemiLights }; + + }; + + function allocateShadows( lights ) { + + var maxShadows = 0; + + for ( var l = 0, ll = lights.length; l < ll; l ++ ) { + + var light = lights[ l ]; + + if ( ! light.castShadow ) continue; + + if ( light instanceof THREE.SpotLight ) maxShadows ++; + if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++; + + } + + return maxShadows; + + }; + + // Initialization + + function initGL() { + + try { + + var attributes = { + alpha: _alpha, + depth: _depth, + stencil: _stencil, + antialias: _antialias, + premultipliedAlpha: _premultipliedAlpha, + preserveDrawingBuffer: _preserveDrawingBuffer + }; + + _gl = _context || _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes ); + + if ( _gl === null ) { + + throw 'Error creating WebGL context.'; + + } + + } catch ( error ) { + + console.error( error ); + + } + + _glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float' ); + _glExtensionTextureFloatLinear = _gl.getExtension( 'OES_texture_float_linear' ); + _glExtensionStandardDerivatives = _gl.getExtension( 'OES_standard_derivatives' ); + + _glExtensionTextureFilterAnisotropic = _gl.getExtension( 'EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + + _glExtensionCompressedTextureS3TC = _gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + + _glExtensionElementIndexUint = _gl.getExtension( 'OES_element_index_uint' ); + + + if ( _glExtensionTextureFloat === null ) { + + console.log( 'THREE.WebGLRenderer: Float textures not supported.' ); + + } + + if ( _glExtensionStandardDerivatives === null ) { + + console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' ); + + } + + if ( _glExtensionTextureFilterAnisotropic === null ) { + + console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' ); + + } + + if ( _glExtensionCompressedTextureS3TC === null ) { + + console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' ); + + } + + if ( _glExtensionElementIndexUint === null ) { + + console.log( 'THREE.WebGLRenderer: elementindex as unsigned integer not supported.' ); + + } + + if ( _gl.getShaderPrecisionFormat === undefined ) { + + _gl.getShaderPrecisionFormat = function () { + + return { + 'rangeMin': 1, + 'rangeMax': 1, + 'precision': 1 + }; + + } + } + + if ( _logarithmicDepthBuffer ) { + + _glExtensionFragDepth = _gl.getExtension( 'EXT_frag_depth' ); + + } + + }; + + function setDefaultGLState () { + + _gl.clearColor( 0, 0, 0, 1 ); + _gl.clearDepth( 1 ); + _gl.clearStencil( 0 ); + + _gl.enable( _gl.DEPTH_TEST ); + _gl.depthFunc( _gl.LEQUAL ); + + _gl.frontFace( _gl.CCW ); + _gl.cullFace( _gl.BACK ); + _gl.enable( _gl.CULL_FACE ); + + _gl.enable( _gl.BLEND ); + _gl.blendEquation( _gl.FUNC_ADD ); + _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA ); + + _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight ); + + _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); + + }; + + // default plugins (order is important) + + this.shadowMapPlugin = new THREE.ShadowMapPlugin(); + this.addPrePlugin( this.shadowMapPlugin ); + + this.addPostPlugin( new THREE.SpritePlugin() ); + this.addPostPlugin( new THREE.LensFlarePlugin() ); + +}; + +// File:src/renderers/WebGLRenderTarget.js + +/** + * @author szimek / https://github.com/szimek/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.WebGLRenderTarget = function ( width, height, options ) { + + this.width = width; + this.height = height; + + options = options || {}; + + this.wrapS = options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping; + this.wrapT = options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping; + + this.magFilter = options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter; + this.minFilter = options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter; + + this.anisotropy = options.anisotropy !== undefined ? options.anisotropy : 1; + + this.offset = new THREE.Vector2( 0, 0 ); + this.repeat = new THREE.Vector2( 1, 1 ); + + this.format = options.format !== undefined ? options.format : THREE.RGBAFormat; + this.type = options.type !== undefined ? options.type : THREE.UnsignedByteType; + + this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; + this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; + + this.generateMipmaps = true; + + this.shareDepthFrom = null; + +}; + +THREE.WebGLRenderTarget.prototype = { + + constructor: THREE.WebGLRenderTarget, + + setSize: function ( width, height ) { + + this.width = width; + this.height = height; + + }, + + clone: function () { + + var tmp = new THREE.WebGLRenderTarget( this.width, this.height ); + + tmp.wrapS = this.wrapS; + tmp.wrapT = this.wrapT; + + tmp.magFilter = this.magFilter; + tmp.minFilter = this.minFilter; + + tmp.anisotropy = this.anisotropy; + + tmp.offset.copy( this.offset ); + tmp.repeat.copy( this.repeat ); + + tmp.format = this.format; + tmp.type = this.type; + + tmp.depthBuffer = this.depthBuffer; + tmp.stencilBuffer = this.stencilBuffer; + + tmp.generateMipmaps = this.generateMipmaps; + + tmp.shareDepthFrom = this.shareDepthFrom; + + return tmp; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +}; + +THREE.EventDispatcher.prototype.apply( THREE.WebGLRenderTarget.prototype ); + +// File:src/renderers/WebGLRenderTargetCube.js + +/** + * @author alteredq / http://alteredqualia.com + */ + +THREE.WebGLRenderTargetCube = function ( width, height, options ) { + + THREE.WebGLRenderTarget.call( this, width, height, options ); + + this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 + +}; + +THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype ); + +// File:src/renderers/webgl/WebGLProgram.js + +THREE.WebGLProgram = ( function () { + + var programIdCount = 0; + + var generateDefines = function ( defines ) { + + var value, chunk, chunks = []; + + for ( var d in defines ) { + + value = defines[ d ]; + if ( value === false ) continue; + + chunk = "#define " + d + " " + value; + chunks.push( chunk ); + + } + + return chunks.join( "\n" ); + + }; + + var cacheUniformLocations = function ( gl, program, identifiers ) { + + var uniforms = {}; + + for ( var i = 0, l = identifiers.length; i < l; i ++ ) { + + var id = identifiers[ i ]; + uniforms[ id ] = gl.getUniformLocation( program, id ); + + } + + return uniforms; + + }; + + var cacheAttributeLocations = function ( gl, program, identifiers ) { + + var attributes = {}; + + for ( var i = 0, l = identifiers.length; i < l; i ++ ) { + + var id = identifiers[ i ]; + attributes[ id ] = gl.getAttribLocation( program, id ); + + } + + return attributes; + + }; + + return function ( renderer, code, material, parameters ) { + + var _this = renderer; + var _gl = _this.context; + + var defines = material.defines; + var uniforms = material.__webglShader.uniforms; + var attributes = material.attributes; + + var vertexShader = material.__webglShader.vertexShader; + var fragmentShader = material.__webglShader.fragmentShader; + + var index0AttributeName = material.index0AttributeName; + + if ( index0AttributeName === undefined && parameters.morphTargets === true ) { + + // programs with morphTargets displace position out of attribute 0 + + index0AttributeName = 'position'; + + } + + var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC"; + + if ( parameters.shadowMapType === THREE.PCFShadowMap ) { + + shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF"; + + } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) { + + shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT"; + + } + + // console.log( "building new program " ); + + // + + var customDefines = generateDefines( defines ); + + // + + var program = _gl.createProgram(); + + var prefix_vertex, prefix_fragment; + + if ( material instanceof THREE.RawShaderMaterial ) { + + prefix_vertex = ''; + prefix_fragment = ''; + + } else { + + prefix_vertex = [ + + "precision " + parameters.precision + " float;", + "precision " + parameters.precision + " int;", + + customDefines, + + parameters.supportsVertexTextures ? "#define VERTEX_TEXTURES" : "", + + _this.gammaInput ? "#define GAMMA_INPUT" : "", + _this.gammaOutput ? "#define GAMMA_OUTPUT" : "", + + "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, + "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, + "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, + "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, + + "#define MAX_SHADOWS " + parameters.maxShadows, + + "#define MAX_BONES " + parameters.maxBones, + + parameters.map ? "#define USE_MAP" : "", + parameters.envMap ? "#define USE_ENVMAP" : "", + parameters.lightMap ? "#define USE_LIGHTMAP" : "", + parameters.bumpMap ? "#define USE_BUMPMAP" : "", + parameters.normalMap ? "#define USE_NORMALMAP" : "", + parameters.specularMap ? "#define USE_SPECULARMAP" : "", + parameters.alphaMap ? "#define USE_ALPHAMAP" : "", + parameters.vertexColors ? "#define USE_COLOR" : "", + + parameters.skinning ? "#define USE_SKINNING" : "", + parameters.useVertexTexture ? "#define BONE_TEXTURE" : "", + + parameters.morphTargets ? "#define USE_MORPHTARGETS" : "", + parameters.morphNormals ? "#define USE_MORPHNORMALS" : "", + parameters.wrapAround ? "#define WRAP_AROUND" : "", + parameters.doubleSided ? "#define DOUBLE_SIDED" : "", + parameters.flipSided ? "#define FLIP_SIDED" : "", + + parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", + parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", + parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", + parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", + + parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "", + + parameters.logarithmicDepthBuffer ? "#define USE_LOGDEPTHBUF" : "", + //_this._glExtensionFragDepth ? "#define USE_LOGDEPTHBUF_EXT" : "", + + + "uniform mat4 modelMatrix;", + "uniform mat4 modelViewMatrix;", + "uniform mat4 projectionMatrix;", + "uniform mat4 viewMatrix;", + "uniform mat3 normalMatrix;", + "uniform vec3 cameraPosition;", + + "attribute vec3 position;", + "attribute vec3 normal;", + "attribute vec2 uv;", + "attribute vec2 uv2;", + + "#ifdef USE_COLOR", + + " attribute vec3 color;", + + "#endif", + + "#ifdef USE_MORPHTARGETS", + + " attribute vec3 morphTarget0;", + " attribute vec3 morphTarget1;", + " attribute vec3 morphTarget2;", + " attribute vec3 morphTarget3;", + + " #ifdef USE_MORPHNORMALS", + + " attribute vec3 morphNormal0;", + " attribute vec3 morphNormal1;", + " attribute vec3 morphNormal2;", + " attribute vec3 morphNormal3;", + + " #else", + + " attribute vec3 morphTarget4;", + " attribute vec3 morphTarget5;", + " attribute vec3 morphTarget6;", + " attribute vec3 morphTarget7;", + + " #endif", + + "#endif", + + "#ifdef USE_SKINNING", + + " attribute vec4 skinIndex;", + " attribute vec4 skinWeight;", + + "#endif", + + "" + + ].join( '\n' ); + + prefix_fragment = [ + + "precision " + parameters.precision + " float;", + "precision " + parameters.precision + " int;", + + ( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "", + + customDefines, + + "#define MAX_DIR_LIGHTS " + parameters.maxDirLights, + "#define MAX_POINT_LIGHTS " + parameters.maxPointLights, + "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights, + "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights, + + "#define MAX_SHADOWS " + parameters.maxShadows, + + parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "", + + _this.gammaInput ? "#define GAMMA_INPUT" : "", + _this.gammaOutput ? "#define GAMMA_OUTPUT" : "", + + ( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "", + ( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "", + + parameters.map ? "#define USE_MAP" : "", + parameters.envMap ? "#define USE_ENVMAP" : "", + parameters.lightMap ? "#define USE_LIGHTMAP" : "", + parameters.bumpMap ? "#define USE_BUMPMAP" : "", + parameters.normalMap ? "#define USE_NORMALMAP" : "", + parameters.specularMap ? "#define USE_SPECULARMAP" : "", + parameters.alphaMap ? "#define USE_ALPHAMAP" : "", + parameters.vertexColors ? "#define USE_COLOR" : "", + + parameters.metal ? "#define METAL" : "", + parameters.wrapAround ? "#define WRAP_AROUND" : "", + parameters.doubleSided ? "#define DOUBLE_SIDED" : "", + parameters.flipSided ? "#define FLIP_SIDED" : "", + + parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "", + parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "", + parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "", + parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "", + + parameters.logarithmicDepthBuffer ? "#define USE_LOGDEPTHBUF" : "", + //_this._glExtensionFragDepth ? "#define USE_LOGDEPTHBUF_EXT" : "", + + "uniform mat4 viewMatrix;", + "uniform vec3 cameraPosition;", + "" + + ].join( '\n' ); + + } + + var glVertexShader = new THREE.WebGLShader( _gl, _gl.VERTEX_SHADER, prefix_vertex + vertexShader ); + var glFragmentShader = new THREE.WebGLShader( _gl, _gl.FRAGMENT_SHADER, prefix_fragment + fragmentShader ); + + _gl.attachShader( program, glVertexShader ); + _gl.attachShader( program, glFragmentShader ); + + if ( index0AttributeName !== undefined ) { + + // Force a particular attribute to index 0. + // because potentially expensive emulation is done by browser if attribute 0 is disabled. + // And, color, for example is often automatically bound to index 0 so disabling it + + _gl.bindAttribLocation( program, 0, index0AttributeName ); + + } + + _gl.linkProgram( program ); + + if ( _gl.getProgramParameter( program, _gl.LINK_STATUS ) === false ) { + + console.error( 'THREE.WebGLProgram: Could not initialise shader.' ); + console.error( 'gl.VALIDATE_STATUS', _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ) ); + console.error( 'gl.getError()', _gl.getError() ); + + } + + if ( _gl.getProgramInfoLog( program ) !== '' ) { + + console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', _gl.getProgramInfoLog( program ) ); + + } + + // clean up + + _gl.deleteShader( glVertexShader ); + _gl.deleteShader( glFragmentShader ); + + // cache uniform locations + + var identifiers = [ + + 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', 'morphTargetInfluences', 'bindMatrix', 'bindMatrixInverse' + + ]; + + if ( parameters.useVertexTexture ) { + + identifiers.push( 'boneTexture' ); + identifiers.push( 'boneTextureWidth' ); + identifiers.push( 'boneTextureHeight' ); + + } else { + + identifiers.push( 'boneGlobalMatrices' ); + + } + + if ( parameters.logarithmicDepthBuffer ) { + + identifiers.push('logDepthBufFC'); + + } + + + for ( var u in uniforms ) { + + identifiers.push( u ); + + } + + this.uniforms = cacheUniformLocations( _gl, program, identifiers ); + + // cache attributes locations + + identifiers = [ + + "position", "normal", "uv", "uv2", "tangent", "color", + "skinIndex", "skinWeight", "lineDistance" + + ]; + + for ( var i = 0; i < parameters.maxMorphTargets; i ++ ) { + + identifiers.push( "morphTarget" + i ); + + } + + for ( var i = 0; i < parameters.maxMorphNormals; i ++ ) { + + identifiers.push( "morphNormal" + i ); + + } + + for ( var a in attributes ) { + + identifiers.push( a ); + + } + + this.attributes = cacheAttributeLocations( _gl, program, identifiers ); + + // + + this.id = programIdCount ++; + this.code = code; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; + + return this; + + }; + +} )(); + +// File:src/renderers/webgl/WebGLShader.js + +THREE.WebGLShader = ( function () { + + var addLineNumbers = function ( string ) { + + var lines = string.split( '\n' ); + + for ( var i = 0; i < lines.length; i ++ ) { + + lines[ i ] = ( i + 1 ) + ': ' + lines[ i ]; + + } + + return lines.join( '\n' ); + + }; + + return function ( gl, type, string ) { + + var shader = gl.createShader( type ); + + gl.shaderSource( shader, string ); + gl.compileShader( shader ); + + if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) { + + console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' ); + + } + + if ( gl.getShaderInfoLog( shader ) !== '' ) { + + console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', gl.getShaderInfoLog( shader ) ); + console.warn( addLineNumbers( string ) ); + + } + + // --enable-privileged-webgl-extension + // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); + + return shader; + + }; + +} )(); + +// File:src/renderers/renderables/RenderableVertex.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +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 ); + +}; + +// File:src/renderers/renderables/RenderableFace.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +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; + +}; + +// File:src/renderers/renderables/RenderableObject.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableObject = function () { + + this.id = 0; + + this.object = null; + this.z = 0; + +}; + +// File:src/renderers/renderables/RenderableSprite.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +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; + +}; + +// File:src/renderers/renderables/RenderableLine.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +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; + +}; + +// File:src/extras/GeometryUtils.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.GeometryUtils = { + + merge: function ( geometry1, geometry2, materialIndexOffset ) { + + console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' ); + + var matrix; + + if ( geometry2 instanceof THREE.Mesh ) { + + geometry2.matrixAutoUpdate && geometry2.updateMatrix(); + + matrix = geometry2.matrix; + geometry2 = geometry2.geometry; + + } + + geometry1.merge( geometry2, matrix, materialIndexOffset ); + + }, + + center: function ( geometry ) { + + console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' ); + return geometry.center(); + + } + +}; + +// File:src/extras/ImageUtils.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author Daosheng Mu / https://github.com/DaoshengMu/ + */ + +THREE.ImageUtils = { + + crossOrigin: undefined, + + loadTexture: function ( url, mapping, onLoad, onError ) { + + var loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; + + var texture = new THREE.Texture( undefined, mapping ); + + loader.load( url, function ( image ) { + + texture.image = image; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + }, undefined, function ( event ) { + + if ( onError ) onError( event ); + + } ); + + texture.sourceFile = url; + + return texture; + + }, + + loadTextureCube: function ( array, mapping, onLoad, onError ) { + + var images = []; + + var loader = new THREE.ImageLoader(); + loader.crossOrigin = this.crossOrigin; + + var texture = new THREE.CubeTexture( images, mapping ); + + // no flipping needed for cube textures + + texture.flipY = false; + + var loaded = 0; + + var loadTexture = function ( i ) { + + loader.load( array[ i ], function ( image ) { + + texture.images[ i ] = image; + + loaded += 1; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + } ); + + } + + for ( var i = 0, il = array.length; i < il; ++ i ) { + + loadTexture( i ); + + } + + return texture; + + }, + + loadCompressedTexture: function () { + + console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' ) + + }, + + loadCompressedTextureCube: function () { + + console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' ) + + }, + + getNormalMap: function ( image, depth ) { + + // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/ + + var cross = function ( a, b ) { + + return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ]; + + } + + var subtract = function ( a, b ) { + + return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ]; + + } + + var normalize = function ( a ) { + + var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] ); + return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ]; + + } + + depth = depth | 1; + + var width = image.width; + var height = image.height; + + var canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0 ); + + var data = context.getImageData( 0, 0, width, height ).data; + var imageData = context.createImageData( width, height ); + var output = imageData.data; + + for ( var x = 0; x < width; x ++ ) { + + for ( var y = 0; y < height; y ++ ) { + + var ly = y - 1 < 0 ? 0 : y - 1; + var uy = y + 1 > height - 1 ? height - 1 : y + 1; + var lx = x - 1 < 0 ? 0 : x - 1; + var ux = x + 1 > width - 1 ? width - 1 : x + 1; + + var points = []; + var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ]; + points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] ); + points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] ); + points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] ); + points.push( [ 1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] ); + points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] ); + points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] ); + + var normals = []; + var num_points = points.length; + + for ( var i = 0; i < num_points; i ++ ) { + + var v1 = points[ i ]; + var v2 = points[ ( i + 1 ) % num_points ]; + v1 = subtract( v1, origin ); + v2 = subtract( v2, origin ); + normals.push( normalize( cross( v1, v2 ) ) ); + + } + + var normal = [ 0, 0, 0 ]; + + for ( var i = 0; i < normals.length; i ++ ) { + + normal[ 0 ] += normals[ i ][ 0 ]; + normal[ 1 ] += normals[ i ][ 1 ]; + normal[ 2 ] += normals[ i ][ 2 ]; + + } + + normal[ 0 ] /= normals.length; + normal[ 1 ] /= normals.length; + normal[ 2 ] /= normals.length; + + var idx = ( y * width + x ) * 4; + + output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0; + output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0; + output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0; + output[ idx + 3 ] = 255; + + } + + } + + context.putImageData( imageData, 0, 0 ); + + return canvas; + + }, + + generateDataTexture: function ( width, height, color ) { + + var size = width * height; + var data = new Uint8Array( 3 * size ); + + var r = Math.floor( color.r * 255 ); + var g = Math.floor( color.g * 255 ); + var b = Math.floor( color.b * 255 ); + + for ( var i = 0; i < size; i ++ ) { + + data[ i * 3 ] = r; + data[ i * 3 + 1 ] = g; + data[ i * 3 + 2 ] = b; + + } + + var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat ); + texture.needsUpdate = true; + + return texture; + + } + +}; + +// File:src/extras/SceneUtils.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SceneUtils = { + + createMultiMaterialObject: function ( geometry, materials ) { + + var group = new THREE.Object3D(); + + for ( var i = 0, l = materials.length; i < l; i ++ ) { + + group.add( new THREE.Mesh( geometry, materials[ i ] ) ); + + } + + return group; + + }, + + detach: function ( child, parent, scene ) { + + child.applyMatrix( parent.matrixWorld ); + parent.remove( child ); + scene.add( child ); + + }, + + attach: function ( child, scene, parent ) { + + var matrixWorldInverse = new THREE.Matrix4(); + matrixWorldInverse.getInverse( parent.matrixWorld ); + child.applyMatrix( matrixWorldInverse ); + + scene.remove( child ); + parent.add( child ); + + } + +}; + +// File:src/extras/FontUtils.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author alteredq / http://alteredqualia.com/ + * + * For Text operations in three.js (See TextGeometry) + * + * It uses techniques used in: + * + * typeface.js and canvastext + * For converting fonts and rendering with javascript + * http://typeface.neocracy.org + * + * Triangulation ported from AS3 + * Simple Polygon Triangulation + * http://actionsnippet.com/?p=1462 + * + * A Method to triangulate shapes with holes + * http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/ + * + */ + +THREE.FontUtils = { + + faces: {}, + + // Just for now. face[weight][style] + + face: 'helvetiker', + weight: 'normal', + style: 'normal', + size: 150, + divisions: 10, + + getFace: function () { + + try { + + return this.faces[ this.face ][ this.weight ][ this.style ]; + + } catch (e) { + + throw "The font " + this.face + " with " + this.weight + " weight and " + this.style + " style is missing." + + }; + + }, + + loadFace: function ( data ) { + + var family = data.familyName.toLowerCase(); + + var ThreeFont = this; + + ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {}; + + ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {}; + ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; + + var face = ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data; + + return data; + + }, + + drawText: function ( text ) { + + var characterPts = [], allPts = []; + + // RenderText + + var i, p, + face = this.getFace(), + scale = this.size / face.resolution, + offset = 0, + chars = String( text ).split( '' ), + length = chars.length; + + var fontPaths = []; + + for ( i = 0; i < length; i ++ ) { + + var path = new THREE.Path(); + + var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path ); + offset += ret.offset; + + fontPaths.push( ret.path ); + + } + + // get the width + + var width = offset / 2; + // + // for ( p = 0; p < allPts.length; p++ ) { + // + // allPts[ p ].x -= width; + // + // } + + //var extract = this.extractPoints( allPts, characterPts ); + //extract.contour = allPts; + + //extract.paths = fontPaths; + //extract.offset = width; + + return { paths: fontPaths, offset: width }; + + }, + + + + + extractGlyphPoints: function ( c, face, scale, offset, path ) { + + var pts = []; + + var i, i2, divisions, + outline, action, length, + scaleX, scaleY, + x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2, + laste, + glyph = face.glyphs[ c ] || face.glyphs[ '?' ]; + + if ( ! glyph ) return; + + if ( glyph.o ) { + + outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + length = outline.length; + + scaleX = scale; + scaleY = scale; + + for ( i = 0; i < length; ) { + + action = outline[ i ++ ]; + + //console.log( action ); + + switch ( action ) { + + case 'm': + + // Move To + + x = outline[ i ++ ] * scaleX + offset; + y = outline[ i ++ ] * scaleY; + + path.moveTo( x, y ); + break; + + case 'l': + + // Line To + + x = outline[ i ++ ] * scaleX + offset; + y = outline[ i ++ ] * scaleY; + path.lineTo( x,y ); + break; + + case 'q': + + // QuadraticCurveTo + + cpx = outline[ i ++ ] * scaleX + offset; + cpy = outline[ i ++ ] * scaleY; + cpx1 = outline[ i ++ ] * scaleX + offset; + cpy1 = outline[ i ++ ] * scaleY; + + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + + laste = pts[ pts.length - 1 ]; + + if ( laste ) { + + cpx0 = laste.x; + cpy0 = laste.y; + + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { + + var t = i2 / divisions; + var tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + var ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); + } + + } + + break; + + case 'b': + + // Cubic Bezier Curve + + cpx = outline[ i ++ ] * scaleX + offset; + cpy = outline[ i ++ ] * scaleY; + cpx1 = outline[ i ++ ] * scaleX + offset; + cpy1 = outline[ i ++ ] * scaleY; + cpx2 = outline[ i ++ ] * scaleX + offset; + cpy2 = outline[ i ++ ] * scaleY; + + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + + laste = pts[ pts.length - 1 ]; + + if ( laste ) { + + cpx0 = laste.x; + cpy0 = laste.y; + + for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) { + + var t = i2 / divisions; + var tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + var ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); + + } + + } + + break; + + } + + } + } + + + + return { offset: glyph.ha * scale, path:path }; + } + +}; + + +THREE.FontUtils.generateShapes = function ( text, parameters ) { + + // Parameters + + parameters = parameters || {}; + + var size = parameters.size !== undefined ? parameters.size : 100; + var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments : 4; + + var font = parameters.font !== undefined ? parameters.font : 'helvetiker'; + var weight = parameters.weight !== undefined ? parameters.weight : 'normal'; + var style = parameters.style !== undefined ? parameters.style : 'normal'; + + THREE.FontUtils.size = size; + THREE.FontUtils.divisions = curveSegments; + + THREE.FontUtils.face = font; + THREE.FontUtils.weight = weight; + THREE.FontUtils.style = style; + + // Get a Font data json object + + var data = THREE.FontUtils.drawText( text ); + + var paths = data.paths; + var shapes = []; + + for ( var p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + +}; + + +/** + * This code is a quick port of code written in C++ which was submitted to + * flipcode.com by John W. Ratcliff // July 22, 2000 + * See original code and more information here: + * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml + * + * ported to actionscript by Zevan Rosser + * www.actionsnippet.com + * + * ported to javascript by Joshua Koo + * http://www.lab4games.net/zz85/blog + * + */ + + +( function ( namespace ) { + + var EPSILON = 0.0000000001; + + // takes in an contour array and returns + + var process = function ( contour, indices ) { + + var n = contour.length; + + if ( n < 3 ) return null; + + var result = [], + verts = [], + vertIndices = []; + + /* we want a counter-clockwise polygon in verts */ + + var u, v, w; + + if ( area( contour ) > 0.0 ) { + + for ( v = 0; v < n; v ++ ) verts[ v ] = v; + + } else { + + for ( v = 0; v < n; v ++ ) verts[ v ] = ( n - 1 ) - v; + + } + + var nv = n; + + /* remove nv - 2 vertices, creating 1 triangle every time */ + + var count = 2 * nv; /* error detection */ + + for ( v = nv - 1; nv > 2; ) { + + /* if we loop, it is probably a non-simple polygon */ + + if ( ( count -- ) <= 0 ) { + + //** Triangulate: ERROR - probable bad polygon! + + //throw ( "Warning, unable to triangulate polygon!" ); + //return null; + // Sometimes warning is fine, especially polygons are triangulated in reverse. + console.log( 'Warning, unable to triangulate polygon!' ); + + if ( indices ) return vertIndices; + return result; + + } + + /* three consecutive vertices in current polygon, */ + + u = v; if ( nv <= u ) u = 0; /* previous */ + v = u + 1; if ( nv <= v ) v = 0; /* new v */ + w = v + 1; if ( nv <= w ) w = 0; /* next */ + + if ( snip( contour, u, v, w, nv, verts ) ) { + + var a, b, c, s, t; + + /* true names of the vertices */ + + a = verts[ u ]; + b = verts[ v ]; + c = verts[ w ]; + + /* output Triangle */ + + result.push( [ contour[ a ], + contour[ b ], + contour[ c ] ] ); + + + vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] ); + + /* remove v from the remaining polygon */ + + for ( s = v, t = v + 1; t < nv; s++, t++ ) { + + verts[ s ] = verts[ t ]; + + } + + nv --; + + /* reset error detection counter */ + + count = 2 * nv; + + } + + } + + if ( indices ) return vertIndices; + return result; + + }; + + // calculate area of the contour polygon + + var area = function ( contour ) { + + var n = contour.length; + var a = 0.0; + + for ( var p = n - 1, q = 0; q < n; p = q ++ ) { + + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + + } + + return a * 0.5; + + }; + + var snip = function ( contour, u, v, w, n, verts ) { + + var p; + var ax, ay, bx, by; + var cx, cy, px, py; + + ax = contour[ verts[ u ] ].x; + ay = contour[ verts[ u ] ].y; + + bx = contour[ verts[ v ] ].x; + by = contour[ verts[ v ] ].y; + + cx = contour[ verts[ w ] ].x; + cy = contour[ verts[ w ] ].y; + + if ( EPSILON > ( ( ( bx - ax ) * ( cy - ay ) ) - ( ( by - ay ) * ( cx - ax ) ) ) ) return false; + + var aX, aY, bX, bY, cX, cY; + var apx, apy, bpx, bpy, cpx, cpy; + var cCROSSap, bCROSScp, aCROSSbp; + + aX = cx - bx; aY = cy - by; + bX = ax - cx; bY = ay - cy; + cX = bx - ax; cY = by - ay; + + for ( p = 0; p < n; p ++ ) { + + px = contour[ verts[ p ] ].x + py = contour[ verts[ p ] ].y + + if ( ( ( px === ax ) && ( py === ay ) ) || + ( ( px === bx ) && ( py === by ) ) || + ( ( px === cx ) && ( py === cy ) ) ) continue; + + apx = px - ax; apy = py - ay; + bpx = px - bx; bpy = py - by; + cpx = px - cx; cpy = py - cy; + + // see if p is inside triangle abc + + aCROSSbp = aX * bpy - aY * bpx; + cCROSSap = cX * apy - cY * apx; + bCROSScp = bX * cpy - bY * cpx; + + if ( ( aCROSSbp >= - EPSILON ) && ( bCROSScp >= - EPSILON ) && ( cCROSSap >= - EPSILON ) ) return false; + + } + + return true; + + }; + + + namespace.Triangulate = process; + namespace.Triangulate.area = area; + + return namespace; + +} )( THREE.FontUtils ); + +// To use the typeface.js face files, hook up the API +self._typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace }; +THREE.typeface_js = self._typeface_js; + +// File:src/extras/core/Curve.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Extensible curve object + * + * Some common of Curve methods + * .getPoint(t), getTangent(t) + * .getPointAt(u), getTagentAt(u) + * .getPoints(), .getSpacedPoints() + * .getLength() + * .updateArcLengths() + * + * This following classes subclasses THREE.Curve: + * + * -- 2d classes -- + * THREE.LineCurve + * THREE.QuadraticBezierCurve + * THREE.CubicBezierCurve + * THREE.SplineCurve + * THREE.ArcCurve + * THREE.EllipseCurve + * + * -- 3d classes -- + * THREE.LineCurve3 + * THREE.QuadraticBezierCurve3 + * THREE.CubicBezierCurve3 + * THREE.SplineCurve3 + * THREE.ClosedSplineCurve3 + * + * A series of curves can be represented as a THREE.CurvePath + * + **/ + +/************************************************************** + * Abstract Curve base class + **************************************************************/ + +THREE.Curve = function () { + +}; + +// Virtual base class method to overwrite and implement in subclasses +// - t [0 .. 1] + +THREE.Curve.prototype.getPoint = function ( t ) { + + console.log( "Warning, getPoint() not implemented!" ); + return null; + +}; + +// Get point at relative position in curve according to arc length +// - u [0 .. 1] + +THREE.Curve.prototype.getPointAt = function ( u ) { + + var t = this.getUtoTmapping( u ); + return this.getPoint( t ); + +}; + +// Get sequence of points using getPoint( t ) + +THREE.Curve.prototype.getPoints = function ( divisions ) { + + if ( ! divisions ) divisions = 5; + + var d, pts = []; + + for ( d = 0; d <= divisions; d ++ ) { + + pts.push( this.getPoint( d / divisions ) ); + + } + + return pts; + +}; + +// Get sequence of points using getPointAt( u ) + +THREE.Curve.prototype.getSpacedPoints = function ( divisions ) { + + if ( ! divisions ) divisions = 5; + + var d, pts = []; + + for ( d = 0; d <= divisions; d ++ ) { + + pts.push( this.getPointAt( d / divisions ) ); + + } + + return pts; + +}; + +// Get total curve arc length + +THREE.Curve.prototype.getLength = function () { + + var lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; + +}; + +// Get list of cumulative segment lengths + +THREE.Curve.prototype.getLengths = function ( divisions ) { + + if ( ! divisions ) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions): 200; + + if ( this.cacheArcLengths + && ( this.cacheArcLengths.length == divisions + 1 ) + && ! this.needsUpdate) { + + //console.log( "cached", this.cacheArcLengths ); + return this.cacheArcLengths; + + } + + this.needsUpdate = false; + + var cache = []; + var current, last = this.getPoint( 0 ); + var p, sum = 0; + + cache.push( 0 ); + + for ( p = 1; p <= divisions; p ++ ) { + + current = this.getPoint ( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; + + } + + this.cacheArcLengths = cache; + + return cache; // { sums: cache, sum:sum }; Sum is in the last element. + +}; + + +THREE.Curve.prototype.updateArcLengths = function() { + this.needsUpdate = true; + this.getLengths(); +}; + +// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance + +THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) { + + var arcLengths = this.getLengths(); + + var i = 0, il = arcLengths.length; + + var targetArcLength; // The targeted u distance value to get + + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; + + } + + //var time = Date.now(); + + // binary search for the index with largest value smaller than target u distance + + var low = 0, high = il - 1, comparison; + + while ( low <= high ) { + + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + + comparison = arcLengths[ i ] - targetArcLength; + + if ( comparison < 0 ) { + + low = i + 1; + continue; + + } else if ( comparison > 0 ) { + + high = i - 1; + continue; + + } else { + + high = i; + break; + + // DONE + + } + + } + + i = high; + + //console.log('b' , i, low, high, Date.now()- time); + + if ( arcLengths[ i ] == targetArcLength ) { + + var t = i / ( il - 1 ); + return t; + + } + + // we could get finer grain at lengths, or use simple interpolatation between two points + + var lengthBefore = arcLengths[ i ]; + var lengthAfter = arcLengths[ i + 1 ]; + + var segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + + var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + + // add that fractional amount to t + + var t = ( i + segmentFraction ) / ( il -1 ); + + return t; + +}; + +// Returns a unit vector tangent at t +// In case any sub curve does not implement its tangent derivation, +// 2 points a small delta apart will be used to find its gradient +// which seems to give a reasonable approximation + +THREE.Curve.prototype.getTangent = function( t ) { + + var delta = 0.0001; + var t1 = t - delta; + var t2 = t + delta; + + // Capping in case of danger + + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; + + var pt1 = this.getPoint( t1 ); + var pt2 = this.getPoint( t2 ); + + var vec = pt2.clone().sub(pt1); + return vec.normalize(); + +}; + + +THREE.Curve.prototype.getTangentAt = function ( u ) { + + var t = this.getUtoTmapping( u ); + return this.getTangent( t ); + +}; + + + + + +/************************************************************** + * Utils + **************************************************************/ + +THREE.Curve.Utils = { + + tangentQuadraticBezier: function ( t, p0, p1, p2 ) { + + return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 ); + + }, + + // Puay Bing, thanks for helping with this derivative! + + tangentCubicBezier: function (t, p0, p1, p2, p3 ) { + + return - 3 * p0 * (1 - t) * (1 - t) + + 3 * p1 * (1 - t) * (1-t) - 6 *t *p1 * (1-t) + + 6 * t * p2 * (1-t) - 3 * t * t * p2 + + 3 * t * t * p3; + }, + + + tangentSpline: function ( t, p0, p1, p2, p3 ) { + + // To check if my formulas are correct + + var h00 = 6 * t * t - 6 * t; // derived from 2t^3 − 3t^2 + 1 + var h10 = 3 * t * t - 4 * t + 1; // t^3 − 2t^2 + t + var h01 = - 6 * t * t + 6 * t; // − 2t3 + 3t2 + var h11 = 3 * t * t - 2 * t; // t3 − t2 + + return h00 + h10 + h01 + h11; + + }, + + // Catmull-Rom + + interpolate: function( p0, p1, p2, p3, t ) { + + var v0 = ( p2 - p0 ) * 0.5; + var v1 = ( p3 - p1 ) * 0.5; + var t2 = t * t; + var t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + } + +}; + + +// TODO: Transformation for Curves? + +/************************************************************** + * 3D Curves + **************************************************************/ + +// A Factory method for creating new curve subclasses + +THREE.Curve.create = function ( constructor, getPointFunc ) { + + constructor.prototype = Object.create( THREE.Curve.prototype ); + constructor.prototype.getPoint = getPointFunc; + + return constructor; + +}; + +// File:src/extras/core/CurvePath.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + **/ + +/************************************************************** + * Curved Path - a curve path is simply a array of connected + * curves, but retains the api of a curve + **************************************************************/ + +THREE.CurvePath = function () { + + this.curves = []; + this.bends = []; + + this.autoClose = false; // Automatically closes the path +}; + +THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype ); + +THREE.CurvePath.prototype.add = function ( curve ) { + + this.curves.push( curve ); + +}; + +THREE.CurvePath.prototype.checkConnection = function() { + // TODO + // If the ending of curve is not connected to the starting + // or the next curve, then, this is not a real path +}; + +THREE.CurvePath.prototype.closePath = function() { + // TODO Test + // and verify for vector3 (needs to implement equals) + // Add a line curve if start and end of lines are not connected + var startPoint = this.curves[0].getPoint(0); + var endPoint = this.curves[this.curves.length-1].getPoint(1); + + if (! startPoint.equals(endPoint)) { + this.curves.push( new THREE.LineCurve(endPoint, startPoint) ); + } + +}; + +// To get accurate point with reference to +// entire path distance at time t, +// following has to be done: + +// 1. Length of each sub path have to be known +// 2. Locate and identify type of curve +// 3. Get t for the curve +// 4. Return curve.getPointAt(t') + +THREE.CurvePath.prototype.getPoint = function( t ) { + + var d = t * this.getLength(); + var curveLengths = this.getCurveLengths(); + var i = 0, diff, curve; + + // To think about boundaries points. + + while ( i < curveLengths.length ) { + + if ( curveLengths[ i ] >= d ) { + + diff = curveLengths[ i ] - d; + curve = this.curves[ i ]; + + var u = 1 - diff / curve.getLength(); + + return curve.getPointAt( u ); + + break; + } + + i ++; + + } + + return null; + + // loop where sum != 0, sum > d , sum+1 maxX ) maxX = p.x; + else if ( p.x < minX ) minX = p.x; + + if ( p.y > maxY ) maxY = p.y; + else if ( p.y < minY ) minY = p.y; + + if ( v3 ) { + + if ( p.z > maxZ ) maxZ = p.z; + else if ( p.z < minZ ) minZ = p.z; + + } + + sum.add( p ); + + } + + var ret = { + + minX: minX, + minY: minY, + maxX: maxX, + maxY: maxY + + }; + + if ( v3 ) { + + ret.maxZ = maxZ; + ret.minZ = minZ; + + } + + return ret; + +}; + +/************************************************************** + * Create Geometries Helpers + **************************************************************/ + +/// Generate geometry from path points (for Line or Points objects) + +THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) { + + var pts = this.getPoints( divisions, true ); + return this.createGeometry( pts ); + +}; + +// Generate geometry from equidistance sampling along the path + +THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) { + + var pts = this.getSpacedPoints( divisions, true ); + return this.createGeometry( pts ); + +}; + +THREE.CurvePath.prototype.createGeometry = function( points ) { + + var geometry = new THREE.Geometry(); + + for ( var i = 0; i < points.length; i ++ ) { + + geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0) ); + + } + + return geometry; + +}; + + +/************************************************************** + * Bend / Wrap Helper Methods + **************************************************************/ + +// Wrap path / Bend modifiers? + +THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) { + + this.bends.push( bendpath ); + +}; + +THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) { + + var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints + var i, il; + + if ( ! bends ) { + + bends = this.bends; + + } + + for ( i = 0, il = bends.length; i < il; i ++ ) { + + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); + + } + + return oldPts; + +}; + +THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) { + + var oldPts = this.getSpacedPoints( segments ); + + var i, il; + + if ( ! bends ) { + + bends = this.bends; + + } + + for ( i = 0, il = bends.length; i < il; i ++ ) { + + oldPts = this.getWrapPoints( oldPts, bends[ i ] ); + + } + + return oldPts; + +}; + +// This returns getPoints() bend/wrapped around the contour of a path. +// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html + +THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) { + + var bounds = this.getBoundingBox(); + + var i, il, p, oldX, oldY, xNorm; + + for ( i = 0, il = oldPts.length; i < il; i ++ ) { + + p = oldPts[ i ]; + + oldX = p.x; + oldY = p.y; + + xNorm = oldX / bounds.maxX; + + // If using actual distance, for length > path, requires line extrusions + //xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance + + xNorm = path.getUtoTmapping( xNorm, oldX ); + + // check for out of bounds? + + var pathPt = path.getPoint( xNorm ); + var normal = path.getTangent( xNorm ); + normal.set( - normal.y, normal.x ).multiplyScalar( oldY ); + + p.x = pathPt.x + normal.x; + p.y = pathPt.y + normal.y; + + } + + return oldPts; + +}; + + +// File:src/extras/core/Gyroscope.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Gyroscope = function () { + + THREE.Object3D.call( this ); + +}; + +THREE.Gyroscope.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Gyroscope.prototype.updateMatrixWorld = function ( force ) { + + this.matrixAutoUpdate && this.updateMatrix(); + + // update matrixWorld + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.parent ) { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + this.matrixWorld.decompose( this.translationWorld, this.quaternionWorld, this.scaleWorld ); + this.matrix.decompose( this.translationObject, this.quaternionObject, this.scaleObject ); + + this.matrixWorld.compose( this.translationWorld, this.quaternionObject, this.scaleWorld ); + + + } else { + + this.matrixWorld.copy( this.matrix ); + + } + + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].updateMatrixWorld( force ); + + } + +}; + +THREE.Gyroscope.prototype.translationWorld = new THREE.Vector3(); +THREE.Gyroscope.prototype.translationObject = new THREE.Vector3(); +THREE.Gyroscope.prototype.quaternionWorld = new THREE.Quaternion(); +THREE.Gyroscope.prototype.quaternionObject = new THREE.Quaternion(); +THREE.Gyroscope.prototype.scaleWorld = new THREE.Vector3(); +THREE.Gyroscope.prototype.scaleObject = new THREE.Vector3(); + + +// File:src/extras/core/Path.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Creates free form 2d path using series of points, lines or curves. + * + **/ + +THREE.Path = function ( points ) { + + THREE.CurvePath.call(this); + + this.actions = []; + + if ( points ) { + + this.fromPoints( points ); + + } + +}; + +THREE.Path.prototype = Object.create( THREE.CurvePath.prototype ); + +THREE.PathActions = { + + MOVE_TO: 'moveTo', + LINE_TO: 'lineTo', + QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve + BEZIER_CURVE_TO: 'bezierCurveTo', // Bezier cubic curve + CSPLINE_THRU: 'splineThru', // Catmull-rom spline + ARC: 'arc', // Circle + ELLIPSE: 'ellipse' +}; + +// TODO Clean up PATH API + +// Create path using straight lines to connect all points +// - vectors: array of Vector2 + +THREE.Path.prototype.fromPoints = function ( vectors ) { + + this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y ); + + for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) { + + this.lineTo( vectors[ v ].x, vectors[ v ].y ); + + }; + +}; + +// startPath() endPath()? + +THREE.Path.prototype.moveTo = function ( x, y ) { + + var args = Array.prototype.slice.call( arguments ); + this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } ); + +}; + +THREE.Path.prototype.lineTo = function ( x, y ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } ); + +}; + +THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ), + new THREE.Vector2( aCPx, aCPy ), + new THREE.Vector2( aX, aY ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } ); + +}; + +THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y, + aCP2x, aCP2y, + aX, aY ) { + + var args = Array.prototype.slice.call( arguments ); + + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ), + new THREE.Vector2( aCP1x, aCP1y ), + new THREE.Vector2( aCP2x, aCP2y ), + new THREE.Vector2( aX, aY ) ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } ); + +}; + +THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) { + + var args = Array.prototype.slice.call( arguments ); + var lastargs = this.actions[ this.actions.length - 1 ].args; + + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; +//--- + var npts = [ new THREE.Vector2( x0, y0 ) ]; + Array.prototype.push.apply( npts, pts ); + + var curve = new THREE.SplineCurve( npts ); + this.curves.push( curve ); + + this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } ); + +}; + +// FUTURE: Change the API or follow canvas API? + +THREE.Path.prototype.arc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var lastargs = this.actions[ this.actions.length - 1].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + this.absarc(aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); + + }; + + THREE.Path.prototype.absarc = function ( aX, aY, aRadius, + aStartAngle, aEndAngle, aClockwise ) { + this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise); + }; + +THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var lastargs = this.actions[ this.actions.length - 1].args; + var x0 = lastargs[ lastargs.length - 2 ]; + var y0 = lastargs[ lastargs.length - 1 ]; + + this.absellipse(aX + x0, aY + y0, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ); + + }; + + +THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ) { + + var args = Array.prototype.slice.call( arguments ); + var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius, + aStartAngle, aEndAngle, aClockwise ); + this.curves.push( curve ); + + var lastPoint = curve.getPoint(1); + args.push(lastPoint.x); + args.push(lastPoint.y); + + this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } ); + + }; + +THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) { + + if ( ! divisions ) divisions = 40; + + var points = []; + + for ( var i = 0; i < divisions; i ++ ) { + + points.push( this.getPoint( i / divisions ) ); + + //if( !this.getPoint( i / divisions ) ) throw "DIE"; + + } + + // if ( closedPath ) { + // + // points.push( points[ 0 ] ); + // + // } + + return points; + +}; + +/* Return an array of vectors based on contour of the path */ + +THREE.Path.prototype.getPoints = function( divisions, closedPath ) { + + if (this.useSpacedPoints) { + console.log('tata'); + return this.getSpacedPoints( divisions, closedPath ); + } + + divisions = divisions || 12; + + var points = []; + + var i, il, item, action, args; + var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0, + laste, j, + t, tx, ty; + + for ( i = 0, il = this.actions.length; i < il; i ++ ) { + + item = this.actions[ i ]; + + action = item.action; + args = item.args; + + switch( action ) { + + case THREE.PathActions.MOVE_TO: + + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); + + break; + + case THREE.PathActions.LINE_TO: + + points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) ); + + break; + + case THREE.PathActions.QUADRATIC_CURVE_TO: + + cpx = args[ 2 ]; + cpy = args[ 3 ]; + + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; + + if ( points.length > 0 ) { + + laste = points[ points.length - 1 ]; + + cpx0 = laste.x; + cpy0 = laste.y; + + } else { + + laste = this.actions[ i - 1 ].args; + + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; + + } + + for ( j = 1; j <= divisions; j ++ ) { + + t = j / divisions; + + tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx ); + ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy ); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + break; + + case THREE.PathActions.BEZIER_CURVE_TO: + + cpx = args[ 4 ]; + cpy = args[ 5 ]; + + cpx1 = args[ 0 ]; + cpy1 = args[ 1 ]; + + cpx2 = args[ 2 ]; + cpy2 = args[ 3 ]; + + if ( points.length > 0 ) { + + laste = points[ points.length - 1 ]; + + cpx0 = laste.x; + cpy0 = laste.y; + + } else { + + laste = this.actions[ i - 1 ].args; + + cpx0 = laste[ laste.length - 2 ]; + cpy0 = laste[ laste.length - 1 ]; + + } + + + for ( j = 1; j <= divisions; j ++ ) { + + t = j / divisions; + + tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx ); + ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy ); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + break; + + case THREE.PathActions.CSPLINE_THRU: + + laste = this.actions[ i - 1 ].args; + + var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] ); + var spts = [ last ]; + + var n = divisions * args[ 0 ].length; + + spts = spts.concat( args[ 0 ] ); + + var spline = new THREE.SplineCurve( spts ); + + for ( j = 1; j <= n; j ++ ) { + + points.push( spline.getPointAt( j / n ) ) ; + + } + + break; + + case THREE.PathActions.ARC: + + var aX = args[ 0 ], aY = args[ 1 ], + aRadius = args[ 2 ], + aStartAngle = args[ 3 ], aEndAngle = args[ 4 ], + aClockwise = !! args[ 5 ]; + + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; + + for ( j = 1; j <= tdivisions; j ++ ) { + + t = j / tdivisions; + + if ( ! aClockwise ) { + + t = 1 - t; + + } + + angle = aStartAngle + t * deltaAngle; + + tx = aX + aRadius * Math.cos( angle ); + ty = aY + aRadius * Math.sin( angle ); + + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + //console.log(points); + + break; + + case THREE.PathActions.ELLIPSE: + + var aX = args[ 0 ], aY = args[ 1 ], + xRadius = args[ 2 ], + yRadius = args[ 3 ], + aStartAngle = args[ 4 ], aEndAngle = args[ 5 ], + aClockwise = !! args[ 6 ]; + + + var deltaAngle = aEndAngle - aStartAngle; + var angle; + var tdivisions = divisions * 2; + + for ( j = 1; j <= tdivisions; j ++ ) { + + t = j / tdivisions; + + if ( ! aClockwise ) { + + t = 1 - t; + + } + + angle = aStartAngle + t * deltaAngle; + + tx = aX + xRadius * Math.cos( angle ); + ty = aY + yRadius * Math.sin( angle ); + + //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty); + + points.push( new THREE.Vector2( tx, ty ) ); + + } + + //console.log(points); + + break; + + } // end switch + + } + + + + // Normalize to remove the closing point by default. + var lastPoint = points[ points.length - 1]; + var EPSILON = 0.0000000001; + if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON && + Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON) + points.splice( points.length - 1, 1); + if ( closedPath ) { + + points.push( points[ 0 ] ); + + } + + return points; + +}; + +// +// Breaks path into shapes +// +// Assumptions (if parameter isCCW==true the opposite holds): +// - solid shapes are defined clockwise (CW) +// - holes are defined counterclockwise (CCW) +// +// If parameter noHoles==true: +// - all subPaths are regarded as solid shapes +// - definition order CW/CCW has no relevance +// + +THREE.Path.prototype.toShapes = function( isCCW, noHoles ) { + + function extractSubpaths( inActions ) { + + var i, il, item, action, args; + + var subPaths = [], lastPath = new THREE.Path(); + + for ( i = 0, il = inActions.length; i < il; i ++ ) { + + item = inActions[ i ]; + + args = item.args; + action = item.action; + + if ( action == THREE.PathActions.MOVE_TO ) { + + if ( lastPath.actions.length != 0 ) { + + subPaths.push( lastPath ); + lastPath = new THREE.Path(); + + } + + } + + lastPath[ action ].apply( lastPath, args ); + + } + + if ( lastPath.actions.length != 0 ) { + + subPaths.push( lastPath ); + + } + + // console.log(subPaths); + + return subPaths; + } + + function toShapesNoHoles( inSubpaths ) { + + var shapes = []; + + for ( var i = 0, il = inSubpaths.length; i < il; i ++ ) { + + var tmpPath = inSubpaths[ i ]; + + var tmpShape = new THREE.Shape(); + tmpShape.actions = tmpPath.actions; + tmpShape.curves = tmpPath.curves; + + shapes.push( tmpShape ); + } + + //console.log("shape", shapes); + + return shapes; + }; + + function isPointInsidePolygon( inPt, inPolygon ) { + var EPSILON = 0.0000000001; + + var polyLen = inPolygon.length; + + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + var inside = false; + for( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + var edgeLowPt = inPolygon[ p ]; + var edgeHighPt = inPolygon[ q ]; + + var edgeDx = edgeHighPt.x - edgeLowPt.x; + var edgeDy = edgeHighPt.y - edgeLowPt.y; + + if ( Math.abs(edgeDy) > EPSILON ) { // not parallel + if ( edgeDy < 0 ) { + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + } + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + + if ( inPt.y == edgeLowPt.y ) { + if ( inPt.x == edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! + } else { + var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y); + if ( perpEdge == 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = ! inside; // true intersection left of inPt + } + } else { // parallel or colinear + if ( inPt.y != edgeLowPt.y ) continue; // parallel + // egde lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; + } + } + + return inside; + } + + + var subPaths = extractSubpaths( this.actions ); + if ( subPaths.length == 0 ) return []; + + if ( noHoles === true ) return toShapesNoHoles( subPaths ); + + + var solid, tmpPath, tmpShape, shapes = []; + + if ( subPaths.length == 1) { + + tmpPath = subPaths[0]; + tmpShape = new THREE.Shape(); + tmpShape.actions = tmpPath.actions; + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; + + } + + var holesFirst = ! THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; + + // console.log("Holes first", holesFirst); + + var betterShapeHoles = []; + var newShapes = []; + var newShapeHoles = []; + var mainIdx = 0; + var tmpPoints; + + newShapes[mainIdx] = undefined; + newShapeHoles[mainIdx] = []; + + var i, il; + + for ( i = 0, il = subPaths.length; i < il; i ++ ) { + + tmpPath = subPaths[ i ]; + tmpPoints = tmpPath.getPoints(); + solid = THREE.Shape.Utils.isClockWise( tmpPoints ); + solid = isCCW ? ! solid : solid; + + if ( solid ) { + + if ( (! holesFirst ) && ( newShapes[mainIdx] ) ) mainIdx ++; + + newShapes[mainIdx] = { s: new THREE.Shape(), p: tmpPoints }; + newShapes[mainIdx].s.actions = tmpPath.actions; + newShapes[mainIdx].s.curves = tmpPath.curves; + + if ( holesFirst ) mainIdx ++; + newShapeHoles[mainIdx] = []; + + //console.log('cw', i); + + } else { + + newShapeHoles[mainIdx].push( { h: tmpPath, p: tmpPoints[0] } ); + + //console.log('ccw', i); + + } + + } + + // only Holes? -> probably all Shapes with wrong orientation + if ( ! newShapes[0] ) return toShapesNoHoles( subPaths ); + + + if ( newShapes.length > 1 ) { + var ambigious = false; + var toChange = []; + + for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + betterShapeHoles[sIdx] = []; + } + for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + var sh = newShapes[sIdx]; + var sho = newShapeHoles[sIdx]; + for (var hIdx = 0; hIdx < sho.length; hIdx ++ ) { + var ho = sho[hIdx]; + var hole_unassigned = true; + for (var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + if ( isPointInsidePolygon( ho.p, newShapes[s2Idx].p ) ) { + if ( sIdx != s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } ); + if ( hole_unassigned ) { + hole_unassigned = false; + betterShapeHoles[s2Idx].push( ho ); + } else { + ambigious = true; + } + } + } + if ( hole_unassigned ) { betterShapeHoles[sIdx].push( ho ); } + } + } + // console.log("ambigious: ", ambigious); + if ( toChange.length > 0 ) { + // console.log("to change: ", toChange); + if (! ambigious) newShapeHoles = betterShapeHoles; + } + } + + var tmpHoles, j, jl; + for ( i = 0, il = newShapes.length; i < il; i ++ ) { + tmpShape = newShapes[i].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[i]; + for ( j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + tmpShape.holes.push( tmpHoles[j].h ); + } + } + + //console.log("shape", shapes); + + return shapes; + +}; + +// File:src/extras/core/Shape.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Defines a 2d shape plane using paths. + **/ + +// STEP 1 Create a path. +// STEP 2 Turn path into shape. +// STEP 3 ExtrudeGeometry takes in Shape/Shapes +// STEP 3a - Extract points from each shape, turn to vertices +// STEP 3b - Triangulate each shape, add faces. + +THREE.Shape = function () { + + THREE.Path.apply( this, arguments ); + this.holes = []; + +}; + +THREE.Shape.prototype = Object.create( THREE.Path.prototype ); + +// Convenience method to return ExtrudeGeometry + +THREE.Shape.prototype.extrude = function ( options ) { + + var extruded = new THREE.ExtrudeGeometry( this, options ); + return extruded; + +}; + +// Convenience method to return ShapeGeometry + +THREE.Shape.prototype.makeGeometry = function ( options ) { + + var geometry = new THREE.ShapeGeometry( this, options ); + return geometry; + +}; + +// Get points of holes + +THREE.Shape.prototype.getPointsHoles = function ( divisions ) { + + var i, il = this.holes.length, holesPts = []; + + for ( i = 0; i < il; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends ); + + } + + return holesPts; + +}; + +// Get points of holes (spaced by regular distance) + +THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) { + + var i, il = this.holes.length, holesPts = []; + + for ( i = 0; i < il; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends ); + + } + + return holesPts; + +}; + + +// Get points of shape and holes (keypoints based on segments parameter) + +THREE.Shape.prototype.extractAllPoints = function ( divisions ) { + + return { + + shape: this.getTransformedPoints( divisions ), + holes: this.getPointsHoles( divisions ) + + }; + +}; + +THREE.Shape.prototype.extractPoints = function ( divisions ) { + + if (this.useSpacedPoints) { + return this.extractAllSpacedPoints(divisions); + } + + return this.extractAllPoints(divisions); + +}; + +// +// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) { +// +// return { +// +// shape: this.transform( bend, divisions ), +// holes: this.getPointsHoles( divisions, bend ) +// +// }; +// +// }; + +// Get points of shape and holes (spaced by regular distance) + +THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) { + + return { + + shape: this.getTransformedSpacedPoints( divisions ), + holes: this.getSpacedPointsHoles( divisions ) + + }; + +}; + +/************************************************************** + * Utils + **************************************************************/ + +THREE.Shape.Utils = { + + triangulateShape: function ( contour, holes ) { + + function point_in_segment_2D_colin( inSegPt1, inSegPt2, inOtherPt ) { + // inOtherPt needs to be colinear to the inSegment + if ( inSegPt1.x != inSegPt2.x ) { + if ( inSegPt1.x < inSegPt2.x ) { + return ( ( inSegPt1.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt2.x ) ); + } else { + return ( ( inSegPt2.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt1.x ) ); + } + } else { + if ( inSegPt1.y < inSegPt2.y ) { + return ( ( inSegPt1.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt2.y ) ); + } else { + return ( ( inSegPt2.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt1.y ) ); + } + } + } + + function intersect_segments_2D( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1, inSeg2Pt2, inExcludeAdjacentSegs ) { + var EPSILON = 0.0000000001; + + var seg1dx = inSeg1Pt2.x - inSeg1Pt1.x, seg1dy = inSeg1Pt2.y - inSeg1Pt1.y; + var seg2dx = inSeg2Pt2.x - inSeg2Pt1.x, seg2dy = inSeg2Pt2.y - inSeg2Pt1.y; + + var seg1seg2dx = inSeg1Pt1.x - inSeg2Pt1.x; + var seg1seg2dy = inSeg1Pt1.y - inSeg2Pt1.y; + + var limit = seg1dy * seg2dx - seg1dx * seg2dy; + var perpSeg1 = seg1dy * seg1seg2dx - seg1dx * seg1seg2dy; + + if ( Math.abs(limit) > EPSILON ) { // not parallel + + var perpSeg2; + if ( limit > 0 ) { + if ( ( perpSeg1 < 0 ) || ( perpSeg1 > limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 < 0 ) || ( perpSeg2 > limit ) ) return []; + } else { + if ( ( perpSeg1 > 0 ) || ( perpSeg1 < limit ) ) return []; + perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; + if ( ( perpSeg2 > 0 ) || ( perpSeg2 < limit ) ) return []; + } + + // i.e. to reduce rounding errors + // intersection at endpoint of segment#1? + if ( perpSeg2 == 0 ) { + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 == 0 ) || ( perpSeg1 == limit ) ) ) return []; + return [ inSeg1Pt1 ]; + } + if ( perpSeg2 == limit ) { + if ( ( inExcludeAdjacentSegs ) && + ( ( perpSeg1 == 0 ) || ( perpSeg1 == limit ) ) ) return []; + return [ inSeg1Pt2 ]; + } + // intersection at endpoint of segment#2? + if ( perpSeg1 == 0 ) return [ inSeg2Pt1 ]; + if ( perpSeg1 == limit ) return [ inSeg2Pt2 ]; + + // return real intersection point + var factorSeg1 = perpSeg2 / limit; + return [ { x: inSeg1Pt1.x + factorSeg1 * seg1dx, + y: inSeg1Pt1.y + factorSeg1 * seg1dy } ]; + + } else { // parallel or colinear + if ( ( perpSeg1 != 0 ) || + ( seg2dy * seg1seg2dx != seg2dx * seg1seg2dy ) ) return []; + + // they are collinear or degenerate + var seg1Pt = ( (seg1dx == 0) && (seg1dy == 0) ); // segment1 ist just a point? + var seg2Pt = ( (seg2dx == 0) && (seg2dy == 0) ); // segment2 ist just a point? + // both segments are points + if ( seg1Pt && seg2Pt ) { + if ( (inSeg1Pt1.x != inSeg2Pt1.x) || + (inSeg1Pt1.y != inSeg2Pt1.y) ) return []; // they are distinct points + return [ inSeg1Pt1 ]; // they are the same point + } + // segment#1 is a single point + if ( seg1Pt ) { + if (! point_in_segment_2D_colin( inSeg2Pt1, inSeg2Pt2, inSeg1Pt1 ) ) return []; // but not in segment#2 + return [ inSeg1Pt1 ]; + } + // segment#2 is a single point + if ( seg2Pt ) { + if (! point_in_segment_2D_colin( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1 ) ) return []; // but not in segment#1 + return [ inSeg2Pt1 ]; + } + + // they are collinear segments, which might overlap + var seg1min, seg1max, seg1minVal, seg1maxVal; + var seg2min, seg2max, seg2minVal, seg2maxVal; + if (seg1dx != 0) { // the segments are NOT on a vertical line + if ( inSeg1Pt1.x < inSeg1Pt2.x ) { + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.x; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.x; + } else { + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.x; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.x; + } + if ( inSeg2Pt1.x < inSeg2Pt2.x ) { + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.x; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.x; + } else { + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.x; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.x; + } + } else { // the segments are on a vertical line + if ( inSeg1Pt1.y < inSeg1Pt2.y ) { + seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.y; + seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.y; + } else { + seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.y; + seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.y; + } + if ( inSeg2Pt1.y < inSeg2Pt2.y ) { + seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.y; + seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.y; + } else { + seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.y; + seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.y; + } + } + if ( seg1minVal <= seg2minVal ) { + if ( seg1maxVal < seg2minVal ) return []; + if ( seg1maxVal == seg2minVal ) { + if ( inExcludeAdjacentSegs ) return []; + return [ seg2min ]; + } + if ( seg1maxVal <= seg2maxVal ) return [ seg2min, seg1max ]; + return [ seg2min, seg2max ]; + } else { + if ( seg1minVal > seg2maxVal ) return []; + if ( seg1minVal == seg2maxVal ) { + if ( inExcludeAdjacentSegs ) return []; + return [ seg1min ]; + } + if ( seg1maxVal <= seg2maxVal ) return [ seg1min, seg1max ]; + return [ seg1min, seg2max ]; + } + } + } + + function isPointInsideAngle( inVertex, inLegFromPt, inLegToPt, inOtherPt ) { + // The order of legs is important + + var EPSILON = 0.0000000001; + + // translation of all points, so that Vertex is at (0,0) + var legFromPtX = inLegFromPt.x - inVertex.x, legFromPtY = inLegFromPt.y - inVertex.y; + var legToPtX = inLegToPt.x - inVertex.x, legToPtY = inLegToPt.y - inVertex.y; + var otherPtX = inOtherPt.x - inVertex.x, otherPtY = inOtherPt.y - inVertex.y; + + // main angle >0: < 180 deg.; 0: 180 deg.; <0: > 180 deg. + var from2toAngle = legFromPtX * legToPtY - legFromPtY * legToPtX; + var from2otherAngle = legFromPtX * otherPtY - legFromPtY * otherPtX; + + if ( Math.abs(from2toAngle) > EPSILON ) { // angle != 180 deg. + + var other2toAngle = otherPtX * legToPtY - otherPtY * legToPtX; + // console.log( "from2to: " + from2toAngle + ", from2other: " + from2otherAngle + ", other2to: " + other2toAngle ); + + if ( from2toAngle > 0 ) { // main angle < 180 deg. + return ( ( from2otherAngle >= 0 ) && ( other2toAngle >= 0 ) ); + } else { // main angle > 180 deg. + return ( ( from2otherAngle >= 0 ) || ( other2toAngle >= 0 ) ); + } + } else { // angle == 180 deg. + // console.log( "from2to: 180 deg., from2other: " + from2otherAngle ); + return ( from2otherAngle > 0 ); + } + } + + + function removeHoles( contour, holes ) { + + var shape = contour.concat(); // work on this shape + var hole; + + function isCutLineInsideAngles( inShapeIdx, inHoleIdx ) { + // Check if hole point lies within angle around shape point + var lastShapeIdx = shape.length - 1; + + var prevShapeIdx = inShapeIdx - 1; + if ( prevShapeIdx < 0 ) prevShapeIdx = lastShapeIdx; + + var nextShapeIdx = inShapeIdx + 1; + if ( nextShapeIdx > lastShapeIdx ) nextShapeIdx = 0; + + var insideAngle = isPointInsideAngle( shape[inShapeIdx], shape[ prevShapeIdx ], shape[ nextShapeIdx ], hole[inHoleIdx] ); + if (! insideAngle ) { + // console.log( "Vertex (Shape): " + inShapeIdx + ", Point: " + hole[inHoleIdx].x + "/" + hole[inHoleIdx].y ); + return false; + } + + // Check if shape point lies within angle around hole point + var lastHoleIdx = hole.length - 1; + + var prevHoleIdx = inHoleIdx - 1; + if ( prevHoleIdx < 0 ) prevHoleIdx = lastHoleIdx; + + var nextHoleIdx = inHoleIdx + 1; + if ( nextHoleIdx > lastHoleIdx ) nextHoleIdx = 0; + + insideAngle = isPointInsideAngle( hole[inHoleIdx], hole[ prevHoleIdx ], hole[ nextHoleIdx ], shape[inShapeIdx] ); + if (! insideAngle ) { + // console.log( "Vertex (Hole): " + inHoleIdx + ", Point: " + shape[inShapeIdx].x + "/" + shape[inShapeIdx].y ); + return false; + } + + return true; + } + + function intersectsShapeEdge( inShapePt, inHolePt ) { + // checks for intersections with shape edges + var sIdx, nextIdx, intersection; + for ( sIdx = 0; sIdx < shape.length; sIdx ++ ) { + nextIdx = sIdx+1; nextIdx %= shape.length; + intersection = intersect_segments_2D( inShapePt, inHolePt, shape[sIdx], shape[nextIdx], true ); + if ( intersection.length > 0 ) return true; + } + + return false; + } + + var indepHoles = []; + + function intersectsHoleEdge( inShapePt, inHolePt ) { + // checks for intersections with hole edges + var ihIdx, chkHole, + hIdx, nextIdx, intersection; + for ( ihIdx = 0; ihIdx < indepHoles.length; ihIdx ++ ) { + chkHole = holes[indepHoles[ihIdx]]; + for ( hIdx = 0; hIdx < chkHole.length; hIdx ++ ) { + nextIdx = hIdx+1; nextIdx %= chkHole.length; + intersection = intersect_segments_2D( inShapePt, inHolePt, chkHole[hIdx], chkHole[nextIdx], true ); + if ( intersection.length > 0 ) return true; + } + } + return false; + } + + var holeIndex, shapeIndex, + shapePt, holePt, + holeIdx, cutKey, failedCuts = [], + tmpShape1, tmpShape2, + tmpHole1, tmpHole2; + + for ( var h = 0, hl = holes.length; h < hl; h ++ ) { + + indepHoles.push( h ); + + } + + var minShapeIndex = 0; + var counter = indepHoles.length * 2; + while ( indepHoles.length > 0 ) { + counter --; + if ( counter < 0 ) { + console.log( "Infinite Loop! Holes left:" + indepHoles.length + ", Probably Hole outside Shape!" ); + break; + } + + // search for shape-vertex and hole-vertex, + // which can be connected without intersections + for ( shapeIndex = minShapeIndex; shapeIndex < shape.length; shapeIndex ++ ) { + + shapePt = shape[ shapeIndex ]; + holeIndex = - 1; + + // search for hole which can be reached without intersections + for ( var h = 0; h < indepHoles.length; h ++ ) { + holeIdx = indepHoles[h]; + + // prevent multiple checks + cutKey = shapePt.x + ":" + shapePt.y + ":" + holeIdx; + if ( failedCuts[cutKey] !== undefined ) continue; + + hole = holes[holeIdx]; + for ( var h2 = 0; h2 < hole.length; h2 ++ ) { + holePt = hole[ h2 ]; + if (! isCutLineInsideAngles( shapeIndex, h2 ) ) continue; + if ( intersectsShapeEdge( shapePt, holePt ) ) continue; + if ( intersectsHoleEdge( shapePt, holePt ) ) continue; + + holeIndex = h2; + indepHoles.splice(h,1); + + tmpShape1 = shape.slice( 0, shapeIndex+1 ); + tmpShape2 = shape.slice( shapeIndex ); + tmpHole1 = hole.slice( holeIndex ); + tmpHole2 = hole.slice( 0, holeIndex+1 ); + + shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 ); + + minShapeIndex = shapeIndex; + + // Debug only, to show the selected cuts + // glob_CutLines.push( [ shapePt, holePt ] ); + + break; + } + if ( holeIndex >= 0 ) break; // hole-vertex found + + failedCuts[cutKey] = true; // remember failure + } + if ( holeIndex >= 0 ) break; // hole-vertex found + } + } + + return shape; /* shape with no holes */ + } + + + var i, il, f, face, + key, index, + allPointsMap = {}; + + // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first. + + var allpoints = contour.concat(); + + for ( var h = 0, hl = holes.length; h < hl; h ++ ) { + + Array.prototype.push.apply( allpoints, holes[h] ); + + } + + //console.log( "allpoints",allpoints, allpoints.length ); + + // prepare all points map + + for ( i = 0, il = allpoints.length; i < il; i ++ ) { + + key = allpoints[ i ].x + ":" + allpoints[ i ].y; + + if ( allPointsMap[ key ] !== undefined ) { + + console.log( "Duplicate point", key ); + + } + + allPointsMap[ key ] = i; + + } + + // remove holes by cutting paths to holes and adding them to the shape + var shapeWithoutHoles = removeHoles( contour, holes ); + + var triangles = THREE.FontUtils.Triangulate( shapeWithoutHoles, false ); // True returns indices for points of spooled shape + //console.log( "triangles",triangles, triangles.length ); + + // check all face vertices against all points map + + for ( i = 0, il = triangles.length; i < il; i ++ ) { + + face = triangles[ i ]; + + for ( f = 0; f < 3; f ++ ) { + + key = face[ f ].x + ":" + face[ f ].y; + + index = allPointsMap[ key ]; + + if ( index !== undefined ) { + + face[ f ] = index; + + } + + } + + } + + return triangles.concat(); + + }, + + isClockWise: function ( pts ) { + + return THREE.FontUtils.Triangulate.area( pts ) < 0; + + }, + + // Bezier Curves formulas obtained from + // http://en.wikipedia.org/wiki/B%C3%A9zier_curve + + // Quad Bezier Functions + + b2p0: function ( t, p ) { + + var k = 1 - t; + return k * k * p; + + }, + + b2p1: function ( t, p ) { + + return 2 * ( 1 - t ) * t * p; + + }, + + b2p2: function ( t, p ) { + + return t * t * p; + + }, + + b2: function ( t, p0, p1, p2 ) { + + return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 ); + + }, + + // Cubic Bezier Functions + + b3p0: function ( t, p ) { + + var k = 1 - t; + return k * k * k * p; + + }, + + b3p1: function ( t, p ) { + + var k = 1 - t; + return 3 * k * k * t * p; + + }, + + b3p2: function ( t, p ) { + + var k = 1 - t; + return 3 * k * t * t * p; + + }, + + b3p3: function ( t, p ) { + + return t * t * t * p; + + }, + + b3: function ( t, p0, p1, p2, p3 ) { + + return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) + this.b3p3( t, p3 ); + + } + +}; + + +// File:src/extras/curves/LineCurve.js + +/************************************************************** + * Line + **************************************************************/ + +THREE.LineCurve = function ( v1, v2 ) { + + this.v1 = v1; + this.v2 = v2; + +}; + +THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.LineCurve.prototype.getPoint = function ( t ) { + + var point = this.v2.clone().sub(this.v1); + point.multiplyScalar( t ).add( this.v1 ); + + return point; + +}; + +// Line curve is linear, so we can overwrite default getPointAt + +THREE.LineCurve.prototype.getPointAt = function ( u ) { + + return this.getPoint( u ); + +}; + +THREE.LineCurve.prototype.getTangent = function( t ) { + + var tangent = this.v2.clone().sub(this.v1); + + return tangent.normalize(); + +}; + +// File:src/extras/curves/QuadraticBezierCurve.js + +/************************************************************** + * Quadratic Bezier curve + **************************************************************/ + + +THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + +}; + +THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype ); + + +THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) { + + var tx, ty; + + tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); + + return new THREE.Vector2( tx, ty ); + +}; + + +THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) { + + var tx, ty; + + tx = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y ); + + // returns unit vector + + var tangent = new THREE.Vector2( tx, ty ); + tangent.normalize(); + + return tangent; + +}; + +// File:src/extras/curves/CubicBezierCurve.js + +/************************************************************** + * Cubic Bezier curve + **************************************************************/ + +THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + +}; + +THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.CubicBezierCurve.prototype.getPoint = function ( t ) { + + var tx, ty; + + tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + + return new THREE.Vector2( tx, ty ); + +}; + +THREE.CubicBezierCurve.prototype.getTangent = function( t ) { + + var tx, ty; + + tx = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + + var tangent = new THREE.Vector2( tx, ty ); + tangent.normalize(); + + return tangent; + +}; + +// File:src/extras/curves/SplineCurve.js + +/************************************************************** + * Spline curve + **************************************************************/ + +THREE.SplineCurve = function ( points /* array of Vector2 */ ) { + + this.points = (points == undefined) ? [] : points; + +}; + +THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.SplineCurve.prototype.getPoint = function ( t ) { + + var v = new THREE.Vector2(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 1 ) * t; + + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? points.length -1 : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? points.length -1 : intPoint + 2; + + v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight ); + v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight ); + + return v; + +}; + +// File:src/extras/curves/EllipseCurve.js + +/************************************************************** + * Ellipse curve + **************************************************************/ + +THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) { + + this.aX = aX; + this.aY = aY; + + this.xRadius = xRadius; + this.yRadius = yRadius; + + this.aStartAngle = aStartAngle; + this.aEndAngle = aEndAngle; + + this.aClockwise = aClockwise; + +}; + +THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype ); + +THREE.EllipseCurve.prototype.getPoint = function ( t ) { + + var angle; + var deltaAngle = this.aEndAngle - this.aStartAngle; + + if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2; + if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2; + + if ( this.aClockwise === true ) { + + angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle ); + + } else { + + angle = this.aStartAngle + t * deltaAngle; + + } + + var tx = this.aX + this.xRadius * Math.cos( angle ); + var ty = this.aY + this.yRadius * Math.sin( angle ); + + return new THREE.Vector2( tx, ty ); + +}; + +// File:src/extras/curves/ArcCurve.js + +/************************************************************** + * Arc curve + **************************************************************/ + +THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); +}; + +THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype ); + +// File:src/extras/curves/LineCurve3.js + +/************************************************************** + * Line3D + **************************************************************/ + +THREE.LineCurve3 = THREE.Curve.create( + + function ( v1, v2 ) { + + this.v1 = v1; + this.v2 = v2; + + }, + + function ( t ) { + + var r = new THREE.Vector3(); + + + r.subVectors( this.v2, this.v1 ); // diff + r.multiplyScalar( t ); + r.add( this.v1 ); + + return r; + + } + +); + +// File:src/extras/curves/QuadraticBezierCurve3.js + +/************************************************************** + * Quadratic Bezier 3D curve + **************************************************************/ + +THREE.QuadraticBezierCurve3 = THREE.Curve.create( + + function ( v0, v1, v2 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + + }, + + function ( t ) { + + var tx, ty, tz; + + tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x ); + ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y ); + tz = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z ); + + return new THREE.Vector3( tx, ty, tz ); + + } + +); + +// File:src/extras/curves/CubicBezierCurve3.js + +/************************************************************** + * Cubic Bezier 3D curve + **************************************************************/ + +THREE.CubicBezierCurve3 = THREE.Curve.create( + + function ( v0, v1, v2, v3 ) { + + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + + }, + + function ( t ) { + + var tx, ty, tz; + + tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x ); + ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y ); + tz = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z ); + + return new THREE.Vector3( tx, ty, tz ); + + } + +); + +// File:src/extras/curves/SplineCurve3.js + +/************************************************************** + * Spline 3D curve + **************************************************************/ + + +THREE.SplineCurve3 = THREE.Curve.create( + + function ( points /* array of Vector3 */) { + + this.points = (points == undefined) ? [] : points; + + }, + + function ( t ) { + + var v = new THREE.Vector3(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 1 ) * t; + + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2; + + var pt0 = points[ c[0] ], + pt1 = points[ c[1] ], + pt2 = points[ c[2] ], + pt3 = points[ c[3] ]; + + v.x = THREE.Curve.Utils.interpolate(pt0.x, pt1.x, pt2.x, pt3.x, weight); + v.y = THREE.Curve.Utils.interpolate(pt0.y, pt1.y, pt2.y, pt3.y, weight); + v.z = THREE.Curve.Utils.interpolate(pt0.z, pt1.z, pt2.z, pt3.z, weight); + + return v; + + } + +); + + +// THREE.SplineCurve3.prototype.getTangent = function(t) { +// var v = new THREE.Vector3(); +// var c = []; +// var points = this.points, point, intPoint, weight; +// point = ( points.length - 1 ) * t; + +// intPoint = Math.floor( point ); +// weight = point - intPoint; + +// c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1; +// c[ 1 ] = intPoint; +// c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1; +// c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2; + +// var pt0 = points[ c[0] ], +// pt1 = points[ c[1] ], +// pt2 = points[ c[2] ], +// pt3 = points[ c[3] ]; + +// // t = weight; +// v.x = THREE.Curve.Utils.tangentSpline( t, pt0.x, pt1.x, pt2.x, pt3.x ); +// v.y = THREE.Curve.Utils.tangentSpline( t, pt0.y, pt1.y, pt2.y, pt3.y ); +// v.z = THREE.Curve.Utils.tangentSpline( t, pt0.z, pt1.z, pt2.z, pt3.z ); + +// return v; + +// } + +// File:src/extras/curves/ClosedSplineCurve3.js + +/************************************************************** + * Closed Spline 3D curve + **************************************************************/ + + +THREE.ClosedSplineCurve3 = THREE.Curve.create( + + function ( points /* array of Vector3 */) { + + this.points = (points == undefined) ? [] : points; + + }, + + function ( t ) { + + var v = new THREE.Vector3(); + var c = []; + var points = this.points, point, intPoint, weight; + point = ( points.length - 0 ) * t; + // This needs to be from 0-length +1 + + intPoint = Math.floor( point ); + weight = point - intPoint; + + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length; + c[ 0 ] = ( intPoint - 1 ) % points.length; + c[ 1 ] = ( intPoint ) % points.length; + c[ 2 ] = ( intPoint + 1 ) % points.length; + c[ 3 ] = ( intPoint + 2 ) % points.length; + + v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight ); + v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight ); + v.z = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].z, points[ c[ 1 ] ].z, points[ c[ 2 ] ].z, points[ c[ 3 ] ].z, weight ); + + return v; + + } + +); + +// File:src/extras/animation/AnimationHandler.js + +/** + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.AnimationHandler = { + + LINEAR: 0, + CATMULLROM: 1, + CATMULLROM_FORWARD: 2, + + // + + add: function () { console.warn( 'THREE.AnimationHandler.add() has been deprecated.' ); }, + get: function () { console.warn( 'THREE.AnimationHandler.get() has been deprecated.' ); }, + remove: function () { console.warn( 'THREE.AnimationHandler.remove() has been deprecated.' ); }, + + // + + animations: [], + + init: function ( data ) { + + if ( data.initialized === true ) return; + + // loop through all keys + + for ( var h = 0; h < data.hierarchy.length; h ++ ) { + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + // remove minus times + + if ( data.hierarchy[ h ].keys[ k ].time < 0 ) { + + data.hierarchy[ h ].keys[ k ].time = 0; + + } + + // create quaternions + + if ( data.hierarchy[ h ].keys[ k ].rot !== undefined && + ! ( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) { + + var quat = data.hierarchy[ h ].keys[ k ].rot; + data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion().fromArray( quat ); + + } + + } + + // prepare morph target keys + + if ( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) { + + // get all used + + var usedMorphTargets = {}; + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { + + var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ]; + usedMorphTargets[ morphTargetName ] = - 1; + + } + + } + + data.hierarchy[ h ].usedMorphTargets = usedMorphTargets; + + + // set all used on all frames + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + var influences = {}; + + for ( var morphTargetName in usedMorphTargets ) { + + for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { + + if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) { + + influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ]; + break; + + } + + } + + if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) { + + influences[ morphTargetName ] = 0; + + } + + } + + data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences; + + } + + } + + + // remove all keys that are on the same time + + for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) { + + if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) { + + data.hierarchy[ h ].keys.splice( k, 1 ); + k --; + + } + + } + + + // set index + + for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { + + data.hierarchy[ h ].keys[ k ].index = k; + + } + + } + + data.initialized = true; + + return data; + + }, + + parse: function ( root ) { + + var parseRecurseHierarchy = function ( root, hierarchy ) { + + hierarchy.push( root ); + + for ( var c = 0; c < root.children.length; c ++ ) + parseRecurseHierarchy( root.children[ c ], hierarchy ); + + }; + + // setup hierarchy + + var hierarchy = []; + + if ( root instanceof THREE.SkinnedMesh ) { + + for ( var b = 0; b < root.skeleton.bones.length; b ++ ) { + + hierarchy.push( root.skeleton.bones[ b ] ); + + } + + } else { + + parseRecurseHierarchy( root, hierarchy ); + + } + + return hierarchy; + + }, + + play: function ( animation ) { + + if ( this.animations.indexOf( animation ) === - 1 ) { + + this.animations.push( animation ); + + } + + }, + + stop: function ( animation ) { + + var index = this.animations.indexOf( animation ); + + if ( index !== - 1 ) { + + this.animations.splice( index, 1 ); + + } + + }, + + update: function ( deltaTimeMS ) { + + for ( var i = 0; i < this.animations.length; i ++ ) { + + this.animations[ i ].update( deltaTimeMS ); + + } + + } + +}; + +// File:src/extras/animation/Animation.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Animation = function ( root, data ) { + + this.root = root; + this.data = THREE.AnimationHandler.init( data ); + this.hierarchy = THREE.AnimationHandler.parse( root ); + + this.currentTime = 0; + this.timeScale = 1; + + this.isPlaying = false; + this.loop = true; + this.weight = 0; + + this.interpolationType = THREE.AnimationHandler.LINEAR; + +}; + + +THREE.Animation.prototype.keyTypes = [ "pos", "rot", "scl" ]; + + +THREE.Animation.prototype.play = function ( startTime, weight ) { + + this.currentTime = startTime !== undefined ? startTime : 0; + this.weight = weight !== undefined ? weight: 1; + + this.isPlaying = true; + + this.reset(); + + THREE.AnimationHandler.play( this ); + +}; + + +THREE.Animation.prototype.stop = function() { + + this.isPlaying = false; + + THREE.AnimationHandler.stop( this ); + +}; + +THREE.Animation.prototype.reset = function () { + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + + object.matrixAutoUpdate = true; + + if ( object.animationCache === undefined ) { + + object.animationCache = {}; + + } + + if ( object.animationCache[this.data.name] === undefined ) { + + object.animationCache[this.data.name] = {}; + object.animationCache[this.data.name].prevKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache[this.data.name].nextKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache[this.data.name].originalMatrix = object.matrix; + + } + + var animationCache = object.animationCache[this.data.name]; + + // Get keys to match our current time + + for ( var t = 0; t < 3; t ++ ) { + + var type = this.keyTypes[ t ]; + + var prevKey = this.data.hierarchy[ h ].keys[ 0 ]; + var nextKey = this.getNextKeyWith( type, h, 1 ); + + while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { + + prevKey = nextKey; + nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); + + } + + animationCache.prevKey[ type ] = prevKey; + animationCache.nextKey[ type ] = nextKey; + + } + + } + +}; + + +THREE.Animation.prototype.update = (function(){ + + var points = []; + var target = new THREE.Vector3(); + var newVector = new THREE.Vector3(); + var newQuat = new THREE.Quaternion(); + + // Catmull-Rom spline + + var interpolateCatmullRom = function ( points, scale ) { + + var c = [], v3 = [], + point, intPoint, weight, w2, w3, + pa, pb, pc, pd; + + point = ( points.length - 1 ) * scale; + intPoint = Math.floor( point ); + weight = point - intPoint; + + c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; + c[ 1 ] = intPoint; + c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1; + c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2; + + pa = points[ c[ 0 ] ]; + pb = points[ c[ 1 ] ]; + pc = points[ c[ 2 ] ]; + pd = points[ c[ 3 ] ]; + + w2 = weight * weight; + w3 = weight * w2; + + v3[ 0 ] = interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 ); + v3[ 1 ] = interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 ); + v3[ 2 ] = interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 ); + + return v3; + + }; + + var interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) { + + var v0 = ( p2 - p0 ) * 0.5, + v1 = ( p3 - p1 ) * 0.5; + + return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + }; + + return function ( delta ) { + + if ( this.isPlaying === false ) return; + + this.currentTime += delta * this.timeScale; + + if ( this.weight === 0 ) + return; + + // + + var duration = this.data.length; + + if ( this.loop === true && this.currentTime > duration ) { + + this.currentTime %= duration; + this.reset(); + + } else if ( this.loop === false && this.currentTime > duration ) { + + this.stop(); + return; + + } + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + var animationCache = object.animationCache[this.data.name]; + + // loop through pos/rot/scl + + for ( var t = 0; t < 3; t ++ ) { + + // get keys + + var type = this.keyTypes[ t ]; + var prevKey = animationCache.prevKey[ type ]; + var nextKey = animationCache.nextKey[ type ]; + + if ( nextKey.time <= this.currentTime ) { + + prevKey = this.data.hierarchy[ h ].keys[ 0 ]; + nextKey = this.getNextKeyWith( type, h, 1 ); + + while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { + + prevKey = nextKey; + nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); + + } + + animationCache.prevKey[ type ] = prevKey; + animationCache.nextKey[ type ] = nextKey; + + } + + object.matrixAutoUpdate = true; + object.matrixWorldNeedsUpdate = true; + + var scale = ( this.currentTime - prevKey.time ) / ( nextKey.time - prevKey.time ); + + var prevXYZ = prevKey[ type ]; + var nextXYZ = nextKey[ type ]; + + if ( scale < 0 ) scale = 0; + if ( scale > 1 ) scale = 1; + + // interpolate + + if ( type === "pos" ) { + + if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) { + + newVector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + newVector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + newVector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + // blend + if ( object instanceof THREE.Bone ) { + + var proportionalWeight = this.weight / ( this.weight + object.accumulatedPosWeight ); + object.position.lerp( newVector, proportionalWeight ); + object.accumulatedPosWeight += this.weight; + + } else { + + object.position.copy( newVector ); + + } + + } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ]; + points[ 1 ] = prevXYZ; + points[ 2 ] = nextXYZ; + points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ]; + + scale = scale * 0.33 + 0.33; + + var currentPoint = interpolateCatmullRom( points, scale ); + var proportionalWeight = 1; + + if ( object instanceof THREE.Bone ) { + + proportionalWeight = this.weight / ( this.weight + object.accumulatedPosWeight ); + object.accumulatedPosWeight += this.weight; + + } + + // blend + + var vector = object.position; + + vector.x = vector.x + ( currentPoint[ 0 ] - vector.x ) * proportionalWeight; + vector.y = vector.y + ( currentPoint[ 1 ] - vector.y ) * proportionalWeight; + vector.z = vector.z + ( currentPoint[ 2 ] - vector.z ) * proportionalWeight; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + var forwardPoint = interpolateCatmullRom( points, scale * 1.01 ); + + target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] ); + target.sub( vector ); + target.y = 0; + target.normalize(); + + var angle = Math.atan2( target.x, target.z ); + object.rotation.set( 0, angle, 0 ); + + } + + } + + } else if ( type === "rot" ) { + + THREE.Quaternion.slerp( prevXYZ, nextXYZ, newQuat, scale ); + + // Avoid paying the cost of an additional slerp if we don't have to + if ( ! ( object instanceof THREE.Bone ) ) { + + object.quaternion.copy(newQuat); + + } else if ( object.accumulatedRotWeight === 0 ) { + + object.quaternion.copy(newQuat); + object.accumulatedRotWeight = this.weight; + + } else { + + var proportionalWeight = this.weight / ( this.weight + object.accumulatedRotWeight ); + THREE.Quaternion.slerp( object.quaternion, newQuat, object.quaternion, proportionalWeight ); + object.accumulatedRotWeight += this.weight; + + } + + } else if ( type === "scl" ) { + + newVector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + newVector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + newVector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + if ( object instanceof THREE.Bone ) { + + var proportionalWeight = this.weight / ( this.weight + object.accumulatedSclWeight); + object.scale.lerp( newVector, proportionalWeight ); + object.accumulatedSclWeight += this.weight; + + } else { + + object.scale.copy( newVector ); + + } + + } + + } + + } + + return true; + + }; + +})(); + + + + + +// Get next key with + +THREE.Animation.prototype.getNextKeyWith = function ( type, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + key = key < keys.length - 1 ? key : keys.length - 1; + + } else { + + key = key % keys.length; + + } + + for ( ; key < keys.length; key ++ ) { + + if ( keys[ key ][ type ] !== undefined ) { + + return keys[ key ]; + + } + + } + + return this.data.hierarchy[ h ].keys[ 0 ]; + +}; + +// Get previous key with + +THREE.Animation.prototype.getPrevKeyWith = function ( type, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + + if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || + this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { + + key = key > 0 ? key : 0; + + } else { + + key = key >= 0 ? key : key + keys.length; + + } + + + for ( ; key >= 0; key -- ) { + + if ( keys[ key ][ type ] !== undefined ) { + + return keys[ key ]; + + } + + } + + return this.data.hierarchy[ h ].keys[ keys.length - 1 ]; + +}; + +// File:src/extras/animation/KeyFrameAnimation.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author khang duong + * @author erik kitson + */ + +THREE.KeyFrameAnimation = function ( data ) { + + this.root = data.node; + this.data = THREE.AnimationHandler.init( data ); + this.hierarchy = THREE.AnimationHandler.parse( this.root ); + this.currentTime = 0; + this.timeScale = 0.001; + this.isPlaying = false; + this.isPaused = true; + this.loop = true; + + // initialize to first keyframes + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var keys = this.data.hierarchy[h].keys, + sids = this.data.hierarchy[h].sids, + obj = this.hierarchy[h]; + + if ( keys.length && sids ) { + + for ( var s = 0; s < sids.length; s ++ ) { + + var sid = sids[ s ], + next = this.getNextKeyWith( sid, h, 0 ); + + if ( next ) { + + next.apply( sid ); + + } + + } + + obj.matrixAutoUpdate = false; + this.data.hierarchy[h].node.updateMatrix(); + obj.matrixWorldNeedsUpdate = true; + + } + + } + +}; + + +THREE.KeyFrameAnimation.prototype.play = function ( startTime ) { + + this.currentTime = startTime !== undefined ? startTime : 0; + + if ( this.isPlaying === false ) { + + this.isPlaying = true; + + // reset key cache + + var h, hl = this.hierarchy.length, + object, + node; + + for ( h = 0; h < hl; h ++ ) { + + object = this.hierarchy[ h ]; + node = this.data.hierarchy[ h ]; + + if ( node.animationCache === undefined ) { + + node.animationCache = {}; + node.animationCache.prevKey = null; + node.animationCache.nextKey = null; + node.animationCache.originalMatrix = object.matrix; + + } + + var keys = this.data.hierarchy[h].keys; + + if (keys.length) { + + node.animationCache.prevKey = keys[ 0 ]; + node.animationCache.nextKey = keys[ 1 ]; + + this.startTime = Math.min( keys[0].time, this.startTime ); + this.endTime = Math.max( keys[keys.length - 1].time, this.endTime ); + + } + + } + + this.update( 0 ); + + } + + this.isPaused = false; + + THREE.AnimationHandler.play( this ); + +}; + + +THREE.KeyFrameAnimation.prototype.stop = function() { + + this.isPlaying = false; + this.isPaused = false; + + THREE.AnimationHandler.stop( this ); + + // reset JIT matrix and remove cache + + for ( var h = 0; h < this.data.hierarchy.length; h ++ ) { + + var obj = this.hierarchy[ h ]; + var node = this.data.hierarchy[ h ]; + + if ( node.animationCache !== undefined ) { + + var original = node.animationCache.originalMatrix; + + original.copy( obj.matrix ); + obj.matrix = original; + + delete node.animationCache; + + } + + } + +}; + + +// Update + +THREE.KeyFrameAnimation.prototype.update = function ( delta ) { + + if ( this.isPlaying === false ) return; + + this.currentTime += delta * this.timeScale; + + // + + var duration = this.data.length; + + if ( this.loop === true && this.currentTime > duration ) { + + this.currentTime %= duration; + + } + + this.currentTime = Math.min( this.currentTime, duration ); + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + var node = this.data.hierarchy[ h ]; + + var keys = node.keys, + animationCache = node.animationCache; + + + if ( keys.length ) { + + var prevKey = animationCache.prevKey; + var nextKey = animationCache.nextKey; + + if ( nextKey.time <= this.currentTime ) { + + while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { + + prevKey = nextKey; + nextKey = keys[ prevKey.index + 1 ]; + + } + + animationCache.prevKey = prevKey; + animationCache.nextKey = nextKey; + + } + + if ( nextKey.time >= this.currentTime ) { + + prevKey.interpolate( nextKey, this.currentTime ); + + } else { + + prevKey.interpolate( nextKey, nextKey.time ); + + } + + this.data.hierarchy[ h ].node.updateMatrix(); + object.matrixWorldNeedsUpdate = true; + + } + + } + +}; + +// Get next key with + +THREE.KeyFrameAnimation.prototype.getNextKeyWith = function( sid, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + key = key % keys.length; + + for ( ; key < keys.length; key ++ ) { + + if ( keys[ key ].hasTarget( sid ) ) { + + return keys[ key ]; + + } + + } + + return keys[ 0 ]; + +}; + +// Get previous key with + +THREE.KeyFrameAnimation.prototype.getPrevKeyWith = function( sid, h, key ) { + + var keys = this.data.hierarchy[ h ].keys; + key = key >= 0 ? key : key + keys.length; + + for ( ; key >= 0; key -- ) { + + if ( keys[ key ].hasTarget( sid ) ) { + + return keys[ key ]; + + } + + } + + return keys[ keys.length - 1 ]; + +}; + +// File:src/extras/animation/MorphAnimation.js + +/** + * @author mrdoob / http://mrdoob.com + */ + +THREE.MorphAnimation = function ( mesh ) { + + this.mesh = mesh; + this.frames = mesh.morphTargetInfluences.length; + this.currentTime = 0; + this.duration = 1000; + this.loop = true; + + this.isPlaying = false; + +}; + +THREE.MorphAnimation.prototype = { + + play: function () { + + this.isPlaying = true; + + }, + + pause: function () { + + this.isPlaying = false; + + }, + + update: ( function () { + + var lastFrame = 0; + var currentFrame = 0; + + return function ( delta ) { + + if ( this.isPlaying === false ) return; + + this.currentTime += delta; + + if ( this.loop === true && this.currentTime > this.duration ) { + + this.currentTime %= this.duration; + + } + + this.currentTime = Math.min( this.currentTime, this.duration ); + + var interpolation = this.duration / this.frames; + var frame = Math.floor( this.currentTime / interpolation ); + + if ( frame != currentFrame ) { + + this.mesh.morphTargetInfluences[ lastFrame ] = 0; + this.mesh.morphTargetInfluences[ currentFrame ] = 1; + this.mesh.morphTargetInfluences[ frame ] = 0; + + lastFrame = currentFrame; + currentFrame = frame; + + } + + this.mesh.morphTargetInfluences[ frame ] = ( this.currentTime % interpolation ) / interpolation; + this.mesh.morphTargetInfluences[ lastFrame ] = 1 - this.mesh.morphTargetInfluences[ frame ]; + + } + + } )() + +}; + +// File:src/extras/geometries/BoxGeometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as + */ + +THREE.BoxGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) { + + THREE.Geometry.call( this ); + + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; + + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + this.depthSegments = depthSegments || 1; + + var scope = this; + + var width_half = width / 2; + var height_half = height / 2; + var depth_half = depth / 2; + + buildPlane( 'z', 'y', - 1, - 1, depth, height, width_half, 0 ); // px + buildPlane( 'z', 'y', 1, - 1, depth, height, - width_half, 1 ); // nx + buildPlane( 'x', 'z', 1, 1, width, depth, height_half, 2 ); // py + buildPlane( 'x', 'z', 1, - 1, width, depth, - height_half, 3 ); // ny + buildPlane( 'x', 'y', 1, - 1, width, height, depth_half, 4 ); // pz + buildPlane( 'x', 'y', - 1, - 1, width, height, - depth_half, 5 ); // nz + + function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) { + + var w, ix, iy, + gridX = scope.widthSegments, + gridY = scope.heightSegments, + width_half = width / 2, + height_half = height / 2, + offset = scope.vertices.length; + + if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) { + + w = 'z'; + + } else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) { + + w = 'y'; + gridY = scope.depthSegments; + + } else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) { + + w = 'x'; + gridX = scope.depthSegments; + + } + + var gridX1 = gridX + 1, + gridY1 = gridY + 1, + segment_width = width / gridX, + segment_height = height / gridY, + normal = new THREE.Vector3(); + + normal[ w ] = depth > 0 ? 1 : - 1; + + for ( iy = 0; iy < gridY1; iy ++ ) { + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var vector = new THREE.Vector3(); + vector[ u ] = ( ix * segment_width - width_half ) * udir; + vector[ v ] = ( iy * segment_height - height_half ) * vdir; + vector[ w ] = depth; + + scope.vertices.push( vector ); + + } + + } + + for ( iy = 0; iy < gridY; iy ++ ) { + + for ( ix = 0; ix < gridX; ix ++ ) { + + var a = ix + gridX1 * iy; + var b = ix + gridX1 * ( iy + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = ( ix + 1 ) + gridX1 * iy; + + var uva = new THREE.Vector2( ix / gridX, 1 - iy / gridY ); + var uvb = new THREE.Vector2( ix / gridX, 1 - ( iy + 1 ) / gridY ); + var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iy + 1 ) / gridY ); + var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iy / gridY ); + + var face = new THREE.Face3( a + offset, b + offset, d + offset ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + face.materialIndex = materialIndex; + + scope.faces.push( face ); + scope.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + face = new THREE.Face3( b + offset, c + offset, d + offset ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + face.materialIndex = materialIndex; + + scope.faces.push( face ); + scope.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + + } + + this.mergeVertices(); + +}; + +THREE.BoxGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/CircleGeometry.js + +/** + * @author hughes + */ + +THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + radius = radius || 50; + segments = segments !== undefined ? Math.max( 3, segments ) : 8; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + var i, uvs = [], + center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 ); + + this.vertices.push(center); + uvs.push( centerUV ); + + for ( i = 0; i <= segments; i ++ ) { + + var vertex = new THREE.Vector3(); + var segment = thetaStart + i / segments * thetaLength; + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) ); + + } + + var n = new THREE.Vector3( 0, 0, 1 ); + + for ( i = 1; i <= segments; i ++ ) { + + this.faces.push( new THREE.Face3( i, i + 1, 0, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] ); + + } + + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/CubeGeometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + + +THREE.CubeGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) { + + console.warn( 'THEE.CubeGeometry has been renamed to THREE.BoxGeometry.' ); + return new THREE.BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ); + + }; + +// File:src/extras/geometries/CylinderGeometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded ) { + + THREE.Geometry.call( this ); + + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded + }; + + radiusTop = radiusTop !== undefined ? radiusTop : 20; + radiusBottom = radiusBottom !== undefined ? radiusBottom : 20; + height = height !== undefined ? height : 100; + + radialSegments = radialSegments || 8; + heightSegments = heightSegments || 1; + + openEnded = openEnded !== undefined ? openEnded : false; + + var heightHalf = height / 2; + + var x, y, vertices = [], uvs = []; + + for ( y = 0; y <= heightSegments; y ++ ) { + + var verticesRow = []; + var uvsRow = []; + + var v = y / heightSegments; + var radius = v * ( radiusBottom - radiusTop ) + radiusTop; + + for ( x = 0; x <= radialSegments; x ++ ) { + + var u = x / radialSegments; + + var vertex = new THREE.Vector3(); + vertex.x = radius * Math.sin( u * Math.PI * 2 ); + vertex.y = - v * height + heightHalf; + vertex.z = radius * Math.cos( u * Math.PI * 2 ); + + this.vertices.push( vertex ); + + verticesRow.push( this.vertices.length - 1 ); + uvsRow.push( new THREE.Vector2( u, 1 - v ) ); + + } + + vertices.push( verticesRow ); + uvs.push( uvsRow ); + + } + + var tanTheta = ( radiusBottom - radiusTop ) / height; + var na, nb; + + for ( x = 0; x < radialSegments; x ++ ) { + + if ( radiusTop !== 0 ) { + + na = this.vertices[ vertices[ 0 ][ x ] ].clone(); + nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone(); + + } else { + + na = this.vertices[ vertices[ 1 ][ x ] ].clone(); + nb = this.vertices[ vertices[ 1 ][ x + 1 ] ].clone(); + + } + + na.setY( Math.sqrt( na.x * na.x + na.z * na.z ) * tanTheta ).normalize(); + nb.setY( Math.sqrt( nb.x * nb.x + nb.z * nb.z ) * tanTheta ).normalize(); + + for ( y = 0; y < heightSegments; y ++ ) { + + var v1 = vertices[ y ][ x ]; + var v2 = vertices[ y + 1 ][ x ]; + var v3 = vertices[ y + 1 ][ x + 1 ]; + var v4 = vertices[ y ][ x + 1 ]; + + var n1 = na.clone(); + var n2 = na.clone(); + var n3 = nb.clone(); + var n4 = nb.clone(); + + var uv1 = uvs[ y ][ x ].clone(); + var uv2 = uvs[ y + 1 ][ x ].clone(); + var uv3 = uvs[ y + 1 ][ x + 1 ].clone(); + var uv4 = uvs[ y ][ x + 1 ].clone(); + + this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] ); + + this.faces.push( new THREE.Face3( v2, v3, v4, [ n2.clone(), n3, n4.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] ); + + } + + } + + // top cap + + if ( openEnded === false && radiusTop > 0 ) { + + this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) ); + + for ( x = 0; x < radialSegments; x ++ ) { + + var v1 = vertices[ 0 ][ x ]; + var v2 = vertices[ 0 ][ x + 1 ]; + var v3 = this.vertices.length - 1; + + var n1 = new THREE.Vector3( 0, 1, 0 ); + var n2 = new THREE.Vector3( 0, 1, 0 ); + var n3 = new THREE.Vector3( 0, 1, 0 ); + + var uv1 = uvs[ 0 ][ x ].clone(); + var uv2 = uvs[ 0 ][ x + 1 ].clone(); + var uv3 = new THREE.Vector2( uv2.x, 0 ); + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } + + } + + // bottom cap + + if ( openEnded === false && radiusBottom > 0 ) { + + this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) ); + + for ( x = 0; x < radialSegments; x ++ ) { + + var v1 = vertices[ y ][ x + 1 ]; + var v2 = vertices[ y ][ x ]; + var v3 = this.vertices.length - 1; + + var n1 = new THREE.Vector3( 0, - 1, 0 ); + var n2 = new THREE.Vector3( 0, - 1, 0 ); + var n3 = new THREE.Vector3( 0, - 1, 0 ); + + var uv1 = uvs[ y ][ x + 1 ].clone(); + var uv2 = uvs[ y ][ x ].clone(); + var uv3 = new THREE.Vector2( uv2.x, 1 ); + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } + + } + + this.computeFaceNormals(); + +} + +THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/ExtrudeGeometry.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + * Creates extruded geometry from a path shape. + * + * parameters = { + * + * curveSegments: , // number of points on the curves + * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too + * amount: , // Depth to extrude the shape + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into the original shape bevel goes + * bevelSize: , // how far from shape outline is bevel + * bevelSegments: , // number of bevel layers + * + * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined) + * frames: // containing arrays of tangents, normals, binormals + * + * material: // material index for front and back faces + * extrudeMaterial: // material index for extrusion and beveled faces + * uvGenerator: // object that provides UV generator functions + * + * } + **/ + +THREE.ExtrudeGeometry = function ( shapes, options ) { + + if ( typeof( shapes ) === "undefined" ) { + shapes = []; + return; + } + + THREE.Geometry.call( this ); + + shapes = shapes instanceof Array ? shapes : [ shapes ]; + + this.addShapeList( shapes, options ); + + this.computeFaceNormals(); + + // can't really use automatic vertex normals + // as then front and back sides get smoothed too + // should do separate smoothing just for sides + + //this.computeVertexNormals(); + + //console.log( "took", ( Date.now() - startTime ) ); + +}; + +THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +THREE.ExtrudeGeometry.prototype.addShapeList = function ( shapes, options ) { + var sl = shapes.length; + + for ( var s = 0; s < sl; s ++ ) { + var shape = shapes[ s ]; + this.addShape( shape, options ); + } +}; + +THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) { + + var amount = options.amount !== undefined ? options.amount : 100; + + var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 + var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 + var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + + var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false + + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + + var steps = options.steps !== undefined ? options.steps : 1; + + var extrudePath = options.extrudePath; + var extrudePts, extrudeByPath = false; + + var material = options.material; + var extrudeMaterial = options.extrudeMaterial; + + // Use default WorldUVGenerator if no UV generators are specified. + var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator; + + var splineTube, binormal, normal, position2; + if ( extrudePath ) { + + extrudePts = extrudePath.getSpacedPoints( steps ); + + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion + + // SETUP TNB variables + + // Reuse TNB from TubeGeomtry for now. + // TODO1 - have a .isClosed in spline? + + splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false); + + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + + binormal = new THREE.Vector3(); + normal = new THREE.Vector3(); + position2 = new THREE.Vector3(); + + } + + // Safeguards if bevels are not enabled + + if ( ! bevelEnabled ) { + + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + + } + + // Variables initalization + + var ahole, h, hl; // looping of holes + var scope = this; + var bevelPoints = []; + + var shapesOffset = this.vertices.length; + + var shapePoints = shape.extractPoints( curveSegments ); + + var vertices = shapePoints.shape; + var holes = shapePoints.holes; + + var reverse = ! THREE.Shape.Utils.isClockWise( vertices ) ; + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe ... + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + if ( THREE.Shape.Utils.isClockWise( ahole ) ) { + + holes[ h ] = ahole.reverse(); + + } + + } + + reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)! + + } + + + var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes ); + + /* Vertices */ + + var contour = vertices; // vertices has all points but contour has only points of circumference + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + vertices = vertices.concat( ahole ); + + } + + + function scalePt2 ( pt, vec, size ) { + + if ( ! vec ) console.log( "die" ); + + return vec.clone().multiplyScalar( size ).add( pt ); + + } + + var b, bs, t, z, + vert, vlen = vertices.length, + face, flen = faces.length, + cont, clen = contour.length; + + + // Find directions for point movement + + var RAD_TO_DEGREES = 180 / Math.PI; + + + function getBevelVec( inPt, inPrev, inNext ) { + + var EPSILON = 0.0000000001; + var sign = THREE.Math.sign; + + // computes for inPt the corresponding point inPt' on a new contour + // shiftet by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. + + var v_trans_x, v_trans_y, shrink_by = 1; // resulting translation vector for inPt + + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html + + var v_prev_x = inPt.x - inPrev.x, v_prev_y = inPt.y - inPrev.y; + var v_next_x = inNext.x - inPt.x, v_next_y = inNext.y - inPt.y; + + var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + + // check for colinear edges + var colinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + if ( Math.abs( colinear0 ) > EPSILON ) { // not colinear + + // length of vectors for normalizing + + var v_prev_len = Math.sqrt( v_prev_lensq ); + var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + + // shift adjacent points by unit vectors to the left + + var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + + var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + + // scaling factor for v_prev to intersection point + + var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + // vector from inPt to intersection point + + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ) + if ( v_trans_lensq <= 2 ) { + return new THREE.Vector2( v_trans_x, v_trans_y ); + } else { + shrink_by = Math.sqrt( v_trans_lensq / 2 ); + } + + } else { // handle special case of colinear edges + + var direction_eq = false; // assumes: opposite + if ( v_prev_x > EPSILON ) { + if ( v_next_x > EPSILON ) { direction_eq = true; } + } else { + if ( v_prev_x < - EPSILON ) { + if ( v_next_x < - EPSILON ) { direction_eq = true; } + } else { + if ( sign(v_prev_y) == sign(v_next_y) ) { direction_eq = true; } + } + } + + if ( direction_eq ) { + // console.log("Warning: lines are a straight sequence"); + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); + } else { + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); + } + + } + + return new THREE.Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + + } + + + var contourMovements = []; + + for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) + + var pt_i = contour[ i ]; + var pt_j = contour[ j ]; + var pt_k = contour[ k ]; + + contourMovements[ i ]= getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + + } + + var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat(); + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + oneHoleMovements = []; + + for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + oneHoleMovements[ i ]= getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + + } + + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); + + } + + + // Loop bevelSegments, 1 for the front, 1 for the back + + for ( b = 0; b < bevelSegments; b ++ ) { + //for ( b = bevelSegments; b > 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * ( 1 - t ); + + //z = bevelThickness * t; + bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved + //bs = bevelSize * t ; // linear + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i ++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + + } + + } + + } + + bs = bevelSize; + + // Back facing vertices + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, 0 ); + + } else { + + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + + normal.copy( splineTube.normals[0] ).multiplyScalar(vert.x); + binormal.copy( splineTube.binormals[0] ).multiplyScalar(vert.y); + + position2.copy( extrudePts[0] ).add(normal).add(binormal); + + v( position2.x, position2.y, position2.z ); + + } + + } + + // Add stepped vertices... + // Including front facing vertices + + var s; + + for ( s = 1; s <= steps; s ++ ) { + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, amount / steps * s ); + + } else { + + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + + normal.copy( splineTube.normals[s] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[s] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[s] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + } + + + // Add bevel segments planes + + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( b = bevelSegments - 1; b >= 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * ( 1 - t ); + //bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) ); + bs = bevelSize * Math.sin ( t * Math.PI/2 ) ; + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, amount + z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i ++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, amount + z ); + + } else { + + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + + } + + } + + } + + } + + /* Faces */ + + // Top and bottom faces + + buildLidFaces(); + + // Sides faces + + buildSideFaces(); + + + ///// Internal functions + + function buildLidFaces() { + + if ( bevelEnabled ) { + + var layer = 0 ; // steps + 1 + var offset = vlen * layer; + + // Bottom faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true ); + + } + + layer = steps + bevelSegments * 2; + offset = vlen * layer; + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false ); + + } + + } else { + + // Bottom faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ], true ); + + } + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps, false ); + + } + } + + } + + // Create faces for the z-sides of the shape + + function buildSideFaces() { + + var layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); + + //, true + layeroffset += ahole.length; + + } + + } + + function sidewalls( contour, layeroffset ) { + + var j, k; + i = contour.length; + + while ( --i >= 0 ) { + + j = i; + k = i - 1; + if ( k < 0 ) k = contour.length - 1; + + //console.log('b', i,j, i-1, k,vertices.length); + + var s = 0, sl = steps + bevelSegments * 2; + + for ( s = 0; s < sl; s ++ ) { + + var slen1 = vlen * s; + var slen2 = vlen * ( s + 1 ); + + var a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; + + f4( a, b, c, d, contour, s, sl, j, k ); + + } + } + + } + + + function v( x, y, z ) { + + scope.vertices.push( new THREE.Vector3( x, y, z ) ); + + } + + function f3( a, b, c, isBottom ) { + + a += shapesOffset; + b += shapesOffset; + c += shapesOffset; + + // normal, color, material + scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); + + var uvs = isBottom ? uvgen.generateBottomUV( scope, shape, options, a, b, c ) : uvgen.generateTopUV( scope, shape, options, a, b, c ); + + scope.faceVertexUvs[ 0 ].push( uvs ); + + } + + function f4( a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2 ) { + + a += shapesOffset; + b += shapesOffset; + c += shapesOffset; + d += shapesOffset; + + scope.faces.push( new THREE.Face3( a, b, d, null, null, extrudeMaterial ) ); + scope.faces.push( new THREE.Face3( b, c, d, null, null, extrudeMaterial ) ); + + var uvs = uvgen.generateSideWallUV( scope, shape, wallContour, options, a, b, c, d, + stepIndex, stepsLength, contourIndex1, contourIndex2 ); + + scope.faceVertexUvs[ 0 ].push( [ uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ] ); + scope.faceVertexUvs[ 0 ].push( [ uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ] ); + + } + +}; + +THREE.ExtrudeGeometry.WorldUVGenerator = { + + generateTopUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { + var ax = geometry.vertices[ indexA ].x, + ay = geometry.vertices[ indexA ].y, + + bx = geometry.vertices[ indexB ].x, + by = geometry.vertices[ indexB ].y, + + cx = geometry.vertices[ indexC ].x, + cy = geometry.vertices[ indexC ].y; + + return [ + new THREE.Vector2( ax, ay ), + new THREE.Vector2( bx, by ), + new THREE.Vector2( cx, cy ) + ]; + + }, + + generateBottomUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) { + + return this.generateTopUV( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ); + + }, + + generateSideWallUV: function( geometry, extrudedShape, wallContour, extrudeOptions, + indexA, indexB, indexC, indexD, stepIndex, stepsLength, + contourIndex1, contourIndex2 ) { + + var ax = geometry.vertices[ indexA ].x, + ay = geometry.vertices[ indexA ].y, + az = geometry.vertices[ indexA ].z, + + bx = geometry.vertices[ indexB ].x, + by = geometry.vertices[ indexB ].y, + bz = geometry.vertices[ indexB ].z, + + cx = geometry.vertices[ indexC ].x, + cy = geometry.vertices[ indexC ].y, + cz = geometry.vertices[ indexC ].z, + + dx = geometry.vertices[ indexD ].x, + dy = geometry.vertices[ indexD ].y, + dz = geometry.vertices[ indexD ].z; + + if ( Math.abs( ay - by ) < 0.01 ) { + return [ + new THREE.Vector2( ax, 1 - az ), + new THREE.Vector2( bx, 1 - bz ), + new THREE.Vector2( cx, 1 - cz ), + new THREE.Vector2( dx, 1 - dz ) + ]; + } else { + return [ + new THREE.Vector2( ay, 1 - az ), + new THREE.Vector2( by, 1 - bz ), + new THREE.Vector2( cy, 1 - cz ), + new THREE.Vector2( dy, 1 - dz ) + ]; + } + } +}; + +THREE.ExtrudeGeometry.__v1 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v2 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v3 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v4 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v5 = new THREE.Vector2(); +THREE.ExtrudeGeometry.__v6 = new THREE.Vector2(); + +// File:src/extras/geometries/ShapeGeometry.js + +/** + * @author jonobr1 / http://jonobr1.com + * + * Creates a one-sided polygonal geometry from a path shape. Similar to + * ExtrudeGeometry. + * + * parameters = { + * + * curveSegments: , // number of points on the curves. NOT USED AT THE MOMENT. + * + * material: // material index for front and back faces + * uvGenerator: // object that provides UV generator functions + * + * } + **/ + +THREE.ShapeGeometry = function ( shapes, options ) { + + THREE.Geometry.call( this ); + + if ( shapes instanceof Array === false ) shapes = [ shapes ]; + + this.addShapeList( shapes, options ); + + this.computeFaceNormals(); + +}; + +THREE.ShapeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * Add an array of shapes to THREE.ShapeGeometry. + */ +THREE.ShapeGeometry.prototype.addShapeList = function ( shapes, options ) { + + for ( var i = 0, l = shapes.length; i < l; i ++ ) { + + this.addShape( shapes[ i ], options ); + + } + + return this; + +}; + +/** + * Adds a shape to THREE.ShapeGeometry, based on THREE.ExtrudeGeometry. + */ +THREE.ShapeGeometry.prototype.addShape = function ( shape, options ) { + + if ( options === undefined ) options = {}; + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + + var material = options.material; + var uvgen = options.UVGenerator === undefined ? THREE.ExtrudeGeometry.WorldUVGenerator : options.UVGenerator; + + // + + var i, l, hole, s; + + var shapesOffset = this.vertices.length; + var shapePoints = shape.extractPoints( curveSegments ); + + var vertices = shapePoints.shape; + var holes = shapePoints.holes; + + var reverse = ! THREE.Shape.Utils.isClockWise( vertices ); + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe... + + for ( i = 0, l = holes.length; i < l; i ++ ) { + + hole = holes[ i ]; + + if ( THREE.Shape.Utils.isClockWise( hole ) ) { + + holes[ i ] = hole.reverse(); + + } + + } + + reverse = false; + + } + + var faces = THREE.Shape.Utils.triangulateShape( vertices, holes ); + + // Vertices + + var contour = vertices; + + for ( i = 0, l = holes.length; i < l; i ++ ) { + + hole = holes[ i ]; + vertices = vertices.concat( hole ); + + } + + // + + var vert, vlen = vertices.length; + var face, flen = faces.length; + var cont, clen = contour.length; + + for ( i = 0; i < vlen; i ++ ) { + + vert = vertices[ i ]; + + this.vertices.push( new THREE.Vector3( vert.x, vert.y, 0 ) ); + + } + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + + var a = face[ 0 ] + shapesOffset; + var b = face[ 1 ] + shapesOffset; + var c = face[ 2 ] + shapesOffset; + + this.faces.push( new THREE.Face3( a, b, c, null, null, material ) ); + this.faceVertexUvs[ 0 ].push( uvgen.generateBottomUV( this, shape, options, a, b, c ) ); + + } + +}; + +// File:src/extras/geometries/LatheGeometry.js + +/** + * @author astrodud / http://astrodud.isgreat.org/ + * @author zz85 / https://github.com/zz85 + * @author bhouston / http://exocortex.com + */ + +// points - to create a closed torus, one must use a set of points +// like so: [ a, b, c, d, a ], see first is the same as last. +// segments - the number of circumference segments to create +// phiStart - the starting radian +// phiLength - the radian (0 to 2*PI) range of the lathed section +// 2*pi is a closed lathe, less than 2PI is a portion. +THREE.LatheGeometry = function ( points, segments, phiStart, phiLength ) { + + THREE.Geometry.call( this ); + + segments = segments || 12; + phiStart = phiStart || 0; + phiLength = phiLength || 2 * Math.PI; + + var inversePointLength = 1.0 / ( points.length - 1 ); + var inverseSegments = 1.0 / segments; + + for ( var i = 0, il = segments; i <= il; i ++ ) { + + var phi = phiStart + i * inverseSegments * phiLength; + + var c = Math.cos( phi ), + s = Math.sin( phi ); + + for ( var j = 0, jl = points.length; j < jl; j ++ ) { + + var pt = points[ j ]; + + var vertex = new THREE.Vector3(); + + vertex.x = c * pt.x - s * pt.y; + vertex.y = s * pt.x + c * pt.y; + vertex.z = pt.z; + + this.vertices.push( vertex ); + + } + + } + + var np = points.length; + + for ( var i = 0, il = segments; i < il; i ++ ) { + + for ( var j = 0, jl = points.length - 1; j < jl; j ++ ) { + + var base = j + np * i; + var a = base; + var b = base + np; + var c = base + 1 + np; + var d = base + 1; + + var u0 = i * inverseSegments; + var v0 = j * inversePointLength; + var u1 = u0 + inverseSegments; + var v1 = v0 + inversePointLength; + + this.faces.push( new THREE.Face3( a, b, d ) ); + + this.faceVertexUvs[ 0 ].push( [ + + new THREE.Vector2( u0, v0 ), + new THREE.Vector2( u1, v0 ), + new THREE.Vector2( u0, v1 ) + + ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + + this.faceVertexUvs[ 0 ].push( [ + + new THREE.Vector2( u1, v0 ), + new THREE.Vector2( u1, v1 ), + new THREE.Vector2( u0, v1 ) + + ] ); + + + } + + } + + this.mergeVertices(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/PlaneGeometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as + */ + +THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ) { + + THREE.Geometry.call( this ); + + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; + + var ix, iz; + var width_half = width / 2; + var height_half = height / 2; + + var gridX = widthSegments || 1; + var gridZ = heightSegments || 1; + + var gridX1 = gridX + 1; + var gridZ1 = gridZ + 1; + + var segment_width = width / gridX; + var segment_height = height / gridZ; + + var normal = new THREE.Vector3( 0, 0, 1 ); + + for ( iz = 0; iz < gridZ1; iz ++ ) { + + var y = iz * segment_height - height_half; + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var x = ix * segment_width - width_half; + + this.vertices.push( new THREE.Vector3( x, - y, 0 ) ); + + } + + } + + for ( iz = 0; iz < gridZ; iz ++ ) { + + for ( ix = 0; ix < gridX; ix ++ ) { + + var a = ix + gridX1 * iz; + var b = ix + gridX1 * ( iz + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iz + 1 ); + var d = ( ix + 1 ) + gridX1 * iz; + + var uva = new THREE.Vector2( ix / gridX, 1 - iz / gridZ ); + var uvb = new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ ); + var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ ); + var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ ); + + var face = new THREE.Face3( a, b, d ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + face = new THREE.Face3( b, c, d ); + face.normal.copy( normal ); + face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() ); + + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + +}; + +THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/RingGeometry.js + +/** + * @author Kaleb Murphy + */ + +THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + innerRadius = innerRadius || 0; + outerRadius = outerRadius || 50; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; + phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 8; + + var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + + for ( i = 0; i < phiSegments + 1; i ++ ) { // concentric circles inside ring + + for ( o = 0; o < thetaSegments + 1; o ++ ) { // number of segments per circle + + var vertex = new THREE.Vector3(); + var segment = thetaStart + o / thetaSegments * thetaLength; + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + this.vertices.push( vertex ); + uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) ); + } + + radius += radiusStep; + + } + + var n = new THREE.Vector3( 0, 0, 1 ); + + for ( i = 0; i < phiSegments; i ++ ) { // concentric circles inside ring + + var thetaSegment = i * (thetaSegments + 1); + + for ( o = 0; o < thetaSegments ; o ++ ) { // number of segments per circle + + var segment = o + thetaSegment; + + var v1 = segment; + var v2 = segment + thetaSegments + 1; + var v3 = segment + thetaSegments + 2; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]); + + v1 = segment; + v2 = segment + thetaSegments + 2; + v3 = segment + 1; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]); + + } + } + + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); + + +// File:src/extras/geometries/SphereGeometry.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + radius = radius || 50; + + widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); + + phiStart = phiStart !== undefined ? phiStart : 0; + phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; + + var x, y, vertices = [], uvs = []; + + for ( y = 0; y <= heightSegments; y ++ ) { + + var verticesRow = []; + var uvsRow = []; + + for ( x = 0; x <= widthSegments; x ++ ) { + + var u = x / widthSegments; + var v = y / heightSegments; + + var vertex = new THREE.Vector3(); + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + + this.vertices.push( vertex ); + + verticesRow.push( this.vertices.length - 1 ); + uvsRow.push( new THREE.Vector2( u, 1 - v ) ); + + } + + vertices.push( verticesRow ); + uvs.push( uvsRow ); + + } + + for ( y = 0; y < heightSegments; y ++ ) { + + for ( x = 0; x < widthSegments; x ++ ) { + + var v1 = vertices[ y ][ x + 1 ]; + var v2 = vertices[ y ][ x ]; + var v3 = vertices[ y + 1 ][ x ]; + var v4 = vertices[ y + 1 ][ x + 1 ]; + + var n1 = this.vertices[ v1 ].clone().normalize(); + var n2 = this.vertices[ v2 ].clone().normalize(); + var n3 = this.vertices[ v3 ].clone().normalize(); + var n4 = this.vertices[ v4 ].clone().normalize(); + + var uv1 = uvs[ y ][ x + 1 ].clone(); + var uv2 = uvs[ y ][ x ].clone(); + var uv3 = uvs[ y + 1 ][ x ].clone(); + var uv4 = uvs[ y + 1 ][ x + 1 ].clone(); + + if ( Math.abs( this.vertices[ v1 ].y ) === radius ) { + + uv1.x = ( uv1.x + uv2.x ) / 2; + this.faces.push( new THREE.Face3( v1, v3, v4, [ n1, n3, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv3, uv4 ] ); + + } else if ( Math.abs( this.vertices[ v3 ].y ) === radius ) { + + uv3.x = ( uv3.x + uv4.x ) / 2; + this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] ); + + } else { + + this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] ); + + this.faces.push( new THREE.Face3( v2, v3, v4, [ n2.clone(), n3, n4.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] ); + + } + + } + + } + + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/TextGeometry.js + +/** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author alteredq / http://alteredqualia.com/ + * + * For creating 3D text geometry in three.js + * + * Text = 3D Text + * + * parameters = { + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * font: , // font name + * weight: , // font weight (normal, bold) + * style: , // font style (normal, italics) + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: , // how far from text outline is bevel + * } + * + */ + +/* Usage Examples + + // TextGeometry wrapper + + var text3d = new TextGeometry( text, options ); + + // Complete manner + + var textShapes = THREE.FontUtils.generateShapes( text, options ); + var text3d = new ExtrudeGeometry( textShapes, options ); + +*/ + + +THREE.TextGeometry = function ( text, parameters ) { + + parameters = parameters || {}; + + var textShapes = THREE.FontUtils.generateShapes( text, parameters ); + + // translate parameters to ExtrudeGeometry API + + parameters.amount = parameters.height !== undefined ? parameters.height : 50; + + // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + + THREE.ExtrudeGeometry.call( this, textShapes, parameters ); + +}; + +THREE.TextGeometry.prototype = Object.create( THREE.ExtrudeGeometry.prototype ); + +// File:src/extras/geometries/TorusGeometry.js + +/** + * @author oosmoxiecode + * @author mrdoob / http://mrdoob.com/ + * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3DLite/src/away3dlite/primitives/Torus.as?r=2888 + */ + +THREE.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments, arc ) { + + THREE.Geometry.call( this ); + + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; + + radius = radius || 100; + tube = tube || 40; + radialSegments = radialSegments || 8; + tubularSegments = tubularSegments || 6; + arc = arc || Math.PI * 2; + + var center = new THREE.Vector3(), uvs = [], normals = []; + + for ( var j = 0; j <= radialSegments; j ++ ) { + + for ( var i = 0; i <= tubularSegments; i ++ ) { + + var u = i / tubularSegments * arc; + var v = j / radialSegments * Math.PI * 2; + + center.x = radius * Math.cos( u ); + center.y = radius * Math.sin( u ); + + var vertex = new THREE.Vector3(); + vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = tube * Math.sin( v ); + + this.vertices.push( vertex ); + + uvs.push( new THREE.Vector2( i / tubularSegments, j / radialSegments ) ); + normals.push( vertex.clone().sub( center ).normalize() ); + + } + + } + + for ( var j = 1; j <= radialSegments; j ++ ) { + + for ( var i = 1; i <= tubularSegments; i ++ ) { + + var a = ( tubularSegments + 1 ) * j + i - 1; + var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; + var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; + var d = ( tubularSegments + 1 ) * j + i; + + var face = new THREE.Face3( a, b, d, [ normals[ a ].clone(), normals[ b ].clone(), normals[ d ].clone() ] ); + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvs[ a ].clone(), uvs[ b ].clone(), uvs[ d ].clone() ] ); + + face = new THREE.Face3( b, c, d, [ normals[ b ].clone(), normals[ c ].clone(), normals[ d ].clone() ] ); + this.faces.push( face ); + this.faceVertexUvs[ 0 ].push( [ uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] ); + + } + + } + + this.computeFaceNormals(); + +}; + +THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/TorusKnotGeometry.js + +/** + * @author oosmoxiecode + * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3D/src/away3d/primitives/TorusKnot.as?spec=svn2473&r=2473 + */ + +THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegments, p, q, heightScale ) { + + THREE.Geometry.call( this ); + + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + p: p, + q: q, + heightScale: heightScale + }; + + radius = radius || 100; + tube = tube || 40; + radialSegments = radialSegments || 64; + tubularSegments = tubularSegments || 8; + p = p || 2; + q = q || 3; + heightScale = heightScale || 1; + + var grid = new Array( radialSegments ); + var tang = new THREE.Vector3(); + var n = new THREE.Vector3(); + var bitan = new THREE.Vector3(); + + for ( var i = 0; i < radialSegments; ++ i ) { + + grid[ i ] = new Array( tubularSegments ); + var u = i / radialSegments * 2 * p * Math.PI; + var p1 = getPos( u, q, p, radius, heightScale ); + var p2 = getPos( u + 0.01, q, p, radius, heightScale ); + tang.subVectors( p2, p1 ); + n.addVectors( p2, p1 ); + + bitan.crossVectors( tang, n ); + n.crossVectors( bitan, tang ); + bitan.normalize(); + n.normalize(); + + for ( var j = 0; j < tubularSegments; ++ j ) { + + var v = j / tubularSegments * 2 * Math.PI; + var cx = - tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + var cy = tube * Math.sin( v ); + + var pos = new THREE.Vector3(); + pos.x = p1.x + cx * n.x + cy * bitan.x; + pos.y = p1.y + cx * n.y + cy * bitan.y; + pos.z = p1.z + cx * n.z + cy * bitan.z; + + grid[ i ][ j ] = this.vertices.push( pos ) - 1; + + } + + } + + for ( var i = 0; i < radialSegments; ++ i ) { + + for ( var j = 0; j < tubularSegments; ++ j ) { + + var ip = ( i + 1 ) % radialSegments; + var jp = ( j + 1 ) % tubularSegments; + + var a = grid[ i ][ j ]; + var b = grid[ ip ][ j ]; + var c = grid[ ip ][ jp ]; + var d = grid[ i ][ jp ]; + + var uva = new THREE.Vector2( i / radialSegments, j / tubularSegments ); + var uvb = new THREE.Vector2( ( i + 1 ) / radialSegments, j / tubularSegments ); + var uvc = new THREE.Vector2( ( i + 1 ) / radialSegments, ( j + 1 ) / tubularSegments ); + var uvd = new THREE.Vector2( i / radialSegments, ( j + 1 ) / tubularSegments ); + + this.faces.push( new THREE.Face3( a, b, d ) ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + } + + this.computeFaceNormals(); + this.computeVertexNormals(); + + function getPos( u, in_q, in_p, radius, heightScale ) { + + var cu = Math.cos( u ); + var su = Math.sin( u ); + var quOverP = in_q / in_p * u; + var cs = Math.cos( quOverP ); + + var tx = radius * ( 2 + cs ) * 0.5 * cu; + var ty = radius * ( 2 + cs ) * su * 0.5; + var tz = heightScale * radius * Math.sin( quOverP ) * 0.5; + + return new THREE.Vector3( tx, ty, tz ); + + } + +}; + +THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/TubeGeometry.js + +/** + * @author WestLangley / https://github.com/WestLangley + * @author zz85 / https://github.com/zz85 + * @author miningold / https://github.com/miningold + * + * Modified from the TorusKnotGeometry by @oosmoxiecode + * + * Creates a tube which extrudes along a 3d spline + * + * Uses parallel transport frames as described in + * http://www.cs.indiana.edu/pub/techreports/TR425.pdf + */ + +THREE.TubeGeometry = function ( path, segments, radius, radialSegments, closed ) { + + THREE.Geometry.call( this ); + + this.parameters = { + path: path, + segments: segments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; + + segments = segments || 64; + radius = radius || 1; + radialSegments = radialSegments || 8; + closed = closed || false; + + var grid = []; + + var scope = this, + + tangent, + normal, + binormal, + + numpoints = segments + 1, + + x, y, z, + tx, ty, tz, + u, v, + + cx, cy, + pos, pos2 = new THREE.Vector3(), + i, j, + ip, jp, + a, b, c, d, + uva, uvb, uvc, uvd; + + var frames = new THREE.TubeGeometry.FrenetFrames( path, segments, closed ), + tangents = frames.tangents, + normals = frames.normals, + binormals = frames.binormals; + + // proxy internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; + + function vert( x, y, z ) { + + return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1; + + } + + // consruct the grid + + for ( i = 0; i < numpoints; i ++ ) { + + grid[ i ] = []; + + u = i / ( numpoints - 1 ); + + pos = path.getPointAt( u ); + + tangent = tangents[ i ]; + normal = normals[ i ]; + binormal = binormals[ i ]; + + for ( j = 0; j < radialSegments; j ++ ) { + + v = j / radialSegments * 2 * Math.PI; + + cx = - radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + cy = radius * Math.sin( v ); + + pos2.copy( pos ); + pos2.x += cx * normal.x + cy * binormal.x; + pos2.y += cx * normal.y + cy * binormal.y; + pos2.z += cx * normal.z + cy * binormal.z; + + grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z ); + + } + } + + + // construct the mesh + + for ( i = 0; i < segments; i ++ ) { + + for ( j = 0; j < radialSegments; j ++ ) { + + ip = ( closed ) ? (i + 1) % segments : i + 1; + jp = (j + 1) % radialSegments; + + a = grid[ i ][ j ]; // *** NOT NECESSARILY PLANAR ! *** + b = grid[ ip ][ j ]; + c = grid[ ip ][ jp ]; + d = grid[ i ][ jp ]; + + uva = new THREE.Vector2( i / segments, j / radialSegments ); + uvb = new THREE.Vector2( ( i + 1 ) / segments, j / radialSegments ); + uvc = new THREE.Vector2( ( i + 1 ) / segments, ( j + 1 ) / radialSegments ); + uvd = new THREE.Vector2( i / segments, ( j + 1 ) / radialSegments ); + + this.faces.push( new THREE.Face3( a, b, d ) ); + this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] ); + + this.faces.push( new THREE.Face3( b, c, d ) ); + this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + } + + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype ); + + +// For computing of Frenet frames, exposing the tangents, normals and binormals the spline +THREE.TubeGeometry.FrenetFrames = function ( path, segments, closed ) { + + var tangent = new THREE.Vector3(), + normal = new THREE.Vector3(), + binormal = new THREE.Vector3(), + + tangents = [], + normals = [], + binormals = [], + + vec = new THREE.Vector3(), + mat = new THREE.Matrix4(), + + numpoints = segments + 1, + theta, + epsilon = 0.0001, + smallest, + + tx, ty, tz, + i, u, v; + + + // expose internals + this.tangents = tangents; + this.normals = normals; + this.binormals = binormals; + + // compute the tangent vectors for each segment on the path + + for ( i = 0; i < numpoints; i ++ ) { + + u = i / ( numpoints - 1 ); + + tangents[ i ] = path.getTangentAt( u ); + tangents[ i ].normalize(); + + } + + initialNormal3(); + + /* + function initialNormal1(lastBinormal) { + // fixed start binormal. Has dangers of 0 vectors + normals[ 0 ] = new THREE.Vector3(); + binormals[ 0 ] = new THREE.Vector3(); + if (lastBinormal===undefined) lastBinormal = new THREE.Vector3( 0, 0, 1 ); + normals[ 0 ].crossVectors( lastBinormal, tangents[ 0 ] ).normalize(); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize(); + } + + function initialNormal2() { + + // This uses the Frenet-Serret formula for deriving binormal + var t2 = path.getTangentAt( epsilon ); + + normals[ 0 ] = new THREE.Vector3().subVectors( t2, tangents[ 0 ] ).normalize(); + binormals[ 0 ] = new THREE.Vector3().crossVectors( tangents[ 0 ], normals[ 0 ] ); + + normals[ 0 ].crossVectors( binormals[ 0 ], tangents[ 0 ] ).normalize(); // last binormal x tangent + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize(); + + } + */ + + function initialNormal3() { + // select an initial normal vector perpenicular to the first tangent vector, + // and in the direction of the smallest tangent xyz component + + normals[ 0 ] = new THREE.Vector3(); + binormals[ 0 ] = new THREE.Vector3(); + smallest = Number.MAX_VALUE; + tx = Math.abs( tangents[ 0 ].x ); + ty = Math.abs( tangents[ 0 ].y ); + tz = Math.abs( tangents[ 0 ].z ); + + if ( tx <= smallest ) { + smallest = tx; + normal.set( 1, 0, 0 ); + } + + if ( ty <= smallest ) { + smallest = ty; + normal.set( 0, 1, 0 ); + } + + if ( tz <= smallest ) { + normal.set( 0, 0, 1 ); + } + + vec.crossVectors( tangents[ 0 ], normal ).normalize(); + + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + } + + + // compute the slowly-varying normal and binormal vectors for each segment on the path + + for ( i = 1; i < numpoints; i ++ ) { + + normals[ i ] = normals[ i-1 ].clone(); + + binormals[ i ] = binormals[ i-1 ].clone(); + + vec.crossVectors( tangents[ i-1 ], tangents[ i ] ); + + if ( vec.length() > epsilon ) { + + vec.normalize(); + + theta = Math.acos( THREE.Math.clamp( tangents[ i-1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors + + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + + } + + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + + if ( closed ) { + + theta = Math.acos( THREE.Math.clamp( normals[ 0 ].dot( normals[ numpoints-1 ] ), - 1, 1 ) ); + theta /= ( numpoints - 1 ); + + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ numpoints-1 ] ) ) > 0 ) { + + theta = - theta; + + } + + for ( i = 1; i < numpoints; i ++ ) { + + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + } +}; + +// File:src/extras/geometries/PolyhedronGeometry.js + +/** + * @author clockworkgeek / https://github.com/clockworkgeek + * @author timothypratley / https://github.com/timothypratley + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.PolyhedronGeometry = function ( vertices, indices, radius, detail ) { + + THREE.Geometry.call( this ); + + radius = radius || 1; + detail = detail || 0; + + var that = this; + + for ( var i = 0, l = vertices.length; i < l; i += 3 ) { + + prepare( new THREE.Vector3( vertices[ i ], vertices[ i + 1 ], vertices[ i + 2 ] ) ); + + } + + var midpoints = [], p = this.vertices; + + var faces = []; + + for ( var i = 0, j = 0, l = indices.length; i < l; i += 3, j ++ ) { + + var v1 = p[ indices[ i ] ]; + var v2 = p[ indices[ i + 1 ] ]; + var v3 = p[ indices[ i + 2 ] ]; + + faces[ j ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] ); + + } + + var centroid = new THREE.Vector3(); + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + subdivide( faces[ i ], detail ); + + } + + + // Handle case when face straddles the seam + + for ( var i = 0, l = this.faceVertexUvs[ 0 ].length; i < l; i ++ ) { + + var uvs = this.faceVertexUvs[ 0 ][ i ]; + + var x0 = uvs[ 0 ].x; + var x1 = uvs[ 1 ].x; + var x2 = uvs[ 2 ].x; + + var max = Math.max( x0, Math.max( x1, x2 ) ); + var min = Math.min( x0, Math.min( x1, x2 ) ); + + if ( max > 0.9 && min < 0.1 ) { // 0.9 is somewhat arbitrary + + if ( x0 < 0.2 ) uvs[ 0 ].x += 1; + if ( x1 < 0.2 ) uvs[ 1 ].x += 1; + if ( x2 < 0.2 ) uvs[ 2 ].x += 1; + + } + + } + + + // Apply radius + + for ( var i = 0, l = this.vertices.length; i < l; i ++ ) { + + this.vertices[ i ].multiplyScalar( radius ); + + } + + + // Merge vertices + + this.mergeVertices(); + + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + + + // Project vector onto sphere's surface + + function prepare( vector ) { + + var vertex = vector.normalize().clone(); + vertex.index = that.vertices.push( vertex ) - 1; + + // Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle. + + var u = azimuth( vector ) / 2 / Math.PI + 0.5; + var v = inclination( vector ) / Math.PI + 0.5; + vertex.uv = new THREE.Vector2( u, 1 - v ); + + return vertex; + + } + + + // Approximate a curved face with recursively sub-divided triangles. + + function make( v1, v2, v3 ) { + + var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] ); + that.faces.push( face ); + + centroid.copy( v1 ).add( v2 ).add( v3 ).divideScalar( 3 ); + + var azi = azimuth( centroid ); + + that.faceVertexUvs[ 0 ].push( [ + correctUV( v1.uv, v1, azi ), + correctUV( v2.uv, v2, azi ), + correctUV( v3.uv, v3, azi ) + ] ); + + } + + + // Analytically subdivide a face to the required detail level. + + function subdivide( face, detail ) { + + var cols = Math.pow(2, detail); + var cells = Math.pow(4, detail); + var a = prepare( that.vertices[ face.a ] ); + var b = prepare( that.vertices[ face.b ] ); + var c = prepare( that.vertices[ face.c ] ); + var v = []; + + // Construct all of the vertices for this subdivision. + + for ( var i = 0 ; i <= cols; i ++ ) { + + v[ i ] = []; + + var aj = prepare( a.clone().lerp( c, i / cols ) ); + var bj = prepare( b.clone().lerp( c, i / cols ) ); + var rows = cols - i; + + for ( var j = 0; j <= rows; j ++) { + + if ( j == 0 && i == cols ) { + + v[ i ][ j ] = aj; + + } else { + + v[ i ][ j ] = prepare( aj.clone().lerp( bj, j / rows ) ); + + } + + } + + } + + // Construct all of the faces. + + for ( var i = 0; i < cols ; i ++ ) { + + for ( var j = 0; j < 2 * (cols - i) - 1; j ++ ) { + + var k = Math.floor( j / 2 ); + + if ( j % 2 == 0 ) { + + make( + v[ i ][ k + 1], + v[ i + 1 ][ k ], + v[ i ][ k ] + ); + + } else { + + make( + v[ i ][ k + 1 ], + v[ i + 1][ k + 1], + v[ i + 1 ][ k ] + ); + + } + + } + + } + + } + + + // Angle around the Y axis, counter-clockwise when looking from above. + + function azimuth( vector ) { + + return Math.atan2( vector.z, - vector.x ); + + } + + + // Angle above the XZ plane. + + function inclination( vector ) { + + return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + + } + + + // Texture fixing helper. Spheres have some odd behaviours. + + function correctUV( uv, vector, azimuth ) { + + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) uv = new THREE.Vector2( uv.x - 1, uv.y ); + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) uv = new THREE.Vector2( azimuth / 2 / Math.PI + 0.5, uv.y ); + return uv.clone(); + + } + + +}; + +THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/IcosahedronGeometry.js + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.IcosahedronGeometry = function ( radius, detail ) { + + this.parameters = { + radius: radius, + detail: detail + }; + + var t = ( 1 + Math.sqrt( 5 ) ) / 2; + + var vertices = [ + - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, + 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, + t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 + ]; + + var indices = [ + 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, + 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, + 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, + 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 + ]; + + THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail ); + +}; + +THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/OctahedronGeometry.js + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.OctahedronGeometry = function ( radius, detail ) { + + this.parameters = { + radius: radius, + detail: detail + }; + + var vertices = [ + 1, 0, 0, - 1, 0, 0, 0, 1, 0, 0,- 1, 0, 0, 0, 1, 0, 0,- 1 + ]; + + var indices = [ + 0, 2, 4, 0, 4, 3, 0, 3, 5, 0, 5, 2, 1, 2, 5, 1, 5, 3, 1, 3, 4, 1, 4, 2 + ]; + + THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail ); +}; + +THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/TetrahedronGeometry.js + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.TetrahedronGeometry = function ( radius, detail ) { + + var vertices = [ + 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 + ]; + + var indices = [ + 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 + ]; + + THREE.PolyhedronGeometry.call( this, vertices, indices, radius, detail ); + +}; + +THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/geometries/ParametricGeometry.js + +/** + * @author zz85 / https://github.com/zz85 + * Parametric Surfaces Geometry + * based on the brilliant article by @prideout http://prideout.net/blog/?p=44 + * + * new THREE.ParametricGeometry( parametricFunction, uSegments, ySegements ); + * + */ + +THREE.ParametricGeometry = function ( func, slices, stacks ) { + + THREE.Geometry.call( this ); + + var verts = this.vertices; + var faces = this.faces; + var uvs = this.faceVertexUvs[ 0 ]; + + var i, il, j, p; + var u, v; + + var stackCount = stacks + 1; + var sliceCount = slices + 1; + + for ( i = 0; i <= stacks; i ++ ) { + + v = i / stacks; + + for ( j = 0; j <= slices; j ++ ) { + + u = j / slices; + + p = func( u, v ); + verts.push( p ); + + } + } + + var a, b, c, d; + var uva, uvb, uvc, uvd; + + for ( i = 0; i < stacks; i ++ ) { + + for ( j = 0; j < slices; j ++ ) { + + a = i * sliceCount + j; + b = i * sliceCount + j + 1; + c = (i + 1) * sliceCount + j + 1; + d = (i + 1) * sliceCount + j; + + uva = new THREE.Vector2( j / slices, i / stacks ); + uvb = new THREE.Vector2( ( j + 1 ) / slices, i / stacks ); + uvc = new THREE.Vector2( ( j + 1 ) / slices, ( i + 1 ) / stacks ); + uvd = new THREE.Vector2( j / slices, ( i + 1 ) / stacks ); + + faces.push( new THREE.Face3( a, b, d ) ); + uvs.push( [ uva, uvb, uvd ] ); + + faces.push( new THREE.Face3( b, c, d ) ); + uvs.push( [ uvb.clone(), uvc, uvd.clone() ] ); + + } + + } + + // console.log(this); + + // magic bullet + // var diff = this.mergeVertices(); + // console.log('removed ', diff, ' vertices by merging'); + + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// File:src/extras/helpers/AxisHelper.js + +/** + * @author sroucheray / http://sroucheray.org/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.AxisHelper = function ( size ) { + + size = size || 1; + + var vertices = new Float32Array( [ + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size + ] ); + + var colors = new Float32Array( [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 + ] ); + + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) ); + + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + +}; + +THREE.AxisHelper.prototype = Object.create( THREE.Line.prototype ); + +// File:src/extras/helpers/ArrowHelper.js + +/** + * @author WestLangley / http://github.com/WestLangley + * @author zz85 / http://github.com/zz85 + * @author bhouston / http://exocortex.com + * + * Creates an arrow for visualizing directions + * + * Parameters: + * dir - Vector3 + * origin - Vector3 + * length - Number + * color - color in hex value + * headLength - Number + * headWidth - Number + */ + +THREE.ArrowHelper = ( function () { + + var lineGeometry = new THREE.Geometry(); + lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) ); + + var coneGeometry = new THREE.CylinderGeometry( 0, 0.5, 1, 5, 1 ); + coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 0 ) ); + + return function ( dir, origin, length, color, headLength, headWidth ) { + + // dir is assumed to be normalized + + THREE.Object3D.call( this ); + + if ( color === undefined ) color = 0xffff00; + if ( length === undefined ) length = 1; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.position.copy( origin ); + + this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: color } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + + this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: color } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); + + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); + + } + +}() ); + +THREE.ArrowHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.ArrowHelper.prototype.setDirection = ( function () { + + var axis = new THREE.Vector3(); + var radians; + + return function ( dir ) { + + // dir is assumed to be normalized + + if ( dir.y > 0.99999 ) { + + this.quaternion.set( 0, 0, 0, 1 ); + + } else if ( dir.y < - 0.99999 ) { + + this.quaternion.set( 1, 0, 0, 0 ); + + } else { + + axis.set( dir.z, 0, - dir.x ).normalize(); + + radians = Math.acos( dir.y ); + + this.quaternion.setFromAxisAngle( axis, radians ); + + } + + }; + +}() ); + +THREE.ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) { + + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.line.scale.set( 1, length, 1 ); + this.line.updateMatrix(); + + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); + +}; + +THREE.ArrowHelper.prototype.setColor = function ( color ) { + + this.line.material.color.set( color ); + this.cone.material.color.set( color ); + +}; + +// File:src/extras/helpers/BoxHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BoxHelper = function ( object ) { + + var geometry = new THREE.BufferGeometry(); + geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( 72 ), 3 ) ); + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: 0xffff00 } ), THREE.LinePieces ); + + if ( object !== undefined ) { + + this.update( object ); + + } + +}; + +THREE.BoxHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.BoxHelper.prototype.update = function ( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + var min = geometry.boundingBox.min; + var max = geometry.boundingBox.max; + + /* + 5____4 + 1/___0/| + | 6__|_7 + 2/___3/ + + 0: max.x, max.y, max.z + 1: min.x, max.y, max.z + 2: min.x, min.y, max.z + 3: max.x, min.y, max.z + 4: max.x, max.y, min.z + 5: min.x, max.y, min.z + 6: min.x, min.y, min.z + 7: max.x, min.y, min.z + */ + + var vertices = this.geometry.attributes.position.array; + + vertices[ 0 ] = max.x; vertices[ 1 ] = max.y; vertices[ 2 ] = max.z; + vertices[ 3 ] = min.x; vertices[ 4 ] = max.y; vertices[ 5 ] = max.z; + + vertices[ 6 ] = min.x; vertices[ 7 ] = max.y; vertices[ 8 ] = max.z; + vertices[ 9 ] = min.x; vertices[ 10 ] = min.y; vertices[ 11 ] = max.z; + + vertices[ 12 ] = min.x; vertices[ 13 ] = min.y; vertices[ 14 ] = max.z; + vertices[ 15 ] = max.x; vertices[ 16 ] = min.y; vertices[ 17 ] = max.z; + + vertices[ 18 ] = max.x; vertices[ 19 ] = min.y; vertices[ 20 ] = max.z; + vertices[ 21 ] = max.x; vertices[ 22 ] = max.y; vertices[ 23 ] = max.z; + + // + + vertices[ 24 ] = max.x; vertices[ 25 ] = max.y; vertices[ 26 ] = min.z; + vertices[ 27 ] = min.x; vertices[ 28 ] = max.y; vertices[ 29 ] = min.z; + + vertices[ 30 ] = min.x; vertices[ 31 ] = max.y; vertices[ 32 ] = min.z; + vertices[ 33 ] = min.x; vertices[ 34 ] = min.y; vertices[ 35 ] = min.z; + + vertices[ 36 ] = min.x; vertices[ 37 ] = min.y; vertices[ 38 ] = min.z; + vertices[ 39 ] = max.x; vertices[ 40 ] = min.y; vertices[ 41 ] = min.z; + + vertices[ 42 ] = max.x; vertices[ 43 ] = min.y; vertices[ 44 ] = min.z; + vertices[ 45 ] = max.x; vertices[ 46 ] = max.y; vertices[ 47 ] = min.z; + + // + + vertices[ 48 ] = max.x; vertices[ 49 ] = max.y; vertices[ 50 ] = max.z; + vertices[ 51 ] = max.x; vertices[ 52 ] = max.y; vertices[ 53 ] = min.z; + + vertices[ 54 ] = min.x; vertices[ 55 ] = max.y; vertices[ 56 ] = max.z; + vertices[ 57 ] = min.x; vertices[ 58 ] = max.y; vertices[ 59 ] = min.z; + + vertices[ 60 ] = min.x; vertices[ 61 ] = min.y; vertices[ 62 ] = max.z; + vertices[ 63 ] = min.x; vertices[ 64 ] = min.y; vertices[ 65 ] = min.z; + + vertices[ 66 ] = max.x; vertices[ 67 ] = min.y; vertices[ 68 ] = max.z; + vertices[ 69 ] = max.x; vertices[ 70 ] = min.y; vertices[ 71 ] = min.z; + + this.geometry.attributes.position.needsUpdate = true; + + this.geometry.computeBoundingSphere(); + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +// File:src/extras/helpers/BoundingBoxHelper.js + +/** + * @author WestLangley / http://github.com/WestLangley + */ + +// a helper to show the world-axis-aligned bounding box for an object + +THREE.BoundingBoxHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0x888888; + + this.object = object; + + this.box = new THREE.Box3(); + + THREE.Mesh.call( this, new THREE.BoxGeometry( 1, 1, 1 ), new THREE.MeshBasicMaterial( { color: color, wireframe: true } ) ); + +}; + +THREE.BoundingBoxHelper.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.BoundingBoxHelper.prototype.update = function () { + + this.box.setFromObject( this.object ); + + this.box.size( this.scale ); + + this.box.center( this.position ); + +}; + +// File:src/extras/helpers/CameraHelper.js + +/** + * @author alteredq / http://alteredqualia.com/ + * + * - shows frustum, line of sight and up of the camera + * - suitable for fast updates + * - based on frustum visualization in lightgl.js shadowmap example + * http://evanw.github.com/lightgl.js/tests/shadowmap.html + */ + +THREE.CameraHelper = function ( camera ) { + + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors } ); + + var pointMap = {}; + + // colors + + var hexFrustum = 0xffaa00; + var hexCone = 0xff0000; + var hexUp = 0x00aaff; + var hexTarget = 0xffffff; + var hexCross = 0x333333; + + // near + + addLine( "n1", "n2", hexFrustum ); + addLine( "n2", "n4", hexFrustum ); + addLine( "n4", "n3", hexFrustum ); + addLine( "n3", "n1", hexFrustum ); + + // far + + addLine( "f1", "f2", hexFrustum ); + addLine( "f2", "f4", hexFrustum ); + addLine( "f4", "f3", hexFrustum ); + addLine( "f3", "f1", hexFrustum ); + + // sides + + addLine( "n1", "f1", hexFrustum ); + addLine( "n2", "f2", hexFrustum ); + addLine( "n3", "f3", hexFrustum ); + addLine( "n4", "f4", hexFrustum ); + + // cone + + addLine( "p", "n1", hexCone ); + addLine( "p", "n2", hexCone ); + addLine( "p", "n3", hexCone ); + addLine( "p", "n4", hexCone ); + + // up + + addLine( "u1", "u2", hexUp ); + addLine( "u2", "u3", hexUp ); + addLine( "u3", "u1", hexUp ); + + // target + + addLine( "c", "t", hexTarget ); + addLine( "p", "c", hexCross ); + + // cross + + addLine( "cn1", "cn2", hexCross ); + addLine( "cn3", "cn4", hexCross ); + + addLine( "cf1", "cf2", hexCross ); + addLine( "cf3", "cf4", hexCross ); + + function addLine( a, b, hex ) { + + addPoint( a, hex ); + addPoint( b, hex ); + + } + + function addPoint( id, hex ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.colors.push( new THREE.Color( hex ) ); + + if ( pointMap[ id ] === undefined ) { + + pointMap[ id ] = []; + + } + + pointMap[ id ].push( geometry.vertices.length - 1 ); + + } + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + + this.camera = camera; + this.matrixWorld = camera.matrixWorld; + this.matrixAutoUpdate = false; + + this.pointMap = pointMap; + + this.update(); + +}; + +THREE.CameraHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.CameraHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + var camera = new THREE.Camera(); + var projector = new THREE.Projector(); + + return function () { + + var scope = this; + + var w = 1, h = 1; + + // we need just camera projection matrix + // world matrix must be identity + + camera.projectionMatrix.copy( this.camera.projectionMatrix ); + + // center / target + + setPoint( "c", 0, 0, - 1 ); + setPoint( "t", 0, 0, 1 ); + + // near + + setPoint( "n1", - w, - h, - 1 ); + setPoint( "n2", w, - h, - 1 ); + setPoint( "n3", - w, h, - 1 ); + setPoint( "n4", w, h, - 1 ); + + // far + + setPoint( "f1", - w, - h, 1 ); + setPoint( "f2", w, - h, 1 ); + setPoint( "f3", - w, h, 1 ); + setPoint( "f4", w, h, 1 ); + + // up + + setPoint( "u1", w * 0.7, h * 1.1, - 1 ); + setPoint( "u2", - w * 0.7, h * 1.1, - 1 ); + setPoint( "u3", 0, h * 2, - 1 ); + + // cross + + setPoint( "cf1", - w, 0, 1 ); + setPoint( "cf2", w, 0, 1 ); + setPoint( "cf3", 0, - h, 1 ); + setPoint( "cf4", 0, h, 1 ); + + setPoint( "cn1", - w, 0, - 1 ); + setPoint( "cn2", w, 0, - 1 ); + setPoint( "cn3", 0, - h, - 1 ); + setPoint( "cn4", 0, h, - 1 ); + + function setPoint( point, x, y, z ) { + + vector.set( x, y, z ); + projector.unprojectVector( vector, camera ); + + var points = scope.pointMap[ point ]; + + if ( points !== undefined ) { + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + scope.geometry.vertices[ points[ i ] ].copy( vector ); + + } + + } + + } + + this.geometry.verticesNeedUpdate = true; + + }; + +}(); + +// File:src/extras/helpers/DirectionalLightHelper.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.DirectionalLightHelper = function ( light, size ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + size = size || 1; + + var geometry = new THREE.Geometry(); + geometry.vertices.push( + new THREE.Vector3( - size, size, 0 ), + new THREE.Vector3( size, size, 0 ), + new THREE.Vector3( size, - size, 0 ), + new THREE.Vector3( - size, - size, 0 ), + new THREE.Vector3( - size, size, 0 ) + ); + + var material = new THREE.LineBasicMaterial( { fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.lightPlane = new THREE.Line( geometry, material ); + this.add( this.lightPlane ); + + geometry = new THREE.Geometry(); + geometry.vertices.push( + new THREE.Vector3(), + new THREE.Vector3() + ); + + material = new THREE.LineBasicMaterial( { fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.targetLine = new THREE.Line( geometry, material ); + this.add( this.targetLine ); + + this.update(); + +}; + +THREE.DirectionalLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.DirectionalLightHelper.prototype.dispose = function () { + + this.lightPlane.geometry.dispose(); + this.lightPlane.material.dispose(); + this.targetLine.geometry.dispose(); + this.targetLine.material.dispose(); +}; + +THREE.DirectionalLightHelper.prototype.update = function () { + + var v1 = new THREE.Vector3(); + var v2 = new THREE.Vector3(); + var v3 = new THREE.Vector3(); + + return function () { + + v1.setFromMatrixPosition( this.light.matrixWorld ); + v2.setFromMatrixPosition( this.light.target.matrixWorld ); + v3.subVectors( v2, v1 ); + + this.lightPlane.lookAt( v3 ); + this.lightPlane.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.targetLine.geometry.vertices[ 1 ].copy( v3 ); + this.targetLine.geometry.verticesNeedUpdate = true; + this.targetLine.material.color.copy( this.lightPlane.material.color ); + + } + +}(); + + +// File:src/extras/helpers/EdgesHelper.js + +/** + * @author WestLangley / http://github.com/WestLangley + */ + +THREE.EdgesHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0xffffff; + + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { return a - b }; + + var keys = [ 'a', 'b', 'c' ]; + var geometry = new THREE.BufferGeometry(); + + var geometry2 = object.geometry.clone(); + + geometry2.mergeVertices(); + geometry2.computeFaceNormals(); + + var vertices = geometry2.vertices; + var faces = geometry2.faces; + var numEdges = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + hash[ key ] = { vert1: edge[ 0 ], vert2: edge[ 1 ], face1: i, face2: undefined }; + numEdges ++; + + } else { + + hash[ key ].face2 = i; + + } + + } + + } + + geometry.addAttribute( 'position', new THREE.Float32Attribute( numEdges * 2 * 3, 3 ) ); + + var coords = geometry.attributes.position.array; + + var index = 0; + + for ( var key in hash ) { + + var h = hash[ key ]; + + if ( h.face2 === undefined || faces[ h.face1 ].normal.dot( faces[ h.face2 ].normal ) < 0.9999 ) { // hardwired const OK + + var vertex = vertices[ h.vert1 ]; + coords[ index ++ ] = vertex.x; + coords[ index ++ ] = vertex.y; + coords[ index ++ ] = vertex.z; + + vertex = vertices[ h.vert2 ]; + coords[ index ++ ] = vertex.x; + coords[ index ++ ] = vertex.y; + coords[ index ++ ] = vertex.z; + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +THREE.EdgesHelper.prototype = Object.create( THREE.Line.prototype ); + +// File:src/extras/helpers/FaceNormalsHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xffff00; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var faces = this.object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + geometry.vertices.push( new THREE.Vector3(), new THREE.Vector3() ); + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.normalMatrix = new THREE.Matrix3(); + + this.update(); + +}; + +THREE.FaceNormalsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.FaceNormalsHelper.prototype.update = function () { + + var vertices = this.geometry.vertices; + + var object = this.object; + var objectVertices = object.geometry.vertices; + var objectFaces = object.geometry.faces; + var objectWorldMatrix = object.matrixWorld; + + object.updateMatrixWorld( true ); + + this.normalMatrix.getNormalMatrix( objectWorldMatrix ); + + for ( var i = 0, i2 = 0, l = objectFaces.length; i < l; i ++, i2 += 2 ) { + + var face = objectFaces[ i ]; + + vertices[ i2 ].copy( objectVertices[ face.a ] ) + .add( objectVertices[ face.b ] ) + .add( objectVertices[ face.c ] ) + .divideScalar( 3 ) + .applyMatrix4( objectWorldMatrix ); + + vertices[ i2 + 1 ].copy( face.normal ) + .applyMatrix3( this.normalMatrix ) + .normalize() + .multiplyScalar( this.size ) + .add( vertices[ i2 ] ); + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + +}; + + +// File:src/extras/helpers/GridHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.GridHelper = function ( size, step ) { + + var geometry = new THREE.Geometry(); + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); + + this.color1 = new THREE.Color( 0x444444 ); + this.color2 = new THREE.Color( 0x888888 ); + + for ( var i = - size; i <= size; i += step ) { + + geometry.vertices.push( + new THREE.Vector3( - size, 0, i ), new THREE.Vector3( size, 0, i ), + new THREE.Vector3( i, 0, - size ), new THREE.Vector3( i, 0, size ) + ); + + var color = i === 0 ? this.color1 : this.color2; + + geometry.colors.push( color, color, color, color ); + + } + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + +}; + +THREE.GridHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.GridHelper.prototype.setColors = function( colorCenterLine, colorGrid ) { + + this.color1.set( colorCenterLine ); + this.color2.set( colorGrid ); + + this.geometry.colorsNeedUpdate = true; + +} + +// File:src/extras/helpers/HemisphereLightHelper.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.HemisphereLightHelper = function ( light, sphereSize, arrowLength, domeSize ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.colors = [ new THREE.Color(), new THREE.Color() ]; + + var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 ); + geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) ); + + for ( var i = 0, il = 8; i < il; i ++ ) { + + geometry.faces[ i ].color = this.colors[ i < 4 ? 0 : 1 ]; + + } + + var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: true } ); + + this.lightSphere = new THREE.Mesh( geometry, material ); + this.add( this.lightSphere ); + + this.update(); + +}; + +THREE.HemisphereLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.HemisphereLightHelper.prototype.dispose = function () { + this.lightSphere.geometry.dispose(); + this.lightSphere.material.dispose(); +}; + +THREE.HemisphereLightHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + + return function () { + + this.colors[ 0 ].copy( this.light.color ).multiplyScalar( this.light.intensity ); + this.colors[ 1 ].copy( this.light.groundColor ).multiplyScalar( this.light.intensity ); + + this.lightSphere.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + this.lightSphere.geometry.colorsNeedUpdate = true; + + } + +}(); + + +// File:src/extras/helpers/PointLightHelper.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.PointLightHelper = function ( light, sphereSize ) { + + this.light = light; + this.light.updateMatrixWorld(); + + var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 ); + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + THREE.Mesh.call( this, geometry, material ); + + this.matrixWorld = this.light.matrixWorld; + this.matrixAutoUpdate = false; + + /* + var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); + var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + + var d = light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.scale.set( d, d, d ); + + } + + this.add( this.lightDistance ); + */ + +}; + +THREE.PointLightHelper.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.PointLightHelper.prototype.dispose = function () { + + this.geometry.dispose(); + this.material.dispose(); +}; + +THREE.PointLightHelper.prototype.update = function () { + + this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + /* + var d = this.light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); + + } + */ + +}; + + +// File:src/extras/helpers/SkeletonHelper.js + +/** + * @author Sean Griffin / http://twitter.com/sgrif + * @author Michael Guerrero / http://realitymeltdown.com + * @author mrdoob / http://mrdoob.com/ + * @author ikerr / http://verold.com + */ + +THREE.SkeletonHelper = function ( object ) { + + this.bones = this.getBoneList( object ); + + var geometry = new THREE.Geometry(); + + for ( var i = 0; i < this.bones.length; i ++ ) { + + var bone = this.bones[ i ]; + + if ( bone.parent instanceof THREE.Bone ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + geometry.colors.push( new THREE.Color( 0, 0, 1 ) ); + geometry.colors.push( new THREE.Color( 0, 1, 0 ) ); + + } + + } + + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors, depthTest: false, depthWrite: false, transparent: true } ); + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + + this.root = object; + + this.matrixWorld = object.matrixWorld; + this.matrixAutoUpdate = false; + + this.update(); + +}; + + +THREE.SkeletonHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.SkeletonHelper.prototype.getBoneList = function( object ) { + + var boneList = []; + + if ( object instanceof THREE.Bone ) { + + boneList.push( object ); + + } + + for ( var i = 0; i < object.children.length; i ++ ) { + + boneList.push.apply( boneList, this.getBoneList( object.children[ i ] ) ); + + } + + return boneList; + +}; + +THREE.SkeletonHelper.prototype.update = function () { + + var geometry = this.geometry; + + var matrixWorldInv = new THREE.Matrix4().getInverse( this.root.matrixWorld ); + + var boneMatrix = new THREE.Matrix4(); + + var j = 0; + + for ( var i = 0; i < this.bones.length; i ++ ) { + + var bone = this.bones[ i ]; + + if ( bone.parent instanceof THREE.Bone ) { + + boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld ); + geometry.vertices[ j ].setFromMatrixPosition( boneMatrix ); + + boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld ); + geometry.vertices[ j + 1 ].setFromMatrixPosition( boneMatrix ); + + j += 2; + + } + + } + + geometry.verticesNeedUpdate = true; + + geometry.computeBoundingSphere(); + +}; + +// File:src/extras/helpers/SpotLightHelper.js + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.SpotLightHelper = function ( light ) { + + THREE.Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrixWorld = light.matrixWorld; + this.matrixAutoUpdate = false; + + var geometry = new THREE.CylinderGeometry( 0, 1, 1, 8, 1, true ); + + geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 0 ) ); + geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) ); + + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + + this.cone = new THREE.Mesh( geometry, material ); + this.add( this.cone ); + + this.update(); + +}; + +THREE.SpotLightHelper.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.SpotLightHelper.prototype.dispose = function () { + this.cone.geometry.dispose(); + this.cone.material.dispose(); +}; + +THREE.SpotLightHelper.prototype.update = function () { + + var vector = new THREE.Vector3(); + var vector2 = new THREE.Vector3(); + + return function () { + + var coneLength = this.light.distance ? this.light.distance : 10000; + var coneWidth = coneLength * Math.tan( this.light.angle ); + + this.cone.scale.set( coneWidth, coneWidth, coneLength ); + + vector.setFromMatrixPosition( this.light.matrixWorld ); + vector2.setFromMatrixPosition( this.light.target.matrixWorld ); + + this.cone.lookAt( vector2.sub( vector ) ); + + this.cone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + }; + +}(); + +// File:src/extras/helpers/VertexNormalsHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.VertexNormalsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xff0000; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var vertices = object.geometry.vertices; + + var faces = object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + geometry.vertices.push( new THREE.Vector3(), new THREE.Vector3() ); + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.normalMatrix = new THREE.Matrix3(); + + this.update(); + +}; + +THREE.VertexNormalsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.VertexNormalsHelper.prototype.update = ( function ( object ) { + + var v1 = new THREE.Vector3(); + + return function( object ) { + + var keys = [ 'a', 'b', 'c', 'd' ]; + + this.object.updateMatrixWorld( true ); + + this.normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var vertices = this.geometry.vertices; + + var verts = this.object.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + var vertexId = face[ keys[ j ] ]; + var vertex = verts[ vertexId ]; + + var normal = face.vertexNormals[ j ]; + + vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix ); + + v1.copy( normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size ); + + v1.add( vertices[ idx ] ); + idx = idx + 1; + + vertices[ idx ].copy( v1 ); + idx = idx + 1; + + } + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + +// File:src/extras/helpers/VertexTangentsHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.VertexTangentsHelper = function ( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0x0000ff; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + var geometry = new THREE.Geometry(); + + var vertices = object.geometry.vertices; + + var faces = object.geometry.faces; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) { + + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( new THREE.Vector3() ); + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + + this.update(); + +}; + +THREE.VertexTangentsHelper.prototype = Object.create( THREE.Line.prototype ); + +THREE.VertexTangentsHelper.prototype.update = ( function ( object ) { + + var v1 = new THREE.Vector3(); + + return function( object ) { + + var keys = [ 'a', 'b', 'c', 'd' ]; + + this.object.updateMatrixWorld( true ); + + var vertices = this.geometry.vertices; + + var verts = this.object.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) { + + var vertexId = face[ keys[ j ] ]; + var vertex = verts[ vertexId ]; + + var tangent = face.vertexTangents[ j ]; + + vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix ); + + v1.copy( tangent ).transformDirection( worldMatrix ).multiplyScalar( this.size ); + + v1.add( vertices[ idx ] ); + idx = idx + 1; + + vertices[ idx ].copy( v1 ); + idx = idx + 1; + + } + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + +// File:src/extras/helpers/WireframeHelper.js + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.WireframeHelper = function ( object, hex ) { + + var color = ( hex !== undefined ) ? hex : 0xffffff; + + var edge = [ 0, 0 ], hash = {}; + var sortFunction = function ( a, b ) { return a - b }; + + var keys = [ 'a', 'b', 'c' ]; + var geometry = new THREE.BufferGeometry(); + + if ( object.geometry instanceof THREE.Geometry ) { + + var vertices = object.geometry.vertices; + var faces = object.geometry.faces; + var numEdges = 0; + + // allocate maximal size + var edges = new Uint32Array( 6 * faces.length ); + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = face[ keys[ j ] ]; + edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + edges[ 2 * numEdges ] = edge[ 0 ]; + edges[ 2 * numEdges + 1 ] = edge[ 1 ]; + hash[ key ] = true; + numEdges ++; + + } + + } + + } + + var coords = new Float32Array( numEdges * 2 * 3 ); + + for ( var i = 0, l = numEdges; i < l; i ++ ) { + + for ( var j = 0; j < 2; j ++ ) { + + var vertex = vertices[ edges [ 2 * i + j] ]; + + var index = 6 * i + 3 * j; + coords[ index + 0 ] = vertex.x; + coords[ index + 1 ] = vertex.y; + coords[ index + 2 ] = vertex.z; + + } + + } + + geometry.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); + + } else if ( object.geometry instanceof THREE.BufferGeometry ) { + + if ( object.geometry.attributes.index !== undefined ) { // Indexed BufferGeometry + + var vertices = object.geometry.attributes.position.array; + var indices = object.geometry.attributes.index.array; + var offsets = object.geometry.offsets; + var numEdges = 0; + + // allocate maximal size + var edges = new Uint32Array( 2 * indices.length ); + + for ( var o = 0, ol = offsets.length; o < ol; ++ o ) { + + var start = offsets[ o ].start; + var count = offsets[ o ].count; + var index = offsets[ o ].index; + + for ( var i = start, il = start + count; i < il; i += 3 ) { + + for ( var j = 0; j < 3; j ++ ) { + + edge[ 0 ] = index + indices[ i + j ]; + edge[ 1 ] = index + indices[ i + ( j + 1 ) % 3 ]; + edge.sort( sortFunction ); + + var key = edge.toString(); + + if ( hash[ key ] === undefined ) { + + edges[ 2 * numEdges ] = edge[ 0 ]; + edges[ 2 * numEdges + 1 ] = edge[ 1 ]; + hash[ key ] = true; + numEdges ++; + + } + + } + + } + + } + + var coords = new Float32Array( numEdges * 2 * 3 ); + + for ( var i = 0, l = numEdges; i < l; i ++ ) { + + for ( var j = 0; j < 2; j ++ ) { + + var index = 6 * i + 3 * j; + var index2 = 3 * edges[ 2 * i + j]; + coords[ index + 0 ] = vertices[ index2 ]; + coords[ index + 1 ] = vertices[ index2 + 1 ]; + coords[ index + 2 ] = vertices[ index2 + 2 ]; + + } + + } + + geometry.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); + + } else { // non-indexed BufferGeometry + + var vertices = object.geometry.attributes.position.array; + var numEdges = vertices.length / 3; + var numTris = numEdges / 3; + + var coords = new Float32Array( numEdges * 2 * 3 ); + + for ( var i = 0, l = numTris; i < l; i ++ ) { + + for ( var j = 0; j < 3; j ++ ) { + + var index = 18 * i + 6 * j; + + var index1 = 9 * i + 3 * j; + coords[ index + 0 ] = vertices[ index1 ]; + coords[ index + 1 ] = vertices[ index1 + 1 ]; + coords[ index + 2 ] = vertices[ index1 + 2 ]; + + var index2 = 9 * i + 3 * ( ( j + 1 ) % 3 ); + coords[ index + 3 ] = vertices[ index2 ]; + coords[ index + 4 ] = vertices[ index2 + 1 ]; + coords[ index + 5 ] = vertices[ index2 + 2 ]; + + } + + } + + geometry.addAttribute( 'position', new THREE.BufferAttribute( coords, 3 ) ); + + } + + } + + THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces ); + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +THREE.WireframeHelper.prototype = Object.create( THREE.Line.prototype ); + +// File:src/extras/objects/ImmediateRenderObject.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ImmediateRenderObject = function () { + + THREE.Object3D.call( this ); + + this.render = function ( renderCallback ) {}; + +}; + +THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype ); + +// File:src/extras/objects/LensFlare.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.LensFlare = function ( texture, size, distance, blending, color ) { + + THREE.Object3D.call( this ); + + this.lensFlares = []; + + this.positionScreen = new THREE.Vector3(); + this.customUpdateCallback = undefined; + + if( texture !== undefined ) { + + this.add( texture, size, distance, blending, color ); + + } + +}; + +THREE.LensFlare.prototype = Object.create( THREE.Object3D.prototype ); + + +/* + * Add: adds another flare + */ + +THREE.LensFlare.prototype.add = function ( texture, size, distance, blending, color, opacity ) { + + if( size === undefined ) size = - 1; + if( distance === undefined ) distance = 0; + if( opacity === undefined ) opacity = 1; + if( color === undefined ) color = new THREE.Color( 0xffffff ); + if( blending === undefined ) blending = THREE.NormalBlending; + + distance = Math.min( distance, Math.max( 0, distance ) ); + + this.lensFlares.push( { texture: texture, // THREE.Texture + size: size, // size in pixels (-1 = use texture.width) + distance: distance, // distance (0-1) from light source (0=at light source) + x: 0, y: 0, z: 0, // screen position (-1 => 1) z = 0 is ontop z = 1 is back + scale: 1, // scale + rotation: 1, // rotation + opacity: opacity, // opacity + color: color, // color + blending: blending } ); // blending + +}; + + +/* + * Update lens flares update positions on all flares based on the screen position + * Set myLensFlare.customUpdateCallback to alter the flares in your project specific way. + */ + +THREE.LensFlare.prototype.updateLensFlares = function () { + + var f, fl = this.lensFlares.length; + var flare; + var vecX = - this.positionScreen.x * 2; + var vecY = - this.positionScreen.y * 2; + + for( f = 0; f < fl; f ++ ) { + + flare = this.lensFlares[ f ]; + + flare.x = this.positionScreen.x + vecX * flare.distance; + flare.y = this.positionScreen.y + vecY * flare.distance; + + flare.wantedRotation = flare.x * Math.PI * 0.25; + flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25; + + } + +}; + + + + + + + + + + + + + +// File:src/extras/objects/MorphBlendMesh.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.MorphBlendMesh = function( geometry, material ) { + + THREE.Mesh.call( this, geometry, material ); + + this.animationsMap = {}; + this.animationsList = []; + + // prepare default animation + // (all frames played together in 1 second) + + var numFrames = this.geometry.morphTargets.length; + + var name = "__default"; + + var startFrame = 0; + var endFrame = numFrames - 1; + + var fps = numFrames / 1; + + this.createAnimation( name, startFrame, endFrame, fps ); + this.setAnimationWeight( name, 1 ); + +}; + +THREE.MorphBlendMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) { + + var animation = { + + startFrame: start, + endFrame: end, + + length: end - start + 1, + + fps: fps, + duration: ( end - start ) / fps, + + lastFrame: 0, + currentFrame: 0, + + active: false, + + time: 0, + direction: 1, + weight: 1, + + directionBackwards: false, + mirroredLoop: false + + }; + + this.animationsMap[ name ] = animation; + this.animationsList.push( animation ); + +}; + +THREE.MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) { + + var pattern = /([a-z]+)_?(\d+)/; + + var firstAnimation, frameRanges = {}; + + var geometry = this.geometry; + + for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) { + + var morph = geometry.morphTargets[ i ]; + var chunks = morph.name.match( pattern ); + + if ( chunks && chunks.length > 1 ) { + + var name = chunks[ 1 ]; + var num = chunks[ 2 ]; + + if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: - Infinity }; + + var range = frameRanges[ name ]; + + if ( i < range.start ) range.start = i; + if ( i > range.end ) range.end = i; + + if ( ! firstAnimation ) firstAnimation = name; + + } + + } + + for ( var name in frameRanges ) { + + var range = frameRanges[ name ]; + this.createAnimation( name, range.start, range.end, fps ); + + } + + this.firstAnimation = firstAnimation; + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.direction = 1; + animation.directionBackwards = false; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.direction = - 1; + animation.directionBackwards = true; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.fps = fps; + animation.duration = ( animation.end - animation.start ) / animation.fps; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.duration = duration; + animation.fps = ( animation.end - animation.start ) / animation.duration; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.weight = weight; + + } + +}; + +THREE.MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.time = time; + + } + +}; + +THREE.MorphBlendMesh.prototype.getAnimationTime = function ( name ) { + + var time = 0; + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + time = animation.time; + + } + + return time; + +}; + +THREE.MorphBlendMesh.prototype.getAnimationDuration = function ( name ) { + + var duration = - 1; + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + duration = animation.duration; + + } + + return duration; + +}; + +THREE.MorphBlendMesh.prototype.playAnimation = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.time = 0; + animation.active = true; + + } else { + + console.warn( "animation[" + name + "] undefined" ); + + } + +}; + +THREE.MorphBlendMesh.prototype.stopAnimation = function ( name ) { + + var animation = this.animationsMap[ name ]; + + if ( animation ) { + + animation.active = false; + + } + +}; + +THREE.MorphBlendMesh.prototype.update = function ( delta ) { + + for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) { + + var animation = this.animationsList[ i ]; + + if ( ! animation.active ) continue; + + var frameTime = animation.duration / animation.length; + + animation.time += animation.direction * delta; + + if ( animation.mirroredLoop ) { + + if ( animation.time > animation.duration || animation.time < 0 ) { + + animation.direction *= - 1; + + if ( animation.time > animation.duration ) { + + animation.time = animation.duration; + animation.directionBackwards = true; + + } + + if ( animation.time < 0 ) { + + animation.time = 0; + animation.directionBackwards = false; + + } + + } + + } else { + + animation.time = animation.time % animation.duration; + + if ( animation.time < 0 ) animation.time += animation.duration; + + } + + var keyframe = animation.startFrame + THREE.Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 ); + var weight = animation.weight; + + if ( keyframe !== animation.currentFrame ) { + + this.morphTargetInfluences[ animation.lastFrame ] = 0; + this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight; + + this.morphTargetInfluences[ keyframe ] = 0; + + animation.lastFrame = animation.currentFrame; + animation.currentFrame = keyframe; + + } + + var mix = ( animation.time % frameTime ) / frameTime; + + if ( animation.directionBackwards ) mix = 1 - mix; + + this.morphTargetInfluences[ animation.currentFrame ] = mix * weight; + this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight; + + } + +}; + +// File:src/extras/renderers/plugins/LensFlarePlugin.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.LensFlarePlugin = function () { + + var flares = []; + + var _gl, _renderer, _precision, _lensFlare = {}; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + _precision = renderer.getPrecision(); + + _lensFlare.vertices = new Float32Array( 8 + 8 ); + _lensFlare.faces = new Uint16Array( 6 ); + + var i = 0; + _lensFlare.vertices[ i ++ ] = - 1; _lensFlare.vertices[ i ++ ] = - 1; // vertex + _lensFlare.vertices[ i ++ ] = 0; _lensFlare.vertices[ i ++ ] = 0; // uv... etc. + + _lensFlare.vertices[ i ++ ] = 1; _lensFlare.vertices[ i ++ ] = - 1; + _lensFlare.vertices[ i ++ ] = 1; _lensFlare.vertices[ i ++ ] = 0; + + _lensFlare.vertices[ i ++ ] = 1; _lensFlare.vertices[ i ++ ] = 1; + _lensFlare.vertices[ i ++ ] = 1; _lensFlare.vertices[ i ++ ] = 1; + + _lensFlare.vertices[ i ++ ] = - 1; _lensFlare.vertices[ i ++ ] = 1; + _lensFlare.vertices[ i ++ ] = 0; _lensFlare.vertices[ i ++ ] = 1; + + i = 0; + _lensFlare.faces[ i ++ ] = 0; _lensFlare.faces[ i ++ ] = 1; _lensFlare.faces[ i ++ ] = 2; + _lensFlare.faces[ i ++ ] = 0; _lensFlare.faces[ i ++ ] = 2; _lensFlare.faces[ i ++ ] = 3; + + // buffers + + _lensFlare.vertexBuffer = _gl.createBuffer(); + _lensFlare.elementBuffer = _gl.createBuffer(); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, _lensFlare.vertices, _gl.STATIC_DRAW ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.faces, _gl.STATIC_DRAW ); + + // textures + + _lensFlare.tempTexture = _gl.createTexture(); + _lensFlare.occlusionTexture = _gl.createTexture(); + + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, 16, 16, 0, _gl.RGB, _gl.UNSIGNED_BYTE, null ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST ); + + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture ); + _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, 16, 16, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MAG_FILTER, _gl.NEAREST ); + _gl.texParameteri( _gl.TEXTURE_2D, _gl.TEXTURE_MIN_FILTER, _gl.NEAREST ); + + if ( _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) <= 0 ) { + + _lensFlare.hasVertexTexture = false; + _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlare" ], _precision ); + + } else { + + _lensFlare.hasVertexTexture = true; + _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlareVertexTexture" ], _precision ); + + } + + _lensFlare.attributes = {}; + _lensFlare.uniforms = {}; + + _lensFlare.attributes.vertex = _gl.getAttribLocation ( _lensFlare.program, "position" ); + _lensFlare.attributes.uv = _gl.getAttribLocation ( _lensFlare.program, "uv" ); + + _lensFlare.uniforms.renderType = _gl.getUniformLocation( _lensFlare.program, "renderType" ); + _lensFlare.uniforms.map = _gl.getUniformLocation( _lensFlare.program, "map" ); + _lensFlare.uniforms.occlusionMap = _gl.getUniformLocation( _lensFlare.program, "occlusionMap" ); + _lensFlare.uniforms.opacity = _gl.getUniformLocation( _lensFlare.program, "opacity" ); + _lensFlare.uniforms.color = _gl.getUniformLocation( _lensFlare.program, "color" ); + _lensFlare.uniforms.scale = _gl.getUniformLocation( _lensFlare.program, "scale" ); + _lensFlare.uniforms.rotation = _gl.getUniformLocation( _lensFlare.program, "rotation" ); + _lensFlare.uniforms.screenPosition = _gl.getUniformLocation( _lensFlare.program, "screenPosition" ); + + }; + + + /* + * Render lens flares + * Method: renders 16x16 0xff00ff-colored points scattered over the light source area, + * reads these back and calculates occlusion. + * Then _lensFlare.update_lensFlares() is called to re-position and + * update transparency of flares. Then they are rendered. + * + */ + + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { + + flares.length = 0; + + scene.traverseVisible( function ( child ) { + + if ( child instanceof THREE.LensFlare ) { + + flares.push( child ); + + } + + } ); + + if ( flares.length === 0 ) return; + + var tempPosition = new THREE.Vector3(); + + var invAspect = viewportHeight / viewportWidth, + halfViewportWidth = viewportWidth * 0.5, + halfViewportHeight = viewportHeight * 0.5; + + var size = 16 / viewportHeight, + scale = new THREE.Vector2( size * invAspect, size ); + + var screenPosition = new THREE.Vector3( 1, 1, 0 ), + screenPositionPixels = new THREE.Vector2( 1, 1 ); + + var uniforms = _lensFlare.uniforms, + attributes = _lensFlare.attributes; + + // set _lensFlare program and reset blending + + _gl.useProgram( _lensFlare.program ); + + _gl.enableVertexAttribArray( _lensFlare.attributes.vertex ); + _gl.enableVertexAttribArray( _lensFlare.attributes.uv ); + + // loop through all lens flares to update their occlusion and positions + // setup gl and common used attribs/unforms + + _gl.uniform1i( uniforms.occlusionMap, 0 ); + _gl.uniform1i( uniforms.map, 1 ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer ); + _gl.vertexAttribPointer( attributes.vertex, 2, _gl.FLOAT, false, 2 * 8, 0 ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer ); + + _gl.disable( _gl.CULL_FACE ); + _gl.depthMask( false ); + + for ( var i = 0, l = flares.length; i < l; i ++ ) { + + size = 16 / viewportHeight; + scale.set( size * invAspect, size ); + + // calc object screen position + + var flare = flares[ i ]; + + tempPosition.set( flare.matrixWorld.elements[12], flare.matrixWorld.elements[13], flare.matrixWorld.elements[14] ); + + tempPosition.applyMatrix4( camera.matrixWorldInverse ); + tempPosition.applyProjection( camera.projectionMatrix ); + + // setup arrays for gl programs + + screenPosition.copy( tempPosition ) + + screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth; + screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight; + + // screen cull + + if ( _lensFlare.hasVertexTexture || ( + screenPositionPixels.x > 0 && + screenPositionPixels.x < viewportWidth && + screenPositionPixels.y > 0 && + screenPositionPixels.y < viewportHeight ) ) { + + // save current RGB to temp texture + + _gl.activeTexture( _gl.TEXTURE1 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); + + + // render pink quad + + _gl.uniform1i( uniforms.renderType, 0 ); + _gl.uniform2f( uniforms.scale, scale.x, scale.y ); + _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); + + _gl.disable( _gl.BLEND ); + _gl.enable( _gl.DEPTH_TEST ); + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + + // copy result to occlusionMap + + _gl.activeTexture( _gl.TEXTURE0 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture ); + _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 ); + + + // restore graphics + + _gl.uniform1i( uniforms.renderType, 1 ); + _gl.disable( _gl.DEPTH_TEST ); + + _gl.activeTexture( _gl.TEXTURE1 ); + _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture ); + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + + // update object positions + + flare.positionScreen.copy( screenPosition ) + + if ( flare.customUpdateCallback ) { + + flare.customUpdateCallback( flare ); + + } else { + + flare.updateLensFlares(); + + } + + // render flares + + _gl.uniform1i( uniforms.renderType, 2 ); + _gl.enable( _gl.BLEND ); + + for ( var j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) { + + var sprite = flare.lensFlares[ j ]; + + if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) { + + screenPosition.x = sprite.x; + screenPosition.y = sprite.y; + screenPosition.z = sprite.z; + + size = sprite.size * sprite.scale / viewportHeight; + + scale.x = size * invAspect; + scale.y = size; + + _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); + _gl.uniform2f( uniforms.scale, scale.x, scale.y ); + _gl.uniform1f( uniforms.rotation, sprite.rotation ); + + _gl.uniform1f( uniforms.opacity, sprite.opacity ); + _gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b ); + + _renderer.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst ); + _renderer.setTexture( sprite.texture, 1 ); + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + } + + } + + } + + } + + // restore gl + + _gl.enable( _gl.CULL_FACE ); + _gl.enable( _gl.DEPTH_TEST ); + _gl.depthMask( true ); + + }; + + function createProgram ( shader, precision ) { + + var program = _gl.createProgram(); + + var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER ); + var vertexShader = _gl.createShader( _gl.VERTEX_SHADER ); + + var prefix = "precision " + precision + " float;\n"; + + _gl.shaderSource( fragmentShader, prefix + shader.fragmentShader ); + _gl.shaderSource( vertexShader, prefix + shader.vertexShader ); + + _gl.compileShader( fragmentShader ); + _gl.compileShader( vertexShader ); + + _gl.attachShader( program, fragmentShader ); + _gl.attachShader( program, vertexShader ); + + _gl.linkProgram( program ); + + return program; + + }; + +}; + +// File:src/extras/renderers/plugins/ShadowMapPlugin.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ShadowMapPlugin = function () { + + var _gl, + _renderer, + _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin, + + _frustum = new THREE.Frustum(), + _projScreenMatrix = new THREE.Matrix4(), + + _min = new THREE.Vector3(), + _max = new THREE.Vector3(), + + _matrixPosition = new THREE.Vector3(), + + _renderList = []; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + var depthShader = THREE.ShaderLib[ "depthRGBA" ]; + var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); + + _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } ); + _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } ); + _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } ); + _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } ); + + _depthMaterial._shadowPass = true; + _depthMaterialMorph._shadowPass = true; + _depthMaterialSkin._shadowPass = true; + _depthMaterialMorphSkin._shadowPass = true; + + }; + + this.render = function ( scene, camera ) { + + if ( ! ( _renderer.shadowMapEnabled && _renderer.shadowMapAutoUpdate ) ) return; + + this.update( scene, camera ); + + }; + + this.update = function ( scene, camera ) { + + var i, il, j, jl, n, + + shadowMap, shadowMatrix, shadowCamera, + program, buffer, material, + webglObject, object, light, + + lights = [], + k = 0, + + fog = null; + + // set GL state for depth map + + _gl.clearColor( 1, 1, 1, 1 ); + _gl.disable( _gl.BLEND ); + + _gl.enable( _gl.CULL_FACE ); + _gl.frontFace( _gl.CCW ); + + if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.FRONT ); + + } else { + + _gl.cullFace( _gl.BACK ); + + } + + _renderer.setDepthTest( true ); + + // preprocess lights + // - skip lights that are not casting shadows + // - create virtual lights for cascaded shadow maps + + for ( i = 0, il = scene.__lights.length; i < il; i ++ ) { + + light = scene.__lights[ i ]; + + if ( ! light.castShadow ) continue; + + if ( ( light instanceof THREE.DirectionalLight ) && light.shadowCascade ) { + + for ( n = 0; n < light.shadowCascadeCount; n ++ ) { + + var virtualLight; + + if ( ! light.shadowCascadeArray[ n ] ) { + + virtualLight = createVirtualLight( light, n ); + virtualLight.originalCamera = camera; + + var gyro = new THREE.Gyroscope(); + gyro.position.copy( light.shadowCascadeOffset ); + + gyro.add( virtualLight ); + gyro.add( virtualLight.target ); + + camera.add( gyro ); + + light.shadowCascadeArray[ n ] = virtualLight; + + console.log( "Created virtualLight", virtualLight ); + + } else { + + virtualLight = light.shadowCascadeArray[ n ]; + + } + + updateVirtualLight( light, n ); + + lights[ k ] = virtualLight; + k ++; + + } + + } else { + + lights[ k ] = light; + k ++; + + } + + } + + // render depth map + + for ( i = 0, il = lights.length; i < il; i ++ ) { + + light = lights[ i ]; + + if ( ! light.shadowMap ) { + + var shadowFilter = THREE.LinearFilter; + + if ( _renderer.shadowMapType === THREE.PCFSoftShadowMap ) { + + shadowFilter = THREE.NearestFilter; + + } + + var pars = { minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat }; + + light.shadowMap = new THREE.WebGLRenderTarget( light.shadowMapWidth, light.shadowMapHeight, pars ); + light.shadowMapSize = new THREE.Vector2( light.shadowMapWidth, light.shadowMapHeight ); + + light.shadowMatrix = new THREE.Matrix4(); + + } + + if ( ! light.shadowCamera ) { + + if ( light instanceof THREE.SpotLight ) { + + light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar ); + + } else if ( light instanceof THREE.DirectionalLight ) { + + light.shadowCamera = new THREE.OrthographicCamera( light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar ); + + } else { + + console.error( "Unsupported light type for shadow" ); + continue; + + } + + scene.add( light.shadowCamera ); + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + } + + if ( light.shadowCameraVisible && ! light.cameraHelper ) { + + light.cameraHelper = new THREE.CameraHelper( light.shadowCamera ); + light.shadowCamera.add( light.cameraHelper ); + + } + + if ( light.isVirtual && virtualLight.originalCamera == camera ) { + + updateShadowCamera( camera, light ); + + } + + shadowMap = light.shadowMap; + shadowMatrix = light.shadowMatrix; + shadowCamera = light.shadowCamera; + + shadowCamera.position.setFromMatrixPosition( light.matrixWorld ); + _matrixPosition.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _matrixPosition ); + shadowCamera.updateMatrixWorld(); + + shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld ); + + if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible; + if ( light.shadowCameraVisible ) light.cameraHelper.update(); + + // compute shadow matrix + + shadowMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + + shadowMatrix.multiply( shadowCamera.projectionMatrix ); + shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); + + // update camera matrices and frustum + + _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // render shadow map + + _renderer.setRenderTarget( shadowMap ); + _renderer.clear(); + + // set object matrices & frustum culling + + _renderList.length = 0; + projectObject(scene,scene,shadowCamera); + + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = _renderList.length; j < jl; j ++ ) { + + webglObject = _renderList[ j ]; + + object = webglObject.object; + buffer = webglObject.buffer; + + // culling is overriden globally for all objects + // while rendering depth map + + // need to deal with MeshFaceMaterial somehow + // in that case just use the first of material.materials for now + // (proper solution would require to break objects by materials + // similarly to regular rendering and then set corresponding + // depth materials per each chunk instead of just once per object) + + objectMaterial = getObjectMaterial( object ); + + useMorphing = object.geometry.morphTargets !== undefined && object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets; + useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning; + + if ( object.customDepthMaterial ) { + + material = object.customDepthMaterial; + + } else if ( useSkinning ) { + + material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin; + + } else if ( useMorphing ) { + + material = _depthMaterialMorph; + + } else { + + material = _depthMaterial; + + } + + _renderer.setMaterialFaces( objectMaterial ); + + if ( buffer instanceof THREE.BufferGeometry ) { + + _renderer.renderBufferDirect( shadowCamera, scene.__lights, fog, material, buffer, object ); + + } else { + + _renderer.renderBuffer( shadowCamera, scene.__lights, fog, material, buffer, object ); + + } + + } + + // set matrices and render immediate objects + + var renderList = scene.__webglObjectsImmediate; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + if ( object.visible && object.castShadow ) { + + object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + + _renderer.renderImmediateObject( shadowCamera, scene.__lights, fog, _depthMaterial, object ); + + } + + } + + } + + // restore GL state + + var clearColor = _renderer.getClearColor(), + clearAlpha = _renderer.getClearAlpha(); + + _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha ); + _gl.enable( _gl.BLEND ); + + if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) { + + _gl.cullFace( _gl.BACK ); + + } + + }; + + function projectObject(scene, object,shadowCamera){ + + if ( object.visible ) { + + var webglObjects = scene.__webglObjects[object.id]; + + if (webglObjects && object.castShadow && (object.frustumCulled === false || _frustum.intersectsObject( object ) === true) ) { + + + for (var i = 0, l = webglObjects.length; i < l; i++){ + + var webglObject = webglObjects[i]; + + object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + _renderList.push(webglObject); + + } + } + + for(var i = 0, l = object.children.length; i < l; i++) { + + projectObject(scene, object.children[i],shadowCamera); + } + + } + } + + function createVirtualLight( light, cascade ) { + + var virtualLight = new THREE.DirectionalLight(); + + virtualLight.isVirtual = true; + + virtualLight.onlyShadow = true; + virtualLight.castShadow = true; + + virtualLight.shadowCameraNear = light.shadowCameraNear; + virtualLight.shadowCameraFar = light.shadowCameraFar; + + virtualLight.shadowCameraLeft = light.shadowCameraLeft; + virtualLight.shadowCameraRight = light.shadowCameraRight; + virtualLight.shadowCameraBottom = light.shadowCameraBottom; + virtualLight.shadowCameraTop = light.shadowCameraTop; + + virtualLight.shadowCameraVisible = light.shadowCameraVisible; + + virtualLight.shadowDarkness = light.shadowDarkness; + + virtualLight.shadowBias = light.shadowCascadeBias[ cascade ]; + virtualLight.shadowMapWidth = light.shadowCascadeWidth[ cascade ]; + virtualLight.shadowMapHeight = light.shadowCascadeHeight[ cascade ]; + + virtualLight.pointsWorld = []; + virtualLight.pointsFrustum = []; + + var pointsWorld = virtualLight.pointsWorld, + pointsFrustum = virtualLight.pointsFrustum; + + for ( var i = 0; i < 8; i ++ ) { + + pointsWorld[ i ] = new THREE.Vector3(); + pointsFrustum[ i ] = new THREE.Vector3(); + + } + + var nearZ = light.shadowCascadeNearZ[ cascade ]; + var farZ = light.shadowCascadeFarZ[ cascade ]; + + pointsFrustum[ 0 ].set( - 1, - 1, nearZ ); + pointsFrustum[ 1 ].set( 1, - 1, nearZ ); + pointsFrustum[ 2 ].set( - 1, 1, nearZ ); + pointsFrustum[ 3 ].set( 1, 1, nearZ ); + + pointsFrustum[ 4 ].set( - 1, - 1, farZ ); + pointsFrustum[ 5 ].set( 1, - 1, farZ ); + pointsFrustum[ 6 ].set( - 1, 1, farZ ); + pointsFrustum[ 7 ].set( 1, 1, farZ ); + + return virtualLight; + + } + + // Synchronize virtual light with the original light + + function updateVirtualLight( light, cascade ) { + + var virtualLight = light.shadowCascadeArray[ cascade ]; + + virtualLight.position.copy( light.position ); + virtualLight.target.position.copy( light.target.position ); + virtualLight.lookAt( virtualLight.target ); + + virtualLight.shadowCameraVisible = light.shadowCameraVisible; + virtualLight.shadowDarkness = light.shadowDarkness; + + virtualLight.shadowBias = light.shadowCascadeBias[ cascade ]; + + var nearZ = light.shadowCascadeNearZ[ cascade ]; + var farZ = light.shadowCascadeFarZ[ cascade ]; + + var pointsFrustum = virtualLight.pointsFrustum; + + pointsFrustum[ 0 ].z = nearZ; + pointsFrustum[ 1 ].z = nearZ; + pointsFrustum[ 2 ].z = nearZ; + pointsFrustum[ 3 ].z = nearZ; + + pointsFrustum[ 4 ].z = farZ; + pointsFrustum[ 5 ].z = farZ; + pointsFrustum[ 6 ].z = farZ; + pointsFrustum[ 7 ].z = farZ; + + } + + // Fit shadow camera's ortho frustum to camera frustum + + function updateShadowCamera( camera, light ) { + + var shadowCamera = light.shadowCamera, + pointsFrustum = light.pointsFrustum, + pointsWorld = light.pointsWorld; + + _min.set( Infinity, Infinity, Infinity ); + _max.set( - Infinity, - Infinity, - Infinity ); + + for ( var i = 0; i < 8; i ++ ) { + + var p = pointsWorld[ i ]; + + p.copy( pointsFrustum[ i ] ); + THREE.ShadowMapPlugin.__projector.unprojectVector( p, camera ); + + p.applyMatrix4( shadowCamera.matrixWorldInverse ); + + if ( p.x < _min.x ) _min.x = p.x; + if ( p.x > _max.x ) _max.x = p.x; + + if ( p.y < _min.y ) _min.y = p.y; + if ( p.y > _max.y ) _max.y = p.y; + + if ( p.z < _min.z ) _min.z = p.z; + if ( p.z > _max.z ) _max.z = p.z; + + } + + shadowCamera.left = _min.x; + shadowCamera.right = _max.x; + shadowCamera.top = _max.y; + shadowCamera.bottom = _min.y; + + // can't really fit near/far + //shadowCamera.near = _min.z; + //shadowCamera.far = _max.z; + + shadowCamera.updateProjectionMatrix(); + + } + + // For the moment just ignore objects that have multiple materials with different animation methods + // Only the first material will be taken into account for deciding which depth material to use for shadow maps + + function getObjectMaterial( object ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ 0 ] + : object.material; + + }; + +}; + +THREE.ShadowMapPlugin.__projector = new THREE.Projector(); + +// File:src/extras/renderers/plugins/SpritePlugin.js + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SpritePlugin = function () { + + var _gl, _renderer, _texture; + + var sprites = []; + + var vertices, faces, vertexBuffer, elementBuffer; + var program, attributes, uniforms; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + vertices = new Float32Array( [ + - 0.5, - 0.5, 0, 0, + 0.5, - 0.5, 1, 0, + 0.5, 0.5, 1, 1, + - 0.5, 0.5, 0, 1 + ] ); + + faces = new Uint16Array( [ + 0, 1, 2, + 0, 2, 3 + ] ); + + vertexBuffer = _gl.createBuffer(); + elementBuffer = _gl.createBuffer(); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer ); + _gl.bufferData( _gl.ARRAY_BUFFER, vertices, _gl.STATIC_DRAW ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faces, _gl.STATIC_DRAW ); + + program = createProgram(); + + attributes = { + position: _gl.getAttribLocation ( program, 'position' ), + uv: _gl.getAttribLocation ( program, 'uv' ) + }; + + uniforms = { + uvOffset: _gl.getUniformLocation( program, 'uvOffset' ), + uvScale: _gl.getUniformLocation( program, 'uvScale' ), + + rotation: _gl.getUniformLocation( program, 'rotation' ), + scale: _gl.getUniformLocation( program, 'scale' ), + + color: _gl.getUniformLocation( program, 'color' ), + map: _gl.getUniformLocation( program, 'map' ), + opacity: _gl.getUniformLocation( program, 'opacity' ), + + modelViewMatrix: _gl.getUniformLocation( program, 'modelViewMatrix' ), + projectionMatrix: _gl.getUniformLocation( program, 'projectionMatrix' ), + + fogType: _gl.getUniformLocation( program, 'fogType' ), + fogDensity: _gl.getUniformLocation( program, 'fogDensity' ), + fogNear: _gl.getUniformLocation( program, 'fogNear' ), + fogFar: _gl.getUniformLocation( program, 'fogFar' ), + fogColor: _gl.getUniformLocation( program, 'fogColor' ), + + alphaTest: _gl.getUniformLocation( program, 'alphaTest' ) + }; + + var canvas = document.createElement( 'canvas' ); + canvas.width = 8; + canvas.height = 8; + + var context = canvas.getContext( '2d' ); + context.fillStyle = 'white'; + context.fillRect( 0, 0, 8, 8 ); + + _texture = new THREE.Texture( canvas ); + _texture.needsUpdate = true; + + }; + + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { + + sprites.length = 0; + + scene.traverseVisible( function ( child ) { + + if ( child instanceof THREE.Sprite ) { + + sprites.push( child ); + + } + + } ); + + if ( sprites.length === 0 ) return; + + // setup gl + + _gl.useProgram( program ); + + _gl.enableVertexAttribArray( attributes.position ); + _gl.enableVertexAttribArray( attributes.uv ); + + _gl.disable( _gl.CULL_FACE ); + _gl.enable( _gl.BLEND ); + + _gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer ); + _gl.vertexAttribPointer( attributes.position, 2, _gl.FLOAT, false, 2 * 8, 0 ); + _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 ); + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + + _gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + _gl.activeTexture( _gl.TEXTURE0 ); + _gl.uniform1i( uniforms.map, 0 ); + + var oldFogType = 0; + var sceneFogType = 0; + var fog = scene.fog; + + if ( fog ) { + + _gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b ); + + if ( fog instanceof THREE.Fog ) { + + _gl.uniform1f( uniforms.fogNear, fog.near ); + _gl.uniform1f( uniforms.fogFar, fog.far ); + + _gl.uniform1i( uniforms.fogType, 1 ); + oldFogType = 1; + sceneFogType = 1; + + } else if ( fog instanceof THREE.FogExp2 ) { + + _gl.uniform1f( uniforms.fogDensity, fog.density ); + + _gl.uniform1i( uniforms.fogType, 2 ); + oldFogType = 2; + sceneFogType = 2; + + } + + } else { + + _gl.uniform1i( uniforms.fogType, 0 ); + oldFogType = 0; + sceneFogType = 0; + + } + + + // update positions and sort + + for ( var i = 0, l = sprites.length; i < l; i ++ ) { + + var sprite = sprites[ i ]; + var material = sprite.material; + + sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); + sprite.z = - sprite._modelViewMatrix.elements[ 14 ]; + + } + + sprites.sort( painterSortStable ); + + // render all sprites + + var scale = []; + + for ( var i = 0, l = sprites.length; i < l; i ++ ) { + + var sprite = sprites[ i ]; + var material = sprite.material; + + _gl.uniform1f( uniforms.alphaTest, material.alphaTest ); + _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements ); + + scale[ 0 ] = sprite.scale.x; + scale[ 1 ] = sprite.scale.y; + + var fogType = 0; + + if ( scene.fog && material.fog ) { + + fogType = sceneFogType; + + } + + if ( oldFogType !== fogType ) { + + _gl.uniform1i( uniforms.fogType, fogType ); + oldFogType = fogType; + + } + + if ( material.map !== null ) { + + _gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y ); + _gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y ); + + } else { + + _gl.uniform2f( uniforms.uvOffset, 0, 0 ); + _gl.uniform2f( uniforms.uvScale, 1, 1 ); + + } + + _gl.uniform1f( uniforms.opacity, material.opacity ); + _gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b ); + + _gl.uniform1f( uniforms.rotation, material.rotation ); + _gl.uniform2fv( uniforms.scale, scale ); + + _renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); + _renderer.setDepthTest( material.depthTest ); + _renderer.setDepthWrite( material.depthWrite ); + + if ( material.map && material.map.image && material.map.image.width ) { + + _renderer.setTexture( material.map, 0 ); + + } else { + + _renderer.setTexture( _texture, 0 ); + + } + + _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 ); + + } + + // restore gl + + _gl.enable( _gl.CULL_FACE ); + + }; + + function createProgram () { + + var program = _gl.createProgram(); + + var vertexShader = _gl.createShader( _gl.VERTEX_SHADER ); + var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER ); + + _gl.shaderSource( vertexShader, [ + + 'precision ' + _renderer.getPrecision() + ' float;', + + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform float rotation;', + 'uniform vec2 scale;', + 'uniform vec2 uvOffset;', + 'uniform vec2 uvScale;', + + 'attribute vec2 position;', + 'attribute vec2 uv;', + + 'varying vec2 vUV;', + + 'void main() {', + + 'vUV = uvOffset + uv * uvScale;', + + 'vec2 alignedPosition = position * scale;', + + 'vec2 rotatedPosition;', + 'rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;', + 'rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;', + + 'vec4 finalPosition;', + + 'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', + 'finalPosition.xy += rotatedPosition;', + 'finalPosition = projectionMatrix * finalPosition;', + + 'gl_Position = finalPosition;', + + '}' + + ].join( '\n' ) ); + + _gl.shaderSource( fragmentShader, [ + + 'precision ' + _renderer.getPrecision() + ' float;', + + 'uniform vec3 color;', + 'uniform sampler2D map;', + 'uniform float opacity;', + + 'uniform int fogType;', + 'uniform vec3 fogColor;', + 'uniform float fogDensity;', + 'uniform float fogNear;', + 'uniform float fogFar;', + 'uniform float alphaTest;', + + 'varying vec2 vUV;', + + 'void main() {', + + 'vec4 texture = texture2D( map, vUV );', + + 'if ( texture.a < alphaTest ) discard;', + + 'gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', + + 'if ( fogType > 0 ) {', + + 'float depth = gl_FragCoord.z / gl_FragCoord.w;', + 'float fogFactor = 0.0;', + + 'if ( fogType == 1 ) {', + + 'fogFactor = smoothstep( fogNear, fogFar, depth );', + + '} else {', + + 'const float LOG2 = 1.442695;', + 'float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );', + 'fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );', + + '}', + + 'gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );', + + '}', + + '}' + + ].join( '\n' ) ); + + _gl.compileShader( vertexShader ); + _gl.compileShader( fragmentShader ); + + _gl.attachShader( program, vertexShader ); + _gl.attachShader( program, fragmentShader ); + + _gl.linkProgram( program ); + + return program; + + }; + + function painterSortStable ( a, b ) { + + if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return b.id - a.id; + + } + + }; + +}; + +// File:src/extras/renderers/plugins/DepthPassPlugin.js + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.DepthPassPlugin = function () { + + this.enabled = false; + this.renderTarget = null; + + var _gl, + _renderer, + _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin, + + _frustum = new THREE.Frustum(), + _projScreenMatrix = new THREE.Matrix4(), + _renderList = []; + + this.init = function ( renderer ) { + + _gl = renderer.context; + _renderer = renderer; + + var depthShader = THREE.ShaderLib[ "depthRGBA" ]; + var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms ); + + _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } ); + _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } ); + _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } ); + _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } ); + + _depthMaterial._shadowPass = true; + _depthMaterialMorph._shadowPass = true; + _depthMaterialSkin._shadowPass = true; + _depthMaterialMorphSkin._shadowPass = true; + + }; + + this.render = function ( scene, camera ) { + + if ( ! this.enabled ) return; + + this.update( scene, camera ); + + }; + + this.update = function ( scene, camera ) { + + var i, il, j, jl, n, + + program, buffer, material, + webglObject, object, light, + renderList, + + fog = null; + + // set GL state for depth map + + _gl.clearColor( 1, 1, 1, 1 ); + _gl.disable( _gl.BLEND ); + + _renderer.setDepthTest( true ); + + // update scene + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // render depth map + + _renderer.setRenderTarget( this.renderTarget ); + _renderer.clear(); + + // set object matrices & frustum culling + + _renderList.length = 0; + projectObject(scene,scene,camera); + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = _renderList.length; j < jl; j ++ ) { + + webglObject = _renderList[ j ]; + + object = webglObject.object; + buffer = webglObject.buffer; + + // todo: create proper depth material for particles + + if ( object instanceof THREE.PointCloud && ! object.customDepthMaterial ) continue; + + objectMaterial = getObjectMaterial( object ); + + if ( objectMaterial ) _renderer.setMaterialFaces( object.material ); + + useMorphing = object.geometry.morphTargets !== undefined && object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets; + useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning; + + if ( object.customDepthMaterial ) { + + material = object.customDepthMaterial; + + } else if ( useSkinning ) { + + material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin; + + } else if ( useMorphing ) { + + material = _depthMaterialMorph; + + } else { + + material = _depthMaterial; + + } + + if ( buffer instanceof THREE.BufferGeometry ) { + + _renderer.renderBufferDirect( camera, scene.__lights, fog, material, buffer, object ); + + } else { + + _renderer.renderBuffer( camera, scene.__lights, fog, material, buffer, object ); + + } + + + } + + // set matrices and render immediate objects + + renderList = scene.__webglObjectsImmediate; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + if ( object.visible ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + _renderer.renderImmediateObject( camera, scene.__lights, fog, _depthMaterial, object ); + + } + + } + + // restore GL state + + var clearColor = _renderer.getClearColor(), + clearAlpha = _renderer.getClearAlpha(); + + _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha ); + _gl.enable( _gl.BLEND ); + + }; + + function projectObject(scene, object,camera){ + + if ( object.visible ) { + + var webglObjects = scene.__webglObjects[object.id]; + + if (webglObjects && (object.frustumCulled === false || _frustum.intersectsObject( object ) === true) ) { + + + for (var i = 0, l = webglObjects.length; i < l; i++){ + + var webglObject = webglObjects[i]; + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + _renderList.push(webglObject); + + } + } + + for(var i = 0, l = object.children.length; i < l; i++) { + + projectObject(scene, object.children[i], camera); + } + + } + } + + // For the moment just ignore objects that have multiple materials with different animation methods + // Only the first material will be taken into account for deciding which depth material to use + + function getObjectMaterial( object ) { + + return object.material instanceof THREE.MeshFaceMaterial + ? object.material.materials[ 0 ] + : object.material; + + }; + +}; + + +// File:src/extras/shaders/ShaderFlares.js + +/** + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.ShaderFlares = { + + 'lensFlareVertexTexture': { + + vertexShader: [ + + "uniform lowp int renderType;", + + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", + + "uniform sampler2D occlusionMap;", + + "attribute vec2 position;", + "attribute vec2 uv;", + + "varying vec2 vUV;", + "varying float vVisibility;", + + "void main() {", + + "vUV = uv;", + + "vec2 pos = position;", + + "if( renderType == 2 ) {", + + "vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );", + + "vVisibility = visibility.r / 9.0;", + "vVisibility *= 1.0 - visibility.g / 9.0;", + "vVisibility *= visibility.b / 9.0;", + "vVisibility *= 1.0 - visibility.a / 9.0;", + + "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;", + "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;", + + "}", + + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "uniform lowp int renderType;", + + "uniform sampler2D map;", + "uniform float opacity;", + "uniform vec3 color;", + + "varying vec2 vUV;", + "varying float vVisibility;", + + "void main() {", + + // pink square + + "if( renderType == 0 ) {", + + "gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );", + + // restore + + "} else if( renderType == 1 ) {", + + "gl_FragColor = texture2D( map, vUV );", + + // flare + + "} else {", + + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * vVisibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", + + "}", + + "}" + ].join( "\n" ) + + }, + + + 'lensFlare': { + + vertexShader: [ + + "uniform lowp int renderType;", + + "uniform vec3 screenPosition;", + "uniform vec2 scale;", + "uniform float rotation;", + + "attribute vec2 position;", + "attribute vec2 uv;", + + "varying vec2 vUV;", + + "void main() {", + + "vUV = uv;", + + "vec2 pos = position;", + + "if( renderType == 2 ) {", + + "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;", + "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;", + + "}", + + "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );", + + "}" + + ].join( "\n" ), + + fragmentShader: [ + + "precision mediump float;", + + "uniform lowp int renderType;", + + "uniform sampler2D map;", + "uniform sampler2D occlusionMap;", + "uniform float opacity;", + "uniform vec3 color;", + + "varying vec2 vUV;", + + "void main() {", + + // pink square + + "if( renderType == 0 ) {", + + "gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );", + + // restore + + "} else if( renderType == 1 ) {", + + "gl_FragColor = texture2D( map, vUV );", + + // flare + + "} else {", + + "float visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a;", + "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;", + "visibility = ( 1.0 - visibility / 4.0 );", + + "vec4 texture = texture2D( map, vUV );", + "texture.a *= opacity * visibility;", + "gl_FragColor = texture;", + "gl_FragColor.rgb *= color;", + + "}", + + "}" + + ].join( "\n" ) + + } + +}; + diff --git a/views/globe/main.ejs b/views/globe/main.ejs index 1967d2a..29ff2ad 100644 --- a/views/globe/main.ejs +++ b/views/globe/main.ejs @@ -66,7 +66,7 @@ - + From d4306c3b4105337960ffb60c48b865454206559d Mon Sep 17 00:00:00 2001 From: Ariel Rodriguez Date: Fri, 24 Oct 2014 15:03:56 -0300 Subject: [PATCH 08/15] Work in progress. Translating Pointcloud API. --- assets/globe/fancyLines.js | 33 +++++++++++++++++++++++++++++---- assets/globe/globe.js | 2 +- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index 848ae46..3175826 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -1,4 +1,4 @@ -var step = 3, // speed of the particles +var step = 1, // speed of the particles globeWidth=200; // width of the globe: // if 200, particles start from the surface of the globe // if 0, the particles start from the center of the globe @@ -13,9 +13,33 @@ Particle = function(pointData, x, y, z){ geometry = new THREE.Geometry(); geometry.vertices.push(vertex); - particleMaterial = new THREE.ParticleBasicMaterial({ + // attributes + attributes = { + + alpha: { type: 'f', value: [] }, + + }; + // uniforms + uniforms = { + + color: { type: "c", value: new THREE.Color( 0x00ff00 ) }, + + }; + // point cloud material + var shaderMaterial = new THREE.ShaderMaterial( { + + uniforms: uniforms, + attributes: attributes, + vertexShader: document.getElementById( 'vertexshader' ).textContent, + fragmentShader: document.getElementById( 'fragmentshader' ).textContent, + transparent: true + + }); + + + particleMaterial = new THREE.PointCloudMaterial({ size: 100, - color: 0xffffff, + color: 0xFFCC00, map: THREE.ImageUtils.loadTexture( "images/particleA.png" ), @@ -23,9 +47,10 @@ Particle = function(pointData, x, y, z){ transparent: true }); - particles = new THREE.ParticleSystem(geometry, particleMaterial); + particles = new THREE.PointCloud(geometry, particleMaterial); particles.dynamic = true; + } this.updateParticle = function(bx,by,bz, max){ diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 4b047e5..9e5104f 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -113,7 +113,7 @@ DAT.Globe = function(container, opts) { fragmentShader: shader.fragmentShader, side: THREE.BackSide, blending: THREE.AdditiveBlending, - transparent: true + transparent: false }); From f2ff63628b2f275ac1c83b2998511186d1315814 Mon Sep 17 00:00:00 2001 From: Agustin Paschetta Date: Tue, 18 Nov 2014 11:56:23 -0300 Subject: [PATCH 09/15] Fix to not display black square around the particle when is passing over the atmosphere --- assets/globe/fancyLines.js | 38 +++++++++++++++++++------------------- assets/globe/globe.js | 4 +--- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index 3175826..1741dd1 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -35,21 +35,21 @@ Particle = function(pointData, x, y, z){ transparent: true }); - - - particleMaterial = new THREE.PointCloudMaterial({ - size: 100, - color: 0xFFCC00, - map: THREE.ImageUtils.loadTexture( - "images/particleA.png" - ), - blending: THREE.AdditiveBlending, - transparent: true - }); - - particles = new THREE.PointCloud(geometry, particleMaterial); - - particles.dynamic = true; + + var particleTexture = THREE.ImageUtils.loadTexture('images/particleB.png'); + + var particleMaterial = new THREE.ParticleBasicMaterial({ + map: particleTexture, + transparent: true, + size: 100, + blending: THREE.NormalBlending, + alphaTest: 0.5, + opacity: 0.8 //If you want to do add transparency to the particle + }); + + particles = new THREE.ParticleSystem(geometry, particleMaterial); + //particles.position.set(0, 0, 0); + particles.dynamic = true; } @@ -70,11 +70,11 @@ Particle = function(pointData, x, y, z){ cx = d*bx; cy = d*by; cz = d*bz; - - vertex.setX(cx); + vertex.setX(cx); vertex.setY(cy); vertex.setZ(cz); - geometry.verticesNeedUpdate = true; + //particles.position.set(cx, cy, cz); + geometry.verticesNeedUpdate = true; } @@ -94,6 +94,6 @@ DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ } DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z, max){ - var particle = textures[pointData.id]; + var particle = textures[pointData.id]; particle.updateParticle(x,y,z, max); } diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 9e5104f..3a4dd84 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -96,7 +96,6 @@ DAT.Globe = function(container, opts) { uniforms: uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader - }); mesh = new THREE.Mesh(geometry, material); @@ -113,8 +112,7 @@ DAT.Globe = function(container, opts) { fragmentShader: shader.fragmentShader, side: THREE.BackSide, blending: THREE.AdditiveBlending, - transparent: false - + transparent: true }); mesh = new THREE.Mesh(geometry, material); From bbc039a1b85d30544c51827989c514e5b552e98c Mon Sep 17 00:00:00 2001 From: Agustin Paschetta Date: Tue, 18 Nov 2014 11:58:20 -0300 Subject: [PATCH 10/15] Add particle image --- assets/images/particleB.png | Bin 0 -> 115718 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/images/particleB.png diff --git a/assets/images/particleB.png b/assets/images/particleB.png new file mode 100644 index 0000000000000000000000000000000000000000..f459621ce88145b243587334d19c4ae81eba4117 GIT binary patch literal 115718 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 z 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 Date: Mon, 1 Dec 2014 10:42:51 -0300 Subject: [PATCH 11/15] Add sprite material to display particles --- assets/globe/fancyLines.js | 98 ++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 26 deletions(-) diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index 1741dd1..e8c00d8 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -21,33 +21,45 @@ Particle = function(pointData, x, y, z){ }; // uniforms uniforms = { - color: { type: "c", value: new THREE.Color( 0x00ff00 ) }, - }; // point cloud material - var shaderMaterial = new THREE.ShaderMaterial( { - - uniforms: uniforms, - attributes: attributes, - vertexShader: document.getElementById( 'vertexshader' ).textContent, - fragmentShader: document.getElementById( 'fragmentshader' ).textContent, - transparent: true + var shaderMaterial = new THREE.ShaderMaterial( { + uniforms: uniforms, + attributes: attributes, + vertexShader: document.getElementById( 'vertexshader' ).textContent, + fragmentShader: document.getElementById( 'fragmentshader' ).textContent, + transparent: true + }); - }); - var particleTexture = THREE.ImageUtils.loadTexture('images/particleB.png'); - var particleMaterial = new THREE.ParticleBasicMaterial({ - map: particleTexture, - transparent: true, - size: 100, - blending: THREE.NormalBlending, - alphaTest: 0.5, - opacity: 0.8 //If you want to do add transparency to the particle + var particleMaterial = new THREE.ParticleBasicMaterial({ + map: particleTexture, + transparent: true, + size: 30, + blending: THREE.AdditiveBlending, + alphaTest: 0.5, + //opacity: 0.8 //If you want to do add transparency to the particle + }); + + particleGroup = new THREE.Object3D({transparent: true}); + var radiusRange = 10; + var spriteMaterial = new THREE.SpriteMaterial( { + map: particleTexture, + transparent: true, + blending: THREE.NormalBlending, }); - - particles = new THREE.ParticleSystem(geometry, particleMaterial); + + var sprite = new THREE.Sprite( spriteMaterial ); + sprite.scale.set( 22, 22, 1.0 ); // imageWidth, imageHeight + sprite.position.set( 0, 0, 0 ); + + sprite.material.color.setHSL( Math.random(), 0.9, 0.7 ); + particleGroup.add( sprite ); + + particles = particleGroup; + //particles = new THREE.ParticleSystem(geometry, particleMaterial); //particles.position.set(0, 0, 0); particles.dynamic = true; @@ -57,7 +69,7 @@ Particle = function(pointData, x, y, z){ var cx,cy,cz, d; currentStep +=step; - if(max == globeWidth){ + if(Math.floor(max) == globeWidth){ // the line stopped moving currentStep = globeWidth; }else if(currentStep> max){ @@ -70,11 +82,14 @@ Particle = function(pointData, x, y, z){ cx = d*bx; cy = d*by; cz = d*bz; - vertex.setX(cx); - vertex.setY(cy); - vertex.setZ(cz); - //particles.position.set(cx, cy, cz); - geometry.verticesNeedUpdate = true; + //vertex.setX(cx); + //vertex.setY(cy); + //vertex.setZ(cz); + particles.position.y = cy; + particles.position.x = cx; + particles.position.z = cz; + //particles.position.set(cx, cy, cz); + //geometry.verticesNeedUpdate = true; } @@ -91,6 +106,37 @@ DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ var particle = new Particle(pointData, x,y,z); textures[pointData.id] = particle; scene.add(particle.getParticles()); + + + var particleTexture = THREE.ImageUtils.loadTexture( 'images/particleB.png' ); + + particleGroup = new THREE.Object3D(); + particleAttributes = { startSize: [], startPosition: [], randomness: [] }; + + var totalParticles = 1; + var radiusRange = 10; + for( var i = 0; i < totalParticles; i++ ) + { + var spriteMaterial = new THREE.SpriteMaterial( { + map: particleTexture, + useScreenCoordinates: false, + color: 0xffffff, + size: 100, + transparent: true + }); + var sprite = new THREE.Sprite( spriteMaterial ); + sprite.scale.set( 30, 10, 1.0 ); // imageWidth, imageHeight + sprite.position.set( 0, 0, 0 ); + sprite.position.setLength( radiusRange * (Math.random() * 0.1 + 0.9) ); + sprite.material.color.setHSL( Math.random(), 0.9, 0.7 ); + sprite.material.blending = THREE.AdditiveBlending; // "glowing" particles + particleGroup.add( sprite ); + particleAttributes.startPosition.push( sprite.position.clone() ); + //particleAttributes.randomness.push( Math.random() ); + } + particleGroup.position.y = 300; + particleGroup.position.x = 300; + scene.add( particleGroup ); } DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z, max){ From ca6961490e1e142efbe5cd8a247726c29cc944d4 Mon Sep 17 00:00:00 2001 From: mmontenegro Date: Wed, 3 Dec 2014 10:21:19 -0300 Subject: [PATCH 12/15] Update globe.js --- assets/globe/globe.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 3a4dd84..74a0df0 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -68,7 +68,6 @@ DAT.Globe = function(container, opts) { var PI_HALF = Math.PI / 2; this.locations = []; - function init() { container.style.color = '#fff'; @@ -348,4 +347,4 @@ DAT.Globe = function(container, opts) { init(); -} \ No newline at end of file +} From 60a179939daf17539f822b1ea917fcf3e6baf4cf Mon Sep 17 00:00:00 2001 From: mmontenegro Date: Wed, 3 Dec 2014 10:22:15 -0300 Subject: [PATCH 13/15] Update fancyLines.js --- assets/globe/fancyLines.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index e8c00d8..2fbe3d5 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -3,8 +3,6 @@ var step = 1, // speed of the particles // if 200, particles start from the surface of the globe // if 0, the particles start from the center of the globe - - Particle = function(pointData, x, y, z){ var vertex, geometry, particles, cx=0, cy=0, cz= 0, currentStep=globeWidth; From 263afe353eb6acba0dc723d633c846d73956361f Mon Sep 17 00:00:00 2001 From: "Matias M. Montenegro" Date: Wed, 3 Dec 2014 11:26:49 -0300 Subject: [PATCH 14/15] Indentation --- assets/globe/boot.js | 31 ++++---- assets/globe/fancyLines.js | 150 +++++++++++++++++-------------------- assets/globe/globe.js | 76 ++++++++----------- 3 files changed, 119 insertions(+), 138 deletions(-) diff --git a/assets/globe/boot.js b/assets/globe/boot.js index 05d51ff..dfecb0d 100644 --- a/assets/globe/boot.js +++ b/assets/globe/boot.js @@ -1,25 +1,26 @@ - if(!Detector.webgl){ - Detector.addGetWebGLMessage(); - } else { +'use strict'; - var container = document.getElementById('container'); - var globe = new DAT.Globe(container); +if(!Detector.webgl){ + Detector.addGetWebGLMessage(); +} else { + var container = document.getElementById('container'), + globe = new DAT.Globe(container); - globe.animate(); + globe.animate(); - globe.parsePoints = function(list){ + globe.parsePoints = function(list) { this.locations = this.locations.concat(list); for (var i = 0; i < list.length; i++) { - this.addPoint(list[i]); + this.addPoint(list[i]); } - }; + }; - globe.autoUpdate = function() { + globe.autoUpdate = function() { window.requestAnimationFrame(function() { - globe.updatePoints(); - globe.autoUpdate(); + globe.updatePoints(); + globe.autoUpdate(); }); - }; + }; - document.body.style.backgroundImage = 'none'; // remove loading - } + document.body.style.backgroundImage = 'none'; // remove loading +} diff --git a/assets/globe/fancyLines.js b/assets/globe/fancyLines.js index 2fbe3d5..4f88fdd 100644 --- a/assets/globe/fancyLines.js +++ b/assets/globe/fancyLines.js @@ -3,67 +3,64 @@ var step = 1, // speed of the particles // if 200, particles start from the surface of the globe // if 0, the particles start from the center of the globe -Particle = function(pointData, x, y, z){ - var vertex, geometry, particles, cx=0, cy=0, cz= 0, currentStep=globeWidth; - - this.inicialize =function(){ - vertex = new THREE.Vector3(0, 0, 0); - geometry = new THREE.Geometry(); - geometry.vertices.push(vertex); - - // attributes - attributes = { - - alpha: { type: 'f', value: [] }, - - }; - // uniforms - uniforms = { - color: { type: "c", value: new THREE.Color( 0x00ff00 ) }, - }; - // point cloud material - var shaderMaterial = new THREE.ShaderMaterial( { - uniforms: uniforms, - attributes: attributes, - vertexShader: document.getElementById( 'vertexshader' ).textContent, - fragmentShader: document.getElementById( 'fragmentshader' ).textContent, - transparent: true - }); - - var particleTexture = THREE.ImageUtils.loadTexture('images/particleB.png'); - - var particleMaterial = new THREE.ParticleBasicMaterial({ - map: particleTexture, - transparent: true, - size: 30, - blending: THREE.AdditiveBlending, - alphaTest: 0.5, - //opacity: 0.8 //If you want to do add transparency to the particle - }); - - particleGroup = new THREE.Object3D({transparent: true}); - var radiusRange = 10; - var spriteMaterial = new THREE.SpriteMaterial( { - map: particleTexture, - transparent: true, - blending: THREE.NormalBlending, - }); - - var sprite = new THREE.Sprite( spriteMaterial ); - sprite.scale.set( 22, 22, 1.0 ); // imageWidth, imageHeight - sprite.position.set( 0, 0, 0 ); - - sprite.material.color.setHSL( Math.random(), 0.9, 0.7 ); - particleGroup.add( sprite ); - - particles = particleGroup; - //particles = new THREE.ParticleSystem(geometry, particleMaterial); - //particles.position.set(0, 0, 0); - particles.dynamic = true; - - } - - this.updateParticle = function(bx,by,bz, max){ +Particle = function(pointData, x, y, z) { + var vertex, geometry, particles, cx=0, cy=0, cz= 0, currentStep=globeWidth; + + this.inicialize = function() { + var shaderMaterial, particleTexture, particleMaterial, radiusRange, spriteMaterial; + + vertex = new THREE.Vector3(0, 0, 0); + geometry = new THREE.Geometry(); + geometry.vertices.push(vertex); + + // attributes + attributes = { + alpha: { type: 'f', value: [] }, + }; + // uniforms + uniforms = { + color: { type: "c", value: new THREE.Color( 0x00ff00 ) }, + }; + // point cloud material + shaderMaterial = new THREE.ShaderMaterial( { + uniforms: uniforms, + attributes: attributes, + vertexShader: document.getElementById( 'vertexshader' ).textContent, + fragmentShader: document.getElementById( 'fragmentshader' ).textContent, + transparent: true + }); + + particleTexture = THREE.ImageUtils.loadTexture('images/particleB.png'); + + particleMaterial = new THREE.ParticleBasicMaterial({ + map: particleTexture, + transparent: true, + size: 30, + blending: THREE.AdditiveBlending, + alphaTest: 0.5 + //opacity: 0.8 //If you want to do add transparency to the particle + }); + + particleGroup = new THREE.Object3D({transparent: true}); + radiusRange = 10; + spriteMaterial = new THREE.SpriteMaterial( { + map: particleTexture, + transparent: true, + blending: THREE.NormalBlending, + }); + + sprite = new THREE.Sprite( spriteMaterial ); + sprite.scale.set( 22, 22, 1.0 ); // imageWidth, imageHeight + sprite.position.set( 0, 0, 0 ); + + sprite.material.color.setHSL( Math.random(), 0.9, 0.7 ); + particleGroup.add( sprite ); + + particles = particleGroup; + particles.dynamic = true; + } + + this.updateParticle = function(bx,by,bz, max) { var cx,cy,cz, d; currentStep +=step; @@ -80,49 +77,43 @@ Particle = function(pointData, x, y, z){ cx = d*bx; cy = d*by; cz = d*bz; - //vertex.setX(cx); - //vertex.setY(cy); - //vertex.setZ(cz); particles.position.y = cy; particles.position.x = cx; particles.position.z = cz; - //particles.position.set(cx, cy, cz); - //geometry.verticesNeedUpdate = true; - } + } this.getParticles = function(){ return particles; } - this.inicialize(); + this.inicialize(); } var textures = {}; DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ - var particle = new Particle(pointData, x,y,z); + var particleTexture, particle, totalParticles, radiusRange, spriteMaterial, sprite; + + particle = new Particle(pointData, x,y,z); textures[pointData.id] = particle; scene.add(particle.getParticles()); - - - var particleTexture = THREE.ImageUtils.loadTexture( 'images/particleB.png' ); - + particleTexture = THREE.ImageUtils.loadTexture( 'images/particleB.png' ); particleGroup = new THREE.Object3D(); particleAttributes = { startSize: [], startPosition: [], randomness: [] }; - var totalParticles = 1; - var radiusRange = 10; + totalParticles = 1; + radiusRange = 10; for( var i = 0; i < totalParticles; i++ ) { - var spriteMaterial = new THREE.SpriteMaterial( { + spriteMaterial = new THREE.SpriteMaterial( { map: particleTexture, useScreenCoordinates: false, color: 0xffffff, size: 100, transparent: true }); - var sprite = new THREE.Sprite( spriteMaterial ); + sprite = new THREE.Sprite( spriteMaterial ); sprite.scale.set( 30, 10, 1.0 ); // imageWidth, imageHeight sprite.position.set( 0, 0, 0 ); sprite.position.setLength( radiusRange * (Math.random() * 0.1 + 0.9) ); @@ -130,7 +121,6 @@ DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ sprite.material.blending = THREE.AdditiveBlending; // "glowing" particles particleGroup.add( sprite ); particleAttributes.startPosition.push( sprite.position.clone() ); - //particleAttributes.randomness.push( Math.random() ); } particleGroup.position.y = 300; particleGroup.position.x = 300; @@ -138,6 +128,6 @@ DAT.Globe.prototype.addLineTexture = function(pointData, x,y,z){ } DAT.Globe.prototype.updateLineTexture = function(pointData, x,y,z, max){ - var particle = textures[pointData.id]; - particle.updateParticle(x,y,z, max); + var particle = textures[pointData.id]; + particle.updateParticle(x,y,z, max); } diff --git a/assets/globe/globe.js b/assets/globe/globe.js index 74a0df0..0920402 100644 --- a/assets/globe/globe.js +++ b/assets/globe/globe.js @@ -91,7 +91,6 @@ DAT.Globe = function(container, opts) { uniforms['texture'].value = THREE.ImageUtils.loadTexture(imgDir+'world.jpg'); material = new THREE.ShaderMaterial({ - uniforms: uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader @@ -127,21 +126,15 @@ DAT.Globe = function(container, opts) { renderer.setSize(w, h); renderer.domElement.style.position = 'absolute'; - container.appendChild(renderer.domElement); container.addEventListener('mousedown', onMouseDown, false); - container.addEventListener('mousewheel', onMouseWheel, false); - document.addEventListener('keydown', onDocumentKeyDown, false); - window.addEventListener('resize', onWindowResize, false); - container.addEventListener('mouseover', function() { overRenderer = true; }, false); - container.addEventListener('mouseout', function() { overRenderer = false; }, false); @@ -213,7 +206,7 @@ DAT.Globe = function(container, opts) { } } - function onWindowResize( event ) { + function onWindowResize(event) { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); @@ -231,7 +224,6 @@ DAT.Globe = function(container, opts) { render(); } - function render() { zoom(curZoomSpeed); @@ -249,7 +241,7 @@ DAT.Globe = function(container, opts) { } - this.addPoint = function(obj){ + this.addPoint = function(obj) { for (var i = 0; i < this.locations.length; i++) { if(this.locations[i].id == obj.id){ this.locations[i].timestamps = obj.timestamps; @@ -260,18 +252,17 @@ DAT.Globe = function(container, opts) { return obj; }; - this.updatePoints = function(){ - var all = [], magnitude, ts, now = Date.now(), msecs, locationsKeep = [], timestampsKeep; - - - for (var i = 0; i < this.locations.length; i++) { - magnitude = 0; - timestampsKeep = []; - /** - * Check each timestamp in the location and see if it is recent - * enough to be rendered. - */ - for (var k = 0; k < this.locations[i].timestamps.length; k++) { + this.updatePoints = function() { + var all = [], magnitude, ts, now = Date.now(), msecs, locationsKeep = [], timestampsKeep; + + for (var i = 0; i < this.locations.length; i++) { + magnitude = 0; + timestampsKeep = []; + /** + * Check each timestamp in the location and see if it is recent + * enough to be rendered. + */ + for (var k = 0; k < this.locations[i].timestamps.length; k++) { ts = this.locations[i].timestamps[k]; msecs = now - ts; @@ -280,19 +271,20 @@ DAT.Globe = function(container, opts) { /** Only keep relevant timestamps for later. */ timestampsKeep.push(ts); } - } - if (magnitude) { + } + if (magnitude) { this.locations[i].magnitude = magnitude/this.locations[i].timestamps.length; this.addData(this.locations[i]); this.locations[i].timestamps = timestampsKeep; /** Save locations with visible timestamps for later. */ locationsKeep.push(this.locations[i]); - } - } - /** Overwrite the old locations with the useful ones. */ - this.locations = locationsKeep; - }; + } + } + + /** Overwrite the old locations with the useful ones. */ + this.locations = locationsKeep; + }; /** * Receives a point object and represent it on the globe. If the point already existed, @@ -302,22 +294,22 @@ DAT.Globe = function(container, opts) { * - longitude * - magnitude */ - this.addData = function(point){ + this.addData = function(point) { //-32.9479009,-60.6650597, 0.7 var lat = point.latitude, lng = point.longitude, mag = point.magnitude, - phi = (90 - lat) * Math.PI / 180, - theta = (180 - lng) * Math.PI / 180, - scale = 200*(1+point.magnitude), - line, geometry, - x = scale * Math.sin(phi) * Math.cos(theta), - y = scale * Math.cos(phi), - z = scale * Math.sin(phi) * Math.sin(theta), - color = new THREE.Color(), - vertex, material; + phi = (90 - lat) * Math.PI / 180, + theta = (180 - lng) * Math.PI / 180, + scale = 200*(1+point.magnitude), + line, geometry, + x = scale * Math.sin(phi) * Math.cos(theta), + y = scale * Math.cos(phi), + z = scale * Math.sin(phi) * Math.sin(theta), + color = new THREE.Color(), + vertex, material; color.setHSL( ( 0.6 - ( point.magnitude * 0.5 ) ), 1.0, 0.5 ); - if (undefined === point.vertex){ + if (undefined === point.vertex) { // the point is not in the globe yet vertex = new THREE.Vector3(x, y, z); geometry = new THREE.CubeGeometry(0.75, 0.75, 1); @@ -332,7 +324,7 @@ DAT.Globe = function(container, opts) { point.vertex = vertex; point.geo = geometry; point.material = material; - }else{ + } else { // The point is already in the globe point.material.color = color; point.vertex.setX(x); @@ -344,7 +336,5 @@ DAT.Globe = function(container, opts) { } this.animate = animate; - init(); - } From c25092bd1820f2703e28500d227d854e1f7d2732 Mon Sep 17 00:00:00 2001 From: Agustin Paschetta Date: Wed, 3 Dec 2014 14:04:12 -0300 Subject: [PATCH 15/15] Add bower.json, add underscore.js and update the way to add a timestamp to an existing line --- .bowerrc | 3 +++ .gitignore | 3 +++ assets/globe/boot.js | 11 +++++++++-- bower.json | 19 +++++++++++++++++++ views/globe/main.ejs | 1 + 5 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .bowerrc create mode 100644 bower.json diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..9b9a0f0 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "assets/bower_components/" +} diff --git a/.gitignore b/.gitignore index d04b048..20c9f1c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ node_modules # Temp directory .tmp + +#Bower components +assets/bower_components/ diff --git a/assets/globe/boot.js b/assets/globe/boot.js index dfecb0d..32aeb40 100644 --- a/assets/globe/boot.js +++ b/assets/globe/boot.js @@ -9,8 +9,15 @@ if(!Detector.webgl){ globe.animate(); globe.parsePoints = function(list) { - this.locations = this.locations.concat(list); - for (var i = 0; i < list.length; i++) { + if(list[0].id){ + //this.locations = _.without(this.locations, _.findWhere(this.locations, {id: list[0].id})); + var match = _.find(this.locations, function(item) { return item.id === list[0].id}); + if(match){ + match.timestamps = list[0].timestamps + } + this.locations = this.locations.concat(list); + } + for (var i = 0; i < list.length; i++) { this.addPoint(list[i]); } }; diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..475f390 --- /dev/null +++ b/bower.json @@ -0,0 +1,19 @@ +{ + "name": "nattyglobe", + "version": "0.0.0", + "homepage": "https://github.com/lsvx/nattyglobe", + "authors": [ + "natgeo" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "underscore": "~1.7.0" + } +} diff --git a/views/globe/main.ejs b/views/globe/main.ejs index 29ff2ad..48576ac 100644 --- a/views/globe/main.ejs +++ b/views/globe/main.ejs @@ -72,6 +72,7 @@ +