From 8e0a07aedeb65762c216b498e824ca38190dafa6 Mon Sep 17 00:00:00 2001 From: Matt Huntington Date: Tue, 24 Jun 2014 17:43:30 -0400 Subject: [PATCH] adding finished product --- img/BasketballColor.jpg | Bin 0 -> 28454 bytes img/earth.jpg | Bin 0 -> 245064 bytes index.html | 18 + js/app.js | 288 + js/three.js | 37760 ++++++++++++++++++++++++++++++++++++++ js/three.min.js | 722 + 6 files changed, 38788 insertions(+) create mode 100755 img/BasketballColor.jpg create mode 100644 img/earth.jpg create mode 100755 index.html create mode 100644 js/app.js create mode 100755 js/three.js create mode 100755 js/three.min.js diff --git a/img/BasketballColor.jpg b/img/BasketballColor.jpg new file mode 100755 index 0000000000000000000000000000000000000000..abb59ce4f530fc555234aa2939ed5385b7babd2c GIT binary patch literal 28454 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

IgmOiMfyofnAox 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�TqbZC+_p-&y?Y1 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 + + + Robot + + + + + + +

+ + diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..bbe7034 --- /dev/null +++ b/js/app.js @@ -0,0 +1,288 @@ +//Matt Huntington +$(function(){ + function debugAxes(parent){ + var xLineGeometry = new THREE.Geometry(); + var xLineMat = new THREE.LineBasicMaterial({color: 0xff0000, lineWidth: 5}); + xLineGeometry.vertices.push(new THREE.Vertex(new THREE.Vector3(1,0,0)), new THREE.Vertex(new THREE.Vector3(0,0,0))); + var x_line = new THREE.Line(xLineGeometry, xLineMat); + + var yLineGeometry = new THREE.Geometry(); + var yLineMat = new THREE.LineBasicMaterial({color: 0x00ff00, lineWidth: 5}); + yLineGeometry.vertices.push(new THREE.Vertex(new THREE.Vector3(0,1,0)), new THREE.Vertex(new THREE.Vector3(0,0,0))); + var y_line = new THREE.Line(yLineGeometry, yLineMat); + + var zLineGeometry = new THREE.Geometry(); + var zLineMat = new THREE.LineBasicMaterial({color: 0x0000ff, lineWidth: 5}); + zLineGeometry.vertices.push(new THREE.Vertex(new THREE.Vector3(0,0,1)), new THREE.Vertex(new THREE.Vector3(0,0,0))); + var z_line = new THREE.Line(zLineGeometry, zLineMat); + + parent.add(x_line); + parent.add(y_line); + parent.add(z_line); + } + // Create the Three.js renderer, add it to our div + var container = document.getElementById("container"); + var renderer = new THREE.WebGLRenderer(); + renderer.shadowMapEnabled = true; + renderer.setSize(container.offsetWidth, container.offsetHeight); + renderer.setClearColor(0x0000cc, 1); + container.appendChild( renderer.domElement ); + var scene = new THREE.Scene(); + + // Create a camera and add it to the scene + var camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 4000 ); + camera.position.set( 0, 7, 7 ); + camera.rotation.x = Math.PI * -1 / 4; + scene.add( camera ); + + // Orthographic camera + var ortho_camera = new THREE.OrthographicCamera( + 10 / -2, // Left + 10 / 2, // Right + 10 / 2, // Top + 10 / -2, // Bottom + 0, // Near clipping plane + 1000 ); // Far clipping plane + ortho_camera.position.set(0,7,0); + //ortho_camera.position.set(0,0,7); + ortho_camera.rotation.x = -1 * Math.PI/2; + scene.add(ortho_camera); + + // Create Light + var spotLight = new THREE.SpotLight( 0xffffff ); + spotLight.position.set( 5, 5, 5 ); + spotLight.castShadow = true; + spotLight.shadowMapWidth = 1024; + spotLight.shadowMapHeight = 1024; + spotLight.shadowCameraNear = 1; + spotLight.shadowCameraFar = 15; + spotLight.shadowCameraFov = 60; + scene.add( spotLight ); + + //Create Grass + var grass_material = new THREE.MeshPhongMaterial( { + ambient: 0x00dd00, + color: 0x00cc00, + specular: 0x009900, + shininess: 30, + shading: THREE.FlatShading } ); + var radius = 5; + var segments = 32; + var circleGeometry = new THREE.CircleGeometry( radius, segments ); + var circle = new THREE.Mesh( circleGeometry, grass_material ); + circle.rotation.x = -1 * Math.PI / 2; + circle.castShadow = true; + circle.receiveShadow = true; + scene.add( circle ); + + //Create Ball + var ball_container = new THREE.Object3D(); + var ball_geometry = new THREE.SphereGeometry(0.3,32,32); + var ball_material = new THREE.MeshPhongMaterial({map: THREE.ImageUtils.loadTexture('img/BasketballColor.jpg'),shininess:15}); + var ball = new THREE.Mesh(ball_geometry, ball_material); + ball.position.set(0,0.3,2); + ball.castShadow = true; + ball.receiveShadow = true; + ball_container.add(ball); + ball_container.rotation.y = 1.75 * Math.PI; + scene.add(ball_container); + + // Create Robot + var robot_group = new THREE.Object3D(); + var base_geometry = new THREE.CubeGeometry( 1, 0.5, 1 ); + var metal_material = new THREE.MeshPhongMaterial( { + ambient: 0x030303, + color: 0xdddddd, + specular: 0x999999, + shininess: 30, + shading: THREE.FlatShading } ); + var cube = new THREE.Mesh( base_geometry, metal_material ); + cube.position.set( 0, 0.7, 2); + cube.castShadow = true; + cube.receiveShadow = true; + + // Wheels + var wheel_geometry = new THREE.TorusGeometry(0.3, 0.15, 20, 100); + var wheel_material = new THREE.MeshLambertMaterial({ambient:0x222222, color:0x555555}); + var wheel1 = new THREE.Mesh( wheel_geometry, wheel_material ); + wheel1.position.set(0.5,-0.3,0.65); + wheel1.castShadow = true; + wheel1.receiveShadow = true; + var wheel2 = new THREE.Mesh( wheel_geometry, wheel_material ); + wheel2.position.set(-0.5,-0.3,0.65); + wheel2.castShadow = true; + wheel2.receiveShadow = true; + var wheel3 = new THREE.Mesh( wheel_geometry, wheel_material ); + wheel3.position.set(0.5,-0.3,-0.65); + wheel3.castShadow = true; + wheel3.receiveShadow = true; + var wheel4 = new THREE.Mesh( wheel_geometry, wheel_material ); + wheel4.position.set(-0.5,-0.3,-0.65); + wheel4.castShadow = true; + wheel4.receiveShadow = true; + cube.add(wheel1); + cube.add(wheel2); + cube.add(wheel3); + cube.add(wheel4); + + // Arm + var arm_up_angle = Math.PI / -4; + var arm_down_angle = Math.PI / -2.5; + var shoulder_geometry = new THREE.SphereGeometry(0.25,32,32); + var shoulder = new THREE.Mesh(shoulder_geometry, metal_material); + shoulder.castShadow = true; + shoulder.receiveShadow = true; + var arm_geometry = new THREE.CylinderGeometry( 0.1, 0.1, 1, 32 ); + var arm = new THREE.Mesh( arm_geometry, metal_material ); + arm.castShadow = true; + arm.receiveShadow = true; + shoulder.position.set(0,0.25,0); + shoulder.rotation.z = arm_up_angle; + arm.position.set(0,0.5,0); + var elbow_geometry = new THREE.SphereGeometry(0.15,32,32); + var elbow = new THREE.Mesh(elbow_geometry, metal_material); + elbow.position.set(0,0.5,0); + elbow.castShadow = true; + elbow.receiveShadow = true; + elbow.rotation.z = Math.PI / -4; + arm.add(elbow); + var forearm = new THREE.Mesh(arm_geometry, metal_material); + forearm.castShadow = true; + forearm.receiveShadow = true; + forearm.position.set(0,0.5,0); + var wrist = new THREE.Mesh(elbow_geometry, metal_material); + wrist.castShadow = true; + wrist.receiveShadow = true; + wrist.position.set(0,0.5,0); + wrist.rotation.z = -7 * Math.PI/20; + var finger_geometry = new THREE.TorusGeometry(0.5/Math.PI, 0.05, 20, 100, Math.PI); + + //Finger1 + var knuckle1 = new THREE.Object3D(); + var finger1 = new THREE.Mesh( finger_geometry, metal_material ); + finger1.castShadow = true; + finger1.receiveShadow = true; + finger1.rotation.z = Math.PI/-2; + finger1.position.set(0,0.5/Math.PI,0); + knuckle1.add(finger1); + knuckle1.position.set(0,0.075,0); + knuckle1.rotation.z = Math.PI / -4; + knuckle1.rotation.y = Math.PI; + wrist.add(knuckle1); + + //Finger2 + var knuckle2 = new THREE.Object3D(); + var finger2 = new THREE.Mesh( finger_geometry, metal_material ); + finger2.castShadow = true; + finger2.receiveShadow = true; + finger2.rotation.z = Math.PI/-2; + finger2.position.set(0,0.5/Math.PI,0); + knuckle2.add(finger2); + knuckle2.position.set(0,0.075,0); + knuckle2.rotation.z = Math.PI / -4; + knuckle2.rotation.y = Math.PI / -3; + wrist.add(knuckle2); + + //Finger3 + var knuckle3 = new THREE.Object3D(); + var finger3 = new THREE.Mesh( finger_geometry, metal_material ); + finger3.castShadow = true; + finger3.receiveShadow = true; + finger3.rotation.z = Math.PI/-2; + finger3.position.set(0,0.5/Math.PI,0); + knuckle3.add(finger3); + knuckle3.position.set(0,0.075,0); + knuckle3.rotation.z = Math.PI / -4; + knuckle3.rotation.y = Math.PI / 3; + wrist.add(knuckle3); + + forearm.add(wrist); + elbow.add(forearm); + shoulder.add(arm); + cube.add(shoulder); + robot_group.add(cube); + scene.add( robot_group ); + + var test_cube = new THREE.Mesh(base_geometry, grass_material); + test_cube.position.set(0,-0.25,0); + test_cube.scale.set(5,1,1); + //scene.add(test_cube); + + // Render it + var angle_between_robot_and_ball; + var angle_between_tangent_and_line_to_ball; + var robot_stopping_point; + function calculateAngles(){ + angle_between_robot_and_ball = (2 * Math.asin(((Math.cos(Math.PI/10)+Math.sin(7*Math.PI/20))/2)/2)); + angle_between_tangent_and_line_to_ball = Math.PI/2 - (Math.PI-Math.PI/2-angle_between_robot_and_ball/2); + robot_stopping_point = ball_container.rotation.y - angle_between_robot_and_ball; + if(robot_stopping_point <= 0){ + robot_stopping_point = robot_stopping_point + 2 * Math.PI; + } + } + var picked_up_ball = false; + var resetting_arm = false; + var drive = true; + calculateAngles(); + run(); + function run(){ + //renderer.render( scene, ortho_camera ); + renderer.render( scene, camera ); + if(drive){ + robot_group.rotation.y += 0.01; + if(robot_group.rotation.y >= Math.PI * 2 && robot_group.rotation.y < Math.PI * 2 + 0.01){ + robot_group.rotation.y = 0; + } + if (robot_group.rotation.y > robot_stopping_point && robot_group.rotation.y < robot_stopping_point+0.01){ + robot_group.rotation.y = robot_stopping_point; + drive = false; + } else if (robot_group.rotation.y == robot_stopping_point){ + drive = false; + } + } else if(!resetting_arm && (shoulder.rotation.y < angle_between_tangent_and_line_to_ball || shoulder.rotation.z > arm_down_angle)) { + drive = false; + if(shoulder.rotation.y < angle_between_tangent_and_line_to_ball){ + shoulder.rotation.y += 0.01; + } + if(shoulder.rotation.z > arm_down_angle){ + shoulder.rotation.z -= 0.01; + } + } else if ( ball_container.parent.id != wrist.id && picked_up_ball == false ) { + scene.remove(ball_container); + var new_wrist_height = 0.25+0.7+Math.sin(Math.PI/10)-Math.cos(7*Math.PI/20); + ball_container.rotation.z = Math.PI; + ball.position.set(0, -1*new_wrist_height+0.3, 0); + ball_container.rotation.y = 0; + ball.rotation.y += angle_between_robot_and_ball/2; + wrist.add(ball_container); + picked_up_ball = true; + } else if ( ball_container.parent.id == wrist.id && shoulder.rotation.y < Math.PI - angle_between_tangent_and_line_to_ball) { + shoulder.rotation.y += 0.01; + } else if ( shoulder.rotation.y > 0) { + shoulder.rotation.y -= 0.01; + if(ball_container.parent == wrist){ + wrist.remove(ball_container); + ball.position.set(0,0.3,2); + ball_container.rotation.z = 0; + if(robot_group.rotation.y - angle_between_robot_and_ball <= 0){ + ball_container.rotation.y = 2 * Math.PI + robot_group.rotation.y - angle_between_robot_and_ball; + } else { + ball_container.rotation.y=robot_group.rotation.y - angle_between_robot_and_ball; + } + ball.rotation.y += Math.PI+angle_between_robot_and_ball/2;; + //ball.rotation.y -= angle_between_robot_and_ball/2; + scene.add(ball_container); + } + resetting_arm = true; + if(shoulder.rotation.z < arm_up_angle){ + shoulder.rotation.z += 0.01; + } + } else { + resetting_arm = false; + picked_up_ball = false; + drive = true; + calculateAngles(); + } + requestAnimationFrame(run); + } +}); diff --git a/js/three.js b/js/three.js new file mode 100755 index 0000000..83c9821 --- /dev/null +++ b/js/three.js @@ -0,0 +1,37760 @@ +/** + * @author mrdoob / http://mrdoob.com/ + * @author Larry Battle / http://bateru.com/news + * @author bhouston / http://exocortex.com + */ + +var THREE = { REVISION: '66' }; + +self.console = self.console || { + + info: function () {}, + log: function () {}, + debug: function () {}, + warn: function () {}, + error: function () {} + +}; + +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating + +// requestAnimationFrame polyfill by Erik Möller +// fixes from Paul Irish and Tino Zijdel +// using 'self' instead of 'window' for compatibility with both NodeJS and IE10. +( function () { + + var lastTime = 0; + var vendors = [ 'ms', 'moz', 'webkit', 'o' ]; + + for ( var x = 0; x < vendors.length && !self.requestAnimationFrame; ++ x ) { + + self.requestAnimationFrame = self[ vendors[ x ] + 'RequestAnimationFrame' ]; + self.cancelAnimationFrame = self[ vendors[ x ] + 'CancelAnimationFrame' ] || self[ vendors[ x ] + 'CancelRequestAnimationFrame' ]; + + } + + if ( self.requestAnimationFrame === undefined && self['setTimeout'] !== undefined ) { + + self.requestAnimationFrame = function ( callback ) { + + var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) ); + var id = self.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall ); + lastTime = currTime + timeToCall; + return id; + + }; + + } + + if( self.cancelAnimationFrame === undefined && self['clearTimeout'] !== undefined ) { + + self.cancelAnimationFrame = function ( id ) { self.clearTimeout( id ) }; + + } + +}() ); + +// 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; +*/ + +/** + * @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 }; + +/** + * @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, + + _euler: undefined, + + _updateEuler: function ( callback ) { + + if ( this._euler !== undefined ) { + + this._euler.setFromQuaternion( this, undefined, false ); + + } + + }, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this._updateEuler(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this._updateEuler(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this._updateEuler(); + + }, + + get w () { + + return this._w; + + }, + + set w ( value ) { + + this._w = value; + this._updateEuler(); + + }, + + set: function ( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this._updateEuler(); + + return this; + + }, + + copy: function ( quaternion ) { + + this._x = quaternion._x; + this._y = quaternion._y; + this._z = quaternion._z; + this._w = quaternion._w; + + this._updateEuler(); + + return this; + + }, + + setFromEuler: function ( euler, update ) { + + if ( euler instanceof THREE.Euler === false ) { + + throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + } + + // 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._updateEuler(); + + return this; + + }, + + setFromAxisAngle: function ( axis, angle ) { + + // from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + // axis have to be 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._updateEuler(); + + 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._updateEuler(); + + return this; + + }, + + inverse: function () { + + this.conjugate().normalize(); + + return this; + + }, + + conjugate: function () { + + this._x *= -1; + this._y *= -1; + this._z *= -1; + + this._updateEuler(); + + return this; + + }, + + 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; + + } + + return this; + + }, + + multiply: function ( q, p ) { + + if ( p !== undefined ) { + + console.warn( 'DEPRECATED: Quaternion\'s .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._updateEuler(); + + return this; + + }, + + multiplyVector3: function ( vector ) { + + console.warn( 'DEPRECATED: Quaternion\'s .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._updateEuler(); + + 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._updateEuler(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._w ]; + + }, + + 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 ); + +} + +/** + * @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( 'DEPRECATED: Vector2\'s .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( 'DEPRECATED: Vector2\'s .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; + + }, + + multiplyScalar: function ( s ) { + + this.x *= s; + this.y *= s; + + 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 () { + + return this.multiplyScalar( - 1 ); + + }, + + 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 ); + + } + +}; + +/** + * @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( 'DEPRECATED: Vector3\'s .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( 'DEPRECATED: Vector3\'s .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( 'DEPRECATED: Vector3\'s .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( 'ERROR: Vector3\'s .applyEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + + } + + 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 () { + + return this.multiplyScalar( - 1 ); + + }, + + 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( 'DEPRECATED: Vector3\'s .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( "REMOVED: Vector3\'s setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code."); + + }, + + setEulerFromQuaternion: function ( q, order ) { + + console.error( "REMOVED: Vector3\'s setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code."); + + }, + + getPositionFromMatrix: function ( m ) { + + console.warn( "DEPRECATED: Vector3\'s .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code." ); + + return this.setFromMatrixPosition( m ); + + }, + + getScaleFromMatrix: function ( m ) { + + console.warn( "DEPRECATED: Vector3\'s .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code." ); + + return this.setFromMatrixScale( m ); + }, + + getColumnFromMatrix: function ( index, matrix ) { + + console.warn( "DEPRECATED: Vector3\'s .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code." ); + + 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 ); + + } + +}; +/** + * @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( 'DEPRECATED: Vector4\'s .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( 'DEPRECATED: Vector4\'s .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 () { + + return this.multiplyScalar( -1 ); + + }, + + 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 ); + + } + +}; + +/** + * @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, + + _quaternion: undefined, + + _updateQuaternion: function () { + + if ( this._quaternion !== undefined ) { + + this._quaternion.setFromEuler( this, false ); + + } + + }, + + get x () { + + return this._x; + + }, + + set x ( value ) { + + this._x = value; + this._updateQuaternion(); + + }, + + get y () { + + return this._y; + + }, + + set y ( value ) { + + this._y = value; + this._updateQuaternion(); + + }, + + get z () { + + return this._z; + + }, + + set z ( value ) { + + this._z = value; + this._updateQuaternion(); + + }, + + get order () { + + return this._order; + + }, + + set order ( value ) { + + this._order = value; + this._updateQuaternion(); + + }, + + set: function ( x, y, z, order ) { + + this._x = x; + this._y = y; + this._z = z; + this._order = order || this._order; + + this._updateQuaternion(); + + return this; + + }, + + copy: function ( euler ) { + + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; + + this._updateQuaternion(); + + return this; + + }, + + setFromRotationMatrix: function ( m, order ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + // clamp, to handle numerical problems + + function clamp( x ) { + + return Math.min( Math.max( x, -1 ), 1 ); + + } + + 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 ) ); + + 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 ) ); + + 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 ) ); + + 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 ) ); + + 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 ) ); + + 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 ) ); + + 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( 'WARNING: Euler.setFromRotationMatrix() given unsupported order: ' + order ) + + } + + this._order = order; + + this._updateQuaternion(); + + return this; + + }, + + setFromQuaternion: function ( q, order, update ) { + + // q is assumed to be normalized + + // clamp, to handle numerical problems + + function clamp( x ) { + + return Math.min( Math.max( x, -1 ), 1 ); + + } + + // 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 ) ) ); + 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 ) ) ); + 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 ) ) ); + 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 ) ) ); + 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 ) ) ); + + } 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 ) ) ); + + } else { + + console.warn( 'WARNING: Euler.setFromQuaternion() given unsupported order: ' + order ) + + } + + this._order = order; + + if ( update !== false ) this._updateQuaternion(); + + 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 ); + + }; + + + }(), + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + + this._updateQuaternion(); + + return this; + + }, + + toArray: function () { + + return [ this._x, this._y, this._z, this._order ]; + + }, + + equals: function ( euler ) { + + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + + }, + + clone: function () { + + return new THREE.Euler( this._x, this._y, this._z, this._order ); + + } + +}; + +/** + * @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 ); + + } + +}; + +/** + * @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 ) { + + if ( points.length > 0 ) { + + var point = points[ 0 ]; + + this.min.copy( point ); + this.max.copy( point ); + + for ( var i = 1, il = points.length; i < il; i ++ ) { + + point = points[ i ]; + + if ( point.x < this.min.x ) { + + this.min.x = point.x; + + } else if ( point.x > this.max.x ) { + + this.max.x = point.x; + + } + + if ( point.y < this.min.y ) { + + this.min.y = point.y; + + } else if ( point.y > this.max.y ) { + + this.max.y = point.y; + + } + + } + + } else { + + this.makeEmpty(); + + } + + 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 ); + + } + +}; + +/** + * @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; + + }, + + addPoint: function ( point ) { + + if ( point.x < this.min.x ) { + + this.min.x = point.x; + + } else if ( point.x > this.max.x ) { + + this.max.x = point.x; + + } + + if ( point.y < this.min.y ) { + + this.min.y = point.y; + + } else if ( point.y > this.max.y ) { + + this.max.y = point.y; + + } + + if ( point.z < this.min.z ) { + + this.min.z = point.z; + + } else if ( point.z > this.max.z ) { + + this.max.z = point.z; + + } + + }, + + setFromPoints: function ( points ) { + + if ( points.length > 0 ) { + + var point = points[ 0 ]; + + this.min.copy( point ); + this.max.copy( point ); + + for ( var i = 1, il = points.length; i < il; i ++ ) { + + this.addPoint( points[ i ] ) + + } + + } else { + + this.makeEmpty(); + + } + + 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 ); + + } + +}; + +/** + * @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); + + this.set( + + ( n11 !== undefined ) ? n11 : 1, n12 || 0, n13 || 0, + n21 || 0, ( n22 !== undefined ) ? n22 : 1, n23 || 0, + n31 || 0, n32 || 0, ( 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( 'DEPRECATED: Matrix3\'s .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); + return vector.applyMatrix3( this ); + + }, + + multiplyVector3Array: function() { + + var v1 = new THREE.Vector3(); + + return function ( a ) { + + for ( var i = 0, il = a.length; i < il; i += 3 ) { + + v1.x = a[ i ]; + v1.y = a[ i + 1 ]; + v1.z = a[ i + 2 ]; + + v1.applyMatrix3(this); + + a[ i ] = v1.x; + a[ i + 1 ] = v1.y; + a[ i + 2 ] = v1.z; + + } + + return a; + + }; + + }(), + + 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; + + }, + + 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] + + ); + + } + +}; + +/** + * @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( 'DEPRECATED: Matrix4\'s .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( 'ERROR: Matrix\'s .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' ); + + } + + 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( 'DEPRECATED: Matrix4\'s .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion. Please update your code.' ); + + 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( 'DEPRECATED: Matrix4\'s .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( 'DEPRECATED: Matrix4\'s .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' ); + return vector.applyProjection( this ); + + }, + + multiplyVector4: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + + multiplyVector3Array: function() { + + var v1 = new THREE.Vector3(); + + return function ( a ) { + + for ( var i = 0, il = a.length; i < il; i += 3 ) { + + v1.x = a[ i ]; + v1.y = a[ i + 1 ]; + v1.z = a[ i + 2 ]; + + v1.applyProjection( this ); + + a[ i ] = v1.x; + a[ i + 1 ] = v1.y; + a[ i + 2 ] = v1.z; + + } + + return a; + + }; + + }(), + + rotateAxis: function ( v ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); + + v.transformDirection( this ); + + }, + + crossVector: function ( vector ) { + + console.warn( 'DEPRECATED: Matrix4\'s .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; + + }, + + flattenToArray: function ( flat ) { + + var te = this.elements; + flat[ 0 ] = te[0]; flat[ 1 ] = te[1]; flat[ 2 ] = te[2]; flat[ 3 ] = te[3]; + flat[ 4 ] = te[4]; flat[ 5 ] = te[5]; flat[ 6 ] = te[6]; flat[ 7 ] = te[7]; + flat[ 8 ] = te[8]; flat[ 9 ] = te[9]; flat[ 10 ] = te[10]; flat[ 11 ] = te[11]; + flat[ 12 ] = te[12]; flat[ 13 ] = te[13]; flat[ 14 ] = te[14]; flat[ 15 ] = te[15]; + + return flat; + + }, + + flattenToArrayOffset: function( flat, offset ) { + + var te = this.elements; + flat[ offset ] = te[0]; + flat[ offset + 1 ] = te[1]; + flat[ offset + 2 ] = te[2]; + flat[ offset + 3 ] = te[3]; + + flat[ offset + 4 ] = te[4]; + flat[ offset + 5 ] = te[5]; + flat[ offset + 6 ] = te[6]; + flat[ offset + 7 ] = te[7]; + + flat[ offset + 8 ] = te[8]; + flat[ offset + 9 ] = te[9]; + flat[ offset + 10 ] = te[10]; + flat[ offset + 11 ] = te[11]; + + flat[ offset + 12 ] = te[12]; + flat[ offset + 13 ] = te[13]; + flat[ offset + 14 ] = te[14]; + flat[ offset + 15 ] = te[15]; + + return flat; + + }, + + getPosition: function() { + + var v1 = new THREE.Vector3(); + + return function () { + + console.warn( 'DEPRECATED: Matrix4\'s .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( 'DEPRECATED: Matrix4\'s .translate() has been removed.'); + + }, + + rotateX: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateX() has been removed.'); + + }, + + rotateY: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateY() has been removed.'); + + }, + + rotateZ: function ( angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .rotateZ() has been removed.'); + + }, + + rotateByAxis: function ( axis, angle ) { + + console.warn( 'DEPRECATED: Matrix4\'s .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] + + ); + + } + +}; + +/** + * @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; + + }, + + 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 ); + + } + +}; + +/** + * @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 ); + + } + +}; + +/** + * @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 ); + + } + +}; + +/** + * @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 ); + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Math = { + + PI2: Math.PI * 2, + + 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; + } + +}; + +/** + * 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; + + }; + +}; + +/** + * @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 ); + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Vertex = function ( v ) { + + console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.') + return v; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.UV = function ( u, v ) { + + console.warn( 'THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.') + return new THREE.Vector2( u, v ); + +}; + +/** + * @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; + + } + +}; + +/** + * 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 () { + + var array = []; + + return function ( event ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + 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 ); + + } + + } + + }; + + }() + +}; + +/** + * @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; + + }; + + var sphere = new THREE.Sphere(); + var localRay = new THREE.Ray(); + var facePlane = new THREE.Plane(); + var intersectPoint = new THREE.Vector3(); + var matrixPosition = new THREE.Vector3(); + + var inverseMatrix = new THREE.Matrix4(); + + var descSort = function ( a, b ) { + + return a.distance - b.distance; + + }; + + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); + + var intersectObject = function ( object, raycaster, intersects ) { + + if ( object instanceof THREE.Sprite ) { + + matrixPosition.setFromMatrixPosition( object.matrixWorld ); + + var distance = raycaster.ray.distanceToPoint( matrixPosition ); + + if ( distance > object.scale.x ) { + + return intersects; + + } + + intersects.push( { + + distance: distance, + point: object.position, + face: null, + object: object + + } ); + + } else if ( object instanceof THREE.LOD ) { + + matrixPosition.setFromMatrixPosition( object.matrixWorld ); + var distance = raycaster.ray.origin.distanceTo( matrixPosition ); + + intersectObject( object.getObjectForDistance( distance ), raycaster, intersects ); + + } else if ( object instanceof THREE.Mesh ) { + + var geometry = object.geometry; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return intersects; + + } + + // Check boundingBox before continuing + + inverseMatrix.getInverse( object.matrixWorld ); + localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + if ( geometry.boundingBox !== null ) { + + if ( localRay.isIntersectionBox( geometry.boundingBox ) === false ) { + + return intersects; + + } + + } + + if ( geometry instanceof THREE.BufferGeometry ) { + + var material = object.material; + + if ( material === undefined ) return intersects; + + var attributes = geometry.attributes; + + var a, b, c; + var precision = raycaster.precision; + + if ( attributes.index !== undefined ) { + + var offsets = geometry.offsets; + var indices = attributes.index.array; + var positions = attributes.position.array; + + 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 = localRay.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.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: object + + } ); + + } + + } + + } else { + + var offsets = geometry.offsets; + var positions = attributes.position.array; + + for ( var i = 0, il = attributes.position.array.length; i < il; i += 3 ) { + + a = i; + b = i + 1; + c = 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 = localRay.intersectTriangle( vC, vB, vA, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.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: object + + } ); + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; + var objectMaterials = isFaceMaterial === true ? object.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 ] : object.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 = object.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 = localRay.intersectTriangle( c, b, a, true ); + + } else { + + var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide ); + + } + + if ( intersectionPoint === null ) continue; + + intersectionPoint.applyMatrix4( object.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: object + + } ); + + } + + } + + } else if ( object instanceof THREE.Line ) { + + var precision = raycaster.linePrecision; + var precisionSq = precision * precision; + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + // Checking boundingSphere distance to ray + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( object.matrixWorld ); + + if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) { + + return intersects; + + } + + inverseMatrix.getInverse( object.matrixWorld ); + localRay.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 = object.type === THREE.LineStrip ? 1 : 2; + + for ( var i = 0; i < nbVertices - 1; i = i + step ) { + + var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + var distance = localRay.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( object.matrixWorld ), + face: null, + faceIndex: null, + object: object + + } ); + + } + + } + + } + + }; + + var intersectDescendants = function ( object, raycaster, intersects ) { + + var descendants = object.getDescendants(); + + for ( var i = 0, l = descendants.length; i < l; i ++ ) { + + intersectObject( descendants[ i ], raycaster, intersects ); + + } + }; + + // + + THREE.Raycaster.prototype.precision = 0.0001; + THREE.Raycaster.prototype.linePrecision = 1; + + THREE.Raycaster.prototype.set = function ( origin, direction ) { + + this.ray.set( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + }; + + THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) { + + var intersects = []; + + if ( recursive === true ) { + + intersectDescendants( object, this, intersects ); + + } + + intersectObject( object, this, intersects ); + + intersects.sort( descSort ); + + return intersects; + + }; + + THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) { + + var intersects = []; + + for ( var i = 0, l = objects.length; i < l; i ++ ) { + + intersectObject( objects[ i ], this, intersects ); + + if ( recursive === true ) { + + intersectDescendants( objects[ i ], this, intersects ); + + } + + } + + intersects.sort( descSort ); + + return intersects; + + }; + +}( THREE ) ); + +/** + * @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 = new THREE.Vector3( 0, 1, 0 ); + + this.position = new THREE.Vector3(); + this._rotation = new THREE.Euler(); + this._quaternion = new THREE.Quaternion(); + this.scale = new THREE.Vector3( 1, 1, 1 ); + + // keep rotation and quaternion in sync + + this._rotation._quaternion = this.quaternion; + this._quaternion._euler = this.rotation; + + this.renderDepth = null; + + this.rotationAutoUpdate = true; + + this.matrix = new THREE.Matrix4(); + this.matrixWorld = new THREE.Matrix4(); + + this.matrixAutoUpdate = true; + this.matrixWorldNeedsUpdate = true; + + this.visible = true; + + this.castShadow = false; + this.receiveShadow = false; + + this.frustumCulled = true; + + this.userData = {}; + +}; + + +THREE.Object3D.prototype = { + + constructor: THREE.Object3D, + + get rotation () { + return this._rotation; + }, + + set rotation ( value ) { + + this._rotation = value; + this._rotation._quaternion = this._quaternion; + this._quaternion._euler = this._rotation; + this._rotation._updateQuaternion(); + + }, + + get quaternion () { + return this._quaternion; + }, + + set quaternion ( value ) { + + this._quaternion = value; + this._quaternion._euler = this._rotation; + this._rotation._quaternion = this._quaternion; + this._quaternion._updateEuler(); + + }, + + get eulerOrder () { + + console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' ); + + return this.rotation.order; + + }, + + set eulerOrder ( value ) { + + console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' ); + + this.rotation.order = value; + + }, + + get useQuaternion () { + + console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + + set useQuaternion ( value ) { + + console.warn( 'DEPRECATED: Object3D\'s .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 ); + + v1.applyQuaternion( this.quaternion ); + + this.position.add( v1.multiplyScalar( distance ) ); + + return this; + + } + + }(), + + translate: function ( distance, axis ) { + + console.warn( 'DEPRECATED: Object3D\'s .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.' ); + 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 ( object === this ) { + + console.warn( 'THREE.Object3D.add: An object can\'t be added as a child of itself.' ); + return; + + } + + 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 ); + + } + + } + + }, + + remove: function ( object ) { + + 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 ); + + } + + } + + }, + + traverse: function ( callback ) { + + callback( this ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].traverse( 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( 'DEPRECATED: Object3D\'s .getChildByName() has been renamed to .getObjectByName().' ); + return this.getObjectByName( name, recursive ); + + }, + + getDescendants: function ( array ) { + + if ( array === undefined ) array = []; + + Array.prototype.push.apply( array, this.children ); + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + this.children[ i ].getDescendants( array ); + + } + + return array; + + }, + + 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; + +/** + * @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 projectObject = function ( object ) { + + if ( object.visible === false ) return; + + if ( object instanceof THREE.Light ) { + + _renderData.lights.push( object ); + + } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.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 ); + + } + + } + + for ( var i = 0, l = object.children.length; i < l; i ++ ) { + + projectObject( object.children[ i ] ); + + } + + }; + + var projectGraph = function ( root, sortObjects ) { + + _objectCount = 0; + + _renderData.objects.length = 0; + _renderData.lights.length = 0; + + projectObject( root ); + + if ( sortObjects === true ) { + + _renderData.objects.sort( painterSort ); + + } + + }; + + var RenderList = function () { + + var normals = []; + + var object = null; + var normalMatrix = new THREE.Matrix3(); + + var setObject = function ( value ) { + + object = value; + normalMatrix.getNormalMatrix( object.matrixWorld ); + + normals.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 checkTriangleVisibility = function ( v1, v2, v3 ) { + + _points3[ 0 ] = v1.positionScreen; + _points3[ 1 ] = v2.positionScreen; + _points3[ 2 ] = v3.positionScreen; + + if ( v1.visible === true || v2.visible === true || v3.visible === true || + _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) { + + 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; + + } + + return false; + + }; + + 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 ) === 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 + 0 ], normals[ offset + 1 ], normals[ offset + 2 ] ); + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + _face.vertexNormalsLength = 3; + + _face.material = object.material; + + _renderData.elements.push( _face ); + + } + + }; + + return { + setObject: setObject, + projectVertex: projectVertex, + checkTriangleVisibility: checkTriangleVisibility, + pushVertex: pushVertex, + pushNormal: pushNormal, + pushLine: pushLine, + pushTriangle: pushTriangle + } + + }; + + var renderList = new RenderList(); + + this.projectScene = function ( scene, camera, sortObjects, sortElements ) { + + var object, geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs, + isFaceMaterial, objectMaterials; + + _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 ); + + projectGraph( scene, sortObjects ); + + for ( var o = 0, ol = _renderData.objects.length; o < ol; o ++ ) { + + object = _renderData.objects[ o ].object; + 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 ] ); + + } + + 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.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 ) { + + vertices = geometry.vertices; + faces = geometry.faces; + faceVertexUvs = geometry.faceVertexUvs; + + _normalMatrix.getNormalMatrix( _modelMatrix ); + + isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial; + 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 ++ ) { + + 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 ); + + } + + var visible = renderList.checkTriangleVisibility( v1, v2, v3 ); + + if ( ( visible === false && side === THREE.FrontSide ) || + ( visible === true && side === THREE.BackSide ) ) 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(); + + _face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix ); + + 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; + + for ( var c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) { + + uvs = faceVertexUvs[ c ][ f ]; + + if ( uvs === undefined ) continue; + + for ( var u = 0, ul = uvs.length; u < ul; u ++ ) { + + _face.uvs[ c ][ u ] = uvs[ 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 { + + for ( var i = 0, l = ( positions.length / 3 ) - 1; i < l; i ++ ) { + + renderList.pushLine( i, i + 1 ); + + } + + } + + } + + } else if ( geometry instanceof THREE.Geometry ) { + + _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix ); + + 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; + + } + + } + + } + +}; + +/** + * @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; + + this.centroid = new THREE.Vector3(); + +}; + +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.centroid.copy( this.centroid ); + + face.materialIndex = this.materialIndex; + + var i, il; + for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone(); + for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone(); + for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone(); + + return face; + + } + +}; + +/** + * @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 ); + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.BufferGeometry = function () { + + this.id = THREE.GeometryIdCount ++; + this.uuid = THREE.Math.generateUUID(); + + this.name = ''; + + // attributes + + this.attributes = {}; + + // offsets for chunks when using indexed elements + + this.offsets = []; + + // boundings + + this.boundingBox = null; + this.boundingSphere = null; + +}; + +THREE.BufferGeometry.prototype = { + + constructor: THREE.BufferGeometry, + + addAttribute: function ( name, type, numItems, itemSize ) { + + this.attributes[ name ] = { + + array: new type( numItems * itemSize ), + itemSize: itemSize + + }; + + return this.attributes[ name ]; + + }, + + applyMatrix: function ( matrix ) { + + var position = this.attributes.position; + + if ( position !== undefined ) { + + matrix.multiplyVector3Array( position.array ); + position.needsUpdate = true; + + } + + var normal = this.attributes.normal; + + if ( normal !== undefined ) { + + var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + + normalMatrix.multiplyVector3Array( normal.array ); + normal.needsUpdate = true; + + } + + }, + + 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 ); + + } + + }, + + 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.addPoint( vector ); + + } + + box.center( center ); + + 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 ); + + } + + } + + }(), + + 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; + + 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; + + } + + }, + + 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; + + } + + }, + + 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; + }, + + /* + 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 ); + +/** + * @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 ParticleSystem 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; + +}; + +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(); + + } + + face.centroid.applyMatrix4( matrix ); + + } + + if ( this.boundingBox instanceof THREE.Box3 ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere instanceof THREE.Sphere ) { + + this.computeBoundingSphere(); + + } + + }, + + computeCentroids: function () { + + var f, fl, face; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + face.centroid.set( 0, 0, 0 ); + + face.centroid.add( this.vertices[ face.a ] ); + face.centroid.add( this.vertices[ face.b ] ); + face.centroid.add( this.vertices[ face.c ] ); + face.centroid.divideScalar( 3 ); + + } + + }, + + 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 ++ ) { + + face = this.faces[ 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 ); + + }, + + /* + * 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 ) { + + var f, fl, face, materialIndex, + groupHash, hash_map = {}; + + var numMorphTargets = this.morphTargets.length; + var numMorphNormals = this.morphNormals.length; + + this.geometryGroups = {}; + + 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 ) ) { + + this.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + + } + + if ( this.geometryGroups[ groupHash ].vertices + 3 > 65535 ) { + + hash_map[ materialIndex ].counter += 1; + groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; + + if ( ! ( groupHash in this.geometryGroups ) ) { + + this.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; + + } + + } + + this.geometryGroups[ groupHash ].faces3.push( f ); + this.geometryGroups[ groupHash ].vertices += 3; + + } + + this.geometryGroupsList = []; + + for ( var g in this.geometryGroups ) { + + this.geometryGroups[ g ].id = geometryGroupCounter ++; + + this.geometryGroupsList.push( this.geometryGroups[ g ] ); + + } + + }; + + } )(), + + 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; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Geometry2 = function ( size ) { + + THREE.BufferGeometry.call( this ); + + this.vertices = this.addAttribute( 'position', Float32Array, size, 3 ).array; + this.normals = this.addAttribute( 'normal', Float32Array, size, 3 ).array; + this.uvs = this.addAttribute( 'uv', Float32Array, size, 2 ).array; + + this.boundingBox = null; + this.boundingSphere = null; + +}; + +THREE.Geometry2.prototype = Object.create( THREE.BufferGeometry.prototype ); +/** + * @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; +}; + +/** + * @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; +}; + +/** + * @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; +}; + +/** + * @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; + +}; + +/** + * @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; + +}; + +/** + * @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 ); + + +/** + * @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; + + return light; + +}; + +/** + * @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; + +}; + +/** + * @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; + +}; + +/** + * @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; + + return light; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Loader = function ( showStatus ) { + + this.showStatus = showStatus; + this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null; + + 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 / 1000 ).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 ] = THREE.Loader.prototype.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 _this = this; + + function is_pow2( n ) { + + var l = Math.log( n ) / Math.LN2; + return Math.floor( l ) == l; + + } + + function nearest_pow2( n ) { + + var l = Math.log( n ) / Math.LN2; + return Math.pow( 2, Math.round( l ) ); + + } + + function load_image( where, url ) { + + var image = new Image(); + + image.onload = function () { + + if ( !is_pow2( this.width ) || !is_pow2( this.height ) ) { + + var width = nearest_pow2( this.width ); + var height = nearest_pow2( this.height ); + + where.image.width = width; + where.image.height = height; + where.image.getContext( '2d' ).drawImage( this, 0, 0, width, height ); + + } else { + + where.image = this; + + } + + where.needsUpdate = true; + + }; + + if ( _this.crossOrigin !== undefined ) image.crossOrigin = _this.crossOrigin; + image.src = url; + + } + + function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) { + + var isCompressed = /\.dds$/i.test( sourceFile ); + + var fullPath = texturePath + sourceFile; + + if ( isCompressed ) { + + var texture = THREE.ImageUtils.loadCompressedTexture( fullPath ); + + where[ name ] = texture; + + } else { + + var texture = document.createElement( 'canvas' ); + + where[ name ] = new THREE.Texture( texture ); + + } + + where[ name ].sourceFile = sourceFile; + + if( repeat ) { + + where[ name ].repeat.set( repeat[ 0 ], repeat[ 1 ] ); + + if ( repeat[ 0 ] !== 1 ) where[ name ].wrapS = THREE.RepeatWrapping; + if ( repeat[ 1 ] !== 1 ) where[ name ].wrapT = THREE.RepeatWrapping; + + } + + if ( offset ) { + + where[ name ].offset.set( offset[ 0 ], offset[ 1 ] ); + + } + + if ( wrap ) { + + var wrapMap = { + "repeat": THREE.RepeatWrapping, + "mirror": THREE.MirroredRepeatWrapping + } + + if ( wrapMap[ wrap[ 0 ] ] !== undefined ) where[ name ].wrapS = wrapMap[ wrap[ 0 ] ]; + if ( wrapMap[ wrap[ 1 ] ] !== undefined ) where[ name ].wrapT = wrapMap[ wrap[ 1 ] ]; + + } + + if ( anisotropy ) { + + where[ name ].anisotropy = anisotropy; + + } + + if ( ! isCompressed ) { + + load_image( where[ name ], fullPath ); + + } + + } + + 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 ); + + } + + // 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.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; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.XHRLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.XHRLoader.prototype = { + + constructor: THREE.XHRLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + var request = new XMLHttpRequest(); + + if ( onLoad !== undefined ) { + + request.addEventListener( 'load', function ( event ) { + + onLoad( event.target.responseText ); + 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; + + request.open( 'GET', url, true ); + request.send( null ); + + scope.manager.itemStart( url ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.ImageLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ImageLoader.prototype = { + + constructor: THREE.ImageLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + var image = document.createElement( 'img' ); + + if ( onLoad !== undefined ) { + + image.addEventListener( 'load', function ( event ) { + + scope.manager.itemEnd( url ); + onLoad( this ); + + }, 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; + + } + +} + +/** + * @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.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.computeCentroids(); + 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() { + + if ( json.skinWeights ) { + + for ( var i = 0, l = json.skinWeights.length; i < l; i += 2 ) { + + var x = json.skinWeights[ i ]; + var y = json.skinWeights[ i + 1 ]; + var z = 0; + var w = 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 += 2 ) { + + var a = json.skinIndices[ i ]; + var b = json.skinIndices[ i + 1 ]; + var c = 0; + var d = 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 ) { + + return { geometry: geometry }; + + } else { + + var materials = this.initMaterials( json.materials, texturePath ); + + if ( this.needsTangents( materials ) ) { + + geometry.computeTangents(); + + } + + return { geometry: geometry, materials: materials }; + + } + +}; + +/** + * @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(); + +/** + * @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 ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.BufferGeometry(); + + var attributes = json.attributes; + var offsets = json.offsets; + var boundingSphere = json.boundingSphere; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + + geometry.attributes[ key ] = { + itemSize: attribute.itemSize, + array: new self[ attribute.type ]( attribute.array ) + } + + } + + if ( offsets !== undefined ) { + + geometry.offsets = JSON.parse( JSON.stringify( offsets ) ); + + } + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new THREE.Sphere( + new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ), + boundingSphere.radius + ); + + } + + return geometry; + + } + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.Geometry2Loader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.Geometry2Loader.prototype = { + + constructor: THREE.Geometry2Loader, + + 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 ) ) ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json ) { + + var geometry = new THREE.Geometry2( json.vertices.length / 3 ); + + var attributes = [ 'vertices', 'normals', 'uvs' ]; + var boundingSphere = json.boundingSphere; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + geometry[ attribute ].set( json[ attribute ] ); + + } + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new THREE.Sphere( + new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ), + boundingSphere.radius + ); + + } + + return geometry; + + } + +}; + +/** + * @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 ) ) ); + + } ); + + }, + + 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.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; + + } + +}; + +/** + * @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 ) ) ); + + } ); + + }, + + 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 geometry2Loader = new THREE.Geometry2Loader(); + 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': // DEPRECATED + + 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 'Geometry2': + + geometry = geometry2Loader.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; + + } + + }() + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SceneLoader = function () { + + this.onLoadStart = function () {}; + this.onLoadProgress = function() {}; + this.onLoadComplete = function () {}; + + this.callbackSync = function () {}; + this.callbackProgress = function () {}; + + this.geometryHandlers = {}; + this.hierarchyHandlers = {}; + + this.addGeometryHandler( "ascii", THREE.JSONLoader ); + +}; + +THREE.SceneLoader.prototype = { + + constructor: THREE.SceneLoader, + + 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 ) { + + scope.parse( JSON.parse( text ), onLoad, url ); + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + addGeometryHandler: function ( typeID, loaderClass ) { + + this.geometryHandlers[ typeID ] = { "loaderClass": loaderClass }; + + }, + + addHierarchyHandler: function ( typeID, loaderClass ) { + + this.hierarchyHandlers[ typeID ] = { "loaderClass": loaderClass }; + + }, + + parse: function ( json, callbackFinished, url ) { + + var scope = this; + + var urlBase = THREE.Loader.prototype.extractUrlBase( url ); + + var geometry, material, camera, fog, + texture, images, color, + light, hex, intensity, + counter_models, counter_textures, + total_models, total_textures, + result; + + var target_array = []; + + var data = json; + + // async geometry loaders + + for ( var typeID in this.geometryHandlers ) { + + var loaderClass = this.geometryHandlers[ typeID ][ "loaderClass" ]; + this.geometryHandlers[ typeID ][ "loaderObject" ] = new loaderClass(); + + } + + // async hierachy loaders + + for ( var typeID in this.hierarchyHandlers ) { + + var loaderClass = this.hierarchyHandlers[ typeID ][ "loaderClass" ]; + this.hierarchyHandlers[ typeID ][ "loaderObject" ] = new loaderClass(); + + } + + counter_models = 0; + counter_textures = 0; + + result = { + + scene: new THREE.Scene(), + geometries: {}, + face_materials: {}, + materials: {}, + textures: {}, + objects: {}, + cameras: {}, + lights: {}, + fogs: {}, + empties: {}, + groups: {} + + }; + + if ( data.transform ) { + + var position = data.transform.position, + rotation = data.transform.rotation, + scale = data.transform.scale; + + if ( position ) { + + result.scene.position.fromArray( position ); + + } + + if ( rotation ) { + + result.scene.rotation.fromArray( rotation ); + + } + + if ( scale ) { + + result.scene.scale.fromArray( scale ); + + } + + if ( position || rotation || scale ) { + + result.scene.updateMatrix(); + result.scene.updateMatrixWorld(); + + } + + } + + function get_url( source_url, url_type ) { + + if ( url_type == "relativeToHTML" ) { + + return source_url; + + } else { + + return urlBase + source_url; + + } + + }; + + // toplevel loader function, delegates to handle_children + + function handle_objects() { + + handle_children( result.scene, data.objects ); + + } + + // handle all the children from the loaded json and attach them to given parent + + function handle_children( parent, children ) { + + var mat, dst, pos, rot, scl, quat; + + for ( var objID in children ) { + + // check by id if child has already been handled, + // if not, create new object + + var object = result.objects[ objID ]; + var objJSON = children[ objID ]; + + if ( object === undefined ) { + + // meshes + + if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) { + + if ( objJSON.loading === undefined ) { + + var reservedTypes = { + "type": 1, "url": 1, "material": 1, + "position": 1, "rotation": 1, "scale" : 1, + "visible": 1, "children": 1, "userData": 1, + "skin": 1, "morph": 1, "mirroredLoop": 1, "duration": 1 + }; + + var loaderParameters = {}; + + for ( var parType in objJSON ) { + + if ( ! ( parType in reservedTypes ) ) { + + loaderParameters[ parType ] = objJSON[ parType ]; + + } + + } + + material = result.materials[ objJSON.material ]; + + objJSON.loading = true; + + var loader = scope.hierarchyHandlers[ objJSON.type ][ "loaderObject" ]; + + // ColladaLoader + + if ( loader.options ) { + + loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) ); + + // UTF8Loader + // OBJLoader + + } else { + + loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters ); + + } + + } + + } else if ( objJSON.geometry !== undefined ) { + + geometry = result.geometries[ objJSON.geometry ]; + + // geometry already loaded + + if ( geometry ) { + + var needsTangents = false; + + material = result.materials[ objJSON.material ]; + needsTangents = material instanceof THREE.ShaderMaterial; + + pos = objJSON.position; + rot = objJSON.rotation; + scl = objJSON.scale; + mat = objJSON.matrix; + quat = objJSON.quaternion; + + // use materials from the model file + // if there is no material specified in the object + + if ( ! objJSON.material ) { + + material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] ); + + } + + // use materials from the model file + // if there is just empty face material + // (must create new material as each model has its own face material) + + if ( ( material instanceof THREE.MeshFaceMaterial ) && material.materials.length === 0 ) { + + material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] ); + + } + + if ( material instanceof THREE.MeshFaceMaterial ) { + + for ( var i = 0; i < material.materials.length; i ++ ) { + + needsTangents = needsTangents || ( material.materials[ i ] instanceof THREE.ShaderMaterial ); + + } + + } + + if ( needsTangents ) { + + geometry.computeTangents(); + + } + + if ( objJSON.skin ) { + + object = new THREE.SkinnedMesh( geometry, material ); + + } else if ( objJSON.morph ) { + + object = new THREE.MorphAnimMesh( geometry, material ); + + if ( objJSON.duration !== undefined ) { + + object.duration = objJSON.duration; + + } + + if ( objJSON.time !== undefined ) { + + object.time = objJSON.time; + + } + + if ( objJSON.mirroredLoop !== undefined ) { + + object.mirroredLoop = objJSON.mirroredLoop; + + } + + if ( material.morphNormals ) { + + geometry.computeMorphNormals(); + + } + + } else { + + object = new THREE.Mesh( geometry, material ); + + } + + object.name = objID; + + if ( mat ) { + + object.matrixAutoUpdate = false; + object.matrix.set( + mat[0], mat[1], mat[2], mat[3], + mat[4], mat[5], mat[6], mat[7], + mat[8], mat[9], mat[10], mat[11], + mat[12], mat[13], mat[14], mat[15] + ); + + } else { + + object.position.fromArray( pos ); + + if ( quat ) { + + object.quaternion.fromArray( quat ); + + } else { + + object.rotation.fromArray( rot ); + + } + + object.scale.fromArray( scl ); + + } + + object.visible = objJSON.visible; + object.castShadow = objJSON.castShadow; + object.receiveShadow = objJSON.receiveShadow; + + parent.add( object ); + + result.objects[ objID ] = object; + + } + + // lights + + } else if ( objJSON.type === "AmbientLight" || objJSON.type === "PointLight" || + objJSON.type === "DirectionalLight" || objJSON.type === "SpotLight" || + objJSON.type === "HemisphereLight" || objJSON.type === "AreaLight" ) { + + var color = objJSON.color; + var intensity = objJSON.intensity; + var distance = objJSON.distance; + var position = objJSON.position; + var rotation = objJSON.rotation; + + switch ( objJSON.type ) { + + case 'AmbientLight': + light = new THREE.AmbientLight( color ); + break; + + case 'PointLight': + light = new THREE.PointLight( color, intensity, distance ); + light.position.fromArray( position ); + break; + + case 'DirectionalLight': + light = new THREE.DirectionalLight( color, intensity ); + light.position.fromArray( objJSON.direction ); + break; + + case 'SpotLight': + light = new THREE.SpotLight( color, intensity, distance, 1 ); + light.angle = objJSON.angle; + light.position.fromArray( position ); + light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] ); + light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) ); + break; + + case 'HemisphereLight': + light = new THREE.DirectionalLight( color, intensity, distance ); + light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] ); + light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) ); + break; + + case 'AreaLight': + light = new THREE.AreaLight(color, intensity); + light.position.fromArray( position ); + light.width = objJSON.size; + light.height = objJSON.size_y; + break; + + } + + parent.add( light ); + + light.name = objID; + result.lights[ objID ] = light; + result.objects[ objID ] = light; + + // cameras + + } else if ( objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera" ) { + + pos = objJSON.position; + rot = objJSON.rotation; + quat = objJSON.quaternion; + + if ( objJSON.type === "PerspectiveCamera" ) { + + camera = new THREE.PerspectiveCamera( objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far ); + + } else if ( objJSON.type === "OrthographicCamera" ) { + + camera = new THREE.OrthographicCamera( objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far ); + + } + + camera.name = objID; + camera.position.fromArray( pos ); + + if ( quat !== undefined ) { + + camera.quaternion.fromArray( quat ); + + } else if ( rot !== undefined ) { + + camera.rotation.fromArray( rot ); + + } + + parent.add( camera ); + + result.cameras[ objID ] = camera; + result.objects[ objID ] = camera; + + // pure Object3D + + } else { + + pos = objJSON.position; + rot = objJSON.rotation; + scl = objJSON.scale; + quat = objJSON.quaternion; + + object = new THREE.Object3D(); + object.name = objID; + object.position.fromArray( pos ); + + if ( quat ) { + + object.quaternion.fromArray( quat ); + + } else { + + object.rotation.fromArray( rot ); + + } + + object.scale.fromArray( scl ); + object.visible = ( objJSON.visible !== undefined ) ? objJSON.visible : false; + + parent.add( object ); + + result.objects[ objID ] = object; + result.empties[ objID ] = object; + + } + + if ( object ) { + + if ( objJSON.userData !== undefined ) { + + for ( var key in objJSON.userData ) { + + var value = objJSON.userData[ key ]; + object.userData[ key ] = value; + + } + + } + + if ( objJSON.groups !== undefined ) { + + for ( var i = 0; i < objJSON.groups.length; i ++ ) { + + var groupID = objJSON.groups[ i ]; + + if ( result.groups[ groupID ] === undefined ) { + + result.groups[ groupID ] = []; + + } + + result.groups[ groupID ].push( objID ); + + } + + } + + } + + } + + if ( object !== undefined && objJSON.children !== undefined ) { + + handle_children( object, objJSON.children ); + + } + + } + + }; + + function handle_mesh( geo, mat, id ) { + + result.geometries[ id ] = geo; + result.face_materials[ id ] = mat; + handle_objects(); + + }; + + function handle_hierarchy( node, id, parent, material, obj ) { + + var p = obj.position; + var r = obj.rotation; + var q = obj.quaternion; + var s = obj.scale; + + node.position.fromArray( p ); + + if ( q ) { + + node.quaternion.fromArray( q ); + + } else { + + node.rotation.fromArray( r ); + + } + + node.scale.fromArray( s ); + + // override children materials + // if object material was specified in JSON explicitly + + if ( material ) { + + node.traverse( function ( child ) { + + child.material = material; + + } ); + + } + + // override children visibility + // with root node visibility as specified in JSON + + var visible = ( obj.visible !== undefined ) ? obj.visible : true; + + node.traverse( function ( child ) { + + child.visible = visible; + + } ); + + parent.add( node ); + + node.name = id; + + result.objects[ id ] = node; + handle_objects(); + + }; + + function create_callback_geometry( id ) { + + return function ( geo, mat ) { + + geo.name = id; + + handle_mesh( geo, mat, id ); + + counter_models -= 1; + + scope.onLoadComplete(); + + async_callback_gate(); + + } + + }; + + function create_callback_hierachy( id, parent, material, obj ) { + + return function ( event ) { + + var result; + + // loaders which use EventDispatcher + + if ( event.content ) { + + result = event.content; + + // ColladaLoader + + } else if ( event.dae ) { + + result = event.scene; + + + // UTF8Loader + + } else { + + result = event; + + } + + handle_hierarchy( result, id, parent, material, obj ); + + counter_models -= 1; + + scope.onLoadComplete(); + + async_callback_gate(); + + } + + }; + + function create_callback_embed( id ) { + + return function ( geo, mat ) { + + geo.name = id; + + result.geometries[ id ] = geo; + result.face_materials[ id ] = mat; + + } + + }; + + function async_callback_gate() { + + var progress = { + + totalModels : total_models, + totalTextures : total_textures, + loadedModels : total_models - counter_models, + loadedTextures : total_textures - counter_textures + + }; + + scope.callbackProgress( progress, result ); + + scope.onLoadProgress(); + + if ( counter_models === 0 && counter_textures === 0 ) { + + finalize(); + callbackFinished( result ); + + } + + }; + + function finalize() { + + // take care of targets which could be asynchronously loaded objects + + for ( var i = 0; i < target_array.length; i ++ ) { + + var ta = target_array[ i ]; + + var target = result.objects[ ta.targetName ]; + + if ( target ) { + + ta.object.target = target; + + } else { + + // if there was error and target of specified name doesn't exist in the scene file + // create instead dummy target + // (target must be added to scene explicitly as parent is already added) + + ta.object.target = new THREE.Object3D(); + result.scene.add( ta.object.target ); + + } + + ta.object.target.userData.targetInverse = ta.object; + + } + + }; + + var callbackTexture = function ( count ) { + + counter_textures -= count; + async_callback_gate(); + + scope.onLoadComplete(); + + }; + + // must use this instead of just directly calling callbackTexture + // because of closure in the calling context loop + + var generateTextureCallback = function ( count ) { + + return function () { + + callbackTexture( count ); + + }; + + }; + + function traverse_json_hierarchy( objJSON, callback ) { + + callback( objJSON ); + + if ( objJSON.children !== undefined ) { + + for ( var objChildID in objJSON.children ) { + + traverse_json_hierarchy( objJSON.children[ objChildID ], callback ); + + } + + } + + }; + + // first go synchronous elements + + // fogs + + var fogID, fogJSON; + + for ( fogID in data.fogs ) { + + fogJSON = data.fogs[ fogID ]; + + if ( fogJSON.type === "linear" ) { + + fog = new THREE.Fog( 0x000000, fogJSON.near, fogJSON.far ); + + } else if ( fogJSON.type === "exp2" ) { + + fog = new THREE.FogExp2( 0x000000, fogJSON.density ); + + } + + color = fogJSON.color; + fog.color.setRGB( color[0], color[1], color[2] ); + + result.fogs[ fogID ] = fog; + + } + + // now come potentially asynchronous elements + + // geometries + + // count how many geometries will be loaded asynchronously + + var geoID, geoJSON; + + for ( geoID in data.geometries ) { + + geoJSON = data.geometries[ geoID ]; + + if ( geoJSON.type in this.geometryHandlers ) { + + counter_models += 1; + + scope.onLoadStart(); + + } + + } + + // count how many hierarchies will be loaded asynchronously + + for ( var objID in data.objects ) { + + traverse_json_hierarchy( data.objects[ objID ], function ( objJSON ) { + + if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) { + + counter_models += 1; + + scope.onLoadStart(); + + } + + }); + + } + + total_models = counter_models; + + for ( geoID in data.geometries ) { + + geoJSON = data.geometries[ geoID ]; + + if ( geoJSON.type === "cube" ) { + + geometry = new THREE.BoxGeometry( geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "plane" ) { + + geometry = new THREE.PlaneGeometry( geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "sphere" ) { + + geometry = new THREE.SphereGeometry( geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "cylinder" ) { + + geometry = new THREE.CylinderGeometry( geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "torus" ) { + + geometry = new THREE.TorusGeometry( geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type === "icosahedron" ) { + + geometry = new THREE.IcosahedronGeometry( geoJSON.radius, geoJSON.subdivisions ); + geometry.name = geoID; + result.geometries[ geoID ] = geometry; + + } else if ( geoJSON.type in this.geometryHandlers ) { + + var loaderParameters = {}; + + for ( var parType in geoJSON ) { + + if ( parType !== "type" && parType !== "url" ) { + + loaderParameters[ parType ] = geoJSON[ parType ]; + + } + + } + + var loader = this.geometryHandlers[ geoJSON.type ][ "loaderObject" ]; + loader.load( get_url( geoJSON.url, data.urlBaseType ), create_callback_geometry( geoID ), loaderParameters ); + + } else if ( geoJSON.type === "embedded" ) { + + var modelJson = data.embeds[ geoJSON.id ], + texture_path = ""; + + // pass metadata along to jsonLoader so it knows the format version + + modelJson.metadata = data.metadata; + + if ( modelJson ) { + + var jsonLoader = this.geometryHandlers[ "ascii" ][ "loaderObject" ]; + var model = jsonLoader.parse( modelJson, texture_path ); + create_callback_embed( geoID )( model.geometry, model.materials ); + + } + + } + + } + + // textures + + // count how many textures will be loaded asynchronously + + var textureID, textureJSON; + + for ( textureID in data.textures ) { + + textureJSON = data.textures[ textureID ]; + + if ( textureJSON.url instanceof Array ) { + + counter_textures += textureJSON.url.length; + + for( var n = 0; n < textureJSON.url.length; n ++ ) { + + scope.onLoadStart(); + + } + + } else { + + counter_textures += 1; + + scope.onLoadStart(); + + } + + } + + total_textures = counter_textures; + + for ( textureID in data.textures ) { + + textureJSON = data.textures[ textureID ]; + + if ( textureJSON.mapping !== undefined && THREE[ textureJSON.mapping ] !== undefined ) { + + textureJSON.mapping = new THREE[ textureJSON.mapping ](); + + } + + if ( textureJSON.url instanceof Array ) { + + var count = textureJSON.url.length; + var url_array = []; + + for( var i = 0; i < count; i ++ ) { + + url_array[ i ] = get_url( textureJSON.url[ i ], data.urlBaseType ); + + } + + var isCompressed = /\.dds$/i.test( url_array[ 0 ] ); + + if ( isCompressed ) { + + texture = THREE.ImageUtils.loadCompressedTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) ); + + } else { + + texture = THREE.ImageUtils.loadTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) ); + + } + + } else { + + var isCompressed = /\.dds$/i.test( textureJSON.url ); + var fullUrl = get_url( textureJSON.url, data.urlBaseType ); + var textureCallback = generateTextureCallback( 1 ); + + if ( isCompressed ) { + + texture = THREE.ImageUtils.loadCompressedTexture( fullUrl, textureJSON.mapping, textureCallback ); + + } else { + + texture = THREE.ImageUtils.loadTexture( fullUrl, textureJSON.mapping, textureCallback ); + + } + + if ( THREE[ textureJSON.minFilter ] !== undefined ) + texture.minFilter = THREE[ textureJSON.minFilter ]; + + if ( THREE[ textureJSON.magFilter ] !== undefined ) + texture.magFilter = THREE[ textureJSON.magFilter ]; + + if ( textureJSON.anisotropy ) texture.anisotropy = textureJSON.anisotropy; + + if ( textureJSON.repeat ) { + + texture.repeat.set( textureJSON.repeat[ 0 ], textureJSON.repeat[ 1 ] ); + + if ( textureJSON.repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping; + if ( textureJSON.repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping; + + } + + if ( textureJSON.offset ) { + + texture.offset.set( textureJSON.offset[ 0 ], textureJSON.offset[ 1 ] ); + + } + + // handle wrap after repeat so that default repeat can be overriden + + if ( textureJSON.wrap ) { + + var wrapMap = { + "repeat": THREE.RepeatWrapping, + "mirror": THREE.MirroredRepeatWrapping + } + + if ( wrapMap[ textureJSON.wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ textureJSON.wrap[ 0 ] ]; + if ( wrapMap[ textureJSON.wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ textureJSON.wrap[ 1 ] ]; + + } + + } + + result.textures[ textureID ] = texture; + + } + + // materials + + var matID, matJSON; + var parID; + + for ( matID in data.materials ) { + + matJSON = data.materials[ matID ]; + + for ( parID in matJSON.parameters ) { + + if ( parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" ) { + + matJSON.parameters[ parID ] = result.textures[ matJSON.parameters[ parID ] ]; + + } else if ( parID === "shading" ) { + + matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] === "flat" ) ? THREE.FlatShading : THREE.SmoothShading; + + } else if ( parID === "side" ) { + + if ( matJSON.parameters[ parID ] == "double" ) { + + matJSON.parameters[ parID ] = THREE.DoubleSide; + + } else if ( matJSON.parameters[ parID ] == "back" ) { + + matJSON.parameters[ parID ] = THREE.BackSide; + + } else { + + matJSON.parameters[ parID ] = THREE.FrontSide; + + } + + } else if ( parID === "blending" ) { + + matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.NormalBlending; + + } else if ( parID === "combine" ) { + + matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation; + + } else if ( parID === "vertexColors" ) { + + if ( matJSON.parameters[ parID ] == "face" ) { + + matJSON.parameters[ parID ] = THREE.FaceColors; + + // default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false + + } else if ( matJSON.parameters[ parID ] ) { + + matJSON.parameters[ parID ] = THREE.VertexColors; + + } + + } else if ( parID === "wrapRGB" ) { + + var v3 = matJSON.parameters[ parID ]; + matJSON.parameters[ parID ] = new THREE.Vector3( v3[ 0 ], v3[ 1 ], v3[ 2 ] ); + + } + + } + + if ( matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0 ) { + + matJSON.parameters.transparent = true; + + } + + if ( matJSON.parameters.normalMap ) { + + var shader = THREE.ShaderLib[ "normalmap" ]; + var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); + + var diffuse = matJSON.parameters.color; + var specular = matJSON.parameters.specular; + var ambient = matJSON.parameters.ambient; + var shininess = matJSON.parameters.shininess; + + uniforms[ "tNormal" ].value = result.textures[ matJSON.parameters.normalMap ]; + + if ( matJSON.parameters.normalScale ) { + + uniforms[ "uNormalScale" ].value.set( matJSON.parameters.normalScale[ 0 ], matJSON.parameters.normalScale[ 1 ] ); + + } + + if ( matJSON.parameters.map ) { + + uniforms[ "tDiffuse" ].value = matJSON.parameters.map; + uniforms[ "enableDiffuse" ].value = true; + + } + + if ( matJSON.parameters.envMap ) { + + uniforms[ "tCube" ].value = matJSON.parameters.envMap; + uniforms[ "enableReflection" ].value = true; + uniforms[ "reflectivity" ].value = matJSON.parameters.reflectivity; + + } + + if ( matJSON.parameters.lightMap ) { + + uniforms[ "tAO" ].value = matJSON.parameters.lightMap; + uniforms[ "enableAO" ].value = true; + + } + + if ( matJSON.parameters.specularMap ) { + + uniforms[ "tSpecular" ].value = result.textures[ matJSON.parameters.specularMap ]; + uniforms[ "enableSpecular" ].value = true; + + } + + if ( matJSON.parameters.displacementMap ) { + + uniforms[ "tDisplacement" ].value = result.textures[ matJSON.parameters.displacementMap ]; + uniforms[ "enableDisplacement" ].value = true; + + uniforms[ "uDisplacementBias" ].value = matJSON.parameters.displacementBias; + uniforms[ "uDisplacementScale" ].value = matJSON.parameters.displacementScale; + + } + + uniforms[ "diffuse" ].value.setHex( diffuse ); + uniforms[ "specular" ].value.setHex( specular ); + uniforms[ "ambient" ].value.setHex( ambient ); + + uniforms[ "shininess" ].value = shininess; + + if ( matJSON.parameters.opacity ) { + + uniforms[ "opacity" ].value = matJSON.parameters.opacity; + + } + + var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true }; + + material = new THREE.ShaderMaterial( parameters ); + + } else { + + material = new THREE[ matJSON.type ]( matJSON.parameters ); + + } + + material.name = matID; + + result.materials[ matID ] = material; + + } + + // second pass through all materials to initialize MeshFaceMaterials + // that could be referring to other materials out of order + + for ( matID in data.materials ) { + + matJSON = data.materials[ matID ]; + + if ( matJSON.parameters.materials ) { + + var materialArray = []; + + for ( var i = 0; i < matJSON.parameters.materials.length; i ++ ) { + + var label = matJSON.parameters.materials[ i ]; + materialArray.push( result.materials[ label ] ); + + } + + result.materials[ matID ].materials = materialArray; + + } + + } + + // objects ( synchronous init of procedural primitives ) + + handle_objects(); + + // defaults + + if ( result.cameras && data.defaults.camera ) { + + result.currentCamera = result.cameras[ data.defaults.camera ]; + + } + + if ( result.fogs && data.defaults.fog ) { + + result.scene.fog = result.fogs[ data.defaults.fog ]; + + } + + // synchronous callback + + scope.callbackSync( result ); + + // just in case there are no async elements + + async_callback_gate(); + + } + +} + +/** + * @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 ); + + } + + } ); + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + } + +}; + +/** + * @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; + +/** + * @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 = false; + + 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; + +}; + +/** + * @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; + +}; + +/** + * @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( ), + * + * 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.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.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; + +}; + +/** + * @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( ), + * + * 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.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.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; + +}; + +/** + * @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( ), + * + * 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.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.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; + +}; + +/** + * @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.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; + +}; + +/** + * @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; + +}; + +/** + * @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; + +}; + +/** + * @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.ParticleSystemMaterial = function ( parameters ) { + + THREE.Material.call( this ); + + this.color = new THREE.Color( 0xffffff ); + + this.map = null; + + this.size = 1; + this.sizeAttenuation = true; + + this.vertexColors = false; + + this.fog = true; + + this.setValues( parameters ); + +}; + +THREE.ParticleSystemMaterial.prototype = Object.create( THREE.Material.prototype ); + +THREE.ParticleSystemMaterial.prototype.clone = function () { + + var material = new THREE.ParticleSystemMaterial(); + + 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 = THREE.ParticleSystemMaterial; + +/** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * fragmentShader: , + * vertexShader: , + * + * uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } }, + * + * defines: { "label" : "value" }, + * + * 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.fragmentShader = "void main() {}"; + this.vertexShader = "void main() {}"; + this.uniforms = {}; + this.defines = {}; + this.attributes = null; + + 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 ] + }; + + // By default, bind position to attribute index 0. In WebGL, attribute 0 + // should always be used to avoid potentially expensive emulation. + this.index0AttributeName = "position"; + + 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; + +}; + +/** + * @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; + +}; + +/** + * @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; +/** + * @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; + this.mipmaps = []; + + this.mapping = mapping !== undefined ? mapping : new THREE.UVMapping(); + + 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.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; + +/** + * @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; + +}; + +/** + * @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; + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ParticleSystem = function ( geometry, material ) { + + THREE.Object3D.call( this ); + + this.geometry = geometry !== undefined ? geometry : new THREE.Geometry(); + this.material = material !== undefined ? material : new THREE.ParticleSystemMaterial( { color: Math.random() * 0xffffff } ); + + this.sortParticles = false; + this.frustumCulled = false; + +}; + +THREE.ParticleSystem.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.ParticleSystem.prototype.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.ParticleSystem( this.geometry, this.material ); + + object.sortParticles = this.sortParticles; + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @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.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; + +}; + +/** + * @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.clone = function ( object ) { + + if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material ); + + THREE.Object3D.prototype.clone.call( this, object ); + + return object; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Bone = function( belongsToSkin ) { + + THREE.Object3D.call( this ); + + this.skin = belongsToSkin; + this.skinMatrix = new THREE.Matrix4(); + +}; + +THREE.Bone.prototype = Object.create( THREE.Object3D.prototype ); + +THREE.Bone.prototype.update = function ( parentSkinMatrix, forceUpdate ) { + + // update local + + if ( this.matrixAutoUpdate ) { + + forceUpdate |= this.updateMatrix(); + + } + + // update skin matrix + + if ( forceUpdate || this.matrixWorldNeedsUpdate ) { + + if( parentSkinMatrix ) { + + this.skinMatrix.multiplyMatrices( parentSkinMatrix, this.matrix ); + + } else { + + this.skinMatrix.copy( this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + forceUpdate = true; + + } + + // update children + + var child, i, l = this.children.length; + + for ( i = 0; i < l; i ++ ) { + + this.children[ i ].update( this.skinMatrix, forceUpdate ); + + } + +}; + + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) { + + THREE.Mesh.call( this, geometry, material ); + + // + + this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true; + + // init bones + + this.identityMatrix = new THREE.Matrix4(); + + this.bones = []; + this.boneMatrices = []; + + var b, bone, gbone, p, q, s; + + if ( this.geometry && this.geometry.bones !== undefined ) { + + for ( b = 0; b < this.geometry.bones.length; b ++ ) { + + gbone = this.geometry.bones[ b ]; + + p = gbone.pos; + q = gbone.rotq; + s = gbone.scl; + + bone = this.addBone(); + + 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 ( b = 0; b < this.bones.length; b ++ ) { + + gbone = this.geometry.bones[ b ]; + bone = this.bones[ b ]; + + if ( gbone.parent === -1 ) { + + this.add( bone ); + + } else { + + this.bones[ gbone.parent ].add( bone ); + + } + + } + + // + + var nBones = this.bones.length; + + 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 ( nBones > 256 ) + size = 64; + else if ( nBones > 64 ) + size = 32; + else if ( nBones > 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 * nBones ); + + } + + this.pose(); + + } + +}; + +THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.SkinnedMesh.prototype.addBone = function( bone ) { + + if ( bone === undefined ) { + + bone = new THREE.Bone( this ); + + } + + this.bones.push( bone ); + + return bone; + +}; + +THREE.SkinnedMesh.prototype.updateMatrixWorld = function () { + + var offsetMatrix = new THREE.Matrix4(); + + return function ( force ) { + + this.matrixAutoUpdate && this.updateMatrix(); + + // update matrixWorld + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.parent ) { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } else { + + this.matrixWorld.copy( this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + + if ( child instanceof THREE.Bone ) { + + child.update( this.identityMatrix, false ); + + } else { + + child.updateMatrixWorld( true ); + + } + + } + + // make a snapshot of the bones' rest position + + if ( this.boneInverses == undefined ) { + + this.boneInverses = []; + + for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) { + + var inverse = new THREE.Matrix4(); + + inverse.getInverse( this.bones[ b ].skinMatrix ); + + this.boneInverses.push( inverse ); + + } + + } + + // 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; + + // TODO: we could get rid of this multiplication step if the skinMatrix + // was already representing the offset; however, this requires some + // major changes to the animation system + + offsetMatrix.multiplyMatrices( this.bones[ b ].skinMatrix, this.boneInverses[ b ] ); + offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 ); + + } + + if ( this.useVertexTexture ) { + + this.boneTexture.needsUpdate = true; + + } + + }; + +}(); + +THREE.SkinnedMesh.prototype.pose = function () { + + this.updateMatrixWorld( true ); + + this.normalizeSkinWeights(); + +}; + +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.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; + +}; + +/** + * @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.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; + +}; + +/** + * @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.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; + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Sprite = ( function () { + + var geometry = new THREE.Geometry2( 3 ); + geometry.vertices.set( [ - 0.5, - 0.5, 0, 0.5, - 0.5, 0, 0.5, 0.5, 0 ] ); + + 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 ); + +/* + * Custom update matrix + */ + +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; +/** + * @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; + +}; + +/** + * @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 ); + +}; + +/** + * @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 ); + +}; + +/** + * @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 ), + + _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, + _contextDashSize = null, + _contextGapSize = 0, + + _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 = {}, + + _near, _far, + + _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 + _normal = new THREE.Vector3(), + _normalViewMatrix = new THREE.Matrix3(), + + _pixelMap, _pixelMapContext, _pixelMapImage, _pixelMapData, + _gradientMap, _gradientMapContext, _gradientMapQuality = 16; + + _pixelMap = document.createElement( 'canvas' ); + _pixelMap.width = _pixelMap.height = 2; + + _pixelMapContext = _pixelMap.getContext( '2d' ); + _pixelMapContext.fillStyle = 'rgba(0,0,0,1)'; + _pixelMapContext.fillRect( 0, 0, 2, 2 ); + + _pixelMapImage = _pixelMapContext.getImageData( 0, 0, 2, 2 ); + _pixelMapData = _pixelMapImage.data; + + _gradientMap = document.createElement( 'canvas' ); + _gradientMap.width = _gradientMap.height = _gradientMapQuality; + + _gradientMapContext = _gradientMap.getContext( '2d' ); + _gradientMapContext.translate( - _gradientMapQuality / 2, - _gradientMapQuality / 2 ); + _gradientMapContext.scale( _gradientMapQuality, _gradientMapQuality ); + + _gradientMapQuality --; // Fix UVs + + // dash+gap fallbacks for Firefox and everything else + + if ( _context.setLineDash === undefined ) { + + if ( _context.mozDash !== undefined ) { + + _context.setLineDash = function ( values ) { + + _context.mozDash = values[ 0 ] !== null ? values : null; + + } + + } else { + + _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; + + _canvasWidthHalf = Math.floor( _canvasWidth / 2 ); + _canvasHeightHalf = Math.floor( _canvasHeight / 2 ); + + _canvas.width = _canvasWidth; + _canvas.height = _canvasHeight; + + if ( this.devicePixelRatio !== 1 && 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.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( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' ); + this.setClearColor( hex, alpha ); + + }; + + this.getMaxAnisotropy = function () { + + return 0; + + }; + + this.clear = function () { + + _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf ); + + if ( _clearBox.empty() === false ) { + + _clearBox.intersect( _clipBox ); + _clearBox.expandByScalar( 2 ); + + 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(); + + _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf ); + + _this.info.render.vertices = 0; + _this.info.render.faces = 0; + + _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.visible === false ) 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 || + material instanceof THREE.ParticleSystemMaterial ) { // Backwards compatibility + + var texture = material.map; + + if ( texture !== null ) { + + 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 )' ); + + } + + // + + 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() ); + setDashAndGap( material.dashSize, material.gapSize ); + + _context.stroke(); + + _elemBox.expandByScalar( material.linewidth * 2 ); + + setDashAndGap( null, null ); + + } + + } + + 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 ); + + } + + if ( material.wireframe === false && material.shading === THREE.SmoothShading && element.vertexNormalsLength === 3 ) { + + _color1.copy( _ambientLight ); + _color2.copy( _ambientLight ); + _color3.copy( _ambientLight ); + + calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 ); + calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 ); + calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color3 ); + + _color1.multiply( _diffuseColor ).add( _emissiveColor ); + _color2.multiply( _diffuseColor ).add( _emissiveColor ); + _color3.multiply( _diffuseColor ).add( _emissiveColor ); + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } else { + + _color.copy( _ambientLight ); + + calculateLight( element.centroidModel, 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[ 0 ]; + 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 === THREE.SphericalRefractionMapping ) { + + + + }*/ + + + } 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 ) { + + _near = _camera.near; + _far = _camera.far; + + _color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far ); + _color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far ); + _color3.r = _color3.g = _color3.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far ); + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } else if ( material instanceof THREE.MeshNormalMaterial ) { + + if ( material.shading === THREE.FlatShading ) { + + _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 if ( material.shading === THREE.SmoothShading ) { + + _normal.copy( element.vertexNormalsModel[ uv1 ] ).applyMatrix3( _normalViewMatrix ); + _color1.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _normal.copy( element.vertexNormalsModel[ uv2 ] ).applyMatrix3( _normalViewMatrix ); + _color2.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _normal.copy( element.vertexNormalsModel[ uv3 ] ).applyMatrix3( _normalViewMatrix ); + _color3.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 ); + + _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 ); + + _image = getGradientTexture( _color1, _color2, _color3, _color4 ); + + clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image ); + + } + + } + + } + + // + + 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 ) { + + 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(); + + } + + function getGradientTexture( color1, color2, color3, color4 ) { + + // http://mrdoob.com/blog/post/710 + + _pixelMapData[ 0 ] = ( color1.r * 255 ) | 0; + _pixelMapData[ 1 ] = ( color1.g * 255 ) | 0; + _pixelMapData[ 2 ] = ( color1.b * 255 ) | 0; + + _pixelMapData[ 4 ] = ( color2.r * 255 ) | 0; + _pixelMapData[ 5 ] = ( color2.g * 255 ) | 0; + _pixelMapData[ 6 ] = ( color2.b * 255 ) | 0; + + _pixelMapData[ 8 ] = ( color3.r * 255 ) | 0; + _pixelMapData[ 9 ] = ( color3.g * 255 ) | 0; + _pixelMapData[ 10 ] = ( color3.b * 255 ) | 0; + + _pixelMapData[ 12 ] = ( color4.r * 255 ) | 0; + _pixelMapData[ 13 ] = ( color4.g * 255 ) | 0; + _pixelMapData[ 14 ] = ( color4.b * 255 ) | 0; + + _pixelMapContext.putImageData( _pixelMapImage, 0, 0 ); + _gradientMapContext.drawImage( _pixelMap, 0, 0 ); + + return _gradientMap; + + } + + // 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 setDashAndGap( dashSizeValue, gapSizeValue ) { + + if ( _contextDashSize !== dashSizeValue || _contextGapSize !== gapSizeValue ) { + + _context.setLineDash( [ dashSizeValue, gapSizeValue ] ); + _contextDashSize = dashSizeValue; + _contextGapSize = gapSizeValue; + + } + + } + +}; + +/** + * Shader chunks for WebLG Shader library + * + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.ShaderChunk = { + + // FOG + + fog_pars_fragment: [ + + "#ifdef USE_FOG", + + "uniform vec3 fogColor;", + + "#ifdef FOG_EXP2", + + "uniform float fogDensity;", + + "#else", + + "uniform float fogNear;", + "uniform float fogFar;", + + "#endif", + + "#endif" + + ].join("\n"), + + fog_fragment: [ + + "#ifdef USE_FOG", + + "float depth = gl_FragCoord.z / gl_FragCoord.w;", + + "#ifdef FOG_EXP2", + + "const float LOG2 = 1.442695;", + "float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );", + "fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );", + + "#else", + + "float fogFactor = smoothstep( fogNear, fogFar, depth );", + + "#endif", + + "gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );", + + "#endif" + + ].join("\n"), + + // ENVIRONMENT MAP + + envmap_pars_fragment: [ + + "#ifdef USE_ENVMAP", + + "uniform float reflectivity;", + "uniform samplerCube envMap;", + "uniform float flipEnvMap;", + "uniform int combine;", + + "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )", + + "uniform bool useRefract;", + "uniform float refractionRatio;", + + "#else", + + "varying vec3 vReflect;", + + "#endif", + + "#endif" + + ].join("\n"), + + envmap_fragment: [ + + "#ifdef USE_ENVMAP", + + "vec3 reflectVec;", + + "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )", + + "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );", + + "if ( useRefract ) {", + + "reflectVec = refract( cameraToVertex, normal, refractionRatio );", + + "} else { ", + + "reflectVec = reflect( cameraToVertex, normal );", + + "}", + + "#else", + + "reflectVec = vReflect;", + + "#endif", + + "#ifdef DOUBLE_SIDED", + + "float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );", + "vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );", + + "#else", + + "vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );", + + "#endif", + + "#ifdef GAMMA_INPUT", + + "cubeColor.xyz *= cubeColor.xyz;", + + "#endif", + + "if ( combine == 1 ) {", + + "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );", + + "} else if ( combine == 2 ) {", + + "gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;", + + "} else {", + + "gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );", + + "}", + + "#endif" + + ].join("\n"), + + envmap_pars_vertex: [ + + "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )", + + "varying vec3 vReflect;", + + "uniform float refractionRatio;", + "uniform bool useRefract;", + + "#endif" + + ].join("\n"), + + worldpos_vertex : [ + + "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )", + + "#ifdef USE_SKINNING", + + "vec4 worldPosition = modelMatrix * skinned;", + + "#endif", + + "#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )", + + "vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );", + + "#endif", + + "#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )", + + "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", + + "#endif", + + "#endif" + + ].join("\n"), + + envmap_vertex : [ + + "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )", + + "vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;", + "worldNormal = normalize( worldNormal );", + + "vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );", + + "if ( useRefract ) {", + + "vReflect = refract( cameraToVertex, worldNormal, refractionRatio );", + + "} else {", + + "vReflect = reflect( cameraToVertex, worldNormal );", + + "}", + + "#endif" + + ].join("\n"), + + // COLOR MAP (particles) + + map_particle_pars_fragment: [ + + "#ifdef USE_MAP", + + "uniform sampler2D map;", + + "#endif" + + ].join("\n"), + + + map_particle_fragment: [ + + "#ifdef USE_MAP", + + "gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );", + + "#endif" + + ].join("\n"), + + // COLOR MAP (triangles) + + map_pars_vertex: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "varying vec2 vUv;", + "uniform vec4 offsetRepeat;", + + "#endif" + + ].join("\n"), + + map_pars_fragment: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "varying vec2 vUv;", + + "#endif", + + "#ifdef USE_MAP", + + "uniform sampler2D map;", + + "#endif" + + ].join("\n"), + + map_vertex: [ + + "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )", + + "vUv = uv * offsetRepeat.zw + offsetRepeat.xy;", + + "#endif" + + ].join("\n"), + + map_fragment: [ + + "#ifdef USE_MAP", + + "vec4 texelColor = texture2D( map, vUv );", + + "#ifdef GAMMA_INPUT", + + "texelColor.xyz *= texelColor.xyz;", + + "#endif", + + "gl_FragColor = gl_FragColor * texelColor;", + + "#endif" + + ].join("\n"), + + // LIGHT MAP + + lightmap_pars_fragment: [ + + "#ifdef USE_LIGHTMAP", + + "varying vec2 vUv2;", + "uniform sampler2D lightMap;", + + "#endif" + + ].join("\n"), + + lightmap_pars_vertex: [ + + "#ifdef USE_LIGHTMAP", + + "varying vec2 vUv2;", + + "#endif" + + ].join("\n"), + + lightmap_fragment: [ + + "#ifdef USE_LIGHTMAP", + + "gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );", + + "#endif" + + ].join("\n"), + + lightmap_vertex: [ + + "#ifdef USE_LIGHTMAP", + + "vUv2 = uv2;", + + "#endif" + + ].join("\n"), + + // BUMP MAP + + bumpmap_pars_fragment: [ + + "#ifdef USE_BUMPMAP", + + "uniform sampler2D bumpMap;", + "uniform float bumpScale;", + + // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen + // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html + + // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) + + "vec2 dHdxy_fwd() {", + + "vec2 dSTdx = dFdx( vUv );", + "vec2 dSTdy = dFdy( vUv );", + + "float Hll = bumpScale * texture2D( bumpMap, vUv ).x;", + "float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;", + "float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;", + + "return vec2( dBx, dBy );", + + "}", + + "vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {", + + "vec3 vSigmaX = dFdx( surf_pos );", + "vec3 vSigmaY = dFdy( surf_pos );", + "vec3 vN = surf_norm;", // normalized + + "vec3 R1 = cross( vSigmaY, vN );", + "vec3 R2 = cross( vN, vSigmaX );", + + "float fDet = dot( vSigmaX, R1 );", + + "vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );", + "return normalize( abs( fDet ) * surf_norm - vGrad );", + + "}", + + "#endif" + + ].join("\n"), + + // NORMAL MAP + + normalmap_pars_fragment: [ + + "#ifdef USE_NORMALMAP", + + "uniform sampler2D normalMap;", + "uniform vec2 normalScale;", + + // Per-Pixel Tangent Space Normal Mapping + // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html + + "vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {", + + "vec3 q0 = dFdx( eye_pos.xyz );", + "vec3 q1 = dFdy( eye_pos.xyz );", + "vec2 st0 = dFdx( vUv.st );", + "vec2 st1 = dFdy( vUv.st );", + + "vec3 S = normalize( q0 * st1.t - q1 * st0.t );", + "vec3 T = normalize( -q0 * st1.s + q1 * st0.s );", + "vec3 N = normalize( surf_norm );", + + "vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;", + "mapN.xy = normalScale * mapN.xy;", + "mat3 tsn = mat3( S, T, N );", + "return normalize( tsn * mapN );", + + "}", + + "#endif" + + ].join("\n"), + + // SPECULAR MAP + + specularmap_pars_fragment: [ + + "#ifdef USE_SPECULARMAP", + + "uniform sampler2D specularMap;", + + "#endif" + + ].join("\n"), + + specularmap_fragment: [ + + "float specularStrength;", + + "#ifdef USE_SPECULARMAP", + + "vec4 texelSpecular = texture2D( specularMap, vUv );", + "specularStrength = texelSpecular.r;", + + "#else", + + "specularStrength = 1.0;", + + "#endif" + + ].join("\n"), + + // LIGHTS LAMBERT + + lights_lambert_pars_vertex: [ + + "uniform vec3 ambient;", + "uniform vec3 diffuse;", + "uniform vec3 emissive;", + + "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 spotLightDistance[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];", + "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];", + + "#endif", + + "#ifdef WRAP_AROUND", + + "uniform vec3 wrapRGB;", + + "#endif" + + ].join("\n"), + + lights_lambert_vertex: [ + + "vLightFront = vec3( 0.0 );", + + "#ifdef DOUBLE_SIDED", + + "vLightBack = vec3( 0.0 );", + + "#endif", + + "transformedNormal = normalize( transformedNormal );", + + "#if MAX_DIR_LIGHTS > 0", + + "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );", + "vec3 dirVector = normalize( lDirection.xyz );", + + "float dotProduct = dot( transformedNormal, dirVector );", + "vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += directionalLightColor[ i ] * directionalLightWeighting;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;", + + "#endif", + + "}", + + "#endif", + + "#if MAX_POINT_LIGHTS > 0", + + "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz - mvPosition.xyz;", + + "float lDistance = 1.0;", + "if ( pointLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + "float dotProduct = dot( transformedNormal, lVector );", + + "vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;", + + "#endif", + + "}", + + "#endif", + + "#if MAX_SPOT_LIGHTS > 0", + + "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {", + + "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );", + "vec3 lVector = lPosition.xyz - mvPosition.xyz;", + + "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );", + + "if ( spotEffect > spotLightAngleCos[ i ] ) {", + + "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );", + + "float lDistance = 1.0;", + "if ( spotLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + "float dotProduct = dot( transformedNormal, lVector );", + "vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );", + + "#ifdef DOUBLE_SIDED", + + "vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );", + + "#ifdef WRAP_AROUND", + + "vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );", + + "#endif", + + "#endif", + + "#ifdef WRAP_AROUND", + + "vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );", + "spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );", + + "#ifdef DOUBLE_SIDED", + + "spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );", + + "#endif", + + "#endif", + + "vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;", + + "#endif", + + "}", + + "}", + + "#endif", + + "#if MAX_HEMI_LIGHTS > 0", + + "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {", + + "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );", + "vec3 lVector = normalize( lDirection.xyz );", + + "float dotProduct = dot( transformedNormal, lVector );", + + "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;", + "float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;", + + "vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );", + + "#ifdef DOUBLE_SIDED", + + "vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );", + + "#endif", + + "}", + + "#endif", + + "vLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;", + + "#ifdef DOUBLE_SIDED", + + "vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;", + + "#endif" + + ].join("\n"), + + // LIGHTS PHONG + + lights_phong_pars_vertex: [ + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "varying vec3 vWorldPosition;", + + "#endif" + + ].join("\n"), + + + lights_phong_vertex: [ + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "vWorldPosition = worldPosition.xyz;", + + "#endif" + + ].join("\n"), + + lights_phong_pars_fragment: [ + + "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", + + "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )", + + "varying vec3 vWorldPosition;", + + "#endif", + + "#ifdef WRAP_AROUND", + + "uniform vec3 wrapRGB;", + + "#endif", + + "varying vec3 vViewPosition;", + "varying vec3 vNormal;" + + ].join("\n"), + + lights_phong_fragment: [ + + "vec3 normal = normalize( vNormal );", + "vec3 viewPosition = normalize( vViewPosition );", + + "#ifdef DOUBLE_SIDED", + + "normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );", + + "#endif", + + "#ifdef USE_NORMALMAP", + + "normal = perturbNormal2Arb( -vViewPosition, normal );", + + "#elif defined( USE_BUMPMAP )", + + "normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );", + + "#endif", + + "#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 lVector = lPosition.xyz + vViewPosition.xyz;", + + "float lDistance = 1.0;", + "if ( pointLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + + "#ifdef WRAP_AROUND", + + "float pointDiffuseWeightFull = max( dotProduct, 0.0 );", + "float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float pointDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;", + + // specular + + "vec3 pointHalfVector = normalize( lVector + viewPosition );", + "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );", + "float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, pointHalfVector ), 0.0 ), 5.0 );", + "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;", + + "}", + + "#endif", + + "#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 lVector = lPosition.xyz + vViewPosition.xyz;", + + "float lDistance = 1.0;", + "if ( spotLightDistance[ i ] > 0.0 )", + "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );", + + "lVector = normalize( lVector );", + + "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );", + + "if ( spotEffect > spotLightAngleCos[ i ] ) {", + + "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );", + + // diffuse + + "float dotProduct = dot( normal, lVector );", + + "#ifdef WRAP_AROUND", + + "float spotDiffuseWeightFull = max( dotProduct, 0.0 );", + "float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float spotDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;", + + // specular + + "vec3 spotHalfVector = normalize( lVector + viewPosition );", + "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );", + "float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, spotHalfVector ), 0.0 ), 5.0 );", + "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;", + + "}", + + "}", + + "#endif", + + "#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 + + "float dotProduct = dot( normal, dirVector );", + + "#ifdef WRAP_AROUND", + + "float dirDiffuseWeightFull = max( dotProduct, 0.0 );", + "float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );", + + "vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );", + + "#else", + + "float dirDiffuseWeight = max( dotProduct, 0.0 );", + + "#endif", + + "dirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;", + + // specular + + "vec3 dirHalfVector = normalize( dirVector + viewPosition );", + "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );", + "float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );", + + /* + // fresnel term from skin shader + "const float F0 = 0.128;", + + "float base = 1.0 - dot( viewPosition, dirHalfVector );", + "float exponential = pow( base, 5.0 );", + + "float fresnel = exponential + F0 * ( 1.0 - exponential );", + */ + + /* + // fresnel term from fresnel shader + "const float mFresnelBias = 0.08;", + "const float mFresnelScale = 0.3;", + "const float mFresnelPower = 5.0;", + + "float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );", + */ + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + //"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;", + + "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", + + "#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 = specularStrength * max( pow( hemiDotNormalHalfSky, 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 = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );", + + "float dotProductGround = dot( normal, lVectorGround );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 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", + + "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 * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );", + + "#else", + + "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;", + + "#endif" + + ].join("\n"), + + // VERTEX COLORS + + color_pars_fragment: [ + + "#ifdef USE_COLOR", + + "varying vec3 vColor;", + + "#endif" + + ].join("\n"), + + + color_fragment: [ + + "#ifdef USE_COLOR", + + "gl_FragColor = gl_FragColor * vec4( vColor, 1.0 );", + + "#endif" + + ].join("\n"), + + color_pars_vertex: [ + + "#ifdef USE_COLOR", + + "varying vec3 vColor;", + + "#endif" + + ].join("\n"), + + + color_vertex: [ + + "#ifdef USE_COLOR", + + "#ifdef GAMMA_INPUT", + + "vColor = color * color;", + + "#else", + + "vColor = color;", + + "#endif", + + "#endif" + + ].join("\n"), + + // SKINNING + + skinning_pars_vertex: [ + + "#ifdef USE_SKINNING", + + "#ifdef BONE_TEXTURE", + + "uniform sampler2D boneTexture;", + "uniform int boneTextureWidth;", + "uniform int boneTextureHeight;", + + "mat4 getBoneMatrix( const in float i ) {", + + "float j = i * 4.0;", + "float x = mod( j, float( boneTextureWidth ) );", + "float y = floor( j / float( boneTextureWidth ) );", + + "float dx = 1.0 / float( boneTextureWidth );", + "float dy = 1.0 / float( boneTextureHeight );", + + "y = dy * ( y + 0.5 );", + + "vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );", + "vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );", + "vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );", + "vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );", + + "mat4 bone = mat4( v1, v2, v3, v4 );", + + "return bone;", + + "}", + + "#else", + + "uniform mat4 boneGlobalMatrices[ MAX_BONES ];", + + "mat4 getBoneMatrix( const in float i ) {", + + "mat4 bone = boneGlobalMatrices[ int(i) ];", + "return bone;", + + "}", + + "#endif", + + "#endif" + + ].join("\n"), + + skinbase_vertex: [ + + "#ifdef USE_SKINNING", + + "mat4 boneMatX = getBoneMatrix( skinIndex.x );", + "mat4 boneMatY = getBoneMatrix( skinIndex.y );", + "mat4 boneMatZ = getBoneMatrix( skinIndex.z );", + "mat4 boneMatW = getBoneMatrix( skinIndex.w );", + + "#endif" + + ].join("\n"), + + skinning_vertex: [ + + "#ifdef USE_SKINNING", + + "#ifdef USE_MORPHTARGETS", + + "vec4 skinVertex = vec4( morphed, 1.0 );", + + "#else", + + "vec4 skinVertex = vec4( position, 1.0 );", + + "#endif", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + "skinned += boneMatZ * skinVertex * skinWeight.z;", + "skinned += boneMatW * skinVertex * skinWeight.w;", + + "#endif" + + ].join("\n"), + + // MORPHING + + morphtarget_pars_vertex: [ + + "#ifdef USE_MORPHTARGETS", + + "#ifndef USE_MORPHNORMALS", + + "uniform float morphTargetInfluences[ 8 ];", + + "#else", + + "uniform float morphTargetInfluences[ 4 ];", + + "#endif", + + "#endif" + + ].join("\n"), + + morphtarget_vertex: [ + + "#ifdef USE_MORPHTARGETS", + + "vec3 morphed = vec3( 0.0 );", + "morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];", + "morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];", + "morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];", + "morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];", + + "#ifndef USE_MORPHNORMALS", + + "morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];", + "morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];", + "morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];", + "morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];", + + "#endif", + + "morphed += position;", + + "#endif" + + ].join("\n"), + + default_vertex : [ + + "vec4 mvPosition;", + + "#ifdef USE_SKINNING", + + "mvPosition = modelViewMatrix * skinned;", + + "#endif", + + "#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )", + + "mvPosition = modelViewMatrix * vec4( morphed, 1.0 );", + + "#endif", + + "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )", + + "mvPosition = modelViewMatrix * vec4( position, 1.0 );", + + "#endif", + + "gl_Position = projectionMatrix * mvPosition;" + + ].join("\n"), + + morphnormal_vertex: [ + + "#ifdef USE_MORPHNORMALS", + + "vec3 morphedNormal = vec3( 0.0 );", + + "morphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];", + "morphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];", + "morphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];", + "morphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];", + + "morphedNormal += normal;", + + "#endif" + + ].join("\n"), + + skinnormal_vertex: [ + + "#ifdef USE_SKINNING", + + "mat4 skinMatrix = skinWeight.x * boneMatX;", + "skinMatrix += skinWeight.y * boneMatY;", + + "#ifdef USE_MORPHNORMALS", + + "vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );", + + "#else", + + "vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );", + + "#endif", + + "#endif" + + ].join("\n"), + + defaultnormal_vertex: [ + + "vec3 objectNormal;", + + "#ifdef USE_SKINNING", + + "objectNormal = skinnedNormal.xyz;", + + "#endif", + + "#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )", + + "objectNormal = morphedNormal;", + + "#endif", + + "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )", + + "objectNormal = normal;", + + "#endif", + + "#ifdef FLIP_SIDED", + + "objectNormal = -objectNormal;", + + "#endif", + + "vec3 transformedNormal = normalMatrix * objectNormal;" + + ].join("\n"), + + // SHADOW MAP + + // based on SpiderGL shadow map and Fabien Sanglard's GLSL shadow mapping examples + // http://spidergl.org/example.php?id=6 + // http://fabiensanglard.net/shadowmapping + + shadowmap_pars_fragment: [ + + "#ifdef USE_SHADOWMAP", + + "uniform sampler2D shadowMap[ MAX_SHADOWS ];", + "uniform vec2 shadowMapSize[ MAX_SHADOWS ];", + + "uniform float shadowDarkness[ MAX_SHADOWS ];", + "uniform float shadowBias[ MAX_SHADOWS ];", + + "varying vec4 vShadowCoord[ MAX_SHADOWS ];", + + "float unpackDepth( const in vec4 rgba_depth ) {", + + "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 );", + "float depth = dot( rgba_depth, bit_shift );", + "return depth;", + + "}", + + "#endif" + + ].join("\n"), + + shadowmap_fragment: [ + + "#ifdef USE_SHADOWMAP", + + "#ifdef SHADOWMAP_DEBUG", + + "vec3 frustumColors[3];", + "frustumColors[0] = vec3( 1.0, 0.5, 0.0 );", + "frustumColors[1] = vec3( 0.0, 1.0, 0.8 );", + "frustumColors[2] = vec3( 0.0, 0.5, 1.0 );", + + "#endif", + + "#ifdef SHADOWMAP_CASCADE", + + "int inFrustumCount = 0;", + + "#endif", + + "float fDepth;", + "vec3 shadowColor = vec3( 1.0 );", + + "for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + "vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;", + + // "if ( something && something )" breaks ATI OpenGL shader compiler + // "if ( all( something, something ) )" using this instead + + "bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );", + "bool inFrustum = all( inFrustumVec );", + + // don't shadow pixels outside of light frustum + // use just first frustum (for cascades) + // don't shadow pixels behind far plane of light frustum + + "#ifdef SHADOWMAP_CASCADE", + + "inFrustumCount += int( inFrustum );", + "bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );", + + "#else", + + "bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );", + + "#endif", + + "bool frustumTest = all( frustumTestVec );", + + "if ( frustumTest ) {", + + "shadowCoord.z += shadowBias[ i ];", + + "#if defined( SHADOWMAP_TYPE_PCF )", + + // Percentage-close filtering + // (9 pixel kernel) + // http://fabiensanglard.net/shadowmappingPCF/ + + "float shadow = 0.0;", + + /* + // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL + // must enroll loop manually + + "for ( float y = -1.25; y <= 1.25; y += 1.25 )", + "for ( float x = -1.25; x <= 1.25; x += 1.25 ) {", + + "vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );", + + // doesn't seem to produce any noticeable visual difference compared to simple "texture2D" lookup + //"vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );", + + "float fDepth = unpackDepth( rgbaDepth );", + + "if ( fDepth < shadowCoord.z )", + "shadow += 1.0;", + + "}", + + "shadow /= 9.0;", + + */ + + "const float shadowDelta = 1.0 / 9.0;", + + "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;", + "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;", + + "float dx0 = -1.25 * xPixelOffset;", + "float dy0 = -1.25 * yPixelOffset;", + "float dx1 = 1.25 * xPixelOffset;", + "float dy1 = 1.25 * yPixelOffset;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );", + "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;", + + "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );", + + "#elif defined( SHADOWMAP_TYPE_PCF_SOFT )", + + // Percentage-close filtering + // (9 pixel kernel) + // http://fabiensanglard.net/shadowmappingPCF/ + + "float shadow = 0.0;", + + "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;", + "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;", + + "float dx0 = -1.0 * xPixelOffset;", + "float dy0 = -1.0 * yPixelOffset;", + "float dx1 = 1.0 * xPixelOffset;", + "float dy1 = 1.0 * yPixelOffset;", + + "mat3 shadowKernel;", + "mat3 depthKernel;", + + "depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );", + "depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );", + "depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );", + "depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );", + "depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );", + "depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );", + "depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );", + "depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );", + "depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );", + + "vec3 shadowZ = vec3( shadowCoord.z );", + "shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));", + "shadowKernel[0] *= vec3(0.25);", + + "shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));", + "shadowKernel[1] *= vec3(0.25);", + + "shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));", + "shadowKernel[2] *= vec3(0.25);", + + "vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );", + + "shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );", + "shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );", + + "vec4 shadowValues;", + "shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );", + "shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );", + "shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );", + "shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );", + + "shadow = dot( shadowValues, vec4( 1.0 ) );", + + "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );", + + "#else", + + "vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );", + "float fDepth = unpackDepth( rgbaDepth );", + + "if ( fDepth < shadowCoord.z )", + + // spot with multiple shadows is darker + + "shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );", + + // spot with multiple shadows has the same color as single shadow spot + + //"shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );", + + "#endif", + + "}", + + + "#ifdef SHADOWMAP_DEBUG", + + "#ifdef SHADOWMAP_CASCADE", + + "if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];", + + "#else", + + "if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];", + + "#endif", + + "#endif", + + "}", + + "#ifdef GAMMA_OUTPUT", + + "shadowColor *= shadowColor;", + + "#endif", + + "gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;", + + "#endif" + + ].join("\n"), + + shadowmap_pars_vertex: [ + + "#ifdef USE_SHADOWMAP", + + "varying vec4 vShadowCoord[ MAX_SHADOWS ];", + "uniform mat4 shadowMatrix[ MAX_SHADOWS ];", + + "#endif" + + ].join("\n"), + + shadowmap_vertex: [ + + "#ifdef USE_SHADOWMAP", + + "for( int i = 0; i < MAX_SHADOWS; i ++ ) {", + + "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;", + + "}", + + "#endif" + + ].join("\n"), + + // ALPHATEST + + alphatest_fragment: [ + + "#ifdef ALPHATEST", + + "if ( gl_FragColor.a < ALPHATEST ) discard;", + + "#endif" + + ].join("\n"), + + // LINEAR SPACE + + linear_to_gamma_fragment: [ + + "#ifdef GAMMA_OUTPUT", + + "gl_FragColor.xyz = sqrt( gl_FragColor.xyz );", + + "#endif" + + ].join("\n") + + +}; +/** + * 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; + + } + +}; +/** + * 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 }, + + "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: [] } + + } + +}; +/** + * 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" ], + + "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[ "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[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( diffuse, opacity );", + + THREE.ShaderChunk[ "map_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" ], + + "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[ "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[ "lightmap_pars_fragment" ], + THREE.ShaderChunk[ "envmap_pars_fragment" ], + THREE.ShaderChunk[ "fog_pars_fragment" ], + THREE.ShaderChunk[ "shadowmap_pars_fragment" ], + THREE.ShaderChunk[ "specularmap_pars_fragment" ], + + "void main() {", + + "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );", + + THREE.ShaderChunk[ "map_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" ], + + "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" ], + + "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[ "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" ], + + "void main() {", + + "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );", + + THREE.ShaderChunk[ "map_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" ], + + "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[ "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" ], + + "void main() {", + + "gl_FragColor = vec4( psColor, opacity );", + + 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" ], + + "void main() {", + + THREE.ShaderChunk[ "color_vertex" ], + + "vLineDistance = scale * lineDistance;", + + "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + "gl_Position = projectionMatrix * mvPosition;", + + "}" + + ].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" ], + + "void main() {", + + "if ( mod( vLineDistance, totalSize ) > dashSize ) {", + + "discard;", + + "}", + + "gl_FragColor = vec4( diffuse, opacity );", + + 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: [ + + "void main() {", + + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float mNear;", + "uniform float mFar;", + "uniform float opacity;", + + "void main() {", + + "float depth = gl_FragCoord.z / gl_FragCoord.w;", + "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" ], + + "void main() {", + + "vNormal = normalize( normalMatrix * normal );", + + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform float opacity;", + "varying vec3 vNormal;", + + "void main() {", + + "gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );", + + "}" + + ].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" ], + + "void main() {", + + "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", + + "}", + + "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 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 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( spotEffect, 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 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 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 );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 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( hemiDotNormalHalfSky, 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( hemiDotNormalHalfGround, shininess ), 0.0 );", + + "float dotProductGround = dot( normal, lVectorGround );", + + // 2.0 => 2.0001 is hack to work around ANGLE bug + + "float specularNormalization = ( shininess + 2.0001 ) / 8.0;", + + "vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );", + "vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 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" ], + + "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 = vec4( position, 1.0 );", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + + "displacedPosition = skinned.xyz;", + + "#else", + + "displacedPosition = position;", + + "#endif", + + "}", + + "#else", + + "#ifdef USE_SKINNING", + + "vec4 skinVertex = vec4( position, 1.0 );", + + "vec4 skinned = boneMatX * skinVertex * skinWeight.x;", + "skinned += boneMatY * skinVertex * skinWeight.y;", + + "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;", + + // + + "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;", + + "void main() {", + + "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", + "vWorldPosition = worldPosition.xyz;", + + "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", + + "}" + + ].join("\n"), + + fragmentShader: [ + + "uniform samplerCube tCube;", + "uniform float tFlip;", + + "varying vec3 vWorldPosition;", + + "void main() {", + + "gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );", + + "}" + + ].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 here: + // 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" ], + + "void main() {", + + THREE.ShaderChunk[ "skinbase_vertex" ], + THREE.ShaderChunk[ "morphtarget_vertex" ], + THREE.ShaderChunk[ "skinning_vertex" ], + THREE.ShaderChunk[ "default_vertex" ], + + "}" + + ].join("\n"), + + fragmentShader: [ + + "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 = fract( depth * bit_shift );", + "res -= res.xxyz * bit_mask;", + "return res;", + + "}", + + "void main() {", + + "gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );", + + //"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") + + } + +}; + +/** + * @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', + + _buffers = {}, + + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, + _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, + _antialias = parameters.antialias !== undefined ? parameters.antialias : false, + _stencil = parameters.stencil !== undefined ? parameters.stencil : true, + _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, + + _clearColor = new THREE.Color( 0x000000 ), + _clearAlpha = 0; + + // 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; + this.autoUpdateObjects = 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 = [], + _programs_counter = 0, + + // 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, + + _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: new Array(), positions: new Array() }, + point: { length: 0, colors: new Array(), positions: new Array(), distances: new Array() }, + spot: { length: 0, colors: new Array(), positions: new Array(), distances: new Array(), directions: new Array(), anglesCos: new Array(), exponents: new Array() }, + hemi: { length: 0, skyColors: new Array(), groundColors: new Array(), positions: new Array() } + + }; + + // initialize + + var _gl; + + var _glExtensionTextureFloat; + var _glExtensionTextureFloatLinear; + var _glExtensionStandardDerivatives; + var _glExtensionTextureFilterAnisotropic; + var _glExtensionCompressedTextureS3TC; + + 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 ); + + var _vertexShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_INT ); + var _vertexShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_INT ); + var _vertexShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_INT ); + + var _fragmentShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_INT ); + var _fragmentShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_INT ); + var _fragmentShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_INT ); + + // 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( "WebGLRenderer: highp not supported, using mediump" ); + + } else { + + _precision = "lowp"; + console.warn( "WebGLRenderer: highp and mediump not supported, using lowp" ); + + } + + } + + if ( _precision === "mediump" && ! mediumpAvailable ) { + + _precision = "lowp"; + console.warn( "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 ( this.devicePixelRatio !== 1 && 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( 'DEPRECATED: .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; + + 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 g in geometry.geometryGroups ) { + + var geometryGroup = geometry.geometryGroups[ g ]; + + if ( geometryGroup.numMorphTargets !== undefined ) { + + for ( var m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { + + _gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] ); + + } + + } + + if ( geometryGroup.numMorphNormals !== undefined ) { + + for ( var m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { + + _gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] ); + + } + + } + + deleteBuffers( geometryGroup ); + + } + + } else { + + deleteBuffers( geometry ); + + } + + } + + }; + + var deallocateTexture = function ( texture ) { + + if ( texture.image && texture.image.__webglTextureCube ) { + + // cube texture + + _gl.deleteTexture( texture.image.__webglTextureCube ); + + } else { + + // 2D texture + + if ( ! texture.__webglInit ) return; + + texture.__webglInit = false; + _gl.deleteTexture( texture.__webglTexture ); + + } + + }; + + var deallocateRenderTarget = function ( renderTarget ) { + + if ( !renderTarget || ! renderTarget.__webglTexture ) return; + + _gl.deleteTexture( renderTarget.__webglTexture ); + + if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { + + for ( var i = 0; i < 6; i ++ ) { + + _gl.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] ); + _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] ); + + } + + } else { + + _gl.deleteFramebuffer( renderTarget.__webglFramebuffer ); + _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer ); + + } + + }; + + var deallocateMaterial = function ( material ) { + + var program = material.program; + + if ( program === undefined ) return; + + material.program = undefined; + + // only deallocate GL program if this was the last use of shared program + // assumed there is only single copy of any program in the _programs list + // (that's how it's constructed) + + var i, il, programInfo; + var deleteProgram = false; + + for ( i = 0, il = _programs.length; i < il; i ++ ) { + + programInfo = _programs[ i ]; + + if ( programInfo.program === program ) { + + programInfo.usedTimes --; + + if ( programInfo.usedTimes === 0 ) { + + deleteProgram = true; + + } + + break; + + } + + } + + if ( deleteProgram === true ) { + + // avoid using array.splice, this is costlier than creating new array from scratch + + var newPrograms = []; + + for ( i = 0, il = _programs.length; i < il; i ++ ) { + + programInfo = _programs[ i ]; + + if ( programInfo.program !== program ) { + + newPrograms.push( programInfo ); + + } + + } + + _programs = newPrograms; + + _gl.deleteProgram( program ); + + _this.info.memory.programs --; + + } + + }; + + // Buffer initialization + + function initCustomAttributes ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + var material = object.material; + + if ( material.attributes ) { + + if ( geometry.__webglCustomAttributesList === undefined ) { + + geometry.__webglCustomAttributesList = []; + + } + + for ( var a in material.attributes ) { + + var attribute = material.attributes[ a ]; + + 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; + + attribute.needsUpdate = true; + + } + + geometry.__webglCustomAttributesList.push( attribute ); + + } + + } + + }; + + function initParticleBuffers ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + geometry.__vertexArray = new Float32Array( nvertices * 3 ); + geometry.__colorArray = new Float32Array( nvertices * 3 ); + + geometry.__sortArray = []; + + geometry.__webglParticleCount = nvertices; + + initCustomAttributes ( geometry, object ); + + }; + + function initLineBuffers ( geometry, object ) { + + var nvertices = geometry.vertices.length; + + geometry.__vertexArray = new Float32Array( nvertices * 3 ); + geometry.__colorArray = new Float32Array( nvertices * 3 ); + geometry.__lineDistanceArray = new Float32Array( nvertices * 1 ); + + geometry.__webglLineCount = nvertices; + + initCustomAttributes ( geometry, object ); + + }; + + function initMeshBuffers ( geometryGroup, object ) { + + var geometry = object.geometry, + faces3 = geometryGroup.faces3, + + nvertices = faces3.length * 3, + ntris = faces3.length * 1, + nlines = faces3.length * 3, + + material = getBufferMaterial( object, geometryGroup ), + + uvType = bufferGuessUVType( material ), + normalType = bufferGuessNormalType( material ), + vertexColorType = bufferGuessVertexColorType( material ); + + // console.log( "uvType", uvType, "normalType", normalType, "vertexColorType", vertexColorType, object, geometryGroup, material ); + + geometryGroup.__vertexArray = new Float32Array( nvertices * 3 ); + + if ( normalType ) { + + geometryGroup.__normalArray = new Float32Array( nvertices * 3 ); + + } + + if ( geometry.hasTangents ) { + + geometryGroup.__tangentArray = new Float32Array( nvertices * 4 ); + + } + + if ( vertexColorType ) { + + geometryGroup.__colorArray = new Float32Array( nvertices * 3 ); + + } + + if ( uvType ) { + + if ( geometry.faceVertexUvs.length > 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 ); + + } + + geometryGroup.__faceArray = new Uint16Array( ntris * 3 ); + geometryGroup.__lineArray = new Uint16Array( 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 instanceof THREE.ShaderMaterial ) { + + return true; + + } + + return false; + + }; + + // + + function initDirectBuffers( geometry ) { + + var a, attribute, type; + + for ( a in geometry.attributes ) { + + if ( a === "index" ) { + + type = _gl.ELEMENT_ARRAY_BUFFER; + + } else { + + type = _gl.ARRAY_BUFFER; + + } + + attribute = geometry.attributes[ a ]; + + attribute.buffer = _gl.createBuffer(); + + _gl.bindBuffer( type, attribute.buffer ); + _gl.bufferData( type, 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, c4, + 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; + + } + + }; + + // used by renderBufferDirect for THREE.Line + function setupLinesVertexAttributes( material, programAttributes, geometryAttributes, startIndex ) { + + var attributeItem, attributeName, attributePointer, attributeSize; + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + 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 ] ); + + } + + } + + } + + } + + } + + 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 ) { + + 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 ); + _gl.enableVertexAttribArray( 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 ); + _gl.enableVertexAttribArray( 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 ); + _gl.enableVertexAttribArray( 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 ); + _gl.enableVertexAttribArray( program.attributes.color ); + _gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } + + _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); + + object.count = 0; + + }; + + 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 ) { + + disableAttributes(); + + } + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + var index = geometryAttributes[ "index" ]; + + // indexed triangles + + if ( index ) { + + var offsets = geometry.offsets; + + // 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 ) { + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + 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 ] ); + + } + + } + + } + + } + + // indices + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed triangles + + _gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16 + + _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; + + } + + // non-indexed triangles + + } else { + + if ( updateBuffers ) { + + for ( attributeName in programAttributes ) { + + if ( attributeName === 'index') continue; + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) { + + 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 ] ); + + } + + } + + } + + } + + } + + 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 / 3 / 3; + + } + + // render particles + + } else if ( object instanceof THREE.ParticleSystem ) { + + if ( updateBuffers ) { + + for ( attributeName in programAttributes ) { + + attributePointer = programAttributes[ attributeName ]; + attributeItem = geometryAttributes[ attributeName ]; + + if ( attributePointer >= 0 ) { + + if ( attributeItem ) { + + attributeSize = attributeItem.itemSize; + _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer ); + enableAttribute( attributePointer ); + _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 ); + + } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) { + + 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 ] ); + + } + + } + + } + + } + + } + + 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 primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + var index = geometryAttributes[ "index" ]; + + // indexed lines + + if ( index ) { + + var offsets = geometry.offsets; + + // 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 ) { + + setupLinesVertexAttributes(material, programAttributes, geometryAttributes, startIndex); + + // indices + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer ); + + } + + // render indexed lines + + _gl.drawElements( _gl.LINES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16Array + + _this.info.render.calls ++; + _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared + + } + + } + + // non-indexed lines + + else { + + if ( updateBuffers ) { + + setupLinesVertexAttributes(material, programAttributes, geometryAttributes, 0); + } + + var position = geometryAttributes[ "position" ]; + + _gl.drawArrays( primitives, 0, position.array.length / 3 ); + _this.info.render.calls ++; + _this.info.render.points += position.array.length; + } + + + + } + + }; + + 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 ) { + + disableAttributes(); + + } + + // 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 ); + + } + + } + + // render mesh + + if ( object instanceof THREE.Mesh ) { + + // wireframe + + if ( material.wireframe ) { + + setLineWidth( material.wireframeLinewidth ); + + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer ); + _gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, _gl.UNSIGNED_SHORT, 0 ); + + // triangles + + } else { + + if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer ); + _gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, _gl.UNSIGNED_SHORT, 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 primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES; + + setLineWidth( material.linewidth ); + + _gl.drawArrays( primitives, 0, geometryGroup.__webglLineCount ); + + _this.info.render.calls ++; + + // render particles + + } else if ( object instanceof THREE.ParticleSystem ) { + + _gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount ); + + _this.info.render.calls ++; + _this.info.render.points += geometryGroup.__webglParticleCount; + + } + + }; + + function enableAttribute( attribute ) { + + if ( _enabledAttributes[ attribute ] === 0 ) { + + _gl.enableVertexAttribArray( attribute ); + _enabledAttributes[ attribute ] = 1; + + } + + }; + + function disableAttributes() { + + for ( var attribute in _enabledAttributes ) { + + if ( _enabledAttributes[ attribute ] === 1 ) { + + _gl.disableVertexAttribArray( attribute ); + _enabledAttributes[ attribute ] = 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 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; + _lightsNeedUpdate = true; + + // update scene graph + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + if ( camera.parent === undefined ) camera.updateMatrixWorld(); + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // update WebGL objects + + if ( this.autoUpdateObjects ) this.initWebGLObjects( scene ); + + // 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) + + renderList = scene.__webglObjects; + + for ( i = 0, il = renderList.length; i < il; i ++ ) { + + webglObject = renderList[ i ]; + object = webglObject.object; + + webglObject.id = i; + webglObject.render = false; + + if ( object.visible ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + setupMatrices( object, camera ); + + 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; + + } + + } + + } + + } + + } + + if ( this.sortObjects ) { + + renderList.sort( painterSortStable ); + + } + + // 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( scene.__webglObjects, false, "", 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( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material ); + renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material ); + + // transparent pass (back-to-front order) + + renderObjects( scene.__webglObjects, false, "transparent", 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 renderPlugins( plugins, scene, camera ) { + + if ( ! plugins.length ) 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, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) { + + var webglObject, object, buffer, material, start, end, delta; + + if ( reverse ) { + + start = renderList.length - 1; + end = -1; + delta = -1; + + } else { + + start = 0; + end = renderList.length; + delta = 1; + } + + for ( var i = start; i !== end; i += delta ) { + + webglObject = renderList[ i ]; + + if ( webglObject.render ) { + + object = webglObject.object; + buffer = webglObject.buffer; + + 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.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.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + } else { + + if ( material ) { + + if ( material.transparent ) { + + globject.transparent = material; + globject.opaque = null; + + } else { + + globject.opaque = material; + globject.transparent = null; + + } + + } + + } + + }; + + // Objects refresh + + this.initWebGLObjects = function ( scene ) { + + if ( !scene.__webglObjects ) { + + scene.__webglObjects = []; + scene.__webglObjectsImmediate = []; + scene.__webglSprites = []; + scene.__webglFlares = []; + + } + + 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 ); + + } + + // update must be called after objects adding / removal + + for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) { + + var object = scene.__webglObjects[ o ].object; + + // TODO: Remove this hack (WebGLRenderer refactoring) + + if ( object.__webglInit === undefined ) { + + if ( object.__webglActive !== undefined ) { + + removeObject( object, scene ); + + } + + addObject( object, scene ); + + } + + updateObject( object ); + + } + + }; + + // Objects adding + + function addObject( object, scene ) { + + var g, geometry, material, geometryGroup; + + if ( object.__webglInit === undefined ) { + + object.__webglInit = true; + + object._modelViewMatrix = new THREE.Matrix4(); + object._normalMatrix = new THREE.Matrix3(); + + if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) { + + object.geometry.__webglInit = true; + object.geometry.addEventListener( 'dispose', onGeometryDispose ); + + } + + geometry = object.geometry; + + if ( geometry === undefined ) { + + // fail silently for now + + } else if ( geometry instanceof THREE.BufferGeometry ) { + + initDirectBuffers( geometry ); + + } else if ( object instanceof THREE.Mesh ) { + + material = object.material; + + if ( geometry.geometryGroups === undefined ) { + + geometry.makeGroups( material instanceof THREE.MeshFaceMaterial ); + + } + + // create separate VBOs per geometry chunk + + for ( g in geometry.geometryGroups ) { + + geometryGroup = geometry.geometryGroups[ g ]; + + // initialise VBO on the first access + + if ( ! geometryGroup.__webglVertexBuffer ) { + + createMeshBuffers( geometryGroup ); + initMeshBuffers( geometryGroup, object ); + + geometry.verticesNeedUpdate = true; + geometry.morphTargetsNeedUpdate = true; + geometry.elementsNeedUpdate = true; + geometry.uvsNeedUpdate = true; + geometry.normalsNeedUpdate = true; + geometry.tangentsNeedUpdate = true; + geometry.colorsNeedUpdate = true; + + } + + } + + } 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.ParticleSystem ) { + + 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 ( g in geometry.geometryGroups ) { + + geometryGroup = geometry.geometryGroups[ g ]; + + addBuffer( scene.__webglObjects, geometryGroup, object ); + + } + + } + + } else if ( object instanceof THREE.Line || + object instanceof THREE.ParticleSystem ) { + + geometry = object.geometry; + addBuffer( scene.__webglObjects, geometry, object ); + + } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { + + addBufferImmediate( scene.__webglObjectsImmediate, object ); + + } else if ( object instanceof THREE.Sprite ) { + + scene.__webglSprites.push( object ); + + } else if ( object instanceof THREE.LensFlare ) { + + scene.__webglFlares.push( object ); + + } + + object.__webglActive = true; + + } + + }; + + function addBuffer( objlist, buffer, object ) { + + objlist.push( + { + id: null, + buffer: buffer, + object: object, + opaque: null, + transparent: null, + z: 0 + } + ); + + }; + + function addBufferImmediate( objlist, object ) { + + objlist.push( + { + id: null, + object: object, + opaque: null, + transparent: null, + z: 0 + } + ); + + }; + + // Objects updates + + function updateObject( object ) { + + var geometry = object.geometry, + geometryGroup, customAttributesDirty, material; + + if ( geometry instanceof THREE.BufferGeometry ) { + + setDirectBuffers( geometry, _gl.DYNAMIC_DRAW ); + + } else if ( object instanceof THREE.Mesh ) { + + // check all geometry groups + + for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) { + + geometryGroup = geometry.geometryGroupsList[ i ]; + + material = getBufferMaterial( object, geometryGroup ); + + if ( geometry.buffersNeedUpdate ) { + + initMeshBuffers( geometryGroup, object ); + + } + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate || + geometry.uvsNeedUpdate || geometry.normalsNeedUpdate || + geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) { + + setMeshBuffers( geometryGroup, object, _gl.DYNAMIC_DRAW, !geometry.dynamic, material ); + + } + + } + + geometry.verticesNeedUpdate = false; + geometry.morphTargetsNeedUpdate = false; + geometry.elementsNeedUpdate = false; + geometry.uvsNeedUpdate = false; + geometry.normalsNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.tangentsNeedUpdate = false; + + geometry.buffersNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + } else if ( object instanceof THREE.Line ) { + + material = getBufferMaterial( object, geometry ); + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) { + + setLineBuffers( geometry, _gl.DYNAMIC_DRAW ); + + } + + geometry.verticesNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.lineDistancesNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + + } else if ( object instanceof THREE.ParticleSystem ) { + + material = getBufferMaterial( object, geometry ); + + customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); + + if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) { + + setParticleBuffers( geometry, _gl.DYNAMIC_DRAW, object ); + + } + + geometry.verticesNeedUpdate = false; + geometry.colorsNeedUpdate = false; + + material.attributes && clearCustomAttributes( material ); + + } + + }; + + // Objects updates - custom attributes check + + function areCustomAttributesDirty( material ) { + + for ( var a in material.attributes ) { + + if ( material.attributes[ a ].needsUpdate ) return true; + + } + + return false; + + }; + + function clearCustomAttributes( material ) { + + for ( var a in material.attributes ) { + + material.attributes[ a ].needsUpdate = false; + + } + + }; + + // Objects removal + + function removeObject( object, scene ) { + + if ( object instanceof THREE.Mesh || + object instanceof THREE.ParticleSystem || + object instanceof THREE.Line ) { + + removeInstances( scene.__webglObjects, object ); + + } else if ( object instanceof THREE.Sprite ) { + + removeInstancesDirect( scene.__webglSprites, object ); + + } else if ( object instanceof THREE.LensFlare ) { + + removeInstancesDirect( scene.__webglFlares, object ); + + } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { + + removeInstances( scene.__webglObjectsImmediate, object ); + + } + + delete object.__webglActive; + + }; + + function removeInstances( objlist, object ) { + + for ( var o = objlist.length - 1; o >= 0; o -- ) { + + if ( objlist[ o ].object === object ) { + + objlist.splice( o, 1 ); + + } + + } + + }; + + function removeInstancesDirect( objlist, object ) { + + for ( var o = objlist.length - 1; o >= 0; o -- ) { + + if ( objlist[ o ] === 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.ParticleSystemMaterial ) { + + shaderID = 'particle_basic'; + + } + + if ( shaderID ) { + + setMaterialShaders( material, THREE.ShaderLib[ shaderID ] ); + + } + + // 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 = { + + map: !!material.map, + envMap: !!material.envMap, + lightMap: !!material.lightMap, + bumpMap: !!material.bumpMap, + normalMap: !!material.normalMap, + specularMap: !!material.specularMap, + + vertexColors: material.vertexColors, + + fog: fog, + useFog: material.fog, + fogExp: fog instanceof THREE.FogExp2, + + sizeAttenuation: material.sizeAttenuation, + + skinning: material.skinning, + maxBones: maxBones, + useVertexTexture: _supportsBoneTextures && object && object.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 + + }; + + material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters, material.index0AttributeName ); + + 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.uniforms ) { + + material.uniformsList.push( [ material.uniforms[ u ], u ] ); + + } + + }; + + function setMaterialShaders( material, shaders ) { + + material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms ); + material.vertexShader = shaders.vertexShader; + material.fragmentShader = shaders.fragmentShader; + + }; + + 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 refreshMaterial = false; + + var program = material.program, + p_uniforms = program.uniforms, + m_uniforms = material.uniforms; + + if ( program !== _currentProgram ) { + + _gl.useProgram( program ); + _currentProgram = program; + + refreshMaterial = true; + + } + + if ( material.id !== _currentMaterialId ) { + + _currentMaterialId = material.id; + refreshMaterial = true; + + } + + if ( refreshMaterial || camera !== _currentCamera ) { + + _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + if ( camera !== _currentCamera ) _currentCamera = camera; + + } + + // 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 ( _supportsBoneTextures && object.useVertexTexture ) { + + if ( p_uniforms.boneTexture !== null ) { + + var textureUnit = getTextureUnit(); + + _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); + _this.setTexture( object.boneTexture, textureUnit ); + + } + + if ( p_uniforms.boneTextureWidth !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth ); + + } + + if ( p_uniforms.boneTextureHeight !== null ) { + + _gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight ); + + } + + } else { + + if ( p_uniforms.boneGlobalMatrices !== null ) { + + _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.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 ) { + + setupLights( program, lights ); + _lightsNeedUpdate = false; + + } + + refreshUniformsLights( m_uniforms, _lights ); + + } + + 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.ParticleSystemMaterial ) { + + 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( program, material.uniformsList ); + + // 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 ); + + } + + } + + } + + 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; + + 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 + + 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; + + } + + 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; + + }; + + 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 ( program, uniforms ) { + + var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset; + + for ( j = 0, jl = uniforms.length; j < jl; j ++ ) { + + location = program.uniforms[ uniforms[ j ][ 1 ] ]; + if ( !location ) continue; + + uniform = uniforms[ j ][ 0 ]; + + type = uniform.type; + value = uniform.value; + + if ( type === "i" ) { // single integer + + _gl.uniform1i( location, value ); + + } else if ( type === "f" ) { // single float + + _gl.uniform1f( location, value ); + + } else if ( type === "v2" ) { // single THREE.Vector2 + + _gl.uniform2f( location, value.x, value.y ); + + } else if ( type === "v3" ) { // single THREE.Vector3 + + _gl.uniform3f( location, value.x, value.y, value.z ); + + } else if ( type === "v4" ) { // single THREE.Vector4 + + _gl.uniform4f( location, value.x, value.y, value.z, value.w ); + + } else if ( type === "c" ) { // single THREE.Color + + _gl.uniform3f( location, value.r, value.g, value.b ); + + } else if ( type === "iv1" ) { // flat array of integers (JS or typed array) + + _gl.uniform1iv( location, value ); + + } else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array) + + _gl.uniform3iv( location, value ); + + } else if ( type === "fv1" ) { // flat array of floats (JS or typed array) + + _gl.uniform1fv( location, value ); + + } else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array) + + _gl.uniform3fv( location, value ); + + } else if ( type === "v2v" ) { // array of THREE.Vector2 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 2 * value.length ); + + } + + for ( 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 ); + + } else if ( type === "v3v" ) { // array of THREE.Vector3 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 3 * value.length ); + + } + + for ( 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 ); + + } else if ( type === "v4v" ) { // array of THREE.Vector4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 4 * value.length ); + + } + + for ( 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 ); + + } else if ( type === "m4") { // single THREE.Matrix4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 16 ); + + } + + value.flattenToArray( uniform._array ); + _gl.uniformMatrix4fv( location, false, uniform._array ); + + } else if ( type === "m4v" ) { // array of THREE.Matrix4 + + if ( uniform._array === undefined ) { + + uniform._array = new Float32Array( 16 * value.length ); + + } + + for ( i = 0, il = value.length; i < il; i ++ ) { + + value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); + + } + + _gl.uniformMatrix4fv( location, false, uniform._array ); + + } else if ( type === "t" ) { // single THREE.Texture (2d or cube) + + texture = value; + textureUnit = getTextureUnit(); + + _gl.uniform1i( location, textureUnit ); + + if ( !texture ) continue; + + if ( texture.image instanceof Array && texture.image.length === 6 ) { + + setCubeTexture( texture, textureUnit ); + + } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { + + setCubeTextureDynamic( texture, textureUnit ); + + } else { + + _this.setTexture( texture, textureUnit ); + + } + + } else if ( type === "tv" ) { // array of THREE.Texture (2d) + + if ( uniform._array === undefined ) { + + uniform._array = []; + + } + + for( i = 0, il = uniform.value.length; i < il; i ++ ) { + + uniform._array[ i ] = getTextureUnit(); + + } + + _gl.uniform1iv( location, uniform._array ); + + for( i = 0, il = uniform.value.length; i < il; i ++ ) { + + texture = uniform.value[ i ]; + textureUnit = uniform._array[ i ]; + + if ( !texture ) continue; + + _this.setTexture( texture, textureUnit ); + + } + + } else { + + 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 ( program, 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(); + + // skip lights with undefined direction + // these create troubles in OpenGL (making pixel black) + + if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; + + 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(); + + // skip lights with undefined direction + // these create troubles in OpenGL (making pixel black) + + if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; + + 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; + + } + + }; + + // Defines + + function generateDefines ( 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" ); + + }; + + // Shaders + + function buildProgram( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters, index0AttributeName ) { + + var p, pl, d, program, code; + var chunks = []; + + // Generate code + + if ( shaderID ) { + + chunks.push( shaderID ); + + } else { + + chunks.push( fragmentShader ); + chunks.push( vertexShader ); + + } + + for ( d in defines ) { + + chunks.push( d ); + chunks.push( defines[ d ] ); + + } + + for ( p in parameters ) { + + chunks.push( p ); + chunks.push( parameters[ p ] ); + + } + + code = chunks.join(); + + // Check if code has been already compiled + + for ( p = 0, pl = _programs.length; p < pl; p ++ ) { + + var programInfo = _programs[ p ]; + + if ( programInfo.code === code ) { + + // console.log( "Code already compiled." /*: \n\n" + code*/ ); + + programInfo.usedTimes ++; + + return programInfo.program; + + } + + } + + 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 ); + + // + + program = _gl.createProgram(); + + var prefix_vertex = [ + + "precision " + _precision + " float;", + "precision " + _precision + " int;", + + customDefines, + + _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.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" : "", + + "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"); + + var prefix_fragment = [ + + "precision " + _precision + " float;", + "precision " + _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.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" : "", + + "uniform mat4 viewMatrix;", + "uniform vec3 cameraPosition;", + "" + + ].join("\n"); + + var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader ); + var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader ); + + _gl.attachShader( program, glVertexShader ); + _gl.attachShader( program, glFragmentShader ); + + // 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 + if ( index0AttributeName !== undefined ) { + + _gl.bindAttribLocation( program, 0, index0AttributeName ); + + } + + _gl.linkProgram( program ); + + if ( _gl.getProgramParameter( program, _gl.LINK_STATUS ) === false ) { + + console.error( '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.error( 'gl.getProgramInfoLog()', _gl.getProgramInfoLog( program ) ); + + } + + // clean up + + _gl.deleteShader( glFragmentShader ); + _gl.deleteShader( glVertexShader ); + + // console.log( prefix_fragment + fragmentShader ); + // console.log( prefix_vertex + vertexShader ); + + program.uniforms = {}; + program.attributes = {}; + + var identifiers, u, a, i; + + // cache uniform locations + + identifiers = [ + + 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', + 'morphTargetInfluences' + + ]; + + if ( parameters.useVertexTexture ) { + + identifiers.push( 'boneTexture' ); + identifiers.push( 'boneTextureWidth' ); + identifiers.push( 'boneTextureHeight' ); + + } else { + + identifiers.push( 'boneGlobalMatrices' ); + + } + + for ( u in uniforms ) { + + identifiers.push( u ); + + } + + cacheUniformLocations( program, identifiers ); + + // cache attributes locations + + identifiers = [ + + "position", "normal", "uv", "uv2", "tangent", "color", + "skinIndex", "skinWeight", "lineDistance" + + ]; + + for ( i = 0; i < parameters.maxMorphTargets; i ++ ) { + + identifiers.push( "morphTarget" + i ); + + } + + for ( i = 0; i < parameters.maxMorphNormals; i ++ ) { + + identifiers.push( "morphNormal" + i ); + + } + + for ( a in attributes ) { + + identifiers.push( a ); + + } + + cacheAttributeLocations( program, identifiers ); + + program.id = _programs_counter ++; + + _programs.push( { program: program, code: code, usedTimes: 1 } ); + + _this.info.memory.programs = _programs.length; + + return program; + + }; + + // Shader parameters cache + + function cacheUniformLocations ( program, identifiers ) { + + var i, l, id; + + for( i = 0, l = identifiers.length; i < l; i ++ ) { + + id = identifiers[ i ]; + program.uniforms[ id ] = _gl.getUniformLocation( program, id ); + + } + + }; + + function cacheAttributeLocations ( program, identifiers ) { + + var i, l, id; + + for( i = 0, l = identifiers.length; i < l; i ++ ) { + + id = identifiers[ i ]; + program.attributes[ id ] = _gl.getAttribLocation( program, id ); + + } + + }; + + function addLineNumbers ( string ) { + + var chunks = string.split( "\n" ); + + for ( var i = 0, il = chunks.length; i < il; i ++ ) { + + // Chrome reports shader errors on lines + // starting counting from 1 + + chunks[ i ] = ( i + 1 ) + ": " + chunks[ i ]; + + } + + return chunks.join( "\n" ); + + }; + + function getShader ( type, string ) { + + var shader; + + if ( type === "fragment" ) { + + shader = _gl.createShader( _gl.FRAGMENT_SHADER ); + + } else if ( type === "vertex" ) { + + shader = _gl.createShader( _gl.VERTEX_SHADER ); + + } + + _gl.shaderSource( shader, string ); + _gl.compileShader( shader ); + + if ( !_gl.getShaderParameter( shader, _gl.COMPILE_STATUS ) ) { + + console.error( _gl.getShaderInfoLog( shader ) ); + console.error( addLineNumbers( string ) ); + return null; + + } + + return shader; + + }; + + // 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.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.bones.length, maxBones ); + + if ( maxBones < object.bones.length ) { + + console.warn( "WebGLRenderer: too many bones - " + object.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, + premultipliedAlpha: _premultipliedAlpha, + antialias: _antialias, + stencil: _stencil, + 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' ); + + if ( ! _glExtensionTextureFloat ) { + + console.log( 'THREE.WebGLRenderer: Float textures not supported.' ); + + } + + if ( ! _glExtensionStandardDerivatives ) { + + console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' ); + + } + + if ( ! _glExtensionTextureFilterAnisotropic ) { + + console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' ); + + } + + if ( ! _glExtensionCompressedTextureS3TC ) { + + console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' ); + + } + + if ( _gl.getShaderPrecisionFormat === undefined ) { + + _gl.getShaderPrecisionFormat = function() { + + return { + "rangeMin" : 1, + "rangeMax" : 1, + "precision" : 1 + }; + + } + } + + }; + + 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() ); + +}; + +/** + * @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, + + 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 ); + +/** + * @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 ); + +/** + * @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 ); + +}; + +/** + * @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.centroidModel = new THREE.Vector3(); + + this.normalModel = new THREE.Vector3(); + + this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; + this.vertexNormalsLength = 0; + + this.color = null; + this.material = null; + this.uvs = [[]]; + + this.z = 0; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.RenderableObject = function () { + + this.id = 0; + + this.object = null; + this.z = 0; + +}; + +/** + * @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; + +}; + +/** + * @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; + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.GeometryUtils = { + + // Merge two geometries or geometry and geometry from object (using object's transform) + + merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) { + + var matrix, normalMatrix, + vertexOffset = geometry1.vertices.length, + uvPosition = geometry1.faceVertexUvs[ 0 ].length, + geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2, + vertices1 = geometry1.vertices, + vertices2 = geometry2.vertices, + faces1 = geometry1.faces, + faces2 = geometry2.faces, + uvs1 = geometry1.faceVertexUvs[ 0 ], + uvs2 = geometry2.faceVertexUvs[ 0 ]; + + if ( materialIndexOffset === undefined ) materialIndexOffset = 0; + + if ( object2 instanceof THREE.Mesh ) { + + object2.matrixAutoUpdate && object2.updateMatrix(); + + matrix = object2.matrix; + + 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 ) 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 ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix ) { + + 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; + + faceCopy.centroid.copy( face.centroid ); + + if ( matrix ) { + + faceCopy.centroid.applyMatrix4( matrix ); + + } + + faces1.push( faceCopy ); + + } + + // uvs + + for ( i = 0, il = uvs2.length; i < il; i ++ ) { + + var uv = uvs2[ i ], uvCopy = []; + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); + + } + + uvs1.push( uvCopy ); + + } + + }, + + // Get random point in triangle (via barycentric coordinates) + // (uniform distribution) + // http://www.cgafaq.info/wiki/Random_Point_In_Triangle + + randomPointInTriangle: function () { + + var vector = new THREE.Vector3(); + + return function ( vectorA, vectorB, vectorC ) { + + var point = new THREE.Vector3(); + + var a = THREE.Math.random16(); + var b = THREE.Math.random16(); + + if ( ( a + b ) > 1 ) { + + a = 1 - a; + b = 1 - b; + + } + + var c = 1 - a - b; + + point.copy( vectorA ); + point.multiplyScalar( a ); + + vector.copy( vectorB ); + vector.multiplyScalar( b ); + + point.add( vector ); + + vector.copy( vectorC ); + vector.multiplyScalar( c ); + + point.add( vector ); + + return point; + + }; + + }(), + + // Get random point in face (triangle / quad) + // (uniform distribution) + + randomPointInFace: function ( face, geometry, useCachedAreas ) { + + var vA, vB, vC, vD; + + vA = geometry.vertices[ face.a ]; + vB = geometry.vertices[ face.b ]; + vC = geometry.vertices[ face.c ]; + + return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC ); + + }, + + // Get uniformly distributed random points in mesh + // - create array with cumulative sums of face areas + // - pick random number from 0 to total area + // - find corresponding place in area array by binary search + // - get random point in face + + randomPointsInGeometry: function ( geometry, n ) { + + var face, i, + faces = geometry.faces, + vertices = geometry.vertices, + il = faces.length, + totalArea = 0, + cumulativeAreas = [], + vA, vB, vC, vD; + + // precompute face areas + + for ( i = 0; i < il; i ++ ) { + + face = faces[ i ]; + + vA = vertices[ face.a ]; + vB = vertices[ face.b ]; + vC = vertices[ face.c ]; + + face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC ); + + totalArea += face._area; + + cumulativeAreas[ i ] = totalArea; + + } + + // binary search cumulative areas array + + function binarySearchIndices( value ) { + + function binarySearch( start, end ) { + + // return closest larger index + // if exact number is not found + + if ( end < start ) + return start; + + var mid = start + Math.floor( ( end - start ) / 2 ); + + if ( cumulativeAreas[ mid ] > value ) { + + return binarySearch( start, mid - 1 ); + + } else if ( cumulativeAreas[ mid ] < value ) { + + return binarySearch( mid + 1, end ); + + } else { + + return mid; + + } + + } + + var result = binarySearch( 0, cumulativeAreas.length - 1 ) + return result; + + } + + // pick random face weighted by face area + + var r, index, + result = []; + + var stats = {}; + + for ( i = 0; i < n; i ++ ) { + + r = THREE.Math.random16() * totalArea; + + index = binarySearchIndices( r ); + + result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true ); + + if ( ! stats[ index ] ) { + + stats[ index ] = 1; + + } else { + + stats[ index ] += 1; + + } + + } + + return result; + + }, + + // Get triangle area (half of parallelogram) + // http://mathworld.wolfram.com/TriangleArea.html + + triangleArea: function () { + + var vector1 = new THREE.Vector3(); + var vector2 = new THREE.Vector3(); + + return function ( vectorA, vectorB, vectorC ) { + + vector1.subVectors( vectorB, vectorA ); + vector2.subVectors( vectorC, vectorA ); + vector1.cross( vector2 ); + + return 0.5 * vector1.length(); + + }; + + }(), + + // Center geometry so that 0,0,0 is in center of bounding box + + center: function ( geometry ) { + + geometry.computeBoundingBox(); + + var bb = geometry.boundingBox; + + var offset = new THREE.Vector3(); + + offset.addVectors( bb.min, bb.max ); + offset.multiplyScalar( -0.5 ); + + geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) ); + geometry.computeBoundingBox(); + + return offset; + + }, + + triangulateQuads: function ( geometry ) { + + var i, il, j, jl; + + var faces = []; + var faceVertexUvs = []; + + for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { + + faceVertexUvs[ i ] = []; + + } + + for ( i = 0, il = geometry.faces.length; i < il; i ++ ) { + + var face = geometry.faces[ i ]; + + faces.push( face ); + + for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) { + + faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] ); + + } + + } + + geometry.faces = faces; + geometry.faceVertexUvs = faceVertexUvs; + + geometry.computeCentroids(); + geometry.computeFaceNormals(); + geometry.computeVertexNormals(); + + if ( geometry.hasTangents ) geometry.computeTangents(); + + } + +}; + +/** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + +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 ); + + var image = loader.load( url, function () { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ); + + texture.image = image; + texture.sourceFile = url; + + return texture; + + }, + + loadCompressedTexture: function ( url, mapping, onLoad, onError ) { + + var texture = new THREE.CompressedTexture(); + texture.mapping = mapping; + + var request = new XMLHttpRequest(); + + request.onload = function () { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + texture.format = dds.format; + + texture.mipmaps = dds.mipmaps; + texture.image.width = dds.width; + texture.image.height = dds.height; + + // gl.generateMipmap fails for compressed textures + // mipmaps must be embedded in the DDS file + // or texture filters must not use mipmapping + + texture.generateMipmaps = false; + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + return texture; + + }, + + loadTextureCube: function ( array, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.Texture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping needed for cube textures + + texture.flipY = false; + + for ( var i = 0, il = array.length; i < il; ++ i ) { + + var cubeImage = new Image(); + images[ i ] = cubeImage; + + cubeImage.onload = function () { + + images.loadCount += 1; + + if ( images.loadCount === 6 ) { + + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + }; + + cubeImage.onerror = onError; + + cubeImage.crossOrigin = this.crossOrigin; + cubeImage.src = array[ i ]; + + } + + return texture; + + }, + + loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.CompressedTexture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) + + texture.flipY = false; + + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files + + texture.generateMipmaps = false; + + var generateCubeFaceCallback = function ( rq, img ) { + + return function () { + + var buffer = rq.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + img.format = dds.format; + + img.mipmaps = dds.mipmaps; + img.width = dds.width; + img.height = dds.height; + + images.loadCount += 1; + + if ( images.loadCount === 6 ) { + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + } + + } + + // compressed cubemap textures as 6 separate DDS files + + if ( array instanceof Array ) { + + for ( var i = 0, il = array.length; i < il; ++ i ) { + + var cubeImage = {}; + images[ i ] = cubeImage; + + var request = new XMLHttpRequest(); + + request.onload = generateCubeFaceCallback( request, cubeImage ); + request.onerror = onError; + + var url = array[ i ]; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + // compressed cubemap texture stored in a single DDS file + + } else { + + var url = array; + var request = new XMLHttpRequest(); + + request.onload = function( ) { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + if ( dds.isCubemap ) { + + var faces = dds.mipmaps.length / dds.mipmapCount; + + for ( var f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps : [] }; + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] ); + images[ f ].format = dds.format; + images[ f ].width = dds.width; + images[ f ].height = dds.height; + + } + + } + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + return texture; + + }, + + loadDDSTexture: function ( url, mapping, onLoad, onError ) { + + var images = []; + images.loadCount = 0; + + var texture = new THREE.CompressedTexture(); + texture.image = images; + if ( mapping !== undefined ) texture.mapping = mapping; + + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) + + texture.flipY = false; + + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files + + texture.generateMipmaps = false; + + { + var request = new XMLHttpRequest(); + + request.onload = function( ) { + + var buffer = request.response; + var dds = THREE.ImageUtils.parseDDS( buffer, true ); + + if ( dds.isCubemap ) { + + var faces = dds.mipmaps.length / dds.mipmapCount; + + for ( var f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps : [] }; + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] ); + images[ f ].format = dds.format; + images[ f ].width = dds.width; + images[ f ].height = dds.height; + + } + + } + + + } else { + texture.image.width = dds.width; + texture.image.height = dds.height; + texture.mipmaps = dds.mipmaps; + } + + texture.format = dds.format; + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + request.onerror = onError; + + request.open( 'GET', url, true ); + request.responseType = "arraybuffer"; + request.send( null ); + + } + + return texture; + + }, + + parseDDS: function ( buffer, loadMipmaps ) { + + var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; + + // Adapted from @toji's DDS utils + // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js + + // All values and structures referenced from: + // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ + + var DDS_MAGIC = 0x20534444; + + var DDSD_CAPS = 0x1, + DDSD_HEIGHT = 0x2, + DDSD_WIDTH = 0x4, + DDSD_PITCH = 0x8, + DDSD_PIXELFORMAT = 0x1000, + DDSD_MIPMAPCOUNT = 0x20000, + DDSD_LINEARSIZE = 0x80000, + DDSD_DEPTH = 0x800000; + + var DDSCAPS_COMPLEX = 0x8, + DDSCAPS_MIPMAP = 0x400000, + DDSCAPS_TEXTURE = 0x1000; + + var DDSCAPS2_CUBEMAP = 0x200, + DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, + DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, + DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, + DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, + DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, + DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, + DDSCAPS2_VOLUME = 0x200000; + + var DDPF_ALPHAPIXELS = 0x1, + DDPF_ALPHA = 0x2, + DDPF_FOURCC = 0x4, + DDPF_RGB = 0x40, + DDPF_YUV = 0x200, + DDPF_LUMINANCE = 0x20000; + + function fourCCToInt32( value ) { + + return value.charCodeAt(0) + + (value.charCodeAt(1) << 8) + + (value.charCodeAt(2) << 16) + + (value.charCodeAt(3) << 24); + + } + + function int32ToFourCC( value ) { + + return String.fromCharCode( + value & 0xff, + (value >> 8) & 0xff, + (value >> 16) & 0xff, + (value >> 24) & 0xff + ); + } + + function loadARGBMip( buffer, dataOffset, width, height ) { + var dataLength = width*height*4; + var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); + var byteArray = new Uint8Array( dataLength ); + var dst = 0; + var src = 0; + for ( var y = 0; y < height; y++ ) { + for ( var x = 0; x < width; x++ ) { + var b = srcBuffer[src]; src++; + var g = srcBuffer[src]; src++; + var r = srcBuffer[src]; src++; + var a = srcBuffer[src]; src++; + byteArray[dst] = r; dst++; //r + byteArray[dst] = g; dst++; //g + byteArray[dst] = b; dst++; //b + byteArray[dst] = a; dst++; //a + } + } + return byteArray; + } + + var FOURCC_DXT1 = fourCCToInt32("DXT1"); + var FOURCC_DXT3 = fourCCToInt32("DXT3"); + var FOURCC_DXT5 = fourCCToInt32("DXT5"); + + var headerLengthInt = 31; // The header length in 32 bit ints + + // Offsets into the header array + + var off_magic = 0; + + var off_size = 1; + var off_flags = 2; + var off_height = 3; + var off_width = 4; + + var off_mipmapCount = 7; + + var off_pfFlags = 20; + var off_pfFourCC = 21; + var off_RGBBitCount = 22; + var off_RBitMask = 23; + var off_GBitMask = 24; + var off_BBitMask = 25; + var off_ABitMask = 26; + + var off_caps = 27; + var off_caps2 = 28; + var off_caps3 = 29; + var off_caps4 = 30; + + // Parse header + + var header = new Int32Array( buffer, 0, headerLengthInt ); + + if ( header[ off_magic ] !== DDS_MAGIC ) { + + console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" ); + return dds; + + } + + if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { + + console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" ); + return dds; + + } + + var blockBytes; + + var fourCC = header[ off_pfFourCC ]; + + var isRGBAUncompressed = false; + + switch ( fourCC ) { + + case FOURCC_DXT1: + + blockBytes = 8; + dds.format = THREE.RGB_S3TC_DXT1_Format; + break; + + case FOURCC_DXT3: + + blockBytes = 16; + dds.format = THREE.RGBA_S3TC_DXT3_Format; + break; + + case FOURCC_DXT5: + + blockBytes = 16; + dds.format = THREE.RGBA_S3TC_DXT5_Format; + break; + + default: + + if( header[off_RGBBitCount] ==32 + && header[off_RBitMask]&0xff0000 + && header[off_GBitMask]&0xff00 + && header[off_BBitMask]&0xff + && header[off_ABitMask]&0xff000000 ) { + isRGBAUncompressed = true; + blockBytes = 64; + dds.format = THREE.RGBAFormat; + } else { + console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) ); + return dds; + } + } + + dds.mipmapCount = 1; + + if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { + + dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); + + } + + //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc. + + dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false; + + dds.width = header[ off_width ]; + dds.height = header[ off_height ]; + + var dataOffset = header[ off_size ] + 4; + + // Extract mipmaps buffers + + var width = dds.width; + var height = dds.height; + + var faces = dds.isCubemap ? 6 : 1; + + for ( var face = 0; face < faces; face ++ ) { + + for ( var i = 0; i < dds.mipmapCount; i ++ ) { + + if( isRGBAUncompressed ) { + var byteArray = loadARGBMip( buffer, dataOffset, width, height ); + var dataLength = byteArray.length; + } else { + var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; + var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); + } + + var mipmap = { "data": byteArray, "width": width, "height": height }; + dds.mipmaps.push( mipmap ); + + dataOffset += dataLength; + + width = Math.max( width * 0.5, 1 ); + height = Math.max( height * 0.5, 1 ); + + } + + width = dds.width; + height = dds.height; + + } + + return dds; + + }, + + 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; + + } + +}; + +/** + * @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 ); + + } + +}; + +/** + * @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() { + + return this.faces[ this.face ][ this.weight ][ this.style ]; + + }, + + 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( cpx, cpy, cpx1, cpy1, cpx2, cpy2 ); + + 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; + +/** + * @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; + +}; + +/** + * @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, + centroid: sum.divideScalar( il ) + + }; + + if ( v3 ) { + + ret.maxZ = maxZ; + ret.minZ = minZ; + + } + + return ret; + +}; + +/************************************************************** + * Create Geometries Helpers + **************************************************************/ + +/// Generate geometry from path points (for Line or ParticleSystem 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; + +}; + + +/** + * @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(); + + +/** + * @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 + +THREE.Path.prototype.toShapes = function( isCCW ) { + + 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 i, il, item, action, args; + + var subPaths = [], lastPath = new THREE.Path(); + + for ( i = 0, il = this.actions.length; i < il; i ++ ) { + + item = this.actions[ 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); + + if ( subPaths.length == 0 ) return []; + + 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] = []; + + 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); + + } + + } + + 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; + +}; + +/** + * @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 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 = 0; 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 ); + + // 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 ); + + } + +}; + + +/************************************************************** + * 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(); + +}; +/************************************************************** + * 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; + +}; +/************************************************************** + * 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; + +}; +/************************************************************** + * 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; + +}; +/************************************************************** + * 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 ); + +}; + +/************************************************************** + * 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 ); +/************************************************************** + * 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; + + } + +); + +/************************************************************** + * 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 ); + + } + +); +/************************************************************** + * 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 ); + + } + +); +/************************************************************** + * 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; + +// } +/************************************************************** + * 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; + + } + +); +/** + * @author mikael emtinger / http://gomo.se/ + */ + +THREE.AnimationHandler = (function() { + + var playing = []; + var library = {}; + var that = {}; + + + //--- update --- + + that.update = function( deltaTimeMS ) { + + for( var i = 0; i < playing.length; i ++ ) + playing[ i ].update( deltaTimeMS ); + + }; + + + //--- add --- + + that.addToUpdate = function( animation ) { + + if ( playing.indexOf( animation ) === -1 ) + playing.push( animation ); + + }; + + + //--- remove --- + + that.removeFromUpdate = function( animation ) { + + var index = playing.indexOf( animation ); + + if( index !== -1 ) + playing.splice( index, 1 ); + + }; + + + //--- add --- + + that.add = function( data ) { + + if ( library[ data.name ] !== undefined ) + console.log( "THREE.AnimationHandler.add: Warning! " + data.name + " already exists in library. Overwriting." ); + + library[ data.name ] = data; + initData( data ); + + }; + + + //--- get --- + + that.get = function( name ) { + + if ( typeof name === "string" ) { + + if ( library[ name ] ) { + + return library[ name ]; + + } else { + + console.log( "THREE.AnimationHandler.get: Couldn't find animation " + name ); + return null; + + } + + } else { + + // todo: add simple tween library + + } + + }; + + //--- parse --- + + that.parse = function( root ) { + + // setup hierarchy + + var hierarchy = []; + + if ( root instanceof THREE.SkinnedMesh ) { + + for( var b = 0; b < root.bones.length; b++ ) { + + hierarchy.push( root.bones[ b ] ); + + } + + } else { + + parseRecurseHierarchy( root, hierarchy ); + + } + + return hierarchy; + + }; + + var parseRecurseHierarchy = function( root, hierarchy ) { + + hierarchy.push( root ); + + for( var c = 0; c < root.children.length; c++ ) + parseRecurseHierarchy( root.children[ c ], hierarchy ); + + } + + + //--- init data --- + + var initData = 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( quat[0], quat[1], quat[2], quat[3] ); + + } + + } + + + // 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; + + } + + } + + // done + + data.initialized = true; + + }; + + + // interpolation types + + that.LINEAR = 0; + that.CATMULLROM = 1; + that.CATMULLROM_FORWARD = 2; + + return that; + +}()); + +/** + * @author mikael emtinger / http://gomo.se/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.Animation = function ( root, name ) { + + this.root = root; + this.data = THREE.AnimationHandler.get( name ); + this.hierarchy = THREE.AnimationHandler.parse( root ); + + this.currentTime = 0; + this.timeScale = 1; + + this.isPlaying = false; + this.isPaused = true; + this.loop = true; + + this.interpolationType = THREE.AnimationHandler.LINEAR; + +}; + +THREE.Animation.prototype.play = function ( startTime ) { + + this.currentTime = startTime !== undefined ? startTime : 0; + + if ( this.isPlaying === false ) { + + this.isPlaying = true; + + this.reset(); + this.update( 0 ); + + } + + this.isPaused = false; + + THREE.AnimationHandler.addToUpdate( this ); + +}; + + +THREE.Animation.prototype.pause = function() { + + if ( this.isPaused === true ) { + + THREE.AnimationHandler.addToUpdate( this ); + + } else { + + THREE.AnimationHandler.removeFromUpdate( this ); + + } + + this.isPaused = !this.isPaused; + +}; + + +THREE.Animation.prototype.stop = function() { + + this.isPlaying = false; + this.isPaused = false; + THREE.AnimationHandler.removeFromUpdate( 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 = {}; + object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 }; + object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix; + + } + + var prevKey = object.animationCache.prevKey; + var nextKey = object.animationCache.nextKey; + + prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ]; + prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ]; + prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ]; + + nextKey.pos = this.getNextKeyWith( "pos", h, 1 ); + nextKey.rot = this.getNextKeyWith( "rot", h, 1 ); + nextKey.scl = this.getNextKeyWith( "scl", h, 1 ); + + } + +}; + + +THREE.Animation.prototype.update = (function(){ + + var points = []; + var target = new THREE.Vector3(); + + // 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; + + // + + var vector; + var types = [ "pos", "rot", "scl" ]; + + 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; + + } + + this.currentTime = Math.min( this.currentTime, duration ); + + for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { + + var object = this.hierarchy[ h ]; + var animationCache = object.animationCache; + + // loop through pos/rot/scl + + for ( var t = 0; t < 3; t ++ ) { + + // get keys + + var type = types[ 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" ) { + + vector = object.position; + + if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) { + + vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + } 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 ); + + vector.x = currentPoint[ 0 ]; + vector.y = currentPoint[ 1 ]; + vector.z = currentPoint[ 2 ]; + + 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, object.quaternion, scale ); + + } else if ( type === "scl" ) { + + vector = object.scale; + + vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; + vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; + vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; + + } + + } + + } + + }; + +})(); + + + + + +// 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 ]; + +}; + +/** + * @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 ( root, data ) { + + this.root = root; + this.data = THREE.AnimationHandler.get( data ); + this.hierarchy = THREE.AnimationHandler.parse( 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; + + } + + } + +}; + +// Play + +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 instanceof THREE.Bone ? object.skinMatrix : 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.addToUpdate( this ); + +}; + + + +// Pause + +THREE.KeyFrameAnimation.prototype.pause = function() { + + if( this.isPaused ) { + + THREE.AnimationHandler.addToUpdate( this ); + + } else { + + THREE.AnimationHandler.removeFromUpdate( this ); + + } + + this.isPaused = !this.isPaused; + +}; + + +// Stop + +THREE.KeyFrameAnimation.prototype.stop = function() { + + this.isPlaying = false; + this.isPaused = false; + + THREE.AnimationHandler.removeFromUpdate( 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; + + if( obj instanceof THREE.Bone ) { + + original.copy( obj.skinMatrix ); + obj.skinMatrix = original; + + } else { + + 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 ]; + +}; + +/** + * @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 ]; + + } + + } )() + +}; + +/** + * 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 ); + +/** + * @author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog + * + * A general perpose camera, for setting FOV, Lens Focal Length, + * and switching between perspective and orthographic views easily. + * Use this only if you do not wish to manage + * both a Orthographic and Perspective Camera + * + */ + + +THREE.CombinedCamera = function ( width, height, fov, near, far, orthoNear, orthoFar ) { + + THREE.Camera.call( this ); + + this.fov = fov; + + this.left = -width / 2; + this.right = width / 2 + this.top = height / 2; + this.bottom = -height / 2; + + // We could also handle the projectionMatrix internally, but just wanted to test nested camera objects + + this.cameraO = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, orthoNear, orthoFar ); + this.cameraP = new THREE.PerspectiveCamera( fov, width / height, near, far ); + + this.zoom = 1; + + this.toPerspective(); + + var aspect = width/height; + +}; + +THREE.CombinedCamera.prototype = Object.create( THREE.Camera.prototype ); + +THREE.CombinedCamera.prototype.toPerspective = function () { + + // Switches to the Perspective Camera + + this.near = this.cameraP.near; + this.far = this.cameraP.far; + + this.cameraP.fov = this.fov / this.zoom ; + + this.cameraP.updateProjectionMatrix(); + + this.projectionMatrix = this.cameraP.projectionMatrix; + + this.inPerspectiveMode = true; + this.inOrthographicMode = false; + +}; + +THREE.CombinedCamera.prototype.toOrthographic = function () { + + // Switches to the Orthographic camera estimating viewport from Perspective + + var fov = this.fov; + var aspect = this.cameraP.aspect; + var near = this.cameraP.near; + var far = this.cameraP.far; + + // The size that we set is the mid plane of the viewing frustum + + var hyperfocus = ( near + far ) / 2; + + var halfHeight = Math.tan( fov / 2 ) * hyperfocus; + var planeHeight = 2 * halfHeight; + var planeWidth = planeHeight * aspect; + var halfWidth = planeWidth / 2; + + halfHeight /= this.zoom; + halfWidth /= this.zoom; + + this.cameraO.left = -halfWidth; + this.cameraO.right = halfWidth; + this.cameraO.top = halfHeight; + this.cameraO.bottom = -halfHeight; + + // this.cameraO.left = -farHalfWidth; + // this.cameraO.right = farHalfWidth; + // this.cameraO.top = farHalfHeight; + // this.cameraO.bottom = -farHalfHeight; + + // this.cameraO.left = this.left / this.zoom; + // this.cameraO.right = this.right / this.zoom; + // this.cameraO.top = this.top / this.zoom; + // this.cameraO.bottom = this.bottom / this.zoom; + + this.cameraO.updateProjectionMatrix(); + + this.near = this.cameraO.near; + this.far = this.cameraO.far; + this.projectionMatrix = this.cameraO.projectionMatrix; + + this.inPerspectiveMode = false; + this.inOrthographicMode = true; + +}; + + +THREE.CombinedCamera.prototype.setSize = function( width, height ) { + + this.cameraP.aspect = width / height; + this.left = -width / 2; + this.right = width / 2 + this.top = height / 2; + this.bottom = -height / 2; + +}; + + +THREE.CombinedCamera.prototype.setFov = function( fov ) { + + this.fov = fov; + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toOrthographic(); + + } + +}; + +// For mantaining similar API with PerspectiveCamera + +THREE.CombinedCamera.prototype.updateProjectionMatrix = function() { + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toPerspective(); + this.toOrthographic(); + + } + +}; + +/* +* 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.CombinedCamera.prototype.setLens = function ( focalLength, frameHeight ) { + + if ( frameHeight === undefined ) frameHeight = 24; + + var fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) ); + + this.setFov( fov ); + + return fov; +}; + + +THREE.CombinedCamera.prototype.setZoom = function( zoom ) { + + this.zoom = zoom; + + if ( this.inPerspectiveMode ) { + + this.toPerspective(); + + } else { + + this.toOrthographic(); + + } + +}; + +THREE.CombinedCamera.prototype.toFrontView = function() { + + this.rotation.x = 0; + this.rotation.y = 0; + this.rotation.z = 0; + + // should we be modifing the matrix instead? + + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toBackView = function() { + + this.rotation.x = 0; + this.rotation.y = Math.PI; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toLeftView = function() { + + this.rotation.x = 0; + this.rotation.y = - Math.PI / 2; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toRightView = function() { + + this.rotation.x = 0; + this.rotation.y = Math.PI / 2; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toTopView = function() { + + this.rotation.x = - Math.PI / 2; + this.rotation.y = 0; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +THREE.CombinedCamera.prototype.toBottomView = function() { + + this.rotation.x = Math.PI / 2; + this.rotation.y = 0; + this.rotation.z = 0; + this.rotationAutoUpdate = false; + +}; + +/** + * @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 ); + + var scope = this; + + this.width = width; + this.height = height; + this.depth = depth; + + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + this.depthSegments = depthSegments || 1; + + var width_half = this.width / 2; + var height_half = this.height / 2; + var depth_half = this.depth / 2; + + buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px + buildPlane( 'z', 'y', 1, - 1, this.depth, this.height, - width_half, 1 ); // nx + buildPlane( 'x', 'z', 1, 1, this.width, this.depth, height_half, 2 ); // py + buildPlane( 'x', 'z', 1, - 1, this.width, this.depth, - height_half, 3 ); // ny + buildPlane( 'x', 'y', 1, - 1, this.width, this.height, depth_half, 4 ); // pz + buildPlane( 'x', 'y', - 1, - 1, this.width, this.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.computeCentroids(); + this.mergeVertices(); + +}; + +THREE.BoxGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author hughes + */ + +THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.radius = radius = radius || 50; + this.segments = segments = segments !== undefined ? Math.max( 3, segments ) : 8; + + this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0; + this.thetaLength = 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 ++ ) { + + var v1 = i; + var v2 = i + 1 ; + var v3 = 0; + + this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) ); + this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] ); + + } + + this.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +// DEPRECATED + +THREE.CubeGeometry = THREE.BoxGeometry; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded ) { + + THREE.Geometry.call( this ); + + this.radiusTop = radiusTop = radiusTop !== undefined ? radiusTop : 20; + this.radiusBottom = radiusBottom = radiusBottom !== undefined ? radiusBottom : 20; + this.height = height = height !== undefined ? height : 100; + + this.radialSegments = radialSegments = radialSegments || 8; + this.heightSegments = heightSegments = heightSegments || 1; + + this.openEnded = 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.computeCentroids(); + this.computeFaceNormals(); + +} + +THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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.shapebb = shapes[ shapes.length - 1 ].getBoundingBox(); + + this.addShapeList( shapes, options ); + + this.computeCentroids(); + 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 shapebb = this.shapebb; + //shapebb = shape.getBoundingBox(); + + + + 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 ); + //vert = scalePt( contour[ i ], contourCentroid, bs, false ); + 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 ); + //vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true ); + + 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(); + +/** + * @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.shapebb = shapes[ shapes.length - 1 ].getBoundingBox(); + + this.addShapeList( shapes, options ); + + this.computeCentroids(); + 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 shapebb = this.shapebb; + + // + + 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 ) ); + + } + +}; + +/** + * @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.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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.width = width; + this.height = height; + + this.widthSegments = widthSegments || 1; + this.heightSegments = heightSegments || 1; + + var ix, iz; + var width_half = width / 2; + var height_half = height / 2; + + var gridX = this.widthSegments; + var gridZ = this.heightSegments; + + var gridX1 = gridX + 1; + var gridZ1 = gridZ + 1; + + var segment_width = this.width / gridX; + var segment_height = this.height / gridZ; + + var normal = new THREE.Vector3( 0, 0, 1 ); + + for ( iz = 0; iz < gridZ1; iz ++ ) { + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var x = ix * segment_width - width_half; + var y = iz * segment_height - height_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() ] ); + + } + + } + + this.computeCentroids(); + +}; + +THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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( 3, phiSegments ) : 8; + + var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + + for ( i = 0; i <= phiSegments; i ++ ) { // concentric circles inside ring + + for ( o = 0; o <= thetaSegments; 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; + + for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle + + var segment = o + thetaSegment; + + var v1 = segment + i; + var v2 = segment + thetaSegments + i; + var v3 = segment + thetaSegments + 1 + i; + + 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 + i; + v2 = segment + thetaSegments + 1 + i; + v3 = segment + 1 + i; + + 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.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + + THREE.Geometry.call( this ); + + this.radius = radius = radius || 50; + + this.widthSegments = widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); + this.heightSegments = heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); + + this.phiStart = phiStart = phiStart !== undefined ? phiStart : 0; + this.phiLength = phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + + this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0; + this.thetaLength = 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 < this.heightSegments; y ++ ) { + + for ( x = 0; x < this.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 ) === this.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 ) === this.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.computeCentroids(); + this.computeFaceNormals(); + + this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius ); + +}; + +THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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 ); + +/** + * @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 ); + + var scope = this; + + this.radius = radius || 100; + this.tube = tube || 40; + this.radialSegments = radialSegments || 8; + this.tubularSegments = tubularSegments || 6; + this.arc = arc || Math.PI * 2; + + var center = new THREE.Vector3(), uvs = [], normals = []; + + for ( var j = 0; j <= this.radialSegments; j ++ ) { + + for ( var i = 0; i <= this.tubularSegments; i ++ ) { + + var u = i / this.tubularSegments * this.arc; + var v = j / this.radialSegments * Math.PI * 2; + + center.x = this.radius * Math.cos( u ); + center.y = this.radius * Math.sin( u ); + + var vertex = new THREE.Vector3(); + vertex.x = ( this.radius + this.tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( this.radius + this.tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = this.tube * Math.sin( v ); + + this.vertices.push( vertex ); + + uvs.push( new THREE.Vector2( i / this.tubularSegments, j / this.radialSegments ) ); + normals.push( vertex.clone().sub( center ).normalize() ); + + } + + } + + + for ( var j = 1; j <= this.radialSegments; j ++ ) { + + for ( var i = 1; i <= this.tubularSegments; i ++ ) { + + var a = ( this.tubularSegments + 1 ) * j + i - 1; + var b = ( this.tubularSegments + 1 ) * ( j - 1 ) + i - 1; + var c = ( this.tubularSegments + 1 ) * ( j - 1 ) + i; + var d = ( this.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.computeCentroids(); + this.computeFaceNormals(); + +}; + +THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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 ); + + var scope = this; + + this.radius = radius || 100; + this.tube = tube || 40; + this.radialSegments = radialSegments || 64; + this.tubularSegments = tubularSegments || 8; + this.p = p || 2; + this.q = q || 3; + this.heightScale = heightScale || 1; + this.grid = new Array( this.radialSegments ); + + var tang = new THREE.Vector3(); + var n = new THREE.Vector3(); + var bitan = new THREE.Vector3(); + + for ( var i = 0; i < this.radialSegments; ++ i ) { + + this.grid[ i ] = new Array( this.tubularSegments ); + var u = i / this.radialSegments * 2 * this.p * Math.PI; + var p1 = getPos( u, this.q, this.p, this.radius, this.heightScale ); + var p2 = getPos( u + 0.01, this.q, this.p, this.radius, this.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 < this.tubularSegments; ++ j ) { + + var v = j / this.tubularSegments * 2 * Math.PI; + var cx = - this.tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + var cy = this.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; + + this.grid[ i ][ j ] = scope.vertices.push( pos ) - 1; + + } + + } + + for ( var i = 0; i < this.radialSegments; ++ i ) { + + for ( var j = 0; j < this.tubularSegments; ++ j ) { + + var ip = ( i + 1 ) % this.radialSegments; + var jp = ( j + 1 ) % this.tubularSegments; + + var a = this.grid[ i ][ j ]; + var b = this.grid[ ip ][ j ]; + var c = this.grid[ ip ][ jp ]; + var d = this.grid[ i ][ jp ]; + + var uva = new THREE.Vector2( i / this.radialSegments, j / this.tubularSegments ); + var uvb = new THREE.Vector2( ( i + 1 ) / this.radialSegments, j / this.tubularSegments ); + var uvc = new THREE.Vector2( ( i + 1 ) / this.radialSegments, ( j + 1 ) / this.tubularSegments ); + var uvd = new THREE.Vector2( i / this.radialSegments, ( j + 1 ) / this.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.computeCentroids(); + 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 ); + +/** + * @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.path = path; + this.segments = segments || 64; + this.radius = radius || 1; + this.radialSegments = radialSegments || 8; + this.closed = closed || false; + + this.grid = []; + + var scope = this, + + tangent, + normal, + binormal, + + numpoints = this.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( this.path, this.segments, this.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++ ) { + + this.grid[ i ] = []; + + u = i / ( numpoints - 1 ); + + pos = path.getPointAt( u ); + + tangent = tangents[ i ]; + normal = normals[ i ]; + binormal = binormals[ i ]; + + for ( j = 0; j < this.radialSegments; j++ ) { + + v = j / this.radialSegments * 2 * Math.PI; + + cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. + cy = this.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; + + this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z ); + + } + } + + + // construct the mesh + + for ( i = 0; i < this.segments; i++ ) { + + for ( j = 0; j < this.radialSegments; j++ ) { + + ip = ( this.closed ) ? (i + 1) % this.segments : i + 1; + jp = (j + 1) % this.radialSegments; + + a = this.grid[ i ][ j ]; // *** NOT NECESSARILY PLANAR ! *** + b = this.grid[ ip ][ j ]; + c = this.grid[ ip ][ jp ]; + d = this.grid[ i ][ jp ]; + + uva = new THREE.Vector2( i / this.segments, j / this.radialSegments ); + uvb = new THREE.Vector2( ( i + 1 ) / this.segments, j / this.radialSegments ); + uvc = new THREE.Vector2( ( i + 1 ) / this.segments, ( j + 1 ) / this.radialSegments ); + uvd = new THREE.Vector2( i / this.segments, ( j + 1 ) / this.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.computeCentroids(); + 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 ] ); + + } + + } +}; + +/** + * @author clockworkgeek / https://github.com/clockworkgeek + * @author timothypratley / https://github.com/timothypratley + * @author WestLangley / http://github.com/WestLangley +*/ + +THREE.PolyhedronGeometry = function ( vertices, faces, 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 ++ ) { + + prepare( new THREE.Vector3( vertices[ i ][ 0 ], vertices[ i ][ 1 ], vertices[ i ][ 2 ] ) ); + + } + + var midpoints = [], p = this.vertices; + + var f = []; + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var v1 = p[ faces[ i ][ 0 ] ]; + var v2 = p[ faces[ i ][ 1 ] ]; + var v3 = p[ faces[ i ][ 2 ] ]; + + f[ i ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] ); + + } + + for ( var i = 0, l = f.length; i < l; i ++ ) { + + subdivide(f[ 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.computeCentroids(); + + 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() ] ); + face.centroid.add( v1 ).add( v2 ).add( v3 ).divideScalar( 3 ); + that.faces.push( face ); + + var azi = azimuth( face.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 ); + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.IcosahedronGeometry = function ( radius, detail ) { + + this.radius = radius; + this.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 faces = [ + [ 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, faces, radius, detail ); + +}; + +THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author timothypratley / https://github.com/timothypratley + */ + +THREE.OctahedronGeometry = function ( radius, detail ) { + + var vertices = [ + [ 1, 0, 0 ], [ -1, 0, 0 ], [ 0, 1, 0 ], [ 0, -1, 0 ], [ 0, 0, 1 ], [ 0, 0, -1 ] + ]; + + var faces = [ + [ 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, faces, radius, detail ); +}; + +THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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 faces = [ + [ 2, 1, 0 ], [ 0, 3, 2 ], [ 1, 3, 0 ], [ 2, 3, 1 ] + ]; + + THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail ); + +}; + +THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @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.computeCentroids(); + this.computeFaceNormals(); + this.computeVertexNormals(); + +}; + +THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype ); + +/** + * @author sroucheray / http://sroucheray.org/ + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.AxisHelper = function ( size ) { + + size = size || 1; + + var geometry = new THREE.Geometry(); + + geometry.vertices.push( + new THREE.Vector3(), new THREE.Vector3( size, 0, 0 ), + new THREE.Vector3(), new THREE.Vector3( 0, size, 0 ), + new THREE.Vector3(), new THREE.Vector3( 0, 0, size ) + ); + + geometry.colors.push( + new THREE.Color( 0xff0000 ), new THREE.Color( 0xffaa00 ), + new THREE.Color( 0x00ff00 ), new THREE.Color( 0xaaff00 ), + new THREE.Color( 0x0000ff ), new THREE.Color( 0x00aaff ) + ); + + var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } ); + + THREE.Line.call( this, geometry, material, THREE.LinePieces ); + +}; + +THREE.AxisHelper.prototype = Object.create( THREE.Line.prototype ); + +/** + * @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 + * hex - color in hex value + * headLength - Number + * headWidth - Number + */ + +THREE.ArrowHelper = function ( dir, origin, length, hex, headLength, headWidth ) { + + // dir is assumed to be normalized + + THREE.Object3D.call( this ); + + if ( hex === undefined ) hex = 0xffff00; + if ( length === undefined ) length = 1; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.position = origin; + + var lineGeometry = new THREE.Geometry(); + lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) ); + lineGeometry.vertices.push( new THREE.Vector3( 0, 1, 0 ) ); + + this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: hex } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + + var coneGeometry = new THREE.CylinderGeometry( 0, 0.5, 1, 5, 1 ); + coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 0 ) ); + + this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: hex } ) ); + 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 ( hex ) { + + this.line.material.color.setHex( hex ); + this.cone.material.color.setHex( hex ); + +}; + +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.BoxHelper = function ( object ) { + + // 5____4 + // 1/___0/| + // | 6__|_7 + // 2/___3/ + + var vertices = [ + new THREE.Vector3( 1, 1, 1 ), + new THREE.Vector3( - 1, 1, 1 ), + new THREE.Vector3( - 1, - 1, 1 ), + new THREE.Vector3( 1, - 1, 1 ), + + new THREE.Vector3( 1, 1, - 1 ), + new THREE.Vector3( - 1, 1, - 1 ), + new THREE.Vector3( - 1, - 1, - 1 ), + new THREE.Vector3( 1, - 1, - 1 ) + ]; + + this.vertices = vertices; + + // TODO: Wouldn't be nice if Line had .segments? + + var geometry = new THREE.Geometry(); + geometry.vertices.push( + vertices[ 0 ], vertices[ 1 ], + vertices[ 1 ], vertices[ 2 ], + vertices[ 2 ], vertices[ 3 ], + vertices[ 3 ], vertices[ 0 ], + + vertices[ 4 ], vertices[ 5 ], + vertices[ 5 ], vertices[ 6 ], + vertices[ 6 ], vertices[ 7 ], + vertices[ 7 ], vertices[ 4 ], + + vertices[ 0 ], vertices[ 4 ], + vertices[ 1 ], vertices[ 5 ], + vertices[ 2 ], vertices[ 6 ], + vertices[ 3 ], vertices[ 7 ] + ); + + 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; + var vertices = this.vertices; + + vertices[ 0 ].set( max.x, max.y, max.z ); + vertices[ 1 ].set( min.x, max.y, max.z ); + vertices[ 2 ].set( min.x, min.y, max.z ); + vertices[ 3 ].set( max.x, min.y, max.z ); + vertices[ 4 ].set( max.x, max.y, min.z ); + vertices[ 5 ].set( min.x, max.y, min.z ); + vertices[ 6 ].set( min.x, min.y, min.z ); + vertices[ 7 ].set( max.x, min.y, min.z ); + + this.geometry.computeBoundingSphere(); + this.geometry.verticesNeedUpdate = true; + + this.matrixAutoUpdate = false; + this.matrixWorld = object.matrixWorld; + +}; + +/** + * @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 ); + +}; + +/** + * @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; + + }; + +}(); + +/** + * @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.PlaneGeometry( size, size ); + var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } ); + material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + this.lightPlane = new THREE.Mesh( geometry, material ); + this.add( this.lightPlane ); + + geometry = new THREE.Geometry(); + geometry.vertices.push( new THREE.Vector3() ); + geometry.vertices.push( 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 ); + + } + +}(); + + +/** + * @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', Float32Array, 2 * numEdges, 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 ); + +/** + * @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() ); + geometry.vertices.push( 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 ( object ) { + + var v1 = new THREE.Vector3(); + + return function ( object ) { + + this.object.updateMatrixWorld( true ); + + this.normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var vertices = this.geometry.vertices; + + var faces = this.object.geometry.faces; + + var worldMatrix = this.object.matrixWorld; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + v1.copy( face.normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size ); + + var idx = 2 * i; + + vertices[ idx ].copy( face.centroid ).applyMatrix4( worldMatrix ); + + vertices[ idx + 1 ].addVectors( vertices[ idx ], v1 ); + + } + + this.geometry.verticesNeedUpdate = true; + + return this; + + } + +}()); + + +/** + * @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; + +} + +/** + * @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; + + } + +}(); + + +/** + * @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 ); + + } + */ + +}; + + +/** + * @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 ); + + }; + +}(); + +/** + * @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() ); + geometry.vertices.push( 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; + + } + +}()); + +/** + * @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; + + } + +}()); + +/** + * @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 ++; + + } + + } + + } + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + 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; + + } + + } + + } else if ( object.geometry instanceof THREE.BufferGeometry && 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 ++; + + } + + } + + } + + } + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + 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 ]; + + } + + } + + } else if ( object.geometry instanceof THREE.BufferGeometry ) { // non-indexed BufferGeometry + + var vertices = object.geometry.attributes.position.array; + var numEdges = vertices.length / 3; + var numTris = numEdges / 3; + + geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 ); + + var coords = geometry.attributes.position.array; + + 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 ]; + + } + + } + + } + + 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 ); + +/** + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.ImmediateRenderObject = function () { + + THREE.Object3D.call( this ); + + this.render = function ( renderCallback ) { }; + +}; + +THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype ); + +/** + * @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; + + } + +}; + + + + + + + + + + + + + +/** + * @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; + + } + +}; + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.LensFlarePlugin = function () { + + 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 ) { + + var flares = scene.__webglFlares, + nFlares = flares.length; + + if ( ! nFlares ) 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 ); + + var i, j, jl, flare, sprite; + + for ( i = 0; i < nFlares; i ++ ) { + + size = 16 / viewportHeight; + scale.set( size * invAspect, size ); + + // calc object screen position + + 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 ( j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) { + + 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; + + }; + +}; + +/** + * @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(); + + 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, + renderList, + + 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 = 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 = scene.__webglObjects; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + webglObject.render = false; + + if ( object.visible && object.castShadow ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + + webglObject.render = true; + + } + + } + + } + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + + if ( webglObject.render ) { + + 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; + + } + + 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 + + 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 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(); + +/** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + +THREE.SpritePlugin = function () { + + var _gl, _renderer, _texture; + + 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 = '#ffffff'; + context.fillRect( 0, 0, canvas.width, canvas.height ); + + _texture = new THREE.Texture( canvas ); + _texture.needsUpdate = true; + + }; + + this.render = function ( scene, camera, viewportWidth, viewportHeight ) { + + var sprites = scene.__webglSprites, + nSprites = sprites.length; + + if ( ! nSprites ) 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 + + var i, sprite, material, fogType, scale = []; + + for( i = 0; i < nSprites; i ++ ) { + + sprite = sprites[ i ]; + material = sprite.material; + + if ( sprite.visible === false ) continue; + + sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); + sprite.z = - sprite._modelViewMatrix.elements[ 14 ]; + + } + + sprites.sort( painterSortStable ); + + // render all sprites + + for( i = 0; i < nSprites; i ++ ) { + + sprite = sprites[ i ]; + + if ( sprite.visible === false ) continue; + + 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; + + if ( scene.fog && material.fog ) { + + fogType = sceneFogType; + + } else { + + fogType = 0; + + } + + 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; + + } + + }; + +}; + +/** + * @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(); + + 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 = scene.__webglObjects; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + object = webglObject.object; + + webglObject.render = false; + + if ( object.visible ) { + + if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { + + object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + webglObject.render = true; + + } + + } + + } + + // render regular objects + + var objectMaterial, useMorphing, useSkinning; + + for ( j = 0, jl = renderList.length; j < jl; j ++ ) { + + webglObject = renderList[ j ]; + + if ( webglObject.render ) { + + object = webglObject.object; + buffer = webglObject.buffer; + + // todo: create proper depth material for particles + + if ( object instanceof THREE.ParticleSystem && !object.customDepthMaterial ) continue; + + objectMaterial = getObjectMaterial( object ); + + if ( objectMaterial ) _renderer.setMaterialFaces( object.material ); + + useMorphing = 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 ); + + }; + + // 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; + + }; + +}; + + +/** + * @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/js/three.min.js b/js/three.min.js new file mode 100755 index 0000000..d8d5bf4 --- /dev/null +++ b/js/three.min.js @@ -0,0 +1,722 @@ +// three.js - http://github.com/mrdoob/three.js +'use strict';var THREE={REVISION:"66"};self.console=self.console||{info:function(){},log:function(){},debug:function(){},warn:function(){},error:function(){}}; +(function(){for(var a=0,b=["ms","moz","webkit","o"],c=0;c>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSL:function(a,b,c){if(0===b)this.r=this.g=this.b=c;else{var d=function(a,b,c){0>c&&(c+=1);1c?b:c<2/3?a+6*(b-a)*(2/3-c):a};b=0.5>=c?c*(1+b):c+b-c*b;c=2*c-b;this.r=d(c,b,a+1/3);this.g=d(c,b,a);this.b=d(c,b,a-1/3)}return this},setStyle:function(a){if(/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test(a))return a=/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec(a),this.r=Math.min(255,parseInt(a[1],10))/255,this.g=Math.min(255,parseInt(a[2],10))/255,this.b=Math.min(255,parseInt(a[3],10))/255,this;if(/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test(a))return a=/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec(a),this.r= +Math.min(100,parseInt(a[1],10))/100,this.g=Math.min(100,parseInt(a[2],10))/100,this.b=Math.min(100,parseInt(a[3],10))/100,this;if(/^\#([0-9a-f]{6})$/i.test(a))return a=/^\#([0-9a-f]{6})$/i.exec(a),this.setHex(parseInt(a[1],16)),this;if(/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(a))return a=/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a),this.setHex(parseInt(a[1]+a[1]+a[2]+a[2]+a[3]+a[3],16)),this;if(/^(\w+)$/i.test(a))return this.setHex(THREE.ColorKeywords[a]),this},copy:function(a){this.r=a.r;this.g= +a.g;this.b=a.b;return this},copyGammaToLinear:function(a){this.r=a.r*a.r;this.g=a.g*a.g;this.b=a.b*a.b;return this},copyLinearToGamma:function(a){this.r=Math.sqrt(a.r);this.g=Math.sqrt(a.g);this.b=Math.sqrt(a.b);return this},convertGammaToLinear:function(){var a=this.r,b=this.g,c=this.b;this.r=a*a;this.g=b*b;this.b=c*c;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 255*this.r<<16^255*this.g<< +8^255*this.b<<0},getHexString:function(){return("000000"+this.getHex().toString(16)).slice(-6)},getHSL:function(a){a=a||{h:0,s:0,l:0};var b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var k=e-f,f=0.5>=h?k/(e+f):k/(2-e-f);switch(e){case b:g=(c-d)/k+(cf&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(k-g)/c,this._x= +0.25*c,this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y=0.25*c,this._z=(g+k)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+k)/c,this._z=0.25*c);this._updateEuler();return this},inverse:function(){this.conjugate().normalize();return this},conjugate:function(){this._x*=-1;this._y*=-1;this._z*=-1;this._updateEuler();return this},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 a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);return this},multiply:function(a,b){return void 0!==b?(console.warn("DEPRECATED: Quaternion's .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z,f= +a._w,g=b._x,h=b._y,k=b._z,l=b._w;this._x=c*l+f*g+d*k-e*h;this._y=d*l+f*h+e*g-c*k;this._z=e*l+f*k+c*h-d*g;this._w=f*l-c*g-d*h-e*k;this._updateEuler();return this},multiplyVector3:function(a){console.warn("DEPRECATED: Quaternion's .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.");return a.applyQuaternion(this)},slerp:function(a,b){var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z= +-a._z,g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;var h=Math.acos(g),k=Math.sqrt(1-g*g);if(0.001>Math.abs(k))return this._w=0.5*(f+this._w),this._x=0.5*(c+this._x),this._y=0.5*(d+this._y),this._z=0.5*(e+this._z),this;g=Math.sin((1-b)*h)/k;h=Math.sin(b*h)/k;this._w=f*g+this._w*h;this._x=c*g+this._x*h;this._y=d*g+this._y*h;this._z=e*g+this._z*h;this._updateEuler();return this},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._w===this._w}, +fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];this._w=a[3];this._updateEuler();return this},toArray:function(){return[this._x,this._y,this._z,this._w]},clone:function(){return new THREE.Quaternion(this._x,this._y,this._z,this._w)}};THREE.Quaternion.slerp=function(a,b,c,d){return c.copy(a).slerp(b,d)};THREE.Vector2=function(a,b){this.x=a||0;this.y=b||0}; +THREE.Vector2.prototype={constructor:THREE.Vector2,set:function(a,b){this.x=a;this.y=b;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a, +b){if(void 0!==b)return console.warn("DEPRECATED: Vector2's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},sub:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector2's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-= +a.y;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a):this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y); +return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector2,b=new THREE.Vector2);a.set(c,c);b.set(d,d);return this.clamp(a,b)}}(),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=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y? +Math.ceil(this.y):Math.floor(this.y);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.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(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x;a=this.y-a.y;return b*b+a*a},setLength:function(a){var b= +this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a){this.x=a[0];this.y=a[1];return this},toArray:function(){return[this.x,this.y]},clone:function(){return new THREE.Vector2(this.x,this.y)}};THREE.Vector3=function(a,b,c){this.x=a||0;this.y=b||0;this.z=c||0}; +THREE.Vector3.prototype={constructor:THREE.Vector3,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+ +a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."), +this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;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 a;return function(b){!1===b instanceof THREE.Euler&&console.error("ERROR: Vector3's .applyEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.");void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromEuler(b));return this}}(),applyAxisAngle:function(){var a;return function(b,c){void 0===a&&(a=new THREE.Quaternion);this.applyQuaternion(a.setFromAxisAngle(b,c));return this}}(), +applyMatrix3:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12];this.y=a[1]*b+a[5]*c+a[9]*d+a[13];this.z=a[2]*b+a[6]*c+a[10]*d+a[14];return this},applyProjection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;var e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y= +(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,c=this.y,d=this.z,e=a.x,f=a.y,g=a.z;a=a.w;var h=a*b+f*d-g*c,k=a*c+g*b-e*d,l=a*d+e*c-f*b,b=-e*b-f*c-g*d;this.x=h*a+b*-e+k*-g-l*-f;this.y=k*a+b*-f+l*-e-h*-g;this.z=l*a+b*-g+h*-f-k*-e;return this},transformDirection:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;this.normalize();return this}, +divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a):this.z=this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.z< +a.z?this.z=a.z:this.z>b.z&&(this.z=b.z);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector3,b=new THREE.Vector3);a.set(c,c,c);b.set(d,d,d);return this.clamp(a,b)}}(),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=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.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(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},cross:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(a,b);var c=this.x,d=this.y,e=this.z;this.x= +d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},crossVectors:function(a,b){var c=a.x,d=a.y,e=a.z,f=b.x,g=b.y,h=b.z;this.x=d*h-e*g;this.y=e*f-c*h;this.z=c*g-d*f;return this},projectOnVector:function(){var a,b;return function(c){void 0===a&&(a=new THREE.Vector3);a.copy(c).normalize();b=this.dot(a);return this.copy(a).multiplyScalar(b)}}(),projectOnPlane:function(){var a;return function(b){void 0===a&&(a=new THREE.Vector3);a.copy(this).projectOnVector(b);return this.sub(a)}}(),reflect:function(){var a; +return function(b){void 0===a&&(a=new THREE.Vector3);return this.sub(a.copy(b).multiplyScalar(2*this.dot(b)))}}(),angleTo:function(a){a=this.dot(a)/(this.length()*a.length());return Math.acos(THREE.Math.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y;a=this.z-a.z;return b*b+c*c+a*a},setEulerFromRotationMatrix:function(a,b){console.error("REMOVED: Vector3's setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code.")}, +setEulerFromQuaternion:function(a,b){console.error("REMOVED: Vector3's setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code.")},getPositionFromMatrix:function(a){console.warn("DEPRECATED: Vector3's .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code.");return this.setFromMatrixPosition(a)},getScaleFromMatrix:function(a){console.warn("DEPRECATED: Vector3's .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code."); +return this.setFromMatrixScale(a)},getColumnFromMatrix:function(a,b){console.warn("DEPRECATED: Vector3's .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code.");return this.setFromMatrixColumn(a,b)},setFromMatrixPosition:function(a){this.x=a.elements[12];this.y=a.elements[13];this.z=a.elements[14];return this},setFromMatrixScale:function(a){var b=this.set(a.elements[0],a.elements[1],a.elements[2]).length(),c=this.set(a.elements[4],a.elements[5],a.elements[6]).length(); +a=this.set(a.elements[8],a.elements[9],a.elements[10]).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){var c=4*a,d=b.elements;this.x=d[c];this.y=d[c+1];this.z=d[c+2];return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z},fromArray:function(a){this.x=a[0];this.y=a[1];this.z=a[2];return this},toArray:function(){return[this.x,this.y,this.z]},clone:function(){return new THREE.Vector3(this.x,this.y,this.z)}};THREE.Vector4=function(a,b,c,d){this.x=a||0;this.y=b||0;this.z=c||0;this.w=void 0!==d?d:1}; +THREE.Vector4.prototype={constructor:THREE.Vector4,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x; +case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.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(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this}, +applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,e=this.w;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a,this.w*=a):(this.z=this.y=this.x=0,this.w=1);return this},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b, +this.y=a.y/b,this.z=a.z/b);return this},setAxisAngleFromRotationMatrix:function(a){var b,c,d;a=a.elements;var e=a[0];d=a[4];var f=a[8],g=a[1],h=a[5],k=a[9];c=a[2];b=a[6];var l=a[10];if(0.01>Math.abs(d-g)&&0.01>Math.abs(f-c)&&0.01>Math.abs(k-b)){if(0.1>Math.abs(d+g)&&0.1>Math.abs(f+c)&&0.1>Math.abs(k+b)&&0.1>Math.abs(e+h+l-3))return this.set(1,0,0,0),this;a=Math.PI;e=(e+1)/2;h=(h+1)/2;l=(l+1)/2;d=(d+g)/4;f=(f+c)/4;k=(k+b)/4;e>h&&e>l?0.01>e?(b=0,d=c=0.707106781):(b=Math.sqrt(e),c=d/b,d=f/b):h>l?0.01> +h?(b=0.707106781,c=0,d=0.707106781):(c=Math.sqrt(h),b=d/c,d=k/c):0.01>l?(c=b=0.707106781,d=0):(d=Math.sqrt(l),b=f/d,c=k/d);this.set(b,c,d,a);return this}a=Math.sqrt((b-k)*(b-k)+(f-c)*(f-c)+(g-d)*(g-d));0.001>Math.abs(a)&&(a=1);this.x=(b-k)/a;this.y=(f-c)/a;this.z=(g-d)/a;this.w=Math.acos((e+h+l-1)/2);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);this.w>a.w&&(this.w=a.w);return this},max:function(a){this.xb.x&&(this.x=b.x);this.yb.y&&(this.y=b.y);this.zb.z&&(this.z=b.z);this.wb.w&&(this.w=b.w);return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new THREE.Vector4,b=new THREE.Vector4);a.set(c,c,c,c);b.set(d,d,d,d);return this.clamp(a,b)}}(),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=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z): +Math.floor(this.z);this.w=0>this.w?Math.ceil(this.w):Math.floor(this.w);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.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(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},fromArray:function(a){this.x=a[0];this.y=a[1];this.z=a[2];this.w=a[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)}};THREE.Euler=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._order=d||THREE.Euler.DefaultOrder};THREE.Euler.RotationOrders="XYZ YZX ZXY XZY YXZ ZYX".split(" ");THREE.Euler.DefaultOrder="XYZ"; +THREE.Euler.prototype={constructor:THREE.Euler,_x:0,_y:0,_z:0,_order:THREE.Euler.DefaultOrder,_quaternion:void 0,_updateQuaternion:function(){void 0!==this._quaternion&&this._quaternion.setFromEuler(this,!1)},get x(){return this._x},set x(a){this._x=a;this._updateQuaternion()},get y(){return this._y},set y(a){this._y=a;this._updateQuaternion()},get z(){return this._z},set z(a){this._z=a;this._updateQuaternion()},get order(){return this._order},set order(a){this._order=a;this._updateQuaternion()}, +set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._order=d||this._order;this._updateQuaternion();return this},copy:function(a){this._x=a._x;this._y=a._y;this._z=a._z;this._order=a._order;this._updateQuaternion();return this},setFromRotationMatrix:function(a,b){function c(a){return Math.min(Math.max(a,-1),1)}var d=a.elements,e=d[0],f=d[4],g=d[8],h=d[1],k=d[5],l=d[9],n=d[2],s=d[6],d=d[10];b=b||this._order;"XYZ"===b?(this._y=Math.asin(c(g)),0.99999>Math.abs(g)?(this._x=Math.atan2(-l,d),this._z= +Math.atan2(-f,e)):(this._x=Math.atan2(s,k),this._z=0)):"YXZ"===b?(this._x=Math.asin(-c(l)),0.99999>Math.abs(l)?(this._y=Math.atan2(g,d),this._z=Math.atan2(h,k)):(this._y=Math.atan2(-n,e),this._z=0)):"ZXY"===b?(this._x=Math.asin(c(s)),0.99999>Math.abs(s)?(this._y=Math.atan2(-n,d),this._z=Math.atan2(-f,k)):(this._y=0,this._z=Math.atan2(h,e))):"ZYX"===b?(this._y=Math.asin(-c(n)),0.99999>Math.abs(n)?(this._x=Math.atan2(s,d),this._z=Math.atan2(h,e)):(this._x=0,this._z=Math.atan2(-f,k))):"YZX"===b?(this._z= +Math.asin(c(h)),0.99999>Math.abs(h)?(this._x=Math.atan2(-l,k),this._y=Math.atan2(-n,e)):(this._x=0,this._y=Math.atan2(g,d))):"XZY"===b?(this._z=Math.asin(-c(f)),0.99999>Math.abs(f)?(this._x=Math.atan2(s,k),this._y=Math.atan2(g,e)):(this._x=Math.atan2(-l,d),this._y=0)):console.warn("WARNING: Euler.setFromRotationMatrix() given unsupported order: "+b);this._order=b;this._updateQuaternion();return this},setFromQuaternion:function(a,b,c){function d(a){return Math.min(Math.max(a,-1),1)}var e=a.x*a.x,f= +a.y*a.y,g=a.z*a.z,h=a.w*a.w;b=b||this._order;"XYZ"===b?(this._x=Math.atan2(2*(a.x*a.w-a.y*a.z),h-e-f+g),this._y=Math.asin(d(2*(a.x*a.z+a.y*a.w))),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h+e-f-g)):"YXZ"===b?(this._x=Math.asin(d(2*(a.x*a.w-a.y*a.z))),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h-e-f+g),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h-e+f-g)):"ZXY"===b?(this._x=Math.asin(d(2*(a.x*a.w+a.y*a.z))),this._y=Math.atan2(2*(a.y*a.w-a.z*a.x),h-e-f+g),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h-e+f-g)):"ZYX"=== +b?(this._x=Math.atan2(2*(a.x*a.w+a.z*a.y),h-e-f+g),this._y=Math.asin(d(2*(a.y*a.w-a.x*a.z))),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h+e-f-g)):"YZX"===b?(this._x=Math.atan2(2*(a.x*a.w-a.z*a.y),h-e+f-g),this._y=Math.atan2(2*(a.y*a.w-a.x*a.z),h+e-f-g),this._z=Math.asin(d(2*(a.x*a.y+a.z*a.w)))):"XZY"===b?(this._x=Math.atan2(2*(a.x*a.w+a.y*a.z),h-e+f-g),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h+e-f-g),this._z=Math.asin(d(2*(a.z*a.w-a.x*a.y)))):console.warn("WARNING: Euler.setFromQuaternion() given unsupported order: "+ +b);this._order=b;!1!==c&&this._updateQuaternion();return this},reorder:function(){var a=new THREE.Quaternion;return function(b){a.setFromEuler(this);this.setFromQuaternion(a,b)}}(),fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];void 0!==a[3]&&(this._order=a[3]);this._updateQuaternion();return this},toArray:function(){return[this._x,this._y,this._z,this._order]},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._order===this._order},clone:function(){return new THREE.Euler(this._x, +this._y,this._z,this._order)}};THREE.Line3=function(a,b){this.start=void 0!==a?a:new THREE.Vector3;this.end=void 0!==b?b:new THREE.Vector3}; +THREE.Line3.prototype={constructor:THREE.Line3,set:function(a,b){this.start.copy(a);this.end.copy(b);return this},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},center:function(a){return(a||new THREE.Vector3).addVectors(this.start,this.end).multiplyScalar(0.5)},delta:function(a){return(a||new THREE.Vector3).subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)},distance:function(){return this.start.distanceTo(this.end)},at:function(a, +b){var c=b||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);var e=b.dot(b),e=b.dot(a)/e;d&&(e=THREE.Math.clamp(e,0,1));return e}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);c=c||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a); +this.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)},clone:function(){return(new THREE.Line3).copy(this)}};THREE.Box2=function(a,b){this.min=void 0!==a?a:new THREE.Vector2(Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector2(-Infinity,-Infinity)}; +THREE.Box2.prototype={constructor:THREE.Box2,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){if(0this.max.x&&(this.max.x=b.x),b.ythis.max.y&&(this.max.y=b.y)}else this.makeEmpty();return this},setFromCenterAndSize:function(){var a=new THREE.Vector2;return function(b,c){var d=a.copy(c).multiplyScalar(0.5); +this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},makeEmpty:function(){this.min.x=this.min.y=Infinity;this.max.x=this.max.y=-Infinity;return this},empty:function(){return this.max.xthis.max.x||a.ythis.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector2).set((a.x-this.min.x)/(this.max.x-this.min.x), +(a.y-this.min.y)/(this.max.y-this.min.y))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector2).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector2;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max); +return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box2).copy(this)}};THREE.Box3=function(a,b){this.min=void 0!==a?a:new THREE.Vector3(Infinity,Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector3(-Infinity,-Infinity,-Infinity)}; +THREE.Box3.prototype={constructor:THREE.Box3,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},addPoint:function(a){a.xthis.max.x&&(this.max.x=a.x);a.ythis.max.y&&(this.max.y=a.y);a.zthis.max.z&&(this.max.z=a.z)},setFromPoints:function(a){if(0this.max.x||a.ythis.max.y||a.zthis.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector3).set((a.x-this.min.x)/(this.max.x- +this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},isIntersectionBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y||a.max.zthis.max.z?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector3).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a= +new THREE.Vector3;return function(b){b=b||new THREE.Sphere;b.center=this.center();b.radius=0.5*this.size(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[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(b){a[0].set(this.min.x,this.min.y, +this.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.makeEmpty();this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a); +this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box3).copy(this)}};THREE.Matrix3=function(a,b,c,d,e,f,g,h,k){this.elements=new Float32Array(9);this.set(void 0!==a?a:1,b||0,c||0,d||0,void 0!==e?e:1,f||0,g||0,h||0,void 0!==k?k:1)}; +THREE.Matrix3.prototype={constructor:THREE.Matrix3,set:function(a,b,c,d,e,f,g,h,k){var l=this.elements;l[0]=a;l[3]=b;l[6]=c;l[1]=d;l[4]=e;l[7]=f;l[2]=g;l[5]=h;l[8]=k;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},copy:function(a){a=a.elements;this.set(a[0],a[3],a[6],a[1],a[4],a[7],a[2],a[5],a[8]);return this},multiplyVector3:function(a){console.warn("DEPRECATED: Matrix3's .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.");return a.applyMatrix3(this)}, +multiplyVector3Array:function(){var a=new THREE.Vector3;return function(b){for(var c=0,d=b.length;cthis.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.elements.set(this.elements);c=1/g;var f=1/h,l=1/k;b.elements[0]*=c;b.elements[1]*= +c;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=l;b.elements[9]*=l;b.elements[10]*=l;d.setFromRotationMatrix(b);e.x=g;e.y=h;e.z=k;return this}}(),makeFrustum:function(a,b,c,d,e,f){var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(d-c);g[9]=(d+c)/(d-c);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makePerspective:function(a,b,c,d){a=c*Math.tan(THREE.Math.degToRad(0.5*a)); +var e=-a;return this.makeFrustum(e*b,a*b,e,a,c,d)},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=b-a,k=c-d,l=f-e;g[0]=2/h;g[4]=0;g[8]=0;g[12]=-((b+a)/h);g[1]=0;g[5]=2/k;g[9]=0;g[13]=-((c+d)/k);g[2]=0;g[6]=0;g[10]=-2/l;g[14]=-((f+e)/l);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]]},clone:function(){var a= +this.elements;return new THREE.Matrix4(a[0],a[4],a[8],a[12],a[1],a[5],a[9],a[13],a[2],a[6],a[10],a[14],a[3],a[7],a[11],a[15])}};THREE.Ray=function(a,b){this.origin=void 0!==a?a:new THREE.Vector3;this.direction=void 0!==b?b:new THREE.Vector3}; +THREE.Ray.prototype={constructor:THREE.Ray,set:function(a,b){this.origin.copy(a);this.direction.copy(b);return this},copy:function(a){this.origin.copy(a.origin);this.direction.copy(a.direction);return this},at:function(a,b){return(b||new THREE.Vector3).copy(this.direction).multiplyScalar(a).add(this.origin)},recast:function(){var a=new THREE.Vector3;return function(b){this.origin.copy(this.at(b,a));return this}}(),closestPointToPoint:function(a,b){var c=b||new THREE.Vector3;c.subVectors(a,this.origin); +var d=c.dot(this.direction);return 0>d?c.copy(this.origin):c.copy(this.direction).multiplyScalar(d).add(this.origin)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){var c=a.subVectors(b,this.origin).dot(this.direction);if(0>c)return this.origin.distanceTo(b);a.copy(this.direction).multiplyScalar(c).add(this.origin);return a.distanceTo(b)}}(),distanceSqToSegment:function(a,b,c,d){var e=a.clone().add(b).multiplyScalar(0.5),f=b.clone().sub(a).normalize(),g=0.5*a.distanceTo(b), +h=this.origin.clone().sub(e);a=-this.direction.dot(f);b=h.dot(this.direction);var k=-h.dot(f),l=h.lengthSq(),n=Math.abs(1-a*a),s,r;0<=n?(h=a*k-b,s=a*b-k,r=g*n,0<=h?s>=-r?s<=r?(g=1/n,h*=g,s*=g,a=h*(h+a*s+2*b)+s*(a*h+s+2*k)+l):(s=g,h=Math.max(0,-(a*s+b)),a=-h*h+s*(s+2*k)+l):(s=-g,h=Math.max(0,-(a*s+b)),a=-h*h+s*(s+2*k)+l):s<=-r?(h=Math.max(0,-(-a*g+b)),s=0a.normal.dot(this.direction)*b?!0:!1},distanceToPlane:function(a){var b=a.normal.dot(this.direction);if(0==b)return 0==a.distanceToPoint(this.origin)? +0:null;a=-(this.origin.dot(a.normal)+a.constant)/b;return 0<=a?a:null},intersectPlane:function(a,b){var c=this.distanceToPlane(a);return null===c?null:this.at(c,b)},isIntersectionBox:function(){var a=new THREE.Vector3;return function(b){return null!==this.intersectBox(b,a)}}(),intersectBox:function(a,b){var c,d,e,f,g;d=1/this.direction.x;f=1/this.direction.y;g=1/this.direction.z;var h=this.origin;0<=d?(c=(a.min.x-h.x)*d,d*=a.max.x-h.x):(c=(a.max.x-h.x)*d,d*=a.min.x-h.x);0<=f?(e=(a.min.y-h.y)*f,f*= +a.max.y-h.y):(e=(a.max.y-h.y)*f,f*=a.min.y-h.y);if(c>f||e>d)return null;if(e>c||c!==c)c=e;if(fg||e>d)return null;if(e>c||c!==c)c=e;if(gd?null:this.at(0<=c?c:d,b)},intersectTriangle:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3,d=new THREE.Vector3;return function(e,f,g,h,k){b.subVectors(f,e);c.subVectors(g,e);d.crossVectors(b,c);f=this.direction.dot(d);if(0< +f){if(h)return null;h=1}else if(0>f)h=-1,f=-f;else return null;a.subVectors(this.origin,e);e=h*this.direction.dot(c.crossVectors(a,c));if(0>e)return null;g=h*this.direction.dot(b.cross(a));if(0>g||e+g>f)return null;e=-h*a.dot(d);return 0>e?null:this.at(e/f,k)}}(),applyMatrix4:function(a){this.direction.add(this.origin).applyMatrix4(a);this.origin.applyMatrix4(a);this.direction.sub(this.origin);this.direction.normalize();return this},equals:function(a){return a.origin.equals(this.origin)&&a.direction.equals(this.direction)}, +clone:function(){return(new THREE.Ray).copy(this)}};THREE.Sphere=function(a,b){this.center=void 0!==a?a:new THREE.Vector3;this.radius=void 0!==b?b:0}; +THREE.Sphere.prototype={constructor:THREE.Sphere,set:function(a,b){this.center.copy(a);this.radius=b;return this},setFromPoints:function(){var a=new THREE.Box3;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).center(d);for(var e=0,f=0,g=b.length;f=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<= +this.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},clampPoint:function(a,b){var c=this.center.distanceToSquared(a),d=b||new THREE.Vector3;d.copy(a);c>this.radius*this.radius&&(d.sub(this.center).normalize(),d.multiplyScalar(this.radius).add(this.center));return d},getBoundingBox:function(a){a=a||new THREE.Box3;a.set(this.center,this.center);a.expandByScalar(this.radius); +return a},applyMatrix4:function(a){this.center.applyMatrix4(a);this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius},clone:function(){return(new THREE.Sphere).copy(this)}};THREE.Frustum=function(a,b,c,d,e,f){this.planes=[void 0!==a?a:new THREE.Plane,void 0!==b?b:new THREE.Plane,void 0!==c?c:new THREE.Plane,void 0!==d?d:new THREE.Plane,void 0!==e?e:new THREE.Plane,void 0!==f?f:new THREE.Plane]}; +THREE.Frustum.prototype={constructor:THREE.Frustum,set:function(a,b,c,d,e,f){var g=this.planes;g[0].copy(a);g[1].copy(b);g[2].copy(c);g[3].copy(d);g[4].copy(e);g[5].copy(f);return this},copy:function(a){for(var b=this.planes,c=0;6>c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements;a=c[0];var d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],k=c[6],l=c[7],n=c[8],s=c[9],r=c[10],q=c[11],u=c[12],p=c[13],v=c[14],c=c[15];b[0].setComponents(f-a,l-g,q-n,c-u).normalize();b[1].setComponents(f+ +a,l+g,q+n,c+u).normalize();b[2].setComponents(f+d,l+h,q+s,c+p).normalize();b[3].setComponents(f-d,l-h,q-s,c-p).normalize();b[4].setComponents(f-e,l-k,q-r,c-v).normalize();b[5].setComponents(f+e,l+k,q+r,c+v).normalize();return this},intersectsObject:function(){var a=new THREE.Sphere;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere);a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSphere:function(a){var b=this.planes, +c=a.center;a=-a.radius;for(var d=0;6>d;d++)if(b[d].distanceToPoint(c)e;e++){var f=d[e];a.x=0g&&0>f)return!1}return!0}}(), +containsPoint:function(a){for(var b=this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0},clone:function(){return(new THREE.Frustum).copy(this)}};THREE.Plane=function(a,b){this.normal=void 0!==a?a:new THREE.Vector3(1,0,0);this.constant=void 0!==b?b:0}; +THREE.Plane.prototype={constructor:THREE.Plane,set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d, +c);return this}}(),copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){return this.orthoPoint(a,b).sub(a).negate()},orthoPoint:function(a, +b){var c=this.distanceToPoint(a);return(b||new THREE.Vector3).copy(this.normal).multiplyScalar(c)},isIntersectionLine:function(a){var b=this.distanceToPoint(a.start);a=this.distanceToPoint(a.end);return 0>b&&0a&&0f||1e;e++)8==e||13==e||18==e||23==e?b[e]="-":14==e?b[e]="4":(2>=c&&(c=33554432+16777216*Math.random()|0),d=c&15,c>>=4,b[e]=a[19==e?d&3|8:d]);return b.join("")}}(),clamp:function(a,b,c){return ac?c:a},clampBottom:function(a,b){return a=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},random16:function(){return(65280*Math.random()+255*Math.random())/65535},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(0.5-Math.random())},sign:function(a){return 0>a?-1:0this.points.length-2?this.points.length-1:f+1;c[3]=f>this.points.length-3?this.points.length-1: +f+2;l=this.points[c[0]];n=this.points[c[1]];s=this.points[c[2]];r=this.points[c[3]];h=g*g;k=g*h;d.x=b(l.x,n.x,s.x,r.x,g,h,k);d.y=b(l.y,n.y,s.y,r.y,g,h,k);d.z=b(l.z,n.z,s.z,r.z,g,h,k);return d};this.getControlPointsArray=function(){var a,b,c=this.points.length,d=[];for(a=0;a=b.x+b.y}}(); +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(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},area:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){a.subVectors(this.c,this.b);b.subVectors(this.a,this.b);return 0.5*a.cross(b).length()}}(),midpoint:function(a){return(a|| +new THREE.Vector3).addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},normal:function(a){return THREE.Triangle.normal(this.a,this.b,this.c,a)},plane:function(a){return(a||new THREE.Plane).setFromCoplanarPoints(this.a,this.b,this.c)},barycoordFromPoint:function(a,b){return THREE.Triangle.barycoordFromPoint(a,this.a,this.b,this.c,b)},containsPoint:function(a){return THREE.Triangle.containsPoint(a,this.a,this.b,this.c)},equals:function(a){return a.a.equals(this.a)&&a.b.equals(this.b)&&a.c.equals(this.c)}, +clone:function(){return(new THREE.Triangle).copy(this)}};THREE.Vertex=function(a){console.warn("THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.");return a};THREE.UV=function(a,b){console.warn("THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.");return new THREE.Vector2(a,b)};THREE.Clock=function(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1}; +THREE.Clock.prototype={constructor:THREE.Clock,start:function(){this.oldTime=this.startTime=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now();this.running=!0},stop:function(){this.getElapsedTime();this.running=!1},getElapsedTime:function(){this.getDelta();return this.elapsedTime},getDelta:function(){var a=0;this.autoStart&&!this.running&&this.start();if(this.running){var b=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now(), +a=0.001*(b-this.oldTime);this.oldTime=b;this.elapsedTime+=a}return a}};THREE.EventDispatcher=function(){}; +THREE.EventDispatcher.prototype={constructor:THREE.EventDispatcher,apply:function(a){a.addEventListener=THREE.EventDispatcher.prototype.addEventListener;a.hasEventListener=THREE.EventDispatcher.prototype.hasEventListener;a.removeEventListener=THREE.EventDispatcher.prototype.removeEventListener;a.dispatchEvent=THREE.EventDispatcher.prototype.dispatchEvent},addEventListener:function(a,b){void 0===this._listeners&&(this._listeners={});var c=this._listeners;void 0===c[a]&&(c[a]=[]);-1===c[a].indexOf(b)&& +c[a].push(b)},hasEventListener:function(a,b){if(void 0===this._listeners)return!1;var c=this._listeners;return void 0!==c[a]&&-1!==c[a].indexOf(b)?!0:!1},removeEventListener:function(a,b){if(void 0!==this._listeners){var c=this._listeners[a];if(void 0!==c){var d=c.indexOf(b);-1!==d&&c.splice(d,1)}}},dispatchEvent:function(){var a=[];return function(b){if(void 0!==this._listeners){var c=this._listeners[b.type];if(void 0!==c){b.target=this;for(var d=c.length,e=0;ef.scale.x)return q;q.push({distance:u,point:f.position,face:null,object:f})}else if(f instanceof +a.LOD)d.setFromMatrixPosition(f.matrixWorld),u=n.ray.origin.distanceTo(d),l(f.getObjectForDistance(u),n,q);else if(f instanceof a.Mesh){var p=f.geometry;null===p.boundingSphere&&p.computeBoundingSphere();b.copy(p.boundingSphere);b.applyMatrix4(f.matrixWorld);if(!1===n.ray.isIntersectionSphere(b))return q;e.getInverse(f.matrixWorld);c.copy(n.ray).applyMatrix4(e);if(null!==p.boundingBox&&!1===c.isIntersectionBox(p.boundingBox))return q;if(p instanceof a.BufferGeometry){var v=f.material;if(void 0=== +v)return q;var w=p.attributes,t,x,z=n.precision;if(void 0!==w.index)for(var B=p.offsets,E=w.index.array,H=w.position.array,D=0,G=B.length;Dn.far||q.push({distance:u,point:K,indices:[w,t,x],face:null,faceIndex:null,object:f}))}else for(H=w.position.array,p=0,O=w.position.array.length;pn.far||q.push({distance:u,point:K, +indices:[w,t,x],face:null,faceIndex:null,object:f}))}else if(p instanceof a.Geometry)for(E=f.material instanceof a.MeshFaceMaterial,H=!0===E?f.material.materials:null,z=n.precision,B=p.vertices,D=0,G=p.faces.length;Dn.far||q.push({distance:u,point:K,face:I,faceIndex:D,object:f}))}}else if(f instanceof +a.Line){z=n.linePrecision;v=z*z;p=f.geometry;null===p.boundingSphere&&p.computeBoundingSphere();b.copy(p.boundingSphere);b.applyMatrix4(f.matrixWorld);if(!1===n.ray.isIntersectionSphere(b))return q;e.getInverse(f.matrixWorld);c.copy(n.ray).applyMatrix4(e);if(p instanceof a.Geometry)for(B=p.vertices,z=B.length,w=new a.Vector3,t=new a.Vector3,x=f.type===a.LineStrip?1:2,p=0;pv||(u=c.origin.distanceTo(t),un.far||q.push({distance:u,point:w.clone().applyMatrix4(f.matrixWorld), +face:null,faceIndex:null,object:f}))}},n=function(a,b,c){a=a.getDescendants();for(var d=0,e=a.length;de&&0>f||0>g&& +0>h)return!1;0>e?c=Math.max(c,e/(e-f)):0>f&&(d=Math.min(d,e/(e-f)));0>g?c=Math.max(c,g/(g-h)):0>h&&(d=Math.min(d,g/(g-h)));if(d=c.x&&-1<=c.y&&1>=c.y&&-1<=c.z&&1>=c.z},h=function(a,b,c){L[0]=a.positionScreen;L[1]=b.positionScreen;L[2]=c.positionScreen;return!0=== +a.visible||!0===b.visible||!0===c.visible||C.isIntersectionBox(A.setFromPoints(L))?0>(c.positionScreen.x-a.positionScreen.x)*(b.positionScreen.y-a.positionScreen.y)-(c.positionScreen.y-a.positionScreen.y)*(b.positionScreen.x-a.positionScreen.x):!1};return{setObject:function(a){e=a;f.getNormalMatrix(e.matrixWorld);d.length=0},projectVertex:g,checkTriangleVisibility:h,pushVertex:function(b,c,d){l=a();l.position.set(b,c,d);g(l)},pushNormal:function(a,b,c){d.push(a,b,c)},pushLine:function(a,b){var d= +s[a],f=s[b];w=c();w.id=e.id;w.v1.copy(d);w.v2.copy(f);w.z=(d.positionScreen.z+f.positionScreen.z)/2;w.material=e.material;G.elements.push(w)},pushTriangle:function(a,c,g){var k=s[a],l=s[c],n=s[g];if(!0===h(k,l,n)){q=b();q.id=e.id;q.v1.copy(k);q.v2.copy(l);q.v3.copy(n);q.z=(k.positionScreen.z+l.positionScreen.z+n.positionScreen.z)/3;for(k=0;3>k;k++)l=3*arguments[k],n=q.vertexNormalsModel[k],n.set(d[l+0],d[l+1],d[l+2]),n.applyMatrix3(f).normalize();q.vertexNormalsLength=3;q.material=e.material;G.elements.push(q)}}}}; +this.projectScene=function(f,h,k,l){var p,r,v,x,y,z,C,L,A;E=t=u=0;G.elements.length=0;!0===f.autoUpdate&&f.updateMatrixWorld();void 0===h.parent&&h.updateMatrixWorld();Q.copy(h.matrixWorldInverse.getInverse(h.matrixWorld));Y.multiplyMatrices(h.projectionMatrix,Q);ga.setFromMatrix(Y);g=0;G.objects.length=0;G.lights.length=0;W(f);!0===k&&G.objects.sort(d);f=0;for(k=G.objects.length;f=F.z&&(E===D?(x=new THREE.RenderableSprite, +H.push(x),D++,E++,B=x):B=H[E++],B.id=p.id,B.x=F.x*r,B.y=F.y*r,B.z=F.z,B.object=p,B.rotation=p.rotation,B.scale.x=p.scale.x*Math.abs(B.x-(F.x+h.projectionMatrix.elements[0])/(F.w+h.projectionMatrix.elements[12])),B.scale.y=p.scale.y*Math.abs(B.y-(F.y+h.projectionMatrix.elements[5])/(F.w+h.projectionMatrix.elements[13])),B.material=p.material,G.elements.push(B)));!0===l&&G.elements.sort(d);return G}};THREE.Face3=function(a,b,c,d,e,f){this.a=a;this.b=b;this.c=c;this.normal=d instanceof THREE.Vector3?d:new THREE.Vector3;this.vertexNormals=d instanceof Array?d:[];this.color=e instanceof THREE.Color?e:new THREE.Color;this.vertexColors=e instanceof Array?e:[];this.vertexTangents=[];this.materialIndex=void 0!==f?f:0;this.centroid=new THREE.Vector3}; +THREE.Face3.prototype={constructor:THREE.Face3,clone:function(){var a=new THREE.Face3(this.a,this.b,this.c);a.normal.copy(this.normal);a.color.copy(this.color);a.centroid.copy(this.centroid);a.materialIndex=this.materialIndex;var b,c;b=0;for(c=this.vertexNormals.length;bb.max.x&&(b.max.x=e);fb.max.y&&(b.max.y=f);gb.max.z&&(b.max.z=g)}}if(void 0===a||0===a.length)this.boundingBox.min.set(0,0,0),this.boundingBox.max.set(0,0,0)},computeBoundingSphere:function(){var a=new THREE.Box3,b=new THREE.Vector3; +return function(){null===this.boundingSphere&&(this.boundingSphere=new THREE.Sphere);var c=this.attributes.position.array;if(c){a.makeEmpty();for(var d=this.boundingSphere.center,e=0,f=c.length;eoa?-1:1;h[4*a]= +T.x;h[4*a+1]=T.y;h[4*a+2]=T.z;h[4*a+3]=Fa}if(void 0===this.attributes.index||void 0===this.attributes.position||void 0===this.attributes.normal||void 0===this.attributes.uv)console.warn("Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()");else{var c=this.attributes.index.array,d=this.attributes.position.array,e=this.attributes.normal.array,f=this.attributes.uv.array,g=d.length/3;void 0===this.attributes.tangent&&(this.attributes.tangent={itemSize:4,array:new Float32Array(4* +g)});for(var h=this.attributes.tangent.array,k=[],l=[],n=0;np;p++)u=a[3*c+p],-1==r[u]?(s[2*p]=u,s[2*p+1]=-1,n++):r[u]k.index+b)for(k={start:f,count:0,index:g},h.push(k),n=0;6>n;n+=2)p=s[n+1],-1n;n+=2)u=s[n],p=s[n+1],-1===p&&(p=g++),r[u]=p,q[p]=u,e[f++]=p-k.index,k.count++}this.reorderBuffers(e,q,g);return this.offsets=h},reorderBuffers:function(a,b,c){var d={},e=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array, +Float64Array],f;for(f in this.attributes)if("index"!=f)for(var g=this.attributes[f].array,h=0,k=e.length;hd?-1:1,e.vertexTangents[c]=new THREE.Vector4(z.x,z.y,z.z,d);this.hasTangents=!0},computeLineDistances:function(){for(var a=0,b=this.vertices,c=0,d=b.length;cd;d++)if(e[d]==e[(d+1)%3]){a.push(f);break}for(f=a.length-1;0<=f;f--)for(e= +a[f],this.faces.splice(e,1),c=0,g=this.faceVertexUvs.length;ca.opacity)l.transparent=a.transparent;void 0!==a.depthTest&&(l.depthTest=a.depthTest);void 0!==a.depthWrite&&(l.depthWrite=a.depthWrite);void 0!==a.visible&&(l.visible=a.visible);void 0!==a.flipSided&&(l.side=THREE.BackSide); +void 0!==a.doubleSided&&(l.side=THREE.DoubleSide);void 0!==a.wireframe&&(l.wireframe=a.wireframe);void 0!==a.vertexColors&&("face"===a.vertexColors?l.vertexColors=THREE.FaceColors:a.vertexColors&&(l.vertexColors=THREE.VertexColors));a.colorDiffuse?l.color=g(a.colorDiffuse):a.DbgColor&&(l.color=a.DbgColor);a.colorSpecular&&(l.specular=g(a.colorSpecular));a.colorAmbient&&(l.ambient=g(a.colorAmbient));a.transparency&&(l.opacity=a.transparency);a.specularCoef&&(l.shininess=a.specularCoef);a.mapDiffuse&& +b&&f(l,"map",a.mapDiffuse,a.mapDiffuseRepeat,a.mapDiffuseOffset,a.mapDiffuseWrap,a.mapDiffuseAnisotropy);a.mapLight&&b&&f(l,"lightMap",a.mapLight,a.mapLightRepeat,a.mapLightOffset,a.mapLightWrap,a.mapLightAnisotropy);a.mapBump&&b&&f(l,"bumpMap",a.mapBump,a.mapBumpRepeat,a.mapBumpOffset,a.mapBumpWrap,a.mapBumpAnisotropy);a.mapNormal&&b&&f(l,"normalMap",a.mapNormal,a.mapNormalRepeat,a.mapNormalOffset,a.mapNormalWrap,a.mapNormalAnisotropy);a.mapSpecular&&b&&f(l,"specularMap",a.mapSpecular,a.mapSpecularRepeat, +a.mapSpecularOffset,a.mapSpecularWrap,a.mapSpecularAnisotropy);a.mapBumpScale&&(l.bumpScale=a.mapBumpScale);a.mapNormal?(k=THREE.ShaderLib.normalmap,n=THREE.UniformsUtils.clone(k.uniforms),n.tNormal.value=l.normalMap,a.mapNormalFactor&&n.uNormalScale.value.set(a.mapNormalFactor,a.mapNormalFactor),l.map&&(n.tDiffuse.value=l.map,n.enableDiffuse.value=!0),l.specularMap&&(n.tSpecular.value=l.specularMap,n.enableSpecular.value=!0),l.lightMap&&(n.tAO.value=l.lightMap,n.enableAO.value=!0),n.diffuse.value.setHex(l.color), +n.specular.value.setHex(l.specular),n.ambient.value.setHex(l.ambient),n.shininess.value=l.shininess,void 0!==l.opacity&&(n.opacity.value=l.opacity),k=new THREE.ShaderMaterial({fragmentShader:k.fragmentShader,vertexShader:k.vertexShader,uniforms:n,lights:!0,fog:!0}),l.transparent&&(k.transparent=!0)):k=new THREE[k](l);void 0!==a.DbgName&&(k.name=a.DbgName);return k}};THREE.XHRLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager}; +THREE.XHRLoader.prototype={constructor:THREE.XHRLoader,load:function(a,b,c,d){var e=this,f=new XMLHttpRequest;void 0!==b&&f.addEventListener("load",function(c){b(c.target.responseText);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);void 0!==d&&f.addEventListener("error",function(a){d(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);f.open("GET",a,!0);f.send(null);e.manager.itemStart(a)},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.ImageLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager}; +THREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b,c,d){var e=this,f=document.createElement("img");void 0!==b&&f.addEventListener("load",function(c){e.manager.itemEnd(a);b(this)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);void 0!==d&&f.addEventListener("error",function(a){d(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);f.src=a;e.manager.itemStart(a);return f},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.JSONLoader=function(a){THREE.Loader.call(this,a);this.withCredentials=!1};THREE.JSONLoader.prototype=Object.create(THREE.Loader.prototype);THREE.JSONLoader.prototype.load=function(a,b,c){c=c&&"string"===typeof c?c:this.extractUrlBase(a);this.onLoadStart();this.loadAjaxJSON(this,a,b,c)}; +THREE.JSONLoader.prototype.loadAjaxJSON=function(a,b,c,d,e){var f=new XMLHttpRequest,g=0;f.onreadystatechange=function(){if(f.readyState===f.DONE)if(200===f.status||0===f.status){if(f.responseText){var h=JSON.parse(f.responseText);if("scene"===h.metadata.type){console.error('THREE.JSONLoader: "'+b+'" seems to be a Scene. Use THREE.SceneLoader instead.');return}h=a.parse(h,d);c(h.geometry,h.materials)}else console.error('THREE.JSONLoader: "'+b+'" seems to be unreachable or the file is empty.');a.onLoadComplete()}else console.error("THREE.JSONLoader: Couldn't load \""+ +b+'" ('+f.status+")");else f.readyState===f.LOADING?e&&(0===g&&(g=f.getResponseHeader("Content-Length")),e({total:g,loaded:f.responseText.length})):f.readyState===f.HEADERS_RECEIVED&&void 0!==e&&(g=f.getResponseHeader("Content-Length"))};f.open("GET",b,!0);f.withCredentials=this.withCredentials;f.send(null)}; +THREE.JSONLoader.prototype.parse=function(a,b){var c=new THREE.Geometry,d=void 0!==a.scale?1/a.scale:1;(function(b){var d,g,h,k,l,n,s,r,q,u,p,v,w,t=a.faces;n=a.vertices;var x=a.normals,z=a.colors,B=0;if(void 0!==a.uvs){for(d=0;dg;g++)r=t[k++],w=v[2*r],r=v[2*r+1],w=new THREE.Vector2(w,r),2!==g&&c.faceVertexUvs[d][h].push(w),0!==g&&c.faceVertexUvs[d][h+1].push(w);s&&(s=3*t[k++],q.normal.set(x[s++],x[s++],x[s]),p.normal.copy(q.normal));if(u)for(d=0;4>d;d++)s=3*t[k++],u=new THREE.Vector3(x[s++], +x[s++],x[s]),2!==d&&q.vertexNormals.push(u),0!==d&&p.vertexNormals.push(u);n&&(n=t[k++],n=z[n],q.color.setHex(n),p.color.setHex(n));if(b)for(d=0;4>d;d++)n=t[k++],n=z[n],2!==d&&q.vertexColors.push(new THREE.Color(n)),0!==d&&p.vertexColors.push(new THREE.Color(n));c.faces.push(q);c.faces.push(p)}else{q=new THREE.Face3;q.a=t[k++];q.b=t[k++];q.c=t[k++];h&&(h=t[k++],q.materialIndex=h);h=c.faces.length;if(d)for(d=0;dg;g++)r=t[k++],w=v[2*r],r=v[2*r+1], +w=new THREE.Vector2(w,r),c.faceVertexUvs[d][h].push(w);s&&(s=3*t[k++],q.normal.set(x[s++],x[s++],x[s]));if(u)for(d=0;3>d;d++)s=3*t[k++],u=new THREE.Vector3(x[s++],x[s++],x[s]),q.vertexNormals.push(u);n&&(n=t[k++],q.color.setHex(z[n]));if(b)for(d=0;3>d;d++)n=t[k++],q.vertexColors.push(new THREE.Color(z[n]));c.faces.push(q)}})(d);(function(){if(a.skinWeights)for(var b=0,d=a.skinWeights.length;bC.parameters.opacity&&(C.parameters.transparent=!0);C.parameters.normalMap?(O=THREE.ShaderLib.normalmap,y=THREE.UniformsUtils.clone(O.uniforms), +t=C.parameters.color,F=C.parameters.specular,w=C.parameters.ambient,K=C.parameters.shininess,y.tNormal.value=D.textures[C.parameters.normalMap],C.parameters.normalScale&&y.uNormalScale.value.set(C.parameters.normalScale[0],C.parameters.normalScale[1]),C.parameters.map&&(y.tDiffuse.value=C.parameters.map,y.enableDiffuse.value=!0),C.parameters.envMap&&(y.tCube.value=C.parameters.envMap,y.enableReflection.value=!0,y.reflectivity.value=C.parameters.reflectivity),C.parameters.lightMap&&(y.tAO.value=C.parameters.lightMap, +y.enableAO.value=!0),C.parameters.specularMap&&(y.tSpecular.value=D.textures[C.parameters.specularMap],y.enableSpecular.value=!0),C.parameters.displacementMap&&(y.tDisplacement.value=D.textures[C.parameters.displacementMap],y.enableDisplacement.value=!0,y.uDisplacementBias.value=C.parameters.displacementBias,y.uDisplacementScale.value=C.parameters.displacementScale),y.diffuse.value.setHex(t),y.specular.value.setHex(F),y.ambient.value.setHex(w),y.shininess.value=K,C.parameters.opacity&&(y.opacity.value= +C.parameters.opacity),p=new THREE.ShaderMaterial({fragmentShader:O.fragmentShader,vertexShader:O.vertexShader,uniforms:y,lights:!0,fog:!0})):p=new THREE[C.type](C.parameters);p.name=A;D.materials[A]=p}for(A in I.materials)if(C=I.materials[A],C.parameters.materials){L=[];for(t=0;th.end&&(h.end=e);b||(b=g)}}a.firstAnimation=b}; +THREE.MorphAnimMesh.prototype.setAnimationLabel=function(a,b,c){this.geometry.animations||(this.geometry.animations={});this.geometry.animations[a]={start:b,end:c}};THREE.MorphAnimMesh.prototype.playAnimation=function(a,b){var c=this.geometry.animations[a];c?(this.setFrameRange(c.start,c.end),this.duration=(c.end-c.start)/b*1E3,this.time=0):console.warn("animation["+a+"] undefined")}; +THREE.MorphAnimMesh.prototype.updateAnimation=function(a){var b=this.duration/this.length;this.time+=this.direction*a;if(this.mirroredLoop){if(this.time>this.duration||0>this.time)this.direction*=-1,this.time>this.duration&&(this.time=this.duration,this.directionBackwards=!0),0>this.time&&(this.time=0,this.directionBackwards=!1)}else this.time%=this.duration,0>this.time&&(this.time+=this.duration);a=this.startKeyframe+THREE.Math.clamp(Math.floor(this.time/b),0,this.length-1);a!==this.currentKeyframe&& +(this.morphTargetInfluences[this.lastKeyframe]=0,this.morphTargetInfluences[this.currentKeyframe]=1,this.morphTargetInfluences[a]=0,this.lastKeyframe=this.currentKeyframe,this.currentKeyframe=a);b=this.time%b/b;this.directionBackwards&&(b=1-b);this.morphTargetInfluences[this.currentKeyframe]=b;this.morphTargetInfluences[this.lastKeyframe]=1-b}; +THREE.MorphAnimMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.MorphAnimMesh(this.geometry,this.material));a.duration=this.duration;a.mirroredLoop=this.mirroredLoop;a.time=this.time;a.lastKeyframe=this.lastKeyframe;a.currentKeyframe=this.currentKeyframe;a.direction=this.direction;a.directionBackwards=this.directionBackwards;THREE.Mesh.prototype.clone.call(this,a);return a};THREE.LOD=function(){THREE.Object3D.call(this);this.objects=[]};THREE.LOD.prototype=Object.create(THREE.Object3D.prototype);THREE.LOD.prototype.addLevel=function(a,b){void 0===b&&(b=0);b=Math.abs(b);for(var c=0;c=this.objects[d].distance)this.objects[d-1].object.visible=!1,this.objects[d].object.visible=!0;else break;for(;d=h||(h*=f.intensity,c.add(Ka.multiplyScalar(h)))}else f instanceof THREE.PointLight&&(g=Na.setFromMatrixPosition(f.matrixWorld),h=b.dot(Na.subVectors(g,a).normalize()),0>=h||(h*=0==f.distance?1:1-Math.min(a.distanceTo(g)/f.distance,1),0!=h&&(h*=f.intensity,c.add(Ka.multiplyScalar(h)))))}} +function c(a,b,c,d){r(b);q(c);u(d);p(a.getStyle());y.stroke();Oa.expandByScalar(2*b)}function d(a){v(a.getStyle());y.fill()}function e(a){f(a.target)}function f(a){var b=a.wrapS===THREE.RepeatWrapping,c=a.wrapT===THREE.RepeatWrapping,d=a.image,e=document.createElement("canvas");e.width=d.width;e.height=d.height;var f=e.getContext("2d");f.setTransform(1,0,0,-1,0,d.height);f.drawImage(d,0,0);la[a.id]=y.createPattern(e,!0===b&&!0===c?"repeat":!0===b&&!1===c?"repeat-x":!1===b&&!0===c?"repeat-y":"no-repeat")} +function g(a,b,c,d,g,h,k,m,l,n,p,r,q){if(!(q instanceof THREE.DataTexture)){!1===q.hasEventListener("update",e)&&(void 0!==q.image&&0C&&y.clearRect(na.min.x|0,na.min.y|0,na.max.x-na.min.x|0,na.max.y-na.min.y|0),0W.positionScreen.z||1N.positionScreen.z||1ca.positionScreen.z||1 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif", +lights_lambert_vertex:"vLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\nvLightBack = vec3( 0.0 );\n#endif\ntransformedNormal = normalize( transformedNormal );\n#if MAX_DIR_LIGHTS > 0\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, dirVector );\nvec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\ndirectionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\ndirectionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += directionalLightColor[ i ] * directionalLightWeighting;\n#ifdef DOUBLE_SIDED\nvLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;\n#endif\n}\n#endif\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\npointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\npointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;\n#ifdef DOUBLE_SIDED\nvLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;\n#endif\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\nspotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\nspotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;\n#ifdef DOUBLE_SIDED\nvLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;\n#endif\n}\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nfloat hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;\nvLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n#ifdef DOUBLE_SIDED\nvLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n#endif\n}\n#endif\nvLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;\n#ifdef DOUBLE_SIDED\nvLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;\n#endif", +lights_phong_pars_vertex:"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif",lights_phong_vertex:"#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvWorldPosition = worldPosition.xyz;\n#endif",lights_phong_pars_fragment:"uniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;", +lights_phong_fragment:"vec3 normal = normalize( vNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#ifdef DOUBLE_SIDED\nnormal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n#endif\n#ifdef USE_NORMALMAP\nnormal = perturbNormal2Arb( -vViewPosition, normal );\n#elif defined( USE_BUMPMAP )\nnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dotProduct, 0.0 );\n#endif\npointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;\nvec3 pointHalfVector = normalize( lVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, pointHalfVector ), 0.0 ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dotProduct, 0.0 );\n#endif\nspotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;\nvec3 spotHalfVector = normalize( lVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, spotHalfVector ), 0.0 ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, dirVector );\n#ifdef WRAP_AROUND\nfloat dirDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dotProduct, 0.0 );\n#endif\ndirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( dirVector, dirHalfVector ), 0.0 ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += diffuse * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVector, hemiHalfVectorSky ), 0.0 ), 5.0 );\nvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( max( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 0.0 ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n#endif", +color_pars_fragment:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_fragment:"#ifdef USE_COLOR\ngl_FragColor = gl_FragColor * vec4( vColor, 1.0 );\n#endif",color_pars_vertex:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_vertex:"#ifdef USE_COLOR\n#ifdef GAMMA_INPUT\nvColor = color * color;\n#else\nvColor = color;\n#endif\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n#ifdef BONE_TEXTURE\nuniform sampler2D boneTexture;\nuniform int boneTextureWidth;\nuniform int boneTextureHeight;\nmat4 getBoneMatrix( const in float i ) {\nfloat j = i * 4.0;\nfloat x = mod( j, float( boneTextureWidth ) );\nfloat y = floor( j / float( boneTextureWidth ) );\nfloat dx = 1.0 / float( boneTextureWidth );\nfloat dy = 1.0 / float( boneTextureHeight );\ny = dy * ( y + 0.5 );\nvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\nvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\nvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\nvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\nmat4 bone = mat4( v1, v2, v3, v4 );\nreturn bone;\n}\n#else\nuniform mat4 boneGlobalMatrices[ MAX_BONES ];\nmat4 getBoneMatrix( const in float i ) {\nmat4 bone = boneGlobalMatrices[ int(i) ];\nreturn bone;\n}\n#endif\n#endif", +skinbase_vertex:"#ifdef USE_SKINNING\nmat4 boneMatX = getBoneMatrix( skinIndex.x );\nmat4 boneMatY = getBoneMatrix( skinIndex.y );\nmat4 boneMatZ = getBoneMatrix( skinIndex.z );\nmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n#ifdef USE_MORPHTARGETS\nvec4 skinVertex = vec4( morphed, 1.0 );\n#else\nvec4 skinVertex = vec4( position, 1.0 );\n#endif\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned += boneMatY * skinVertex * skinWeight.y;\nskinned += boneMatZ * skinVertex * skinWeight.z;\nskinned += boneMatW * skinVertex * skinWeight.w;\n#endif", +morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n#ifndef USE_MORPHNORMALS\nuniform float morphTargetInfluences[ 8 ];\n#else\nuniform float morphTargetInfluences[ 4 ];\n#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\nvec3 morphed = vec3( 0.0 );\nmorphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\nmorphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\nmorphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\nmorphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n#ifndef USE_MORPHNORMALS\nmorphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\nmorphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\nmorphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\nmorphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n#endif\nmorphed += position;\n#endif", +default_vertex:"vec4 mvPosition;\n#ifdef USE_SKINNING\nmvPosition = modelViewMatrix * skinned;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( morphed, 1.0 );\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( position, 1.0 );\n#endif\ngl_Position = projectionMatrix * mvPosition;",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\nvec3 morphedNormal = vec3( 0.0 );\nmorphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\nmorphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\nmorphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\nmorphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\nmorphedNormal += normal;\n#endif", +skinnormal_vertex:"#ifdef USE_SKINNING\nmat4 skinMatrix = skinWeight.x * boneMatX;\nskinMatrix \t+= skinWeight.y * boneMatY;\n#ifdef USE_MORPHNORMALS\nvec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\n#else\nvec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\n#endif\n#endif",defaultnormal_vertex:"vec3 objectNormal;\n#ifdef USE_SKINNING\nobjectNormal = skinnedNormal.xyz;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )\nobjectNormal = morphedNormal;\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )\nobjectNormal = normal;\n#endif\n#ifdef FLIP_SIDED\nobjectNormal = -objectNormal;\n#endif\nvec3 transformedNormal = normalMatrix * objectNormal;", +shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\nuniform sampler2D shadowMap[ MAX_SHADOWS ];\nuniform vec2 shadowMapSize[ MAX_SHADOWS ];\nuniform float shadowDarkness[ MAX_SHADOWS ];\nuniform float shadowBias[ MAX_SHADOWS ];\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nfloat unpackDepth( const in vec4 rgba_depth ) {\nconst vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\nfloat depth = dot( rgba_depth, bit_shift );\nreturn depth;\n}\n#endif",shadowmap_fragment:"#ifdef USE_SHADOWMAP\n#ifdef SHADOWMAP_DEBUG\nvec3 frustumColors[3];\nfrustumColors[0] = vec3( 1.0, 0.5, 0.0 );\nfrustumColors[1] = vec3( 0.0, 1.0, 0.8 );\nfrustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n#endif\n#ifdef SHADOWMAP_CASCADE\nint inFrustumCount = 0;\n#endif\nfloat fDepth;\nvec3 shadowColor = vec3( 1.0 );\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\nbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\nbool inFrustum = all( inFrustumVec );\n#ifdef SHADOWMAP_CASCADE\ninFrustumCount += int( inFrustum );\nbvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\n#else\nbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n#endif\nbool frustumTest = all( frustumTestVec );\nif ( frustumTest ) {\nshadowCoord.z += shadowBias[ i ];\n#if defined( SHADOWMAP_TYPE_PCF )\nfloat shadow = 0.0;\nconst float shadowDelta = 1.0 / 9.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.25 * xPixelOffset;\nfloat dy0 = -1.25 * yPixelOffset;\nfloat dx1 = 1.25 * xPixelOffset;\nfloat dy1 = 1.25 * yPixelOffset;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\nfloat shadow = 0.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.0 * xPixelOffset;\nfloat dy0 = -1.0 * yPixelOffset;\nfloat dx1 = 1.0 * xPixelOffset;\nfloat dy1 = 1.0 * yPixelOffset;\nmat3 shadowKernel;\nmat3 depthKernel;\ndepthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\ndepthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\ndepthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\ndepthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\ndepthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\ndepthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\ndepthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\ndepthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\ndepthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nvec3 shadowZ = vec3( shadowCoord.z );\nshadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\nshadowKernel[0] *= vec3(0.25);\nshadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\nshadowKernel[1] *= vec3(0.25);\nshadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\nshadowKernel[2] *= vec3(0.25);\nvec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\nshadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\nshadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\nvec4 shadowValues;\nshadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\nshadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\nshadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\nshadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\nshadow = dot( shadowValues, vec4( 1.0 ) );\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#else\nvec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\nfloat fDepth = unpackDepth( rgbaDepth );\nif ( fDepth < shadowCoord.z )\nshadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\n#endif\n}\n#ifdef SHADOWMAP_DEBUG\n#ifdef SHADOWMAP_CASCADE\nif ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];\n#else\nif ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];\n#endif\n#endif\n}\n#ifdef GAMMA_OUTPUT\nshadowColor *= shadowColor;\n#endif\ngl_FragColor.xyz = gl_FragColor.xyz * shadowColor;\n#endif", +shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nuniform mat4 shadowMatrix[ MAX_SHADOWS ];\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif",alphatest_fragment:"#ifdef ALPHATEST\nif ( gl_FragColor.a < ALPHATEST ) discard;\n#endif",linear_to_gamma_fragment:"#ifdef GAMMA_OUTPUT\ngl_FragColor.xyz = sqrt( gl_FragColor.xyz );\n#endif"};THREE.UniformsUtils={merge:function(a){var b,c,d,e={};for(b=0;b dashSize ) {\ndiscard;\n}\ngl_FragColor = vec4( diffuse, opacity );",THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n")},depth:{uniforms:{mNear:{type:"f",value:1},mFar:{type:"f",value:2E3},opacity:{type:"f", +value:1}},vertexShader:"void main() {\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform float mNear;\nuniform float mFar;\nuniform float opacity;\nvoid main() {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat color = 1.0 - smoothstep( mNear, mFar, depth );\ngl_FragColor = vec4( vec3( color ), opacity );\n}"},normal:{uniforms:{opacity:{type:"f",value:1}},vertexShader:["varying vec3 vNormal;",THREE.ShaderChunk.morphtarget_pars_vertex,"void main() {\nvNormal = normalize( normalMatrix * normal );", +THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,"}"].join("\n"),fragmentShader:"uniform float opacity;\nvarying vec3 vNormal;\nvoid main() {\ngl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );\n}"},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},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},uDisplacementScale:{type:"f",value:1},diffuse:{type:"c",value:new THREE.Color(16777215)},specular:{type:"c",value:new THREE.Color(1118481)},ambient:{type:"c",value:new THREE.Color(16777215)},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;\nuniform vec3 diffuse;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\nuniform bool enableDiffuse;\nuniform bool enableSpecular;\nuniform bool enableAO;\nuniform bool enableReflection;\nuniform sampler2D tDiffuse;\nuniform sampler2D tNormal;\nuniform sampler2D tSpecular;\nuniform sampler2D tAO;\nuniform samplerCube tCube;\nuniform vec2 uNormalScale;\nuniform bool useRefract;\nuniform float refractionRatio;\nuniform float reflectivity;\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,"void main() {\ngl_FragColor = vec4( vec3( 1.0 ), opacity );\nvec3 specularTex = vec3( 1.0 );\nvec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;\nnormalTex.xy *= uNormalScale;\nnormalTex = normalize( normalTex );\nif( enableDiffuse ) {\n#ifdef GAMMA_INPUT\nvec4 texelColor = texture2D( tDiffuse, vUv );\ntexelColor.xyz *= texelColor.xyz;\ngl_FragColor = gl_FragColor * texelColor;\n#else\ngl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );\n#endif\n}\nif( enableAO ) {\n#ifdef GAMMA_INPUT\nvec4 aoColor = texture2D( tAO, vUv );\naoColor.xyz *= aoColor.xyz;\ngl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;\n#endif\n}\nif( enableSpecular )\nspecularTex = texture2D( tSpecular, vUv ).xyz;\nmat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );\nvec3 finalNormal = tsb * normalTex;\n#ifdef FLIP_SIDED\nfinalNormal = -finalNormal;\n#endif\nvec3 normal = normalize( finalNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 pointVector = lPosition.xyz + vViewPosition.xyz;\nfloat pointDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\npointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );\npointVector = normalize( pointVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\n#endif\npointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;\nvec3 pointHalfVector = normalize( pointVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 spotVector = lPosition.xyz + vViewPosition.xyz;\nfloat spotDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nspotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );\nspotVector = normalize( spotVector );\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );\n#endif\nspotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;\nvec3 spotHalfVector = normalize( spotVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\n#ifdef WRAP_AROUND\nfloat directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );\nfloat directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\n#endif\ndirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += diffuse * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );\nvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n#endif\nif ( enableReflection ) {\nvec3 vReflect;\nvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\nif ( useRefract ) {\nvReflect = refract( cameraToVertex, normal, refractionRatio );\n} else {\nvReflect = reflect( cameraToVertex, normal );\n}\nvec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );\n#ifdef GAMMA_INPUT\ncubeColor.xyz *= cubeColor.xyz;\n#endif\ngl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );\n}", +THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n"),vertexShader:["attribute vec4 tangent;\nuniform vec2 uOffset;\nuniform vec2 uRepeat;\nuniform bool enableDisplacement;\n#ifdef VERTEX_TEXTURES\nuniform sampler2D tDisplacement;\nuniform float uDisplacementScale;\nuniform float uDisplacementBias;\n#endif\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;", +THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,"#ifdef USE_SKINNING\nvNormal = normalize( normalMatrix * skinnedNormal.xyz );\nvec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );\nvTangent = normalize( normalMatrix * skinnedTangent.xyz );\n#else\nvNormal = normalize( normalMatrix * normal );\nvTangent = normalize( normalMatrix * tangent.xyz );\n#endif\nvBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\nvUv = uv * uRepeat + uOffset;\nvec3 displacedPosition;\n#ifdef VERTEX_TEXTURES\nif ( enableDisplacement ) {\nvec3 dv = texture2D( tDisplacement, uv ).xyz;\nfloat df = uDisplacementScale * dv.x + uDisplacementBias;\ndisplacedPosition = position + normalize( normal ) * df;\n} else {\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned \t += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n}\n#else\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned = boneMatX * skinVertex * skinWeight.x;\nskinned \t += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n#endif\nvec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );\nvec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\nvWorldPosition = worldPosition.xyz;\nvViewPosition = -mvPosition.xyz;\n#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif\n}"].join("\n")}, +cube:{uniforms:{tCube:{type:"t",value:null},tFlip:{type:"f",value:-1}},vertexShader:"varying vec3 vWorldPosition;\nvoid main() {\nvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\nvWorldPosition = worldPosition.xyz;\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform samplerCube tCube;\nuniform float tFlip;\nvarying vec3 vWorldPosition;\nvoid main() {\ngl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n}"}, +depthRGBA:{uniforms:{},vertexShader:[THREE.ShaderChunk.morphtarget_pars_vertex,THREE.ShaderChunk.skinning_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.skinning_vertex,THREE.ShaderChunk.default_vertex,"}"].join("\n"),fragmentShader:"vec4 pack_depth( const in float depth ) {\nconst vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\nconst vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\nvec4 res = fract( depth * bit_shift );\nres -= res.xxyz * bit_mask;\nreturn res;\n}\nvoid main() {\ngl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );\n}"}};THREE.WebGLRenderer=function(a){function b(a,b){var c=a.vertices.length,d=b.material;if(d.attributes){void 0===a.__webglCustomAttributesList&&(a.__webglCustomAttributesList=[]);for(var e in d.attributes){var f=d.attributes[e];if(!f.__webglInitialized||f.createUniqueBuffers){f.__webglInitialized=!0;var g=1;"v2"===f.type?g=2:"v3"===f.type?g=3:"v4"===f.type?g=4:"c"===f.type&&(g=3);f.size=g;f.array=new Float32Array(c*g);f.buffer=m.createBuffer();f.buffer.belongsToAttribute=e;f.needsUpdate=!0}a.__webglCustomAttributesList.push(f)}}} +function c(a,b){var c=b.geometry,g=a.faces3,h=3*g.length,k=1*g.length,l=3*g.length,g=d(b,a),n=f(g),p=e(g),q=g.vertexColors?g.vertexColors:!1;a.__vertexArray=new Float32Array(3*h);p&&(a.__normalArray=new Float32Array(3*h));c.hasTangents&&(a.__tangentArray=new Float32Array(4*h));q&&(a.__colorArray=new Float32Array(3*h));n&&(0p;p++)N.autoScaleCubemaps&&!f?(q=l,t=p,v=c.image[p],x=db,v.width<=x&&v.height<=x||(z=Math.max(v.width,v.height),w=Math.floor(v.width*x/z),x=Math.floor(v.height*x/z),z=document.createElement("canvas"),z.width=w,z.height=x,z.getContext("2d").drawImage(v,0,0,v.width,v.height,0,0,w,x),v=z),q[t]=v):l[p]=c.image[p];p=l[0];q=THREE.Math.isPowerOfTwo(p.width)&& +THREE.Math.isPowerOfTwo(p.height);t=A(c.format);v=A(c.type);y(m.TEXTURE_CUBE_MAP,c,q);for(p=0;6>p;p++)if(f)for(x=l[p].mipmaps,z=0,B=x.length;z=cb&&console.warn("WebGLRenderer: trying to use "+a+" texture units while this GPU supports only "+cb);ta+=1;return a}function H(a,b,c,d){a[b]=c.r*c.r*d;a[b+1]=c.g*c.g*d;a[b+2]=c.b*c.b*d}function D(a,b,c,d){a[b]=c.r*d;a[b+1]=c.g*d;a[b+2]=c.b*d}function G(a){a!==Ca&&(m.lineWidth(a),Ca=a)}function I(a,b,c){Ba!==a&&(a?m.enable(m.POLYGON_OFFSET_FILL):m.disable(m.POLYGON_OFFSET_FILL), +Ba=a);!a||Ia===b&&ma===c||(m.polygonOffset(b,c),Ia=b,ma=c)}function O(a){a=a.split("\n");for(var b=0,c=a.length;bb;b++)m.deleteFramebuffer(a.__webglFramebuffer[b]),m.deleteRenderbuffer(a.__webglRenderbuffer[b]);else m.deleteFramebuffer(a.__webglFramebuffer),m.deleteRenderbuffer(a.__webglRenderbuffer);N.info.memory.textures--},Mb=function(a){a=a.target;a.removeEventListener("dispose",Mb);Eb(a)},Db=function(a){void 0!== +a.__webglVertexBuffer&&m.deleteBuffer(a.__webglVertexBuffer);void 0!==a.__webglNormalBuffer&&m.deleteBuffer(a.__webglNormalBuffer);void 0!==a.__webglTangentBuffer&&m.deleteBuffer(a.__webglTangentBuffer);void 0!==a.__webglColorBuffer&&m.deleteBuffer(a.__webglColorBuffer);void 0!==a.__webglUVBuffer&&m.deleteBuffer(a.__webglUVBuffer);void 0!==a.__webglUV2Buffer&&m.deleteBuffer(a.__webglUV2Buffer);void 0!==a.__webglSkinIndicesBuffer&&m.deleteBuffer(a.__webglSkinIndicesBuffer);void 0!==a.__webglSkinWeightsBuffer&& +m.deleteBuffer(a.__webglSkinWeightsBuffer);void 0!==a.__webglFaceBuffer&&m.deleteBuffer(a.__webglFaceBuffer);void 0!==a.__webglLineBuffer&&m.deleteBuffer(a.__webglLineBuffer);void 0!==a.__webglLineDistanceBuffer&&m.deleteBuffer(a.__webglLineDistanceBuffer);if(void 0!==a.__webglCustomAttributesList)for(var b in a.__webglCustomAttributesList)m.deleteBuffer(a.__webglCustomAttributesList[b].buffer);N.info.memory.geometries--},Eb=function(a){var b=a.program;if(void 0!==b){a.program=void 0;var c,d,e=!1; +a=0;for(c=ca.length;ad.numSupportedMorphTargets?(l.sort(n),l.length=d.numSupportedMorphTargets):l.length>d.numSupportedMorphNormals?l.sort(n):0===l.length&&l.push([0,0]);for(p=0;pba;ba++)Ca=ca[ba],Wa[ib]=Ca.x,Wa[ib+1]=Ca.y,Wa[ib+2]=Ca.z,ib+=3;else for(ba=0;3>ba;ba++)Wa[ib]=R.x,Wa[ib+1]=R.y,Wa[ib+2]=R.z,ib+=3;m.bindBuffer(m.ARRAY_BUFFER,A.__webglNormalBuffer);m.bufferData(m.ARRAY_BUFFER,Wa,C)}if(Bb&&Db&&L){E=0;for(J=ea.length;Eba;ba++)Ka=$[ba],cb[Na]=Ka.x,cb[Na+1]=Ka.y,Na+=2;0ba;ba++)Ia=sa[ba],db[Pa]=Ia.x,db[Pa+1]=Ia.y,Pa+=2;0f;f++){a.__webglFramebuffer[f]=m.createFramebuffer();a.__webglRenderbuffer[f]=m.createRenderbuffer();m.texImage2D(m.TEXTURE_CUBE_MAP_POSITIVE_X+f,0,d,a.width,a.height,0,d,e,null);var g=a,h=m.TEXTURE_CUBE_MAP_POSITIVE_X+f;m.bindFramebuffer(m.FRAMEBUFFER,a.__webglFramebuffer[f]);m.framebufferTexture2D(m.FRAMEBUFFER,m.COLOR_ATTACHMENT0,h,g.__webglTexture,0);F(a.__webglRenderbuffer[f],a)}c&&m.generateMipmap(m.TEXTURE_CUBE_MAP)}else a.__webglFramebuffer=m.createFramebuffer(),a.__webglRenderbuffer= +a.shareDepthFrom?a.shareDepthFrom.__webglRenderbuffer:m.createRenderbuffer(),m.bindTexture(m.TEXTURE_2D,a.__webglTexture),y(m.TEXTURE_2D,a,c),m.texImage2D(m.TEXTURE_2D,0,d,a.width,a.height,0,d,e,null),d=m.TEXTURE_2D,m.bindFramebuffer(m.FRAMEBUFFER,a.__webglFramebuffer),m.framebufferTexture2D(m.FRAMEBUFFER,m.COLOR_ATTACHMENT0,d,a.__webglTexture,0),a.shareDepthFrom?a.depthBuffer&&!a.stencilBuffer?m.framebufferRenderbuffer(m.FRAMEBUFFER,m.DEPTH_ATTACHMENT,m.RENDERBUFFER,a.__webglRenderbuffer):a.depthBuffer&& +a.stencilBuffer&&m.framebufferRenderbuffer(m.FRAMEBUFFER,m.DEPTH_STENCIL_ATTACHMENT,m.RENDERBUFFER,a.__webglRenderbuffer):F(a.__webglRenderbuffer,a),c&&m.generateMipmap(m.TEXTURE_2D);b?m.bindTexture(m.TEXTURE_CUBE_MAP,null):m.bindTexture(m.TEXTURE_2D,null);m.bindRenderbuffer(m.RENDERBUFFER,null);m.bindFramebuffer(m.FRAMEBUFFER,null)}a?(b=b?a.__webglFramebuffer[a.activeCubeFace]:a.__webglFramebuffer,c=a.width,a=a.height,e=d=0):(b=null,c=xa,a=Da,d=ba,e=wa);b!==Ja&&(m.bindFramebuffer(m.FRAMEBUFFER,b), +m.viewport(d,e,c,a),Ja=b);Wa=c;La=a};this.shadowMapPlugin=new THREE.ShadowMapPlugin;this.addPrePlugin(this.shadowMapPlugin);this.addPostPlugin(new THREE.SpritePlugin);this.addPostPlugin(new THREE.LensFlarePlugin)};THREE.WebGLRenderTarget=function(a,b,c){this.width=a;this.height=b;c=c||{};this.wrapS=void 0!==c.wrapS?c.wrapS:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==c.wrapT?c.wrapT:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==c.magFilter?c.magFilter:THREE.LinearFilter;this.minFilter=void 0!==c.minFilter?c.minFilter:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==c.anisotropy?c.anisotropy:1;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.format=void 0!==c.format?c.format: +THREE.RGBAFormat;this.type=void 0!==c.type?c.type:THREE.UnsignedByteType;this.depthBuffer=void 0!==c.depthBuffer?c.depthBuffer:!0;this.stencilBuffer=void 0!==c.stencilBuffer?c.stencilBuffer:!0;this.generateMipmaps=!0;this.shareDepthFrom=null}; +THREE.WebGLRenderTarget.prototype={constructor:THREE.WebGLRenderTarget,clone:function(){var a=new THREE.WebGLRenderTarget(this.width,this.height);a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.format=this.format;a.type=this.type;a.depthBuffer=this.depthBuffer;a.stencilBuffer=this.stencilBuffer;a.generateMipmaps=this.generateMipmaps;a.shareDepthFrom=this.shareDepthFrom; +return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.WebGLRenderTarget.prototype);THREE.WebGLRenderTargetCube=function(a,b,c){THREE.WebGLRenderTarget.call(this,a,b,c);this.activeCubeFace=0};THREE.WebGLRenderTargetCube.prototype=Object.create(THREE.WebGLRenderTarget.prototype);THREE.RenderableVertex=function(){this.position=new THREE.Vector3;this.positionWorld=new THREE.Vector3;this.positionScreen=new THREE.Vector4;this.visible=!0};THREE.RenderableVertex.prototype.copy=function(a){this.positionWorld.copy(a.positionWorld);this.positionScreen.copy(a.positionScreen)};THREE.RenderableFace=function(){this.id=0;this.v1=new THREE.RenderableVertex;this.v2=new THREE.RenderableVertex;this.v3=new THREE.RenderableVertex;this.centroidModel=new THREE.Vector3;this.normalModel=new THREE.Vector3;this.vertexNormalsModel=[new THREE.Vector3,new THREE.Vector3,new THREE.Vector3];this.vertexNormalsLength=0;this.material=this.color=null;this.uvs=[[]];this.z=0};THREE.RenderableObject=function(){this.id=0;this.object=null;this.z=0};THREE.RenderableSprite=function(){this.id=0;this.object=null;this.rotation=this.z=this.y=this.x=0;this.scale=new THREE.Vector2;this.material=null};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};THREE.GeometryUtils={merge:function(a,b,c){var d,e,f=a.vertices.length,g=b instanceof THREE.Mesh?b.geometry:b,h=a.vertices,k=g.vertices,l=a.faces,n=g.faces;a=a.faceVertexUvs[0];g=g.faceVertexUvs[0];void 0===c&&(c=0);b instanceof THREE.Mesh&&(b.matrixAutoUpdate&&b.updateMatrix(),d=b.matrix,e=(new THREE.Matrix3).getNormalMatrix(d));b=0;for(var s=k.length;ba?b(c,e-1):l[e]>8&255,l>>16&255,l>>24&255)),e}e.mipmapCount=1;k[2]&131072&&!1!==b&&(e.mipmapCount=Math.max(1,k[7]));e.isCubemap=k[28]&512?!0:!1;e.width=k[4];e.height=k[3];for(var k=k[1]+4,g=e.width,h=e.height,l=e.isCubemap?6:1,s=0;ss-1?0:s-1,q=s+1>e-1?e-1:s+1,u=0>n-1?0:n-1,p=n+1>d-1?d-1:n+1,v=[],w=[0,0,h[4*(s*d+n)]/255*b];v.push([-1,0,h[4*(s*d+u)]/255*b]);v.push([-1,-1,h[4*(r*d+u)]/255*b]);v.push([0,-1,h[4*(r*d+n)]/255*b]);v.push([1,-1,h[4*(r*d+p)]/255*b]);v.push([1,0,h[4*(s*d+p)]/255*b]);v.push([1,1,h[4*(q*d+p)]/255*b]);v.push([0,1,h[4*(q*d+n)]/255*b]);v.push([-1,1,h[4*(q*d+u)]/255*b]);r=[];u=v.length;for(q=0;qe)return null;var f=[],g=[],h=[],k,l,n;if(0=s--){console.log("Warning, unable to triangulate polygon!");break}k=l;e<=k&&(k=0);l=k+1;e<=l&&(l=0);n=l+1;e<=n&&(n=0);var r;a:{var q=r=void 0,u=void 0,p=void 0,v=void 0,w=void 0,t=void 0,x=void 0,z= +void 0,q=a[g[k]].x,u=a[g[k]].y,p=a[g[l]].x,v=a[g[l]].y,w=a[g[n]].x,t=a[g[n]].y;if(1E-10>(p-q)*(t-u)-(v-u)*(w-q))r=!1;else{var B=void 0,E=void 0,H=void 0,D=void 0,G=void 0,I=void 0,O=void 0,K=void 0,y=void 0,F=void 0,y=K=O=z=x=void 0,B=w-p,E=t-v,H=q-w,D=u-t,G=p-q,I=v-u;for(r=0;rk)g=d+1;else if(0b&&(b=0);1=b)return b=c[a]-b,a=this.curves[a],b=1-b/a.getLength(),a.getPointAt(b);a++}return null};THREE.CurvePath.prototype.getLength=function(){var a=this.getCurveLengths();return a[a.length-1]}; +THREE.CurvePath.prototype.getCurveLengths=function(){if(this.cacheLengths&&this.cacheLengths.length==this.curves.length)return this.cacheLengths;var a=[],b=0,c,d=this.curves.length;for(c=0;cb?b=h.x:h.xc?c=h.y:h.yd?d=h.z:h.zMath.abs(d.x-c[0].x)&&1E-10>Math.abs(d.y-c[0].y)&&c.splice(c.length-1,1);b&&c.push(c[0]);return c}; +THREE.Path.prototype.toShapes=function(a){function b(a,b){for(var c=b.length,d=!1,e=c-1,f=0;fl&&(g=b[f],k=-k,h=b[e],l=-l),!(a.yh.y))if(a.y==g.y){if(a.x==g.x)return!0}else{e=l*(a.x-g.x)-k*(a.y-g.y);if(0==e)return!0;0>e||(d=!d)}}else if(a.y==g.y&&(h.x<=a.x&&a.x<=g.x||g.x<=a.x&&a.x<=h.x))return!0}return d}var c,d,e,f,g=[],h=new THREE.Path;c=0;for(d=this.actions.length;cD||D>H)return[];k=l*n-k*s;if(0>k||k>H)return[]}else{if(0d?[]:k==d?f?[]:[g]:a<=d?[g,h]: +[g,l]}function e(a,b,c,d){var e=b.x-a.x,f=b.y-a.y;b=c.x-a.x;c=c.y-a.y;var g=d.x-a.x;d=d.y-a.y;a=e*c-f*b;e=e*d-f*g;return 1E-10f&&(f=d);var g=a+1;g>d&&(g=0);d=e(h[a],h[f],h[g],k[b]);if(!d)return!1; +d=k.length-1;f=b-1;0>f&&(f=d);g=b+1;g>d&&(g=0);return(d=e(k[b],k[f],k[g],h[a]))?!0:!1}function f(a,b){var c,e;for(c=0;cF){console.log("Infinite Loop! Holes left:"+ +l.length+", Probably Hole outside Shape!");break}for(s=0;sh;h++)l=k[h].x+":"+k[h].y,l= +n[l],void 0!==l&&(k[h]=l);return s.concat()},isClockWise:function(a){return 0>THREE.FontUtils.Triangulate.area(a)},b2p0:function(a,b){var c=1-a;return c*c*b},b2p1:function(a,b){return 2*(1-a)*a*b},b2p2:function(a,b){return a*a*b},b2:function(a,b,c,d){return this.b2p0(a,b)+this.b2p1(a,c)+this.b2p2(a,d)},b3p0:function(a,b){var c=1-a;return c*c*c*b},b3p1:function(a,b){var c=1-a;return 3*c*c*a*b},b3p2:function(a,b){return 3*(1-a)*a*a*b},b3p3:function(a,b){return a*a*a*b},b3:function(a,b,c,d,e){return this.b3p0(a, +b)+this.b3p1(a,c)+this.b3p2(a,d)+this.b3p3(a,e)}};THREE.LineCurve=function(a,b){this.v1=a;this.v2=b};THREE.LineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.LineCurve.prototype.getPoint=function(a){var b=this.v2.clone().sub(this.v1);b.multiplyScalar(a).add(this.v1);return b};THREE.LineCurve.prototype.getPointAt=function(a){return this.getPoint(a)};THREE.LineCurve.prototype.getTangent=function(a){return this.v2.clone().sub(this.v1).normalize()};THREE.QuadraticBezierCurve=function(a,b,c){this.v0=a;this.v1=b;this.v2=c};THREE.QuadraticBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.QuadraticBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);a=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);return new THREE.Vector2(b,a)}; +THREE.QuadraticBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.x,this.v1.x,this.v2.x);a=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.y,this.v1.y,this.v2.y);b=new THREE.Vector2(b,a);b.normalize();return b};THREE.CubicBezierCurve=function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d};THREE.CubicBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.CubicBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);return new THREE.Vector2(b,a)}; +THREE.CubicBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b=new THREE.Vector2(b,a);b.normalize();return b};THREE.SplineCurve=function(a){this.points=void 0==a?[]:a};THREE.SplineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.SplineCurve.prototype.getPoint=function(a){var b=new THREE.Vector2,c=[],d=this.points,e;e=(d.length-1)*a;a=Math.floor(e);e-=a;c[0]=0==a?a:a-1;c[1]=a;c[2]=a>d.length-2?d.length-1:a+1;c[3]=a>d.length-3?d.length-1:a+2;b.x=THREE.Curve.Utils.interpolate(d[c[0]].x,d[c[1]].x,d[c[2]].x,d[c[3]].x,e);b.y=THREE.Curve.Utils.interpolate(d[c[0]].y,d[c[1]].y,d[c[2]].y,d[c[3]].y,e);return b};THREE.EllipseCurve=function(a,b,c,d,e,f,g){this.aX=a;this.aY=b;this.xRadius=c;this.yRadius=d;this.aStartAngle=e;this.aEndAngle=f;this.aClockwise=g};THREE.EllipseCurve.prototype=Object.create(THREE.Curve.prototype); +THREE.EllipseCurve.prototype.getPoint=function(a){var b;b=this.aEndAngle-this.aStartAngle;0>b&&(b+=2*Math.PI);b>2*Math.PI&&(b-=2*Math.PI);b=!0===this.aClockwise?this.aEndAngle+(1-a)*(2*Math.PI-b):this.aStartAngle+a*b;a=this.aX+this.xRadius*Math.cos(b);b=this.aY+this.yRadius*Math.sin(b);return new THREE.Vector2(a,b)};THREE.ArcCurve=function(a,b,c,d,e,f){THREE.EllipseCurve.call(this,a,b,c,c,d,e,f)};THREE.ArcCurve.prototype=Object.create(THREE.EllipseCurve.prototype);THREE.LineCurve3=THREE.Curve.create(function(a,b){this.v1=a;this.v2=b},function(a){var b=new THREE.Vector3;b.subVectors(this.v2,this.v1);b.multiplyScalar(a);b.add(this.v1);return b});THREE.QuadraticBezierCurve3=THREE.Curve.create(function(a,b,c){this.v0=a;this.v1=b;this.v2=c},function(a){var b,c;b=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);c=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);a=THREE.Shape.Utils.b2(a,this.v0.z,this.v1.z,this.v2.z);return new THREE.Vector3(b,c,a)});THREE.CubicBezierCurve3=THREE.Curve.create(function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d},function(a){var b,c;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);c=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);a=THREE.Shape.Utils.b3(a,this.v0.z,this.v1.z,this.v2.z,this.v3.z);return new THREE.Vector3(b,c,a)});THREE.SplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e;a*=d.length-1;e=Math.floor(a);a-=e;c[0]=0==e?e:e-1;c[1]=e;c[2]=e>d.length-2?d.length-1:e+1;c[3]=e>d.length-3?d.length-1:e+2;e=d[c[0]];var f=d[c[1]],g=d[c[2]],c=d[c[3]];b.x=THREE.Curve.Utils.interpolate(e.x,f.x,g.x,c.x,a);b.y=THREE.Curve.Utils.interpolate(e.y,f.y,g.y,c.y,a);b.z=THREE.Curve.Utils.interpolate(e.z,f.z,g.z,c.z,a);return b});THREE.ClosedSplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e;e=(d.length-0)*a;a=Math.floor(e);e-=a;a+=0a.hierarchy[c].keys[d].time&& +(a.hierarchy[c].keys[d].time=0),void 0!==a.hierarchy[c].keys[d].rot&&!(a.hierarchy[c].keys[d].rot instanceof THREE.Quaternion)){var h=a.hierarchy[c].keys[d].rot;a.hierarchy[c].keys[d].rot=new THREE.Quaternion(h[0],h[1],h[2],h[3])}if(a.hierarchy[c].keys.length&&void 0!==a.hierarchy[c].keys[0].morphTargets){h={};for(d=0;da.length-2?l:l+1;c[3]=l>a.length-3?l:l+2;l=a[c[0]];s=a[c[1]];r=a[c[2]];q=a[c[3]];c=k*k;n=k*c;h[0]=d(l[0],s[0],r[0],q[0],k,c,n);h[1]=d(l[1],s[1],r[1],q[1],k,c,n);h[2]=d(l[2],s[2],r[2],q[2],k,c,n);return h},d=function(a,b,c,d,k,l,n){a=0.5*(c-a);d=0.5*(d-b);return(2*(b-c)+a+d)*n+(-3*(b-c)-2*a-d)*l+a*k+b};return function(d){if(!1!== +this.isPlaying){this.currentTime+=d*this.timeScale;var f;d=["pos","rot","scl"];var g=this.data.length;if(!0===this.loop&&this.currentTime>g)this.currentTime%=g,this.reset();else if(!1===this.loop&&this.currentTime>g){this.stop();return}this.currentTime=Math.min(this.currentTime,g);for(var g=0,h=this.hierarchy.length;gn;n++){f=d[n];var s=l.prevKey[f],r=l.nextKey[f];if(r.time<=this.currentTime){s=this.data.hierarchy[g].keys[0];for(r=this.getNextKeyWith(f, +g,1);r.times.index;)s=r,r=this.getNextKeyWith(f,g,r.index+1);l.prevKey[f]=s;l.nextKey[f]=r}k.matrixAutoUpdate=!0;k.matrixWorldNeedsUpdate=!0;var q=(this.currentTime-s.time)/(r.time-s.time),u=s[f],p=r[f];0>q&&(q=0);1a&&(this.currentTime%=a);this.currentTime=Math.min(this.currentTime,a);a=0;for(var b=this.hierarchy.length;af.index;)f=g,g=e[f.index+1];d.prevKey= +f;d.nextKey=g}g.time>=this.currentTime?f.interpolate(g,this.currentTime):f.interpolate(g,g.time);this.data.hierarchy[a].node.updateMatrix();c.matrixWorldNeedsUpdate=!0}}}};THREE.KeyFrameAnimation.prototype.getNextKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c%=b.length;cthis.duration&&(this.currentTime%=this.duration);this.currentTime=Math.min(this.currentTime,this.duration);c=this.duration/this.frames;var d=Math.floor(this.currentTime/c);d!=b&&(this.mesh.morphTargetInfluences[a]=0,this.mesh.morphTargetInfluences[b]=1,this.mesh.morphTargetInfluences[d]= +0,a=b,b=d);this.mesh.morphTargetInfluences[d]=this.currentTime%c/c;this.mesh.morphTargetInfluences[a]=1-this.mesh.morphTargetInfluences[d]}}}()};THREE.CubeCamera=function(a,b,c){THREE.Object3D.call(this);var d=new THREE.PerspectiveCamera(90,1,a,b);d.up.set(0,-1,0);d.lookAt(new THREE.Vector3(1,0,0));this.add(d);var e=new THREE.PerspectiveCamera(90,1,a,b);e.up.set(0,-1,0);e.lookAt(new THREE.Vector3(-1,0,0));this.add(e);var f=new THREE.PerspectiveCamera(90,1,a,b);f.up.set(0,0,1);f.lookAt(new THREE.Vector3(0,1,0));this.add(f);var g=new THREE.PerspectiveCamera(90,1,a,b);g.up.set(0,0,-1);g.lookAt(new THREE.Vector3(0,-1,0));this.add(g);var h=new THREE.PerspectiveCamera(90, +1,a,b);h.up.set(0,-1,0);h.lookAt(new THREE.Vector3(0,0,1));this.add(h);var k=new THREE.PerspectiveCamera(90,1,a,b);k.up.set(0,-1,0);k.lookAt(new THREE.Vector3(0,0,-1));this.add(k);this.renderTarget=new THREE.WebGLRenderTargetCube(c,c,{format:THREE.RGBFormat,magFilter:THREE.LinearFilter,minFilter:THREE.LinearFilter});this.updateCubeMap=function(a,b){var c=this.renderTarget,r=c.generateMipmaps;c.generateMipmaps=!1;c.activeCubeFace=0;a.render(b,d,c);c.activeCubeFace=1;a.render(b,e,c);c.activeCubeFace= +2;a.render(b,f,c);c.activeCubeFace=3;a.render(b,g,c);c.activeCubeFace=4;a.render(b,h,c);c.generateMipmaps=r;c.activeCubeFace=5;a.render(b,k,c)}};THREE.CubeCamera.prototype=Object.create(THREE.Object3D.prototype);THREE.CombinedCamera=function(a,b,c,d,e,f,g){THREE.Camera.call(this);this.fov=c;this.left=-a/2;this.right=a/2;this.top=b/2;this.bottom=-b/2;this.cameraO=new THREE.OrthographicCamera(a/-2,a/2,b/2,b/-2,f,g);this.cameraP=new THREE.PerspectiveCamera(c,a/b,d,e);this.zoom=1;this.toPerspective()};THREE.CombinedCamera.prototype=Object.create(THREE.Camera.prototype); +THREE.CombinedCamera.prototype.toPerspective=function(){this.near=this.cameraP.near;this.far=this.cameraP.far;this.cameraP.fov=this.fov/this.zoom;this.cameraP.updateProjectionMatrix();this.projectionMatrix=this.cameraP.projectionMatrix;this.inPerspectiveMode=!0;this.inOrthographicMode=!1}; +THREE.CombinedCamera.prototype.toOrthographic=function(){var a=this.cameraP.aspect,b=(this.cameraP.near+this.cameraP.far)/2,b=Math.tan(this.fov/2)*b,a=2*b*a/2,b=b/this.zoom,a=a/this.zoom;this.cameraO.left=-a;this.cameraO.right=a;this.cameraO.top=b;this.cameraO.bottom=-b;this.cameraO.updateProjectionMatrix();this.near=this.cameraO.near;this.far=this.cameraO.far;this.projectionMatrix=this.cameraO.projectionMatrix;this.inPerspectiveMode=!1;this.inOrthographicMode=!0}; +THREE.CombinedCamera.prototype.setSize=function(a,b){this.cameraP.aspect=a/b;this.left=-a/2;this.right=a/2;this.top=b/2;this.bottom=-b/2};THREE.CombinedCamera.prototype.setFov=function(a){this.fov=a;this.inPerspectiveMode?this.toPerspective():this.toOrthographic()};THREE.CombinedCamera.prototype.updateProjectionMatrix=function(){this.inPerspectiveMode?this.toPerspective():(this.toPerspective(),this.toOrthographic())}; +THREE.CombinedCamera.prototype.setLens=function(a,b){void 0===b&&(b=24);var c=2*THREE.Math.radToDeg(Math.atan(b/(2*a)));this.setFov(c);return c};THREE.CombinedCamera.prototype.setZoom=function(a){this.zoom=a;this.inPerspectiveMode?this.toPerspective():this.toOrthographic()};THREE.CombinedCamera.prototype.toFrontView=function(){this.rotation.x=0;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1}; +THREE.CombinedCamera.prototype.toBackView=function(){this.rotation.x=0;this.rotation.y=Math.PI;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toLeftView=function(){this.rotation.x=0;this.rotation.y=-Math.PI/2;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toRightView=function(){this.rotation.x=0;this.rotation.y=Math.PI/2;this.rotation.z=0;this.rotationAutoUpdate=!1}; +THREE.CombinedCamera.prototype.toTopView=function(){this.rotation.x=-Math.PI/2;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toBottomView=function(){this.rotation.x=Math.PI/2;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.BoxGeometry=function(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,p){var v,w=h.widthSegments,t=h.heightSegments,x=e/2,z=f/2,B=h.vertices.length;if("x"===a&&"y"===b||"y"===a&&"x"===b)v="z";else if("x"===a&&"z"===b||"z"===a&&"x"===b)v="y",t=h.depthSegments;else if("z"===a&&"y"===b||"y"===a&&"z"===b)v="x",w=h.depthSegments;var E=w+1,H=t+1,D=e/w,G=f/t,I=new THREE.Vector3;I[v]=0=e)return new THREE.Vector2(c,a);e=Math.sqrt(e/2)}else a=!1,1E-10e?-1E-10>g&& +(a=!0):d(f)==d(h)&&(a=!0),a?(c=-f,a=e,e=Math.sqrt(k)):(c=e,a=f,e=Math.sqrt(k/2));return new THREE.Vector2(c/e,a/e)}function e(c,d){var e,f;for(J=c.length;0<=--J;){e=J;f=J-1;0>f&&(f=c.length-1);for(var g=0,h=q+2*n,g=0;gMath.abs(c-k)?[new THREE.Vector2(b,1-e),new THREE.Vector2(d,1-f),new THREE.Vector2(l,1-g),new THREE.Vector2(s,1-a)]:[new THREE.Vector2(c,1-e),new THREE.Vector2(k,1-f),new THREE.Vector2(n,1-g),new THREE.Vector2(r,1-a)]}};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;THREE.ShapeGeometry=function(a,b){THREE.Geometry.call(this);!1===a instanceof Array&&(a=[a]);this.shapebb=a[a.length-1].getBoundingBox();this.addShapeList(a,b);this.computeCentroids();this.computeFaceNormals()};THREE.ShapeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ShapeGeometry.prototype.addShapeList=function(a,b){for(var c=0,d=a.length;cc&&1===a.x&&(a=new THREE.Vector2(a.x-1,a.y));0===b.x&&0===b.z&&(a=new THREE.Vector2(c/ +2/Math.PI+0.5,a.y));return a.clone()}THREE.Geometry.call(this);c=c||1;d=d||0;for(var k=this,l=0,n=a.length;lq&&(0.2>d&&(b[0].x+=1),0.2>a&&(b[1].x+=1),0.2>s&&(b[2].x+=1));l=0;for(n=this.vertices.length;lc.y?this.quaternion.set(1,0,0,0):(a.set(c.z,0,-c.x).normalize(),b=Math.acos(c.y),this.quaternion.setFromAxisAngle(a,b))}}(); +THREE.ArrowHelper.prototype.setLength=function(a,b,c){void 0===b&&(b=0.2*a);void 0===c&&(c=0.2*b);this.line.scale.set(1,a,1);this.line.updateMatrix();this.cone.scale.set(c,b,c);this.cone.position.y=a;this.cone.updateMatrix()};THREE.ArrowHelper.prototype.setColor=function(a){this.line.material.color.setHex(a);this.cone.material.color.setHex(a)};THREE.BoxHelper=function(a){var b=[new THREE.Vector3(1,1,1),new THREE.Vector3(-1,1,1),new THREE.Vector3(-1,-1,1),new THREE.Vector3(1,-1,1),new THREE.Vector3(1,1,-1),new THREE.Vector3(-1,1,-1),new THREE.Vector3(-1,-1,-1),new THREE.Vector3(1,-1,-1)];this.vertices=b;var c=new THREE.Geometry;c.vertices.push(b[0],b[1],b[1],b[2],b[2],b[3],b[3],b[0],b[4],b[5],b[5],b[6],b[6],b[7],b[7],b[4],b[0],b[4],b[1],b[5],b[2],b[6],b[3],b[7]);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:16776960}),THREE.LinePieces); +void 0!==a&&this.update(a)};THREE.BoxHelper.prototype=Object.create(THREE.Line.prototype); +THREE.BoxHelper.prototype.update=function(a){var b=a.geometry;null===b.boundingBox&&b.computeBoundingBox();var c=b.boundingBox.min,b=b.boundingBox.max,d=this.vertices;d[0].set(b.x,b.y,b.z);d[1].set(c.x,b.y,b.z);d[2].set(c.x,c.y,b.z);d[3].set(b.x,c.y,b.z);d[4].set(b.x,b.y,c.z);d[5].set(c.x,b.y,c.z);d[6].set(c.x,c.y,c.z);d[7].set(b.x,c.y,c.z);this.geometry.computeBoundingSphere();this.geometry.verticesNeedUpdate=!0;this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.BoundingBoxHelper=function(a,b){var c=void 0!==b?b:8947848;this.object=a;this.box=new THREE.Box3;THREE.Mesh.call(this,new THREE.BoxGeometry(1,1,1),new THREE.MeshBasicMaterial({color:c,wireframe:!0}))};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)};THREE.CameraHelper=function(a){function b(a,b,d){c(a,d);c(b,d)}function c(a,b){d.vertices.push(new THREE.Vector3);d.colors.push(new THREE.Color(b));void 0===f[a]&&(f[a]=[]);f[a].push(d.vertices.length-1)}var d=new THREE.Geometry,e=new THREE.LineBasicMaterial({color:16777215,vertexColors:THREE.FaceColors}),f={};b("n1","n2",16755200);b("n2","n4",16755200);b("n4","n3",16755200);b("n3","n1",16755200);b("f1","f2",16755200);b("f2","f4",16755200);b("f4","f3",16755200);b("f3","f1",16755200);b("n1","f1",16755200); +b("n2","f2",16755200);b("n3","f3",16755200);b("n4","f4",16755200);b("p","n1",16711680);b("p","n2",16711680);b("p","n3",16711680);b("p","n4",16711680);b("u1","u2",43775);b("u2","u3",43775);b("u3","u1",43775);b("c","t",16777215);b("p","c",3355443);b("cn1","cn2",3355443);b("cn3","cn4",3355443);b("cf1","cf2",3355443);b("cf3","cf4",3355443);THREE.Line.call(this,d,e,THREE.LinePieces);this.camera=a;this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;this.pointMap=f;this.update()}; +THREE.CameraHelper.prototype=Object.create(THREE.Line.prototype); +THREE.CameraHelper.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Camera,c=new THREE.Projector;return function(){function d(d,g,h,k){a.set(g,h,k);c.unprojectVector(a,b);d=e.pointMap[d];if(void 0!==d)for(g=0,h=d.length;gu;u++){d[0]=q[g[u]];d[1]=q[g[(u+1)%3]];d.sort(f);var p=d.toString();void 0===e[p]?(e[p]={vert1:d[0],vert2:d[1],face1:s,face2:void 0},n++):e[p].face2=s}h.addAttribute("position",Float32Array,2*n,3);d=h.attributes.position.array; +f=0;for(p in e)if(g=e[p],void 0===g.face2||0.9999>k[g.face1].normal.dot(k[g.face2].normal))n=l[g.vert1],d[f++]=n.x,d[f++]=n.y,d[f++]=n.z,n=l[g.vert2],d[f++]=n.x,d[f++]=n.y,d[f++]=n.z;THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.EdgesHelper.prototype=Object.create(THREE.Line.prototype);THREE.FaceNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;a=void 0!==c?c:16776960;d=void 0!==d?d:1;b=new THREE.Geometry;c=0;for(var e=this.object.geometry.faces.length;cb;b++)a.faces[b].color=this.colors[4>b?0:1];b=new THREE.MeshBasicMaterial({vertexColors:THREE.FaceColors,wireframe:!0});this.lightSphere=new THREE.Mesh(a,b);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 a=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(a.setFromMatrixPosition(this.light.matrixWorld).negate());this.lightSphere.geometry.colorsNeedUpdate=!0}}();THREE.PointLightHelper=function(a,b){this.light=a;this.light.updateMatrixWorld();var c=new THREE.SphereGeometry(b,4,2),d=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);THREE.Mesh.call(this,c,d);this.matrixWorld=this.light.matrixWorld;this.matrixAutoUpdate=!1};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)};THREE.SpotLightHelper=function(a){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;a=new THREE.CylinderGeometry(0,1,1,8,1,!0);a.applyMatrix((new THREE.Matrix4).makeTranslation(0,-0.5,0));a.applyMatrix((new THREE.Matrix4).makeRotationX(-Math.PI/2));var b=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});this.cone=new THREE.Mesh(a,b);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 a=new THREE.Vector3,b=new THREE.Vector3;return function(){var c=this.light.distance?this.light.distance:1E4,d=c*Math.tan(this.light.angle);this.cone.scale.set(d,d,c);a.setFromMatrixPosition(this.light.matrixWorld);b.setFromMatrixPosition(this.light.target.matrixWorld);this.cone.lookAt(b.sub(a));this.cone.material.color.copy(this.light.color).multiplyScalar(this.light.intensity)}}();THREE.VertexNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;b=void 0!==c?c:16711680;d=void 0!==d?d:1;c=new THREE.Geometry;a=a.geometry.faces;for(var e=0,f=a.length;ep;p++){d[0]=u[g[p]];d[1]=u[g[(p+1)%3]];d.sort(f);var v=d.toString();void 0===e[v]&&(s[2*n]=d[0],s[2*n+1]=d[1],e[v]=!0,n++)}h.addAttribute("position",Float32Array,2*n,3);d=h.attributes.position.array; +r=0;for(q=n;rp;p++)n=k[s[2*r+p]],g=6*r+3*p,d[g+0]=n.x,d[g+1]=n.y,d[g+2]=n.z}else if(a.geometry instanceof THREE.BufferGeometry&&void 0!==a.geometry.attributes.index){for(var k=a.geometry.attributes.position.array,q=a.geometry.attributes.index.array,l=a.geometry.offsets,n=0,s=new Uint32Array(2*q.length),u=0,w=l.length;up;p++)d[0]=g+q[r+p],d[1]=g+q[r+(p+1)%3],d.sort(f),v=d.toString(),void 0===e[v]&&(s[2* +n]=d[0],s[2*n+1]=d[1],e[v]=!0,n++);h.addAttribute("position",Float32Array,2*n,3);d=h.attributes.position.array;r=0;for(q=n;rp;p++)g=6*r+3*p,n=3*s[2*r+p],d[g+0]=k[n],d[g+1]=k[n+1],d[g+2]=k[n+2]}else if(a.geometry instanceof THREE.BufferGeometry)for(k=a.geometry.attributes.position.array,n=k.length/3,s=n/3,h.addAttribute("position",Float32Array,2*n,3),d=h.attributes.position.array,r=0,q=s;rp;p++)g=18*r+6*p,s=9*r+3*p,d[g+0]=k[s],d[g+1]=k[s+1],d[g+2]=k[s+2],n=9*r+(p+ +1)%3*3,d[g+3]=k[n],d[g+4]=k[n+1],d[g+5]=k[n+2];THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.WireframeHelper.prototype=Object.create(THREE.Line.prototype);THREE.ImmediateRenderObject=function(){THREE.Object3D.call(this);this.render=function(a){}};THREE.ImmediateRenderObject.prototype=Object.create(THREE.Object3D.prototype);THREE.LensFlare=function(a,b,c,d,e){THREE.Object3D.call(this);this.lensFlares=[];this.positionScreen=new THREE.Vector3;this.customUpdateCallback=void 0;void 0!==a&&this.add(a,b,c,d,e)};THREE.LensFlare.prototype=Object.create(THREE.Object3D.prototype); +THREE.LensFlare.prototype.add=function(a,b,c,d,e,f){void 0===b&&(b=-1);void 0===c&&(c=0);void 0===f&&(f=1);void 0===e&&(e=new THREE.Color(16777215));void 0===d&&(d=THREE.NormalBlending);c=Math.min(c,Math.max(0,c));this.lensFlares.push({texture:a,size:b,distance:c,x:0,y:0,z:0,scale:1,rotation:1,opacity:f,color:e,blending:d})}; +THREE.LensFlare.prototype.updateLensFlares=function(){var a,b=this.lensFlares.length,c,d=2*-this.positionScreen.x,e=2*-this.positionScreen.y;for(a=0;ah.end&&(h.end=f);c||(c=k)}}for(k in d)h=d[k],this.createAnimation(k,h.start,h.end,a);this.firstAnimation=c}; +THREE.MorphBlendMesh.prototype.setAnimationDirectionForward=function(a){if(a=this.animationsMap[a])a.direction=1,a.directionBackwards=!1};THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward=function(a){if(a=this.animationsMap[a])a.direction=-1,a.directionBackwards=!0};THREE.MorphBlendMesh.prototype.setAnimationFPS=function(a,b){var c=this.animationsMap[a];c&&(c.fps=b,c.duration=(c.end-c.start)/c.fps)}; +THREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.animationsMap[a];c&&(c.duration=b,c.fps=(c.end-c.start)/c.duration)};THREE.MorphBlendMesh.prototype.setAnimationWeight=function(a,b){var c=this.animationsMap[a];c&&(c.weight=b)};THREE.MorphBlendMesh.prototype.setAnimationTime=function(a,b){var c=this.animationsMap[a];c&&(c.time=b)};THREE.MorphBlendMesh.prototype.getAnimationTime=function(a){var b=0;if(a=this.animationsMap[a])b=a.time;return b}; +THREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn("animation["+a+"] undefined")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1}; +THREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;bd.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.startFrame+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight; +f!==d.currentFrame&&(this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);this.morphTargetInfluences[d.currentFrame]=e*g;this.morphTargetInfluences[d.lastFrame]=(1-e)*g}}};THREE.LensFlarePlugin=function(){function a(a,c){var d=b.createProgram(),e=b.createShader(b.FRAGMENT_SHADER),f=b.createShader(b.VERTEX_SHADER),g="precision "+c+" float;\n";b.shaderSource(e,g+a.fragmentShader);b.shaderSource(f,g+a.vertexShader);b.compileShader(e);b.compileShader(f);b.attachShader(d,e);b.attachShader(d,f);b.linkProgram(d);return d}var b,c,d,e,f,g,h,k,l,n,s,r,q;this.init=function(u){b=u.context;c=u;d=u.getPrecision();e=new Float32Array(16);f=new Uint16Array(6);u=0;e[u++]=-1;e[u++]=-1; +e[u++]=0;e[u++]=0;e[u++]=1;e[u++]=-1;e[u++]=1;e[u++]=0;e[u++]=1;e[u++]=1;e[u++]=1;e[u++]=1;e[u++]=-1;e[u++]=1;e[u++]=0;e[u++]=1;u=0;f[u++]=0;f[u++]=1;f[u++]=2;f[u++]=0;f[u++]=2;f[u++]=3;g=b.createBuffer();h=b.createBuffer();b.bindBuffer(b.ARRAY_BUFFER,g);b.bufferData(b.ARRAY_BUFFER,e,b.STATIC_DRAW);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.bufferData(b.ELEMENT_ARRAY_BUFFER,f,b.STATIC_DRAW);k=b.createTexture();l=b.createTexture();b.bindTexture(b.TEXTURE_2D,k);b.texImage2D(b.TEXTURE_2D,0,b.RGB,16,16, +0,b.RGB,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);b.bindTexture(b.TEXTURE_2D,l);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,16,16,0,b.RGBA,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE); +b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);0>=b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)?(n=!1,s=a(THREE.ShaderFlares.lensFlare,d)):(n=!0,s=a(THREE.ShaderFlares.lensFlareVertexTexture,d));r={};q={};r.vertex=b.getAttribLocation(s,"position");r.uv=b.getAttribLocation(s,"uv");q.renderType=b.getUniformLocation(s,"renderType");q.map=b.getUniformLocation(s,"map");q.occlusionMap=b.getUniformLocation(s,"occlusionMap");q.opacity= +b.getUniformLocation(s,"opacity");q.color=b.getUniformLocation(s,"color");q.scale=b.getUniformLocation(s,"scale");q.rotation=b.getUniformLocation(s,"rotation");q.screenPosition=b.getUniformLocation(s,"screenPosition")};this.render=function(a,d,e,f){a=a.__webglFlares;var t=a.length;if(t){var x=new THREE.Vector3,z=f/e,B=0.5*e,E=0.5*f,H=16/f,D=new THREE.Vector2(H*z,H),G=new THREE.Vector3(1,1,0),I=new THREE.Vector2(1,1),O=q,H=r;b.useProgram(s);b.enableVertexAttribArray(r.vertex);b.enableVertexAttribArray(r.uv); +b.uniform1i(O.occlusionMap,0);b.uniform1i(O.map,1);b.bindBuffer(b.ARRAY_BUFFER,g);b.vertexAttribPointer(H.vertex,2,b.FLOAT,!1,16,0);b.vertexAttribPointer(H.uv,2,b.FLOAT,!1,16,8);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.disable(b.CULL_FACE);b.depthMask(!1);var K,y,F,C,A;for(K=0;KD;D++)z[D]=new THREE.Vector3,t[D]=new THREE.Vector3;z=B.shadowCascadeNearZ[x];B=B.shadowCascadeFarZ[x];t[0].set(-1,-1,z);t[1].set(1,-1,z);t[2].set(-1, +1,z);t[3].set(1,1,z);t[4].set(-1,-1,B);t[5].set(1,-1,B);t[6].set(-1,1,B);t[7].set(1,1,B);H.originalCamera=r;t=new THREE.Gyroscope;t.position=p.shadowCascadeOffset;t.add(H);t.add(H.target);r.add(t);p.shadowCascadeArray[w]=H;console.log("Created virtualLight",H)}x=p;z=w;B=x.shadowCascadeArray[z];B.position.copy(x.position);B.target.position.copy(x.target.position);B.lookAt(B.target);B.shadowCameraVisible=x.shadowCameraVisible;B.shadowDarkness=x.shadowDarkness;B.shadowBias=x.shadowCascadeBias[z];t=x.shadowCascadeNearZ[z]; +x=x.shadowCascadeFarZ[z];B=B.pointsFrustum;B[0].z=t;B[1].z=t;B[2].z=t;B[3].z=t;B[4].z=x;B[5].z=x;B[6].z=x;B[7].z=x;E[v]=H;v++}else E[v]=p,v++;q=0;for(u=E.length;qx;x++)z=B[x],z.copy(t[x]),THREE.ShadowMapPlugin.__projector.unprojectVector(z,w),z.applyMatrix4(v.matrixWorldInverse),z.xl.x&&(l.x=z.x),z.yl.y&&(l.y=z.y),z.zl.z&& +(l.z=z.z);v.left=k.x;v.right=l.x;v.top=l.y;v.bottom=k.y;v.updateProjectionMatrix()}v=p.shadowMap;t=p.shadowMatrix;w=p.shadowCamera;w.position.setFromMatrixPosition(p.matrixWorld);n.setFromMatrixPosition(p.target.matrixWorld);w.lookAt(n);w.updateMatrixWorld();w.matrixWorldInverse.getInverse(w.matrixWorld);p.cameraHelper&&(p.cameraHelper.visible=p.shadowCameraVisible);p.shadowCameraVisible&&p.cameraHelper.update();t.set(0.5,0,0,0.5,0,0.5,0,0.5,0,0,0.5,0.5,0,0,0,1);t.multiply(w.projectionMatrix);t.multiply(w.matrixWorldInverse); +h.multiplyMatrices(w.projectionMatrix,w.matrixWorldInverse);g.setFromMatrix(h);b.setRenderTarget(v);b.clear();B=s.__webglObjects;p=0;for(v=B.length;p 0 ) {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat fogFactor = 0.0;\nif ( fogType == 1 ) {\nfogFactor = smoothstep( fogNear, fogFar, depth );\n} else {\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n}\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n}\n}"].join("\n")); +t.compileShader(O);t.compileShader(K);t.attachShader(w,O);t.attachShader(w,K);t.linkProgram(w);G=w;p=t.getAttribLocation(G,"position");v=t.getAttribLocation(G,"uv");a=t.getUniformLocation(G,"uvOffset");b=t.getUniformLocation(G,"uvScale");c=t.getUniformLocation(G,"rotation");d=t.getUniformLocation(G,"scale");e=t.getUniformLocation(G,"color");f=t.getUniformLocation(G,"map");g=t.getUniformLocation(G,"opacity");h=t.getUniformLocation(G,"modelViewMatrix");k=t.getUniformLocation(G,"projectionMatrix");l= +t.getUniformLocation(G,"fogType");n=t.getUniformLocation(G,"fogDensity");s=t.getUniformLocation(G,"fogNear");r=t.getUniformLocation(G,"fogFar");q=t.getUniformLocation(G,"fogColor");u=t.getUniformLocation(G,"alphaTest");w=document.createElement("canvas");w.width=8;w.height=8;O=w.getContext("2d");O.fillStyle="#ffffff";O.fillRect(0,0,w.width,w.height);z=new THREE.Texture(w);z.needsUpdate=!0};this.render=function(E,B,K,y){K=E.__webglSprites;if(y=K.length){t.useProgram(G);t.enableVertexAttribArray(p); +t.enableVertexAttribArray(v);t.disable(t.CULL_FACE);t.enable(t.BLEND);t.bindBuffer(t.ARRAY_BUFFER,H);t.vertexAttribPointer(p,2,t.FLOAT,!1,16,0);t.vertexAttribPointer(v,2,t.FLOAT,!1,16,8);t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,D);t.uniformMatrix4fv(k,!1,B.projectionMatrix.elements);t.activeTexture(t.TEXTURE0);t.uniform1i(f,0);var F=0,C=0,A=E.fog;A?(t.uniform3f(q,A.color.r,A.color.g,A.color.b),A instanceof THREE.Fog?(t.uniform1f(s,A.near),t.uniform1f(r,A.far),t.uniform1i(l,1),C=F=1):A instanceof THREE.FogExp2&& +(t.uniform1f(n,A.density),t.uniform1i(l,2),C=F=2)):(t.uniform1i(l,0),C=F=0);for(var L,Q=[],A=0;A