Browse Source

Merge pull request #1 from mrdoob/dev

Dev
Arthur Silber 10 years ago
parent
commit
187009850b
80 changed files with 1160 additions and 718 deletions
  1. 51 20
      build/three.js
  2. 66 64
      build/three.min.js
  3. 1 1
      docs/api/extras/helpers/CameraHelper.html
  4. 5 5
      docs/api/extras/helpers/DirectionalLightHelper.html
  5. 9 11
      docs/api/extras/helpers/HemisphereLightHelper.html
  6. 6 13
      docs/api/materials/MeshNormalMaterial.html
  7. 4 4
      docs/index.html
  8. 16 0
      editor/css/main.css
  9. 1 1
      editor/examples/arkanoid.app.json
  10. 63 49
      editor/examples/camera.app.json
  11. 1 1
      editor/examples/pong.app.json
  12. 1 0
      editor/index.html
  13. 3 0
      editor/js/Editor.js
  14. 0 32
      editor/js/Menubar.Edit.js
  15. 2 3
      editor/js/Player.js
  16. 89 4
      editor/js/Script.js
  17. 50 0
      editor/js/Sidebar.Geometry.js
  18. 2 0
      editor/js/Sidebar.Object3D.js
  19. 41 18
      editor/js/libs/app.js
  20. 197 0
      editor/js/libs/esprima.js
  21. 45 8
      examples/js/controls/OrbitControls.js
  22. 37 168
      examples/js/controls/TrackballControls.js
  23. 3 3
      examples/js/effects/VREffect.js
  24. 3 5
      examples/js/loaders/ColladaLoader.js
  25. 16 15
      examples/js/loaders/OBJLoader.js
  26. 9 6
      examples/js/loaders/PLYLoader.js
  27. 10 1
      examples/js/renderers/RaytracingRenderer.js
  28. 4 2
      examples/js/shaders/ColorCorrectionShader.js
  29. 1 14
      examples/misc_controls_trackball.html
  30. 0 1
      examples/models/skinned/marine/marine.js
  31. 0 1
      examples/models/skinned/marine/marine_anims.js
  32. 0 1
      examples/models/skinned/marine/marine_ikrig.js
  33. 21 42
      examples/obj/f50/F50NoUv_bin.js
  34. 7 14
      examples/obj/gallardo/GallardoNoUv_bin.js
  35. 7 14
      examples/obj/gallardo/parts/gallardo_body_bin.js
  36. 2 4
      examples/obj/gallardo/parts/gallardo_wheel_bin.js
  37. 5 10
      examples/obj/veyron/VeyronNoUv_bin.js
  38. 5 10
      examples/obj/veyron/parts/veyron_body_bin.js
  39. 2 4
      examples/obj/veyron/parts/veyron_wheel_bin.js
  40. 1 1
      examples/webgl_lights_pointlights.html
  41. 3 1
      src/Three.js
  42. 13 7
      src/core/BufferGeometry.js
  43. 2 0
      src/core/Object3D.js
  44. 1 1
      src/extras/helpers/HemisphereLightHelper.js
  45. 10 4
      src/loaders/Loader.js
  46. 16 4
      src/loaders/ObjectLoader.js
  47. 6 3
      src/materials/Material.js
  48. 0 4
      src/materials/MeshNormalMaterial.js
  49. 1 1
      src/objects/Line.js
  50. 23 18
      src/renderers/WebGLRenderer.js
  51. 4 4
      src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl
  52. 15 4
      src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl
  53. 6 1
      src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl
  54. 2 2
      src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl
  55. 11 2
      src/renderers/shaders/ShaderLib.js
  56. 6 2
      src/renderers/webgl/WebGLProgram.js
  57. 14 0
      src/renderers/webgl/WebGLState.js
  58. 35 0
      test/unit/math/Math.js
  59. 1 0
      test/unit/unittests_sources.html
  60. 1 0
      test/unit/unittests_three-math.html
  61. 1 0
      test/unit/unittests_three.html
  62. 1 0
      test/unit/unittests_three.min.html
  63. 29 2
      utils/exporters/blender/addons/io_three/__init__.py
  64. 10 4
      utils/exporters/blender/addons/io_three/constants.py
  65. 6 6
      utils/exporters/blender/addons/io_three/exporter/__init__.py
  66. 12 0
      utils/exporters/blender/addons/io_three/exporter/api/__init__.py
  67. 34 0
      utils/exporters/blender/addons/io_three/exporter/api/animation.py
  68. 1 1
      utils/exporters/blender/addons/io_three/exporter/api/material.py
  69. 1 1
      utils/exporters/blender/addons/io_three/exporter/api/mesh.py
  70. 32 46
      utils/exporters/blender/addons/io_three/exporter/api/object.py
  71. 6 3
      utils/exporters/blender/addons/io_three/exporter/api/texture.py
  72. 12 10
      utils/exporters/blender/addons/io_three/exporter/geometry.py
  73. 2 1
      utils/exporters/blender/addons/io_three/exporter/image.py
  74. 1 0
      utils/exporters/blender/addons/io_three/exporter/io.py
  75. 34 24
      utils/exporters/blender/addons/io_three/exporter/object.py
  76. 11 2
      utils/exporters/blender/addons/io_three/exporter/scene.py
  77. 11 10
      utils/exporters/blender/addons/io_three/logger.py
  78. BIN
      utils/exporters/blender/tests/blend/scene_orthographic_camera.blend
  79. BIN
      utils/exporters/blender/tests/blend/scene_perspective_camera.blend
  80. BIN
      utils/exporters/blender/tests/blend/scene_spot_light.blend

File diff suppressed because it is too large
+ 51 - 20
build/three.js


File diff suppressed because it is too large
+ 66 - 64
build/three.min.js


+ 1 - 1
docs/api/extras/helpers/CameraHelper.html

@@ -45,7 +45,7 @@
 		<h2>Methods</h2>
 
 
-		<h3>[method:todo update]()</h3>
+		<h3>[method:null update]()</h3>
 		<div>
 		Updates the helper based on the projectionMatrix of the camera.
 		</div>

+ 5 - 5
docs/api/extras/helpers/DirectionalLightHelper.html

@@ -17,7 +17,7 @@
 		<h2>Constructor</h2>
 
 
-		<h3>[name]([DirectionalLight:todo light], [page:Number size])</h3>
+		<h3>[name]([page:DirectionalLight light], [page:Number size])</h3>
 		<div>
 		light -- [page:DirectionalLight] -- Light to visualize <br />
 		size -- dimensions of the plane
@@ -32,18 +32,18 @@
 
 		<h3>[property:Line lightPlane]</h3>
 		<div>
-		todo
+		Contains the line mesh showing the location of the directional light.
 		</div> 
 
 		<h3>[property:DirectionalLight light]</h3>
 		<div>
-		todo
+		Contains the directionalLight.
 		</div> 
 
 		<h3>[property:Line targetLine]</h3>
 		<div>
-		todo
-		</div> 
+		Contains the line mesh that shows the direction of the light. 
+		</div>
 
 		<h2>Methods</h2>
 

+ 9 - 11
docs/api/extras/helpers/HemisphereLightHelper.html

@@ -11,20 +11,18 @@
 		
 		<h1>[name]</h1>
 
-		<div class="desc">todo</div>
+		<div class="desc">Creates a visual aid for a [page:HemisphereLight HemisphereLight].</div>
 
 
 		<h2>Constructor</h2>
 
-		<h3>[name]([page:todo light], [page:todo sphereSize], [page:todo arrowLength], [page:todo domeSize])</h3>
+		<h3>[name]([page:HemisphereLight light], [page:Number sphereSize])</h3>
 		<div>
-		light -- todo <br />
-		sphereSize -- todo <br />
-		arrowLength -- todo <br />
-		domeSize -- todo
+		light -- The HemisphereLight. <br />
+		sphereSize -- The size of the sphere that shows the location.
 		</div>
 		<div>
-		todo
+		Creates an helper for the hemispherelight.
 		</div>
 
 
@@ -32,20 +30,20 @@
 
 		<h3>[property:Mesh lightSphere]</h3>
 		<div>
-		todo
+		The sphere mesh that shows the location of the hemispherelight. 
 		</div> 
 
 		<h3>[property:HemisphereLight light]</h3>
 		<div>
-		todo
+		Contains the HemisphereLight.
 		</div> 
 
 
 		<h2>Methods</h2>
 
-		<h3>[method:todo update]()</h3>
+		<h3>[method:null update]()</h3>
 		<div>
-		todo
+		Updates the helper to match the position and direction of the [page:.light].
 		</div>
 
 

+ 6 - 13
docs/api/materials/MeshNormalMaterial.html

@@ -12,7 +12,7 @@
 		<h1>[name]</h1>
 
 		<div class="desc">A material that maps the normal vectors to RGB colors.</div>
-		
+
 		<iframe src='../../scenes/material-browser.html#MeshNormalMaterial'></iframe>
 
 
@@ -21,10 +21,9 @@
 
 		<h3>[name]([page:Object parameters])</h3>
 		<div>
-		parameters is an object with one or more properties defining the material's appearance. 
+		parameters is an object with one or more properties defining the material's appearance.
 		</div>
 		<div>
-		shading --  How the triangles of a curved surface are rendered. Default is [page:Materials THREE.FlatShading].<br/>
 		wireframe -- Render geometry as wireframe. Default is false (i.e. render as smooth shaded).<br/>
 		wireframeLinewidth -- Controls wireframe thickness. Default is 1.<br/>
 		morphTargets -- Define whether the material uses morphTargets. Default is false.<br/>
@@ -34,25 +33,19 @@
 		<h2>Properties</h2>
 
 
-		<h3>[property:number shading]</h3>
+		<h3>[property:boolean wireframe]</h3>
 		<div>
-			How the triangles of a curved surface are rendered: as a smooth surface, as flat separate facets, or no shading at all.<br/><br/>
-			Options are [page:Materials THREE.SmoothShading], [page:Materials THREE.FlatShading](default)
+			Render geometry as wireframe. Default is false (i.e. render as smooth shaded).
 		</div>
 
-		<h3>[property:boolean wireframe]</h3>
-		<div>
-			Render geometry as wireframe. Default is false (i.e. render as smooth shaded). 
-		</div> 
-		
 		<h3>[property:number wireframeLinewidth]</h3>
 		<div>
 			Controls wireframe thickness. Default is 1.<br/><br/>
 			Due to limitations in the ANGLE layer, on Windows platforms linewidth will always be 1 regardless of the set value.
-		</div> 
+		</div>
 
 		<h3>[property:boolean morphTargets]</h3>
-		<div>Define whether the material uses morphTargets. Default is false.</div> 
+		<div>Define whether the material uses morphTargets. Default is false.</div>
 
 		<h2>Methods</h2>
 

+ 4 - 4
docs/index.html

@@ -406,10 +406,10 @@
 			if ( window.location.hash.length > 0 ) goToHash();
 
 			console.log([
-				'_______   __',
-				'/ _______\\/ __\\__   ____   _____   _____',
-				'\\/__   __/ /  /___\\/ ____\\/ _____\\/ _____\\',
-				' / /  / / /   _   / /  __/ / __  / / __  /_   __   _____',
+				'   __     __',
+				' __/ __\\  / __\\__   ____   _____   _____',
+				'/ __/  /\\/ /  /___\\/ ____\\/ _____\\/ _____\\',
+				'\\/_   __/ /   _   / /  __/ / __  / / __  /_   __   _____',
 				'/ /  / / /  / /  / /  / / /  ___/ /  ___/\\ _\\/ __\\/ _____\\',
 				'\\/__/  \\/__/\\/__/\\/__/  \\/_____/\\/_____/\\/__/ /  / /  ___/',
 				'                                         / __/  /  \\__  \\',

+ 16 - 0
editor/css/main.css

@@ -67,6 +67,8 @@ textarea, input { outline: none; } /* osx */
 		display: none;
 	}
 
+/* CodeMirror */
+
 .CodeMirror {
 
 	position: absolute !important;
@@ -76,6 +78,20 @@ textarea, input { outline: none; } /* osx */
 
 }
 
+	.CodeMirror .errorLine {
+
+		background: rgba(255,0,0,0.25);
+
+	}
+
+	.CodeMirror .esprima-error {
+
+		color: #f00;
+		text-align: right;
+		padding: 0px 20px;
+
+	}
+
 /* scene types */
 
 .type {

+ 1 - 1
editor/examples/arkanoid.app.json

@@ -162,7 +162,7 @@
 		"31517222-A9A7-4EAF-B5F6-60751C0BABA3": [
 			{
 				"name": "Game Logic",
-				"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = - 0.5;\ndirection.normalize();\n\nvar speed = new THREE.Vector3();\n\n//\n\nvar group = new THREE.Group();\nthis.add( group );\n\nvar paddle = this.getObjectByName( 'Paddle' );\ngroup.add( paddle );\n\nvar brick = this.getObjectByName( 'Brick' );\n\nfor ( var j = 0; j < 8; j ++ ) {\n\n\tvar material = new THREE.MeshPhongMaterial( { color: Math.random() * 0xffffff } );\n\n\tfor ( var i = 0; i < 12; i ++ ) {\n\t\t\n\t\tvar object = brick.clone();\n\t\tobject.material = material;\n\t\tobject.position.x = i * 22 - 120;\n\t\tobject.position.z = j * 14 - 120;\n\t\tgroup.add( object );\n\t\t\n\t}\n\t\n}\n\nbrick.visible = false;\n\n//\n\nvar raycaster = new THREE.Raycaster();\n\nfunction update( event ) {\n\t\n\tif ( ball.position.x < - 150 || ball.position.x > 150 ) direction.x = - direction.x;\n\tif ( ball.position.z < - 200 || ball.position.z > 200 ) direction.z = - direction.z;\n\n\tball.position.add( speed.copy( direction ).multiplyScalar( event.delta / 4 ) );\n\t\n\traycaster.set( ball.position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( group.children );\n\t\n\tif ( intersections.length > 0 ) {\n\t\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 5 ) {\n\t\t\t\n\t\t\tif ( intersection.object !== paddle ) {\n\n\t\t\t\tgroup.remove( intersection.object );\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n}"
+				"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = - 0.5;\ndirection.normalize();\n\nvar speed = new THREE.Vector3();\n\n//\n\nvar group = new THREE.Group();\nthis.add( group );\n\nvar paddle = this.getObjectByName( 'Paddle' );\ngroup.add( paddle );\n\nvar brick = this.getObjectByName( 'Brick' );\n\nfor ( var j = 0; j < 8; j ++ ) {\n\n\tvar material = new THREE.MeshPhongMaterial( { color: Math.random() * 0xffffff } );\n\n\tfor ( var i = 0; i < 12; i ++ ) {\n\t\t\n\t\tvar object = brick.clone();\n\t\tobject.material = material;\n\t\tobject.position.x = i * 22 - 120;\n\t\tobject.position.z = j * 14 - 120;\n\t\tgroup.add( object );\n\t\t\n\t}\n\t\n}\n\nbrick.visible = false;\n\n//\n\nvar raycaster = new THREE.Raycaster();\n\nfunction update( event ) {\n\t\n\tif ( ball.position.x < - 150 || ball.position.x > 150 ) direction.x = - direction.x;\n\tif ( ball.position.z < - 200 || ball.position.z > 200 ) direction.z = - direction.z;\n\n\tball.position.x = Math.max( - 150, Math.min( 150, ball.position.x ) );\n\tball.position.z = Math.max( - 200, Math.min( 200, ball.position.z ) );\n\t\n\tball.position.add( speed.copy( direction ).multiplyScalar( event.delta / 4 ) );\n\t\n\traycaster.set( ball.position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( group.children );\n\t\n\tif ( intersections.length > 0 ) {\n\t\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 5 ) {\n\t\t\t\n\t\t\tif ( intersection.object !== paddle ) {\n\n\t\t\t\tgroup.remove( intersection.object );\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n}"
 			}]
 	}
 }

+ 63 - 49
editor/examples/camera.app.json

@@ -6,14 +6,14 @@
 			"generator": "ObjectExporter"
 		},
 		"object": {
-			"uuid": "F0D8434F-4603-415B-8024-792FE97B9600",
+			"uuid": "C7FB195B-270E-47B4-95C9-1754652A9D11",
 			"type": "PerspectiveCamera",
 			"name": "Camera",
 			"fov": 50,
 			"aspect": 1.2252042007001167,
 			"near": 0.1,
 			"far": 100000,
-			"matrix": [0.9700406789779663,-5.500052080442686e-10,-0.24294254183769226,0,-0.04822639003396034,0.9800989627838135,-0.19256223738193512,0,0.23810774087905884,0.19850945472717285,0.950735867023468,0,159.0158233642578,132.5708465576172,634.9312744140625,1]
+			"matrix": [0.9700406789779663,-2.851828329042405e-9,-0.24294254183769226,0,-0.04822639003396034,0.9800989627838135,-0.1925622522830963,0,0.23810774087905884,0.19850945472717285,0.950735867023468,0,154.7735595703125,129.03408813476562,617.992431640625,1]
 		}
 	},
 	"scene": {
@@ -23,6 +23,14 @@
 			"generator": "ObjectExporter"
 		},
 		"geometries": [
+			{
+				"uuid": "51BB3E54-D2DF-4576-9953-FB8E940588B5",
+				"type": "PlaneGeometry",
+				"width": 1000,
+				"height": 1000,
+				"widthSegments": 1,
+				"heightSegments": 1
+			},
 			{
 				"uuid": "D8E200D3-27BC-49F8-A5C5-7384206E70FE",
 				"type": "BoxGeometry",
@@ -43,14 +51,6 @@
 				"heightSegments": 1,
 				"openEnded": false
 			},
-			{
-				"uuid": "51BB3E54-D2DF-4576-9953-FB8E940588B5",
-				"type": "PlaneGeometry",
-				"width": 1000,
-				"height": 1000,
-				"widthSegments": 1,
-				"heightSegments": 1
-			},
 			{
 				"uuid": "4DECFAB5-6FD1-4D84-9A29-565807B074EA",
 				"type": "IcosahedronGeometry",
@@ -59,25 +59,25 @@
 			}],
 		"materials": [
 			{
-				"uuid": "B5943856-E404-45D9-A427-4774202C2CD0",
+				"uuid": "4AE8130E-B6A8-47BC-ACCF-060973C74044",
 				"type": "MeshPhongMaterial",
-				"color": 37119,
+				"color": 16777215,
 				"emissive": 0,
 				"specular": 1118481,
 				"shininess": 30
 			},
 			{
-				"uuid": "3F872310-2067-4BE4-9250-5B3F4E43797E",
+				"uuid": "B5943856-E404-45D9-A427-4774202C2CD0",
 				"type": "MeshPhongMaterial",
-				"color": 15859456,
+				"color": 37119,
 				"emissive": 0,
 				"specular": 1118481,
 				"shininess": 30
 			},
 			{
-				"uuid": "4AE8130E-B6A8-47BC-ACCF-060973C74044",
+				"uuid": "3F872310-2067-4BE4-9250-5B3F4E43797E",
 				"type": "MeshPhongMaterial",
-				"color": 16777215,
+				"color": 15859456,
 				"emissive": 0,
 				"specular": 1118481,
 				"shininess": 30
@@ -97,19 +97,34 @@
 			"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],
 			"children": [
 				{
-					"uuid": "60B69C58-4201-43FD-815E-AD2EDFBBD0CE",
-					"type": "PerspectiveCamera",
-					"name": "PerspectiveCamera 1",
-					"fov": 50,
-					"aspect": 1,
-					"near": 100,
-					"far": 10000,
-					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,100,400,1]
+					"uuid": "B7CBBC6F-EC26-49B5-8D0D-67D9C535924B",
+					"type": "Group",
+					"name": "Dummy",
+					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,100,400,1],
+					"children": [
+						{
+							"uuid": "60B69C58-4201-43FD-815E-AD2EDFBBD0CE",
+							"type": "PerspectiveCamera",
+							"name": "PerspectiveCamera",
+							"fov": 50,
+							"aspect": 1,
+							"near": 100,
+							"far": 10000,
+							"matrix": [-1,0,-1.2246468525851679e-16,0,0,1,0,0,1.2246468525851679e-16,0,-1,0,0,0,0,1]
+						}]
+				},
+				{
+					"uuid": "A460C230-DC88-4A8F-A3FB-AA0FE735F3ED",
+					"type": "Mesh",
+					"name": "Plane",
+					"geometry": "51BB3E54-D2DF-4576-9953-FB8E940588B5",
+					"material": "4AE8130E-B6A8-47BC-ACCF-060973C74044",
+					"matrix": [1,0,0,0,0,0.040785226970911026,-0.9991679191589355,0,0,0.9991679191589355,0.040785226970911026,0,0,-50,0,1]
 				},
 				{
 					"uuid": "26DAAD69-725D-43B7-AF9D-990A99DEF8C5",
 					"type": "Mesh",
-					"name": "Box 1",
+					"name": "Box",
 					"geometry": "D8E200D3-27BC-49F8-A5C5-7384206E70FE",
 					"material": "B5943856-E404-45D9-A427-4774202C2CD0",
 					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
@@ -117,27 +132,18 @@
 				{
 					"uuid": "AAAFF2D6-4725-4AFC-A9FE-26419B11011F",
 					"type": "Mesh",
-					"name": "Cylinder 3",
+					"name": "Cylinder",
 					"geometry": "25BA32DB-8B02-4ABA-A77C-69868C464A1A",
 					"material": "3F872310-2067-4BE4-9250-5B3F4E43797E",
 					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-130,-15,0,1]
 				},
 				{
-					"uuid": "A460C230-DC88-4A8F-A3FB-AA0FE735F3ED",
+					"uuid": "B855E267-A266-4098-ACD6-6A1FDE7B88BA",
 					"type": "Mesh",
-					"name": "Plane 4",
-					"geometry": "51BB3E54-D2DF-4576-9953-FB8E940588B5",
-					"material": "4AE8130E-B6A8-47BC-ACCF-060973C74044",
-					"matrix": [1,0,0,0,0,0.040785059332847595,-0.9991679191589355,0,0,0.9991679191589355,0.040785059332847595,0,0,-50,0,1]
-				},
-				{
-					"uuid": "3412781E-27CC-43C3-A5DB-54C0C8E42ED6",
-					"type": "PointLight",
-					"name": "PointLight 2",
-					"color": 12773063,
-					"intensity": 1,
-					"distance": 0,
-					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,88.12999725341797,8.3100004196167,125.44999694824219,1]
+					"name": "Icosahedron",
+					"geometry": "4DECFAB5-6FD1-4D84-9A29-565807B074EA",
+					"material": "E1826901-7922-4584-A25D-6D487E2C9BBD",
+					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,130,-10,0,1]
 				},
 				{
 					"uuid": "E2939A7B-5E40-438A-8C1B-32126FBC6892",
@@ -146,23 +152,31 @@
 					"color": 9474221,
 					"intensity": 0.75,
 					"distance": 0,
+					"decay": 1,
 					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,-93.86000061035156,127.12999725341797,-114.30000305175781,1]
 				},
 				{
-					"uuid": "B855E267-A266-4098-ACD6-6A1FDE7B88BA",
-					"type": "Mesh",
-					"name": "Icosahedron 1",
-					"geometry": "4DECFAB5-6FD1-4D84-9A29-565807B074EA",
-					"material": "E1826901-7922-4584-A25D-6D487E2C9BBD",
-					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,130,-10,0,1]
+					"uuid": "3412781E-27CC-43C3-A5DB-54C0C8E42ED6",
+					"type": "PointLight",
+					"name": "PointLight 2",
+					"color": 12773063,
+					"intensity": 1,
+					"distance": 0,
+					"decay": 1,
+					"matrix": [1,0,0,0,0,1,0,0,0,0,1,0,88.12999725341797,8.3100004196167,125.44999694824219,1]
 				}]
 		}
 	},
 	"scripts": {
 		"60B69C58-4201-43FD-815E-AD2EDFBBD0CE": [
 			{
-				"name": "Camera Orbit",
-				"source": "player.setCamera( this );\n\nfunction update( event ) {\n\n\tvar time = event.time * 0.001;\n\n\tthis.position.x = Math.sin( time ) * 400;\n\tthis.position.z = Math.cos( time ) * 400;\n\tthis.lookAt( scene.position );\n\n}"
+				"name": "Player Camera",
+				"source": "player.setCamera( this );"
+			}],
+		"B7CBBC6F-EC26-49B5-8D0D-67D9C535924B": [
+			{
+				"name": "Orbit",
+				"source": "function update( event ) {\n\n\tvar time = event.time * 0.001;\n\n\tthis.position.x = Math.sin( time ) * 400;\n\tthis.position.z = Math.cos( time ) * 400;\n\tthis.lookAt( scene.position );\n\n}"
 			}]
 	}
-}
+}

+ 1 - 1
editor/examples/pong.app.json

@@ -128,7 +128,7 @@
 		"31517222-A9A7-4EAF-B5F6-60751C0BABA3": [
 			{
 				"name": "Game logic",
-				"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar position = ball.position;\n\nvar velocity = new THREE.Vector3();\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = Math.random() - 0.5;\ndirection.normalize();\n\nvar pad1 = this.getObjectByName( 'Pad 1' );\nvar pad2 = this.getObjectByName( 'Pad 2' );\n\nvar raycaster = new THREE.Raycaster();\nvar objects = [ pad1, pad2 ];\n\n//\n\nfunction mousemove( event ) {\n\n\tpad1.position.z = ( event.clientX / player.width ) * 300 - 150;\n\tpad2.position.z = - pad1.position.z;\n\n}\n\nfunction update( event ) {\n\n\tif ( position.x < -300 || position.x > 300 ) {\n\t\t\n\t\tdirection.x = - direction.x;\n\t\t\n\t}\n\n\tif ( position.z < -200 || position.z > 200 ) {\n\t\t\n\t\tdirection.z = - direction.z;\n\t\t\n\t}\n\t\n\traycaster.set( position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( objects );\n\t\n\tif ( intersections.length > 0 ) {\n\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 10 ) {\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tposition.add( velocity.copy( direction ).multiplyScalar( event.delta / 2 ) );\n\n}"
+				"source": "var ball = this.getObjectByName( 'Ball' );\n\nvar position = ball.position;\n\nvar velocity = new THREE.Vector3();\n\nvar direction = new THREE.Vector3();\ndirection.x = Math.random() - 0.5;\ndirection.z = Math.random() - 0.5;\ndirection.normalize();\n\nvar pad1 = this.getObjectByName( 'Pad 1' );\nvar pad2 = this.getObjectByName( 'Pad 2' );\n\nvar raycaster = new THREE.Raycaster();\nvar objects = [ pad1, pad2 ];\n\n//\n\nfunction mousemove( event ) {\n\n\tpad1.position.z = ( event.clientX / player.width ) * 300 - 150;\n\tpad2.position.z = - pad1.position.z;\n\n}\n\nfunction update( event ) {\n\t\n\tif ( position.x < -300 || position.x > 300 ) direction.x = - direction.x;\n\tif ( position.z < -200 || position.z > 200 ) direction.z = - direction.z;\n\t\n\tposition.x = Math.max( - 300, Math.min( 300, position.x ) );\n\tposition.z = Math.max( - 200, Math.min( 200, position.z ) );\n\t\n\traycaster.set( position, direction );\n\t\n\tvar intersections = raycaster.intersectObjects( objects );\n\t\n\tif ( intersections.length > 0 ) {\n\n\t\tvar intersection = intersections[ 0 ];\n\t\t\n\t\tif ( intersection.distance < 10 ) {\n\t\t\t\n\t\t\tdirection.reflect( intersection.face.normal );\n\t\t\t\n\t\t}\n\t\t\n\t}\n\n\tposition.add( velocity.copy( direction ).multiplyScalar( event.delta / 2 ) );\n\n}"
 			}]
 	}
 }

+ 1 - 0
editor/index.html

@@ -41,6 +41,7 @@
 		<link rel="stylesheet" href="js/libs/codemirror/theme/monokai.css">
 		<script src="js/libs/codemirror/codemirror.js"></script>
 		<script src="js/libs/codemirror/mode/javascript.js"></script>
+		<script src="js/libs/esprima.js"></script>
 
 		<script src="js/libs/jszip.min.js"></script>
 		<script src="js/libs/sortable.min.js"></script>

+ 3 - 0
editor/js/Editor.js

@@ -470,6 +470,9 @@ Editor.prototype = {
 
 		return {
 
+			project: {
+				vr: this.config.getKey( 'project/vr' )
+			},
 			camera: this.camera.toJSON(),
 			scene: this.scene.toJSON(),
 			scripts: this.scripts

+ 0 - 32
editor/js/Menubar.Edit.js

@@ -53,38 +53,6 @@ Menubar.Edit = function ( editor ) {
 	} );
 	options.add( option );
 
-	//
-
-	options.add( new UI.HorizontalRule() );
-
-	// Flatten
-
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Flatten' );
-	option.onClick( function () {
-
-		var object = editor.selected;
-
-		if ( object.parent === undefined ) return; // avoid flattening the camera or scene
-
-		if ( confirm( 'Flatten ' + object.name + '?' ) === false ) return;
-
-		var geometry = object.geometry;
-
-		geometry.applyMatrix( object.matrix );
-		geometry.verticesNeedUpdate = true;
-		geometry.normalsNeedUpdate = true;
-
-		object.position.set( 0, 0, 0 );
-		object.rotation.set( 0, 0, 0 );
-		object.scale.set( 1, 1, 1 );
-
-		editor.signals.objectChanged.dispatch( object );
-
-	} );
-	options.add( option );
-
 	return container;
 
 };

+ 2 - 3
editor/js/Player.js

@@ -19,7 +19,7 @@ var Player = function ( editor ) {
 
 		if ( player.dom === undefined ) return;
 
-		player.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
+		player.setSize( container.dom.clientWidth, container.dom.clientHeight );
 
 	} );
 
@@ -27,9 +27,8 @@ var Player = function ( editor ) {
 
 		container.setDisplay( '' );
 
-		player.setVR( editor.config.getKey( 'project/vr' ) );
 		player.load( editor.toJSON() );
-		player.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
+		player.setSize( container.dom.clientWidth, container.dom.clientHeight );
 		player.play();
 
 		container.dom.appendChild( player.dom );

+ 89 - 4
editor/js/Script.js

@@ -15,7 +15,7 @@ var Script = function ( editor ) {
 	var header = new UI.Panel();
 	header.setPadding( '10px' );
 	container.add( header );
-	
+
 	var title = new UI.Text().setColor( '#fff' );
 	header.add( title );
 
@@ -59,16 +59,101 @@ var Script = function ( editor ) {
 		clearTimeout( delay );
 		delay = setTimeout( function () {
 
-			currentScript.source = codemirror.getValue();
+			var value = codemirror.getValue();
+
+			if ( validate( value ) ) {
+
+				currentScript.source = value;
+				signals.scriptChanged.dispatch( currentScript );
 
-			signals.scriptChanged.dispatch( currentScript );
+			}
 
 		}, 300 );
 
 	});
 
+	// validate
+
+	var errorLines = [];
+	var widgets = [];
+
+	var validate = function ( string ) {
+
+		var syntax, errors;
+
+		return codemirror.operation( function () {
+
+			while ( errorLines.length > 0 ) {
+
+				codemirror.removeLineClass( errorLines.shift(), 'background', 'errorLine' );
+
+			}
+
+			for ( var i = 0; i < widgets.length; i ++ ) {
+
+				codemirror.removeLineWidget( widgets[ i ] );
+
+			}
+
+			widgets.length = 0;
+
+			//
+
+			try {
+
+				syntax = esprima.parse( string, { tolerant: true } );
+				errors = syntax.errors;
+
+				for ( var i = 0; i < errors.length; i ++ ) {
+
+					var error = errors[ i ];
+
+					var message = document.createElement( 'div' );
+					message.className = 'esprima-error';
+					message.textContent = error.message.replace(/Line [0-9]+: /, '');
+
+					var lineNumber = error.lineNumber - 1;
+					errorLines.push( lineNumber );
+
+					codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
+
+					var widget = codemirror.addLineWidget(
+						lineNumber,
+						message
+					);
+
+					widgets.push( widget );
+
+				}
+
+			} catch ( error ) {
+
+				var message = document.createElement( 'div' );
+				message.className = 'esprima-error';
+				message.textContent = error.message.replace(/Line [0-9]+: /, '');
+
+				var lineNumber = error.lineNumber - 1;
+				errorLines.push( lineNumber );
+
+				codemirror.addLineClass( lineNumber, 'background', 'errorLine' );
+
+				var widget = codemirror.addLineWidget(
+					lineNumber,
+					message
+				);
+
+				widgets.push( widget );
+
+			}
+
+			return errorLines.length === 0;
+
+		});
+
+	};
+
 	//
-	
+
 	signals.editorCleared.add( function () {
 
 		container.setDisplay( 'none' );

+ 50 - 0
editor/js/Sidebar.Geometry.js

@@ -17,6 +17,56 @@ Sidebar.Geometry = function ( editor ) {
 
 	var geometryType = new UI.Text().setTextTransform( 'uppercase' );
 	container.addStatic( geometryType );
+	
+	// Actions
+	
+	var objectActions = new UI.Select().setPosition('absolute').setRight( '8px' ).setFontSize( '11px' );
+	objectActions.setOptions( {
+
+		'Actions': 'Actions',
+		'Flatten': 'Flatten'
+
+	} );
+	objectActions.onClick( function ( event ) {
+
+		event.stopPropagation(); // Avoid panel collapsing
+
+	} );
+	objectActions.onChange( function ( event ) {
+
+		var object = editor.selected;
+
+		switch ( this.getValue() ) {
+
+			case 'Flatten':
+
+				var object = editor.selected;
+
+				if ( confirm( 'Flatten ' + object.name + '?' ) === false ) return;
+
+				var geometry = object.geometry;
+
+				geometry.applyMatrix( object.matrix );
+				geometry.verticesNeedUpdate = true;
+				geometry.normalsNeedUpdate = true;
+
+				object.position.set( 0, 0, 0 );
+				object.rotation.set( 0, 0, 0 );
+				object.scale.set( 1, 1, 1 );
+
+				editor.signals.objectChanged.dispatch( object );
+
+				break;
+
+		}
+
+		this.setValue( 'Actions' );
+
+		signals.objectChanged.dispatch( object );
+
+	} );
+	container.addStatic( objectActions );
+
 	container.add( new UI.Break() );
 
 	// uuid

+ 2 - 0
editor/js/Sidebar.Object3D.js

@@ -18,6 +18,8 @@ Sidebar.Object3D = function ( editor ) {
 	var objectType = new UI.Text().setTextTransform( 'uppercase' );
 	container.addStatic( objectType );
 
+	// Actions
+
 	var objectActions = new UI.Select().setPosition('absolute').setRight( '8px' ).setFontSize( '11px' );
 	objectActions.setOptions( {
 

+ 41 - 18
editor/js/libs/app.js

@@ -22,12 +22,15 @@ var APP = {
 
 		this.load = function ( json ) {
 
+			vr = json.project.vr;
+
 			renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer.setClearColor( 0x000000 );
 			renderer.setPixelRatio( window.devicePixelRatio );
+			this.dom = renderer.domElement;
 
-			camera = loader.parse( json.camera );
-			scene = loader.parse( json.scene );
+			this.setScene( loader.parse( json.scene ) );
+			this.setCamera( loader.parse( json.camera ) );
 
 			events = {
 				keydown: [],
@@ -72,42 +75,62 @@ var APP = {
 
 			}
 
-			this.dom = renderer.domElement;
+		};
+
+		this.setCamera = function ( value ) {
+
+			camera = value;
+			camera.aspect = this.width / this.height;
+			camera.updateProjectionMatrix();
+
 
 			if ( vr === true ) {
 
+				if ( camera.parent === undefined ) {
+
+					// camera needs to be in the scene so camera2 matrix updates
+					
+					scene.add( camera );
+
+				}
+
+				var camera2 = camera.clone();
+				camera.add( camera2 );
+
+				camera = camera2;
+
 				controls = new THREE.VRControls( camera );
 				renderer = new THREE.VREffect( renderer );
 
-				this.dom.addEventListener( 'dblclick', function () {
+				document.addEventListener( 'keyup', function ( event ) {
 
-					renderer.setFullScreen( true );
+					switch ( event.keyCode ) {
+						case 90:
+							controls.zeroSensor();
+							break;
+					}
 
 				} );
-			}
 
-		};
+				this.dom.addEventListener( 'dblclick', function () {
 
-		this.setCamera = function ( value ) {
+					renderer.setFullScreen( true );
 
-			camera = value;
-			camera.aspect = this.width / this.height;
-			camera.updateProjectionMatrix();
+				} );
+
+			}
 
 		};
 
-		this.setVR = function ( value ) {
+		this.setScene = function ( value ) {
 
-			vr = value;
+			scene = value;
 
-		};
+		},
 
 		this.setSize = function ( width, height ) {
 
-			if ( vr ) {
-				width = 1280;
-				height = 800;
-			}
+			if ( renderer._fullScreen ) return;
 
 			this.width = width;
 			this.height = height;

File diff suppressed because it is too large
+ 197 - 0
editor/js/libs/esprima.js


+ 45 - 8
examples/js/controls/OrbitControls.js

@@ -37,10 +37,14 @@ THREE.OrbitControls = function ( object, domElement ) {
 	this.noZoom = false;
 	this.zoomSpeed = 1.0;
 
-	// Limits to how far you can dolly in and out
+	// Limits to how far you can dolly in and out ( PerspectiveCamera only )
 	this.minDistance = 0;
 	this.maxDistance = Infinity;
 
+	// Limits to how far you can zoom in and out ( OrthographicCamera only )
+	this.minZoom = 0;
+	this.maxZoom = Infinity;
+
 	// Set to true to disable this control
 	this.noRotate = false;
 	this.rotateSpeed = 1.0;
@@ -112,6 +116,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 	this.target0 = this.target.clone();
 	this.position0 = this.object.position.clone();
+	this.zoom0 = this.object.zoom;
 
 	// so camera.up is the orbit axis
 
@@ -180,7 +185,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
 
-		if ( scope.object.fov !== undefined ) {
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
 
 			// perspective
 			var position = scope.object.position;
@@ -194,7 +199,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 			scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
 			scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
 
-		} else if ( scope.object.top !== undefined ) {
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
 
 			// orthographic
 			scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
@@ -217,7 +222,21 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		}
 
-		scale /= dollyScale;
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+			scale /= dollyScale;
+
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
+
+		} else {
+
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+
+		}
 
 	};
 
@@ -229,7 +248,21 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		}
 
-		scale *= dollyScale;
+		if ( scope.object instanceof THREE.PerspectiveCamera ) {
+
+			scale *= dollyScale;
+
+		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
+
+			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) );
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
+
+		} else {
+
+			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+
+		}
 
 	};
 
@@ -315,6 +348,10 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 		this.target.copy( this.target0 );
 		this.object.position.copy( this.position0 );
+		this.object.zoom = this.zoom0;
+
+		this.object.updateProjectionMatrix();
+		this.dispatchEvent( changeEvent );
 
 		this.update();
 
@@ -414,7 +451,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 				scope.dollyIn();
 
-			} else {
+			} else if ( dollyDelta.y < 0 ) {
 
 				scope.dollyOut();
 
@@ -473,7 +510,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 			scope.dollyOut();
 
-		} else {
+		} else if ( delta < 0 ) {
 
 			scope.dollyIn();
 
@@ -606,7 +643,7 @@ THREE.OrbitControls = function ( object, domElement ) {
 
 					scope.dollyOut();
 
-				} else {
+				} else if ( dollyDelta.y < 0 ) {
 
 					scope.dollyIn();
 

+ 37 - 168
examples/js/controls/TrackballControls.js

@@ -26,11 +26,9 @@ THREE.TrackballControls = function ( object, domElement ) {
 	this.noRotate = false;
 	this.noZoom = false;
 	this.noPan = false;
-	this.noRoll = false;
 
 	this.staticMoving = false;
 	this.dynamicDampingFactor = 0.2;
-	this.cylindricalRotation = true;
 
 	this.minDistance = 0;
 	this.maxDistance = Infinity;
@@ -50,9 +48,6 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 	_eye = new THREE.Vector3(),
 
-	_rotateStart = new THREE.Vector3(),
-	_rotateEnd = new THREE.Vector3(),
-
 	_movePrev = new THREE.Vector2(),
 	_moveCurr = new THREE.Vector2(),
 
@@ -141,7 +136,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 			vector.set(
 				( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
-				( ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / ( _this.screen.height * 0.5 ) / _this.screen.width * _this.screen.height )
+				( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
 			);
 
 			return vector;
@@ -149,56 +144,6 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 	}() );
 
-	var getMouseProjectionOnBall = ( function () {
-
-		var vector = new THREE.Vector3();
-		var objectUp = new THREE.Vector3();
-		var mouseOnBall = new THREE.Vector3();
-
-		return function ( pageX, pageY ) {
-
-			mouseOnBall.set(
-				( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ),
-				( _this.screen.height * 0.5 + _this.screen.top - pageY ) / ( _this.screen.height * 0.5 ),
-				0.0
-			);
-
-			var length = mouseOnBall.length();
-
-			if ( _this.noRoll ) {
-
-				if ( length < Math.SQRT1_2 ) {
-
-					mouseOnBall.z = Math.sqrt( 1.0 - length * length );
-
-				} else {
-
-					mouseOnBall.z = 0.5 / length;
-
-				}
-
-			} else if ( length > 1.0 ) {
-
-				mouseOnBall.normalize();
-
-			} else {
-
-				mouseOnBall.z = Math.sqrt( 1.0 - length * length );
-
-			}
-
-			_eye.copy( _this.object.position ).sub( _this.target );
-
-			vector.copy( _this.object.up ).setLength( mouseOnBall.y );
-			vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
-			vector.add( _eye.setLength( mouseOnBall.z ) );
-
-			return vector;
-
-		};
-
-	}() );
-
 	this.rotateCamera = (function() {
 
 		var axis = new THREE.Vector3(),
@@ -211,80 +156,47 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 		return function () {
 
-			if ( _this.cylindricalRotation ) {
-
-				moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
-				angle = moveDirection.length();
-
-				if ( angle ) {
-
-					_eye.copy( _this.object.position ).sub( _this.target );
-
-					eyeDirection.copy( _eye ).normalize();
-					objectUpDirection.copy( _this.object.up ).normalize();
-					objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
-
-					objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
-					objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
-
-					moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
-
-					axis.crossVectors( moveDirection, _eye ).normalize();
-
-					angle *= _this.rotateSpeed;
-					quaternion.setFromAxisAngle( axis, angle );
-
-					_eye.applyQuaternion( quaternion );
-					_this.object.up.applyQuaternion( quaternion );
-
-					_lastAxis.copy( axis );
-					_lastAngle = angle;
-
-				}
-				else if ( !_this.staticMoving && _lastAngle ) {
-
-					_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
-					_eye.copy( _this.object.position ).sub( _this.target );
-					quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
-					_eye.applyQuaternion( quaternion );
-					_this.object.up.applyQuaternion( quaternion );
-
-				}
-
-				_movePrev.copy( _moveCurr );
-
-			} else {
-
-				angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
+			moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
+			angle = moveDirection.length();
 
-				if ( angle ) {
+			if ( angle ) {
 
-					axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
+				_eye.copy( _this.object.position ).sub( _this.target );
 
-					angle *= _this.rotateSpeed;
+				eyeDirection.copy( _eye ).normalize();
+				objectUpDirection.copy( _this.object.up ).normalize();
+				objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
 
-					quaternion.setFromAxisAngle( axis, -angle );
+				objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
+				objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
 
-					_eye.applyQuaternion( quaternion );
-					_this.object.up.applyQuaternion( quaternion );
+				moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
 
-					_rotateEnd.applyQuaternion( quaternion );
+				axis.crossVectors( moveDirection, _eye ).normalize();
 
-					if ( _this.staticMoving ) {
+				angle *= _this.rotateSpeed;
+				quaternion.setFromAxisAngle( axis, angle );
 
-						_rotateStart.copy( _rotateEnd );
+				_eye.applyQuaternion( quaternion );
+				_this.object.up.applyQuaternion( quaternion );
 
-					} else {
+				_lastAxis.copy( axis );
+				_lastAngle = angle;
 
-						quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
-						_rotateStart.applyQuaternion( quaternion );
+			}
 
-					}
+			else if ( !_this.staticMoving && _lastAngle ) {
 
-				}
+				_lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
+				_eye.copy( _this.object.position ).sub( _this.target );
+				quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
+				_eye.applyQuaternion( quaternion );
+				_this.object.up.applyQuaternion( quaternion );
 
 			}
 
+			_movePrev.copy( _moveCurr );
+
 		};
 
 	}());
@@ -491,17 +403,8 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 		if ( _state === STATE.ROTATE && !_this.noRotate ) {
 
-			if ( _this.cylindricalRotation ) {
-
-				_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-				_movePrev.copy(_moveCurr);
-
-			} else {
-
-				_rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
-				_rotateEnd.copy( _rotateStart );
-
-			}
+			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
+			_movePrev.copy(_moveCurr);
 
 		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
 
@@ -531,16 +434,8 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 		if ( _state === STATE.ROTATE && !_this.noRotate ) {
 
-			if ( _this.cylindricalRotation ) {
-
-				_movePrev.copy(_moveCurr);
-				_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-
-			} else {
-
-				_rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
-
-			}
+			_movePrev.copy(_moveCurr);
+			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
 
 		} else if ( _state === STATE.ZOOM && !_this.noZoom ) {
 
@@ -602,18 +497,8 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 			case 1:
 				_state = STATE.TOUCH_ROTATE;
-
-				if ( _this.cylindricalRotation ) {
-
-					_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-					_movePrev.copy(_moveCurr);
-
-				} else {
-
-					_rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-					_rotateEnd.copy( _rotateStart );
-
-				}
+				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				_movePrev.copy(_moveCurr);
 				break;
 
 			case 2:
@@ -647,16 +532,8 @@ THREE.TrackballControls = function ( object, domElement ) {
 		switch ( event.touches.length ) {
 
 			case 1:
-				if ( _this.cylindricalRotation ) {
-
-					_movePrev.copy(_moveCurr);
-					_moveCurr.copy( getMouseOnCircle(  event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-
-				} else {
-
-					_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-
-				}
+				_movePrev.copy(_moveCurr);
+				_moveCurr.copy( getMouseOnCircle(  event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 				break;
 
 			case 2:
@@ -683,16 +560,8 @@ THREE.TrackballControls = function ( object, domElement ) {
 		switch ( event.touches.length ) {
 
 			case 1:
-				if ( _this.cylindricalRotation ) {
-
-					_movePrev.copy(_moveCurr);
-					_moveCurr.copy( getMouseOnCircle(  event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-
-				} else {
-
-					_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-					_rotateStart.copy( _rotateEnd );
-				}
+				_movePrev.copy(_moveCurr);
+				_moveCurr.copy( getMouseOnCircle(  event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 				break;
 
 			case 2:

+ 3 - 3
examples/js/effects/VREffect.js

@@ -82,8 +82,8 @@ THREE.VREffect = function ( renderer, done ) {
 		var leftEyeTranslation = this.leftEyeTranslation;
 		var rightEyeTranslation = this.rightEyeTranslation;
 		var renderer = this._renderer;
-		var rendererWidth = renderer.domElement.clientWidth;
-		var rendererHeight = renderer.domElement.clientHeight;
+		var rendererWidth = renderer.context.drawingBufferWidth;
+		var rendererHeight = renderer.context.drawingBufferHeight;
 		var eyeDivisionLine = rendererWidth / 2;
 
 		renderer.enableScissorTest( true );
@@ -165,7 +165,7 @@ THREE.VREffect = function ( renderer, done ) {
 		}
 		if ( canvas.mozRequestFullScreen ) {
 			canvas.mozRequestFullScreen( { vrDisplay: vrHMD } );
-		} else {
+		} else if ( canvas.webkitRequestFullscreen ) {
 			canvas.webkitRequestFullscreen( { vrDisplay: vrHMD } );
 		}
 	};

+ 3 - 5
examples/js/loaders/ColladaLoader.js

@@ -3035,9 +3035,9 @@ THREE.ColladaLoader = function () {
 
 				} else if ( vcount === 4 ) {
 
-					faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ ns[0], ns[1], ns[3]], cs.length ? [ cs[0], cs[1], cs[3]] : new THREE.Color() ) );
+					faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ ns[0].clone(), ns[1].clone(), ns[3].clone() ], cs.length ? [ cs[0], cs[1], cs[3] ] : new THREE.Color() ) );
 
-					faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ ns[1], ns[2], ns[3]], cs.length ? [ cs[1], cs[2], cs[3]] : new THREE.Color() ) );
+					faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ ns[1].clone(), ns[2].clone(), ns[3].clone() ], cs.length ? [ cs[1], cs[2], cs[3] ] : new THREE.Color() ) );
 
 				} else if ( vcount > 4 && options.subdivideFaces ) {
 
@@ -3048,9 +3048,7 @@ THREE.ColladaLoader = function () {
 
 					for ( k = 1; k < vcount - 1; ) {
 
-						// FIXME: normals don't seem to be quite right
-
-						faces.push( new THREE.Face3( vs[0], vs[k], vs[k + 1], [ ns[0], ns[k ++], ns[k] ], clr ) );
+						faces.push( new THREE.Face3( vs[0], vs[k], vs[k + 1], [ ns[0].clone(), ns[k ++].clone(), ns[k].clone() ], clr ) );
 
 					}
 

+ 16 - 15
examples/js/loaders/OBJLoader.js

@@ -87,11 +87,12 @@ THREE.OBJLoader.prototype = {
 
 		}
 
-		function addFace( a, b, c, d,  ua, ub, uc, ud,  na, nb, nc, nd ) {
+		function addFace( a, b, c, d,  ua, ub, uc, ud, na, nb, nc, nd ) {
 
 			var ia = parseVertexIndex( a );
 			var ib = parseVertexIndex( b );
 			var ic = parseVertexIndex( c );
+			var id;
 
 			if ( d === undefined ) {
 
@@ -99,7 +100,7 @@ THREE.OBJLoader.prototype = {
 
 			} else {
 
-				var id = parseVertexIndex( d );
+				id = parseVertexIndex( d );
 
 				addVertex( ia, ib, id );
 				addVertex( ib, ic, id );
@@ -108,9 +109,9 @@ THREE.OBJLoader.prototype = {
 
 			if ( ua !== undefined ) {
 
-				var ia = parseUVIndex( ua );
-				var ib = parseUVIndex( ub );
-				var ic = parseUVIndex( uc );
+				ia = parseUVIndex( ua );
+				ib = parseUVIndex( ub );
+				ic = parseUVIndex( uc );
 
 				if ( d === undefined ) {
 
@@ -118,7 +119,7 @@ THREE.OBJLoader.prototype = {
 
 				} else {
 
-					var id = parseUVIndex( ud );
+					id = parseUVIndex( ud );
 
 					addUV( ia, ib, id );
 					addUV( ib, ic, id );
@@ -129,9 +130,9 @@ THREE.OBJLoader.prototype = {
 
 			if ( na !== undefined ) {
 
-				var ia = parseNormalIndex( na );
-				var ib = parseNormalIndex( nb );
-				var ic = parseNormalIndex( nc );
+				ia = parseNormalIndex( na );
+				ib = parseNormalIndex( nb );
+				ic = parseNormalIndex( nc );
 
 				if ( d === undefined ) {
 
@@ -139,7 +140,7 @@ THREE.OBJLoader.prototype = {
 
 				} else {
 
-					var id = parseNormalIndex( nd );
+					id = parseNormalIndex( nd );
 
 					addNormal( ia, ib, id );
 					addNormal( ib, ic, id );
@@ -202,7 +203,7 @@ THREE.OBJLoader.prototype = {
 
 		var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/;
 
-		// f vertex//normal vertex//normal vertex//normal ... 
+		// f vertex//normal vertex//normal vertex//normal ...
 
 		var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/
 
@@ -261,7 +262,7 @@ THREE.OBJLoader.prototype = {
 			} else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
 
 				// ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
-				
+
 				addFace(
 					result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ],
 					result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ]
@@ -337,8 +338,8 @@ THREE.OBJLoader.prototype = {
 
 		for ( var i = 0, l = objects.length; i < l; i ++ ) {
 
-			var object = objects[ i ];
-			var geometry = object.geometry;
+			object = objects[ i ];
+			geometry = object.geometry;
 
 			var buffergeometry = new THREE.BufferGeometry();
 
@@ -352,7 +353,7 @@ THREE.OBJLoader.prototype = {
 				buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) );
 			}
 
-			var material = new THREE.MeshLambertMaterial();
+			material = new THREE.MeshLambertMaterial();
 			material.name = object.material.name;
 
 			var mesh = new THREE.Mesh( buffergeometry, material );

+ 9 - 6
examples/js/loaders/PLYLoader.js

@@ -116,14 +116,17 @@ THREE.PLYLoader.prototype = {
 
 		var patternHeader = /ply([\s\S]*)end_header\s/;
 		var headerText = "";
-		if ( ( result = patternHeader.exec( data ) ) !== null ) {
+		var headerLength = 0;
+		var result = patternHeader.exec( data );
+		if ( result !== null ) {
 			headerText = result [ 1 ];
+			headerLength = result[ 0 ].length;
 		}
 
 		var header = {
 			comments: [],
 			elements: [],
-			headerLength: result[ 0 ].length
+			headerLength: headerLength
 		};
 
 		var lines = headerText.split( '\n' );
@@ -241,7 +244,7 @@ THREE.PLYLoader.prototype = {
 
 	parseASCIIElement: function ( properties, line ) {
 
-		values = line.split( /\s+/ );
+		var values = line.split( /\s+/ );
 
 		var element = Object();
 
@@ -252,7 +255,7 @@ THREE.PLYLoader.prototype = {
 				var list = [];
 				var n = this.parseASCIINumber( values.shift(), properties[i].countType );
 
-				for ( j = 0; j < n; j ++ ) {
+				for ( var j = 0; j < n; j ++ ) {
 
 					list.push( this.parseASCIINumber( values.shift(), properties[i].itemType ) );
 
@@ -354,7 +357,7 @@ THREE.PLYLoader.prototype = {
 
 				geometry.useColor = true;
 
-				color = new THREE.Color();
+				var color = new THREE.Color();
 				color.setRGB( element.red / 255.0, element.green / 255.0, element.blue / 255.0 );
 				geometry.colors.push( color );
 
@@ -423,7 +426,7 @@ THREE.PLYLoader.prototype = {
 				var n = result[0];
 				read += result[1];
 
-				for ( j = 0; j < n; j ++ ) {
+				for ( var j = 0; j < n; j ++ ) {
 
 					result = this.binaryRead( dataview, at + read, properties[i].itemType, little_endian );
 					list.push( result[0] );

+ 10 - 1
examples/js/renderers/RaytracingRenderer.js

@@ -9,6 +9,8 @@ THREE.RaytracingRenderer = function ( parameters ) {
 
 	parameters = parameters || {};
 
+	var scope = this;
+
 	var canvas = document.createElement( 'canvas' );
 	var context = canvas.getContext( '2d', {
 		alpha: parameters.alpha === true
@@ -458,7 +460,12 @@ THREE.RaytracingRenderer = function ( parameters ) {
 				blockX = 0;
 				blockY += blockSize;
 
-				if ( blockY >= canvasHeight ) return;
+				if ( blockY >= canvasHeight ) {
+
+					scope.dispatchEvent( { type: "complete" } );
+					return;
+
+				}
 
 			}
 
@@ -535,3 +542,5 @@ THREE.RaytracingRenderer = function ( parameters ) {
 	};
 
 };
+
+THREE.EventDispatcher.prototype.apply(THREE.RaytracingRenderer.prototype);

+ 4 - 2
examples/js/shaders/ColorCorrectionShader.js

@@ -10,7 +10,8 @@ THREE.ColorCorrectionShader = {
 
 		"tDiffuse": { type: "t", value: null },
 		"powRGB":   { type: "v3", value: new THREE.Vector3( 2, 2, 2 ) },
-		"mulRGB":   { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+		"mulRGB":   { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) },
+		"addRGB":   { type: "v3", value: new THREE.Vector3( 0, 0, 0 ) }
 
 	},
 
@@ -33,13 +34,14 @@ THREE.ColorCorrectionShader = {
 		"uniform sampler2D tDiffuse;",
 		"uniform vec3 powRGB;",
 		"uniform vec3 mulRGB;",
+		"uniform vec3 addRGB;",
 
 		"varying vec2 vUv;",
 
 		"void main() {",
 
 			"gl_FragColor = texture2D( tDiffuse, vUv );",
-			"gl_FragColor.rgb = mulRGB * pow( gl_FragColor.rgb, powRGB );",
+			"gl_FragColor.rgb = mulRGB * pow( ( gl_FragColor.rgb + addRGB ), powRGB );",
 
 		"}"
 

+ 1 - 14
examples/misc_controls_trackball.html

@@ -35,8 +35,7 @@
 		<div id="container"></div>
 		<div id="info">
 			<a href="http://threejs.org" target="_blank">three.js</a> - trackball controls example</br>
-			MOVE mouse &amp; press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan</br>
-			R: switch from cylindrical (default) to spherical rotation
+			MOVE mouse &amp; press LEFT/A: rotate, MIDDLE/S: zoom, RIGHT/D: pan
 		</div>
 
 		<script src="../build/three.min.js"></script>
@@ -134,24 +133,12 @@
 				//
 
 				window.addEventListener( 'resize', onWindowResize, false );
-				window.addEventListener( 'keypress', onKeyPress, true);
-
 				//
 
 				render();
 
 			}
 
-			function onKeyPress ( e ) {
-
-				if ( e.keyCode === 114 ) {
-
-					controls.cylindricalRotation = !controls.cylindricalRotation;
-
-				}
-
-			}
-
 			function onWindowResize() {
 
 				camera.aspect = window.innerWidth / window.innerHeight;

+ 0 - 1
examples/models/skinned/marine/marine.js

@@ -30,7 +30,6 @@
     "mapDiffuseWrap" : ["repeat", "repeat"],
     "shading" : "Lambert",
     "specularCoef" : 9,
-    "opacity" : 0.0,
     "transparent" : true,
     "vertexColors" : false
   }],

+ 0 - 1
examples/models/skinned/marine/marine_anims.js

@@ -30,7 +30,6 @@
 		"mapDiffuseWrap" : ["repeat", "repeat"],
 		"shading" : "Lambert",
 		"specularCoef" : 9,
-		"opacity" : 0.0,
 		"transparent" : true,
 		"vertexColors" : false
 	}],

+ 0 - 1
examples/models/skinned/marine/marine_ikrig.js

@@ -30,7 +30,6 @@
 		"mapDiffuseWrap" : ["repeat", "repeat"],
 		"shading" : "Lambert",
 		"specularCoef" : 9,
-		"opacity" : 0.0,
 		"transparent" : true,
 		"vertexColors" : false
 	}],

+ 21 - 42
examples/obj/f50/F50NoUv_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -60,8 +58,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -73,8 +70,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -86,8 +82,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -99,8 +94,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -112,8 +106,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -125,8 +118,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -138,8 +130,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -151,8 +142,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -164,8 +154,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -177,8 +166,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -190,8 +178,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -203,8 +190,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -216,8 +202,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -242,8 +227,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -281,8 +265,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -294,8 +277,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -307,8 +289,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -320,8 +301,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	},
 
 	{
@@ -333,8 +313,7 @@
 	"colorSpecular" : [0.025, 0.025, 0.025],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 98.039216,
-	"opacity" : 0.0
+	"specularCoef" : 98.039216
 	}],
 
     "buffers": "F50NoUv_bin.bin"

+ 7 - 14
examples/obj/gallardo/GallardoNoUv_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 29.411765,
-	"opacity" : 0.0
+	"specularCoef" : 29.411765
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 29.411765,
-	"opacity" : 0.0
+	"specularCoef" : 29.411765
 	},
 
 	{
@@ -47,8 +45,7 @@
 	"colorSpecular" : [0.0, 0.0, 0.0],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 7.843137,
-	"opacity" : 0.0
+	"specularCoef" : 7.843137
 	},
 
 	{
@@ -60,8 +57,7 @@
 	"colorSpecular" : [0.6525, 0.6525, 0.6525],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 25.490196,
-	"opacity" : 0.0
+	"specularCoef" : 25.490196
 	},
 
 	{
@@ -73,8 +69,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 29.411765,
-	"opacity" : 0.0
+	"specularCoef" : 29.411765
 	},
 
 	{
@@ -86,8 +81,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 29.411765,
-	"opacity" : 0.0
+	"specularCoef" : 29.411765
 	},
 
 	{
@@ -99,8 +93,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 29.411765,
-	"opacity" : 0.0
+	"specularCoef" : 29.411765
 	}],
 
     "buffers": "GallardoNoUv_bin.bin"

+ 7 - 14
examples/obj/gallardo/parts/gallardo_body_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.6525, 0.6525, 0.6525],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 21.568627,
-	"opacity" : 0.0
+	"specularCoef" : 21.568627
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	},
 
 	{
@@ -47,8 +45,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	},
 
 	{
@@ -60,8 +57,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	},
 
 	{
@@ -73,8 +69,7 @@
 	"colorSpecular" : [0.0, 0.0, 0.0],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 3.921569,
-	"opacity" : 0.0
+	"specularCoef" : 3.921569
 	},
 
 	{
@@ -86,8 +81,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	},
 
 	{
@@ -99,8 +93,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	}],
 
     "buffers": "gallardo_body_bin.bin"

+ 2 - 4
examples/obj/gallardo/parts/gallardo_wheel_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.175, 0.175, 0.175],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 27.45098,
-	"opacity" : 0.0
+	"specularCoef" : 27.45098
 	}],
 
     "buffers": "gallardo_wheel_bin.bin"

+ 5 - 10
examples/obj/veyron/VeyronNoUv_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.14825, 0.14825, 0.14825],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.6, 0.6, 0.6],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 37.254902,
-	"opacity" : 0.0
+	"specularCoef" : 37.254902
 	},
 
 	{
@@ -47,8 +45,7 @@
 	"colorSpecular" : [0.75, 0.75, 0.75],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 56.862745,
-	"opacity" : 0.0
+	"specularCoef" : 56.862745
 	},
 
 	{
@@ -73,8 +70,7 @@
 	"colorSpecular" : [0.75, 0.75, 0.75],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 56.862745,
-	"opacity" : 0.0
+	"specularCoef" : 56.862745
 	},
 
 	{
@@ -86,8 +82,7 @@
 	"colorSpecular" : [0.4, 0.4, 0.4],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 17.647059,
-	"opacity" : 0.0
+	"specularCoef" : 17.647059
 	},
 
 	{

+ 5 - 10
examples/obj/veyron/parts/veyron_body_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.75, 0.75, 0.75],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 52.941176,
-	"opacity" : 0.0
+	"specularCoef" : 52.941176
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.75, 0.75, 0.75],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 52.941176,
-	"opacity" : 0.0
+	"specularCoef" : 52.941176
 	},
 
 	{
@@ -47,8 +45,7 @@
 	"colorSpecular" : [0.4, 0.4, 0.4],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 15.686275,
-	"opacity" : 0.0
+	"specularCoef" : 15.686275
 	},
 
 	{
@@ -60,8 +57,7 @@
 	"colorSpecular" : [0.6, 0.6, 0.6],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 35.294118,
-	"opacity" : 0.0
+	"specularCoef" : 35.294118
 	},
 
 	{
@@ -99,8 +95,7 @@
 	"colorSpecular" : [0.14825, 0.14825, 0.14825],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 15.686275,
-	"opacity" : 0.0
+	"specularCoef" : 15.686275
 	},
 
 	{

+ 2 - 4
examples/obj/veyron/parts/veyron_wheel_bin.js

@@ -21,8 +21,7 @@
 	"colorSpecular" : [0.6, 0.6, 0.6],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 35.294118,
-	"opacity" : 0.0
+	"specularCoef" : 35.294118
 	},
 
 	{
@@ -34,8 +33,7 @@
 	"colorSpecular" : [0.14825, 0.14825, 0.14825],
 	"illumination" : 2,
 	"opticalDensity" : 1.0,
-	"specularCoef" : 15.686275,
-	"opacity" : 0.0
+	"specularCoef" : 15.686275
 	}],
 
     "buffers": "veyron_wheel_bin.bin"

+ 1 - 1
examples/webgl_lights_pointlights.html

@@ -73,7 +73,7 @@
 
 				var callback = function( geometry ) {
 
-					object = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0x555555, specular: 0xffffff, shininess: 50, shading: THREE.SmoothShading }  )  );
+					object = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial( { color: 0x555555, specular: 0xffffff, shininess: 50 }  )  );
 					object.scale.x = object.scale.y = object.scale.z = 0.80;
 					scene.add( object );
 

+ 3 - 1
src/Three.js

@@ -16,9 +16,11 @@ if ( typeof module === 'object' ) {
 
 if ( Math.sign === undefined ) {
 
+	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign 
+
 	Math.sign = function ( x ) {
 
-		return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : 0;
+		return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : +x;
 
 	};
 

+ 13 - 7
src/core/BufferGeometry.js

@@ -84,6 +84,18 @@ THREE.BufferGeometry.prototype = {
 
 		}
 
+		if ( this.boundingBox instanceof THREE.Box3 ) {
+
+			this.computeBoundingBox();
+
+		}
+
+		if ( this.boundingSphere instanceof THREE.Sphere ) {
+
+			this.computeBoundingSphere();
+
+		}
+
 	},
 
 	center: function () {
@@ -884,13 +896,7 @@ THREE.BufferGeometry.prototype = {
 
 			var attribute = attributes[ key ];
 
-			var array = [], typeArray = attribute.array;
-
-			for ( var i = 0, l = typeArray.length; i < l; i ++ ) {
-
-				array[ i ] = typeArray[ i ];
-
-			}
+			var array = Array.prototype.slice.call( attribute.array );
 
 			output.data.attributes[ key ] = {
 				itemSize: attribute.itemSize,

+ 2 - 0
src/core/Object3D.js

@@ -699,6 +699,8 @@ THREE.Object3D.prototype = {
 				data.geometry = parseGeometry( object.geometry );
 				data.material = parseMaterial( object.material );
 
+				if ( object instanceof THREE.Line ) data.mode = object.mode;
+
 			} else if ( object instanceof THREE.Sprite ) {
 
 				data.material = parseMaterial( object.material );

+ 1 - 1
src/extras/helpers/HemisphereLightHelper.js

@@ -3,7 +3,7 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-THREE.HemisphereLightHelper = function ( light, sphereSize, arrowLength, domeSize ) {
+THREE.HemisphereLightHelper = function ( light, sphereSize ) {
 
 	THREE.Object3D.call( this );
 

+ 10 - 4
src/loaders/Loader.js

@@ -226,12 +226,18 @@ THREE.Loader.prototype = {
 
 		}
 
-		if ( m.transparent !== undefined || m.opacity < 1.0 ) {
+		if ( m.transparent !== undefined ) {
 
 			mpars.transparent = m.transparent;
 
 		}
 
+		if ( m.opacity !== undefined && m.opacity < 1.0 ) {
+
+			mpars.transparent = true;
+
+		}
+
 		if ( m.depthTest !== undefined ) {
 
 			mpars.depthTest = m.depthTest;
@@ -308,14 +314,14 @@ THREE.Loader.prototype = {
 
 		// modifiers
 
-		if ( m.transparency ) {
+		if ( m.transparency !== undefined ) {
 
 			console.warn( 'THREE.Loader: transparency has been renamed to opacity' );
-			mpars.opacity = m.transparency;
+			m.opacity = m.transparency;
 
 		}
 
-		if ( m.opacity ) {
+		if ( m.opacity !== undefined ) {
 
 			mpars.opacity = m.opacity;
 

+ 16 - 4
src/loaders/ObjectLoader.js

@@ -5,6 +5,7 @@
 THREE.ObjectLoader = function ( manager ) {
 
 	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+	this.texturePath = '';
 
 };
 
@@ -14,7 +15,7 @@ THREE.ObjectLoader.prototype = {
 
 	load: function ( url, onLoad, onProgress, onError ) {
 
-		if ( this.texturePath === undefined ) {
+		if ( this.texturePath === '' ) {
 
 			this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 );
 
@@ -48,7 +49,11 @@ THREE.ObjectLoader.prototype = {
 
 		var geometries = this.parseGeometries( json.geometries );
 
-		var images = this.parseImages( json.images, onLoad );
+		var images = this.parseImages( json.images, function () {
+
+			if ( onLoad !== undefined ) onLoad( object );
+
+		} );
 		var textures  = this.parseTextures( json.textures, images );
 		var materials = this.parseMaterials( json.materials, textures );
 		var object = this.parseObject( json.object, geometries, materials );
@@ -241,6 +246,9 @@ THREE.ObjectLoader.prototype = {
 				if ( data.bumpMap !== undefined ) {
 
 					material.bumpMap = getTexture( data.bumpMap );
+					if ( data.bumpScale ) {
+						material.bumpScale = new THREE.Vector2( data.bumpScale, data.bumpScale );
+					}
 
 				}
 
@@ -259,6 +267,9 @@ THREE.ObjectLoader.prototype = {
 				if ( data.normalMap !== undefined ) {
 
 					material.normalMap = getTexture( data.normalMap );
+					if ( data.normalScale ) {
+						material.normalScale = new THREE.Vector2( data.normalScale, data.normalScale );
+					}
 
 				}
 
@@ -311,8 +322,9 @@ THREE.ObjectLoader.prototype = {
 			for ( var i = 0, l = json.length; i < l; i ++ ) {
 
 				var image = json[ i ];
+				var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url;
 
-				images[ image.uuid ] = loadImage( scope.texturePath + image.url );
+				images[ image.uuid ] = loadImage( path );
 
 			}
 
@@ -461,7 +473,7 @@ THREE.ObjectLoader.prototype = {
 
 				case 'Line':
 
-					object = new THREE.Line( getGeometry( data.geometry ), getMaterial( data.material ) );
+					object = new THREE.Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode );
 
 					break;
 

+ 6 - 3
src/materials/Material.js

@@ -29,6 +29,8 @@ THREE.Material = function () {
 	this.depthTest = true;
 	this.depthWrite = true;
 
+	this.colorWrite = true;
+
 	this.polygonOffset = false;
 	this.polygonOffsetFactor = 0;
 	this.polygonOffsetUnits = 0;
@@ -131,6 +133,7 @@ THREE.Material.prototype = {
 			output.color = this.color.getHex();
 			output.emissive = this.emissive.getHex();
 			if ( this.vertexColors !== THREE.NoColors ) output.vertexColors = this.vertexColors;
+			if ( this.shading !== THREE.SmoothShading ) output.shading = this.shading;
 			if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
 			if ( this.side !== THREE.FrontSide ) output.side = this.side;
 
@@ -141,12 +144,12 @@ THREE.Material.prototype = {
 			output.specular = this.specular.getHex();
 			output.shininess = this.shininess;
 			if ( this.vertexColors !== THREE.NoColors ) output.vertexColors = this.vertexColors;
+			if ( this.shading !== THREE.SmoothShading ) output.shading = this.shading;
 			if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
 			if ( this.side !== THREE.FrontSide ) output.side = this.side;
 
 		} else if ( this instanceof THREE.MeshNormalMaterial ) {
 
-			if ( this.shading !== THREE.FlatShading ) output.shading = this.shading;
 			if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
 			if ( this.side !== THREE.FrontSide ) output.side = this.side;
 
@@ -160,10 +163,10 @@ THREE.Material.prototype = {
 			output.size  = this.size;
 			output.sizeAttenuation = this.sizeAttenuation;
 			output.color = this.color.getHex();
-			
+
 			if ( this.vertexColors !== THREE.NoColors ) output.vertexColors = this.vertexColors;
 			if ( this.blending !== THREE.NormalBlending ) output.blending = this.blending;
-			
+
 		} else if ( this instanceof THREE.ShaderMaterial ) {
 
 			output.uniforms = this.uniforms;

+ 0 - 4
src/materials/MeshNormalMaterial.js

@@ -20,8 +20,6 @@ THREE.MeshNormalMaterial = function ( parameters ) {
 
 	this.type = 'MeshNormalMaterial';
 
-	this.shading = THREE.FlatShading;
-
 	this.wireframe = false;
 	this.wireframeLinewidth = 1;
 
@@ -40,8 +38,6 @@ THREE.MeshNormalMaterial.prototype.clone = function () {
 
 	THREE.Material.prototype.clone.call( this, material );
 
-	material.shading = this.shading;
-
 	material.wireframe = this.wireframe;
 	material.wireframeLinewidth = this.wireframeLinewidth;
 

+ 1 - 1
src/objects/Line.js

@@ -11,7 +11,7 @@ THREE.Line = function ( geometry, material, mode ) {
 	this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
 	this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } );
 
-	this.mode = ( mode !== undefined ) ? mode : THREE.LineStrip;
+	this.mode = mode !== undefined ? mode : THREE.LineStrip;
 
 };
 

+ 23 - 18
src/renderers/WebGLRenderer.js

@@ -1179,13 +1179,13 @@ THREE.WebGLRenderer = function ( parameters ) {
 			 ? object.material.materials[ geometryGroup.materialIndex ]
 			 : object.material;
 
-	};
+	}
 
-	function materialNeedsSmoothNormals ( material ) {
+	function materialNeedsFaceNormals ( material ) {
 
-		return material && material.shading !== undefined && material.shading === THREE.SmoothShading;
+		return material instanceof THREE.MeshPhongMaterial === false && material.shading === THREE.FlatShading;
 
-	};
+	}
 
 	// Buffer setting
 
@@ -1526,7 +1526,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		}
 
-		var needsSmoothNormals = materialNeedsSmoothNormals( material );
+		var needsFaceNormals = materialNeedsFaceNormals( material );
 
 		var f, fl, fi, face,
 		vertexNormals, faceNormal, normal,
@@ -1671,7 +1671,13 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					if ( material.morphNormals ) {
 
-						if ( needsSmoothNormals ) {
+						if ( needsFaceNormals ) {
+
+							n1 = morphNormals[ vk ].faceNormals[ chf ];
+							n2 = n1;
+							n3 = n1;
+
+						} else {
 
 							faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ];
 
@@ -1679,12 +1685,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 							n2 = faceVertexNormals.b;
 							n3 = faceVertexNormals.c;
 
-						} else {
-
-							n1 = morphNormals[ vk ].faceNormals[ chf ];
-							n2 = n1;
-							n3 = n1;
-
 						}
 
 						nka = morphNormalsArrays[ vk ];
@@ -1880,7 +1880,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 				vertexNormals = face.vertexNormals;
 				faceNormal = face.normal;
 
-				if ( vertexNormals.length === 3 && needsSmoothNormals ) {
+				if ( vertexNormals.length === 3 && needsFaceNormals === false ) {
 
 					for ( i = 0; i < 3; i ++ ) {
 
@@ -2343,7 +2343,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			_gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer );
 
-			if ( material.shading === THREE.FlatShading ) {
+			if ( material instanceof THREE.MeshPhongMaterial === false &&
+				   material.shading === THREE.FlatShading ) {
 
 				var nx, ny, nz,
 					nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz,
@@ -3233,9 +3234,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	function painterSortStable ( a, b ) {
 
-		if ( a.renderOrder !== b.renderOrder ) {
+		if ( a.object.renderOrder !== b.object.renderOrder ) {
 
-			return a.renderOrder - b.renderOrder;
+			return a.object.renderOrder - b.object.renderOrder;
 
 		} else if ( a.material.id !== b.material.id ) {
 
@@ -3255,9 +3256,9 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	function reversePainterSortStable ( a, b ) {
 
-		if ( a.renderOrder !== b.renderOrder ) {
+		if ( a.object.renderOrder !== b.object.renderOrder ) {
 
-			return a.renderOrder - b.renderOrder;
+			return a.object.renderOrder - b.object.renderOrder;
 
 		} if ( a.z !== b.z ) {
 
@@ -3417,6 +3418,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		state.setDepthTest( true );
 		state.setDepthWrite( true );
+		state.setColorWrite( true );
 
 		// _gl.finish();
 
@@ -4164,6 +4166,8 @@ THREE.WebGLRenderer = function ( parameters ) {
 			useFog: material.fog,
 			fogExp: fog instanceof THREE.FogExp2,
 
+			flatShading: material.shading === THREE.FlatShading,
+
 			sizeAttenuation: material.sizeAttenuation,
 			logarithmicDepthBuffer: _logarithmicDepthBuffer,
 
@@ -4330,6 +4334,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		state.setDepthTest( material.depthTest );
 		state.setDepthWrite( material.depthWrite );
+		state.setColorWrite( material.colorWrite );
 		state.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
 
 	}

+ 4 - 4
src/renderers/shaders/ShaderChunk/bumpmap_pars_fragment.glsl

@@ -3,10 +3,10 @@
 	uniform sampler2D bumpMap;
 	uniform float bumpScale;
 
-			// Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen
-			//	http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html
+	// 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)
+	// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
 
 	vec2 dHdxy_fwd() {
 
@@ -37,4 +37,4 @@
 
 	}
 
-#endif
+#endif

+ 15 - 4
src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl

@@ -1,12 +1,23 @@
-vec3 normal = normalize( vNormal );
-vec3 viewPosition = normalize( vViewPosition );
+#ifndef FLAT_SHADED
+
+	vec3 normal = normalize( vNormal );
+
+	#ifdef DOUBLE_SIDED
+
+		normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );
 
-#ifdef DOUBLE_SIDED
+	#endif
 
-	normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );
+#else
+
+	vec3 fdx = dFdx( vViewPosition );
+	vec3 fdy = dFdy( vViewPosition );
+	vec3 normal = normalize( cross( fdx, fdy ) );
 
 #endif
 
+vec3 viewPosition = normalize( vViewPosition );
+
 #ifdef USE_NORMALMAP
 
 	normal = perturbNormal2Arb( -vViewPosition, normal );

+ 6 - 1
src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl

@@ -50,4 +50,9 @@ uniform vec3 ambientLightColor;
 #endif
 
 varying vec3 vViewPosition;
-varying vec3 vNormal;
+
+#ifndef FLAT_SHADED
+
+	varying vec3 vNormal;
+
+#endif

+ 2 - 2
src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl

@@ -3,8 +3,8 @@
 	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
+	// 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 ) {
 

+ 11 - 2
src/renderers/shaders/ShaderLib.js

@@ -252,7 +252,12 @@ THREE.ShaderLib = {
 			"#define PHONG",
 
 			"varying vec3 vViewPosition;",
-			"varying vec3 vNormal;",
+
+			"#ifndef FLAT_SHADED",
+
+			"	varying vec3 vNormal;",
+
+			"#endif",
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "map_pars_vertex" ],
@@ -276,8 +281,12 @@ THREE.ShaderLib = {
 				THREE.ShaderChunk[ "skinnormal_vertex" ],
 				THREE.ShaderChunk[ "defaultnormal_vertex" ],
 
+			"#ifndef FLAT_SHADED",
+
 			"	vNormal = normalize( transformedNormal );",
 
+			"#endif",
+
 				THREE.ShaderChunk[ "morphtarget_vertex" ],
 				THREE.ShaderChunk[ "skinning_vertex" ],
 				THREE.ShaderChunk[ "default_vertex" ],
@@ -692,7 +701,7 @@ THREE.ShaderLib = {
 				"vec3 direction = normalize( vWorldPosition );",
 				"vec2 sampleUV;",
 				"sampleUV.y = saturate( tFlip * direction.y * -0.5 + 0.5 );",
-				"sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;", 
+				"sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;",
 				"gl_FragColor = texture2D( tEquirect, sampleUV );",
 
 				THREE.ShaderChunk[ "logdepthbuf_fragment" ],

+ 6 - 2
src/renderers/webgl/WebGLProgram.js

@@ -188,6 +188,8 @@ THREE.WebGLProgram = ( function () {
 				parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 				parameters.vertexColors ? '#define USE_COLOR' : '',
 
+				parameters.flatShading ? '#define FLAT_SHADED': '',
+
 				parameters.skinning ? '#define USE_SKINNING' : '',
 				parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
 
@@ -267,7 +269,7 @@ THREE.WebGLProgram = ( function () {
 				'precision ' + parameters.precision + ' float;',
 				'precision ' + parameters.precision + ' int;',
 
-				( parameters.bumpMap || parameters.normalMap ) ? '#extension GL_OES_standard_derivatives : enable' : '',
+				( parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '',
 
 				customDefines,
 
@@ -299,6 +301,8 @@ THREE.WebGLProgram = ( function () {
 				parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 				parameters.vertexColors ? '#define USE_COLOR' : '',
 
+				parameters.flatShading ? '#define FLAT_SHADED': '',
+
 				parameters.metal ? '#define METAL' : '',
 				parameters.wrapAround ? '#define WRAP_AROUND' : '',
 				parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
@@ -345,7 +349,7 @@ THREE.WebGLProgram = ( function () {
 			THREE.error( 'THREE.WebGLProgram: shader error: ' + _gl.getError(), 'gl.VALIDATE_STATUS', _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ), 'gl.getPRogramInfoLog', programLogInfo );
 
 		}
-		
+
 		if ( programLogInfo !== '' ) {
 
 			THREE.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()' + programLogInfo );

+ 14 - 0
src/renderers/webgl/WebGLState.js

@@ -18,6 +18,8 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 	var currentDepthTest = null;
 	var currentDepthWrite = null;
 
+	var currentColorWrite = null;
+
 	var currentDoubleSided = null;
 	var currentFlipSided = null;
 
@@ -179,6 +181,17 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 
 	};
 
+	this.setColorWrite = function ( colorWrite ) {
+
+		if ( currentColorWrite !== colorWrite ) {
+
+			gl.colorMask( colorWrite, colorWrite, colorWrite, colorWrite );
+			currentColorWrite = colorWrite;
+
+		}
+
+	};
+
 	this.setDoubleSided = function ( doubleSided ) {
 
 		if ( currentDoubleSided !== doubleSided ) {
@@ -271,6 +284,7 @@ THREE.WebGLState = function ( gl, paramThreeToGL ) {
 		currentBlending = null;
 		currentDepthTest = null;
 		currentDepthWrite = null;
+		currentColorWrite = null;
 		currentDoubleSided = null;
 		currentFlipSided = null;
 

+ 35 - 0
test/unit/math/Math.js

@@ -0,0 +1,35 @@
+/**
+ * @author humbletim / https://github.com/humbletim
+ */
+
+module( "Math" );
+
+//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign
+//http://people.mozilla.org/~jorendorff/es6-draft.html#sec-math.sign
+/* 
+20.2.2.29 Math.sign(x)
+
+Returns the sign of the x, indicating whether x is positive, negative or zero.
+
+If x is NaN, the result is NaN.
+If x is -0, the result is -0.
+If x is +0, the result is +0.
+If x is negative and not -0, the result is -1.
+If x is positive and not +0, the result is +1.
+*/
+
+test( "Math.sign/polyfill", function() {
+
+	ok( isNaN( Math.sign(NaN) ) , "If x is NaN<NaN>, the result is NaN.");
+	ok( isNaN( Math.sign(new THREE.Vector3()) ) , "If x is NaN<object>, the result is NaN.");
+	ok( isNaN( Math.sign() ) , "If x is NaN<undefined>, the result is NaN.");
+	ok( isNaN( Math.sign('--3') ) , "If x is NaN<'--3'>, the result is NaN.");
+	ok( Math.sign(-0) === -0 , "If x is -0, the result is -0.");
+	ok( Math.sign(+0) === +0 , "If x is +0, the result is +0.");
+	ok( Math.sign(-Infinity) === -1 , "If x is negative<-Infinity> and not -0, the result is -1.");
+	ok( Math.sign('-3') === -1 , "If x is negative<'-3'> and not -0, the result is -1.");
+	ok( Math.sign('-1e-10') === -1 , "If x is negative<'-1e-10'> and not -0, the result is -1.");
+	ok( Math.sign(+Infinity) === +1 , "If x is positive<+Infinity> and not +0, the result is +1.");
+	ok( Math.sign('+3') === +1 , "If x is positive<'+3'> and not +0, the result is +1.");
+
+});

+ 1 - 0
test/unit/unittests_sources.html

@@ -45,6 +45,7 @@
   <script src="math/Euler.js"></script>
   <script src="math/Line3.js"></script>
   <script src="math/Quaternion.js"></script>
+  <script src="math/Math.js"></script>
   <script src="math/Matrix3.js"></script>
   <script src="math/Matrix4.js"></script>
   <script src="math/Frustum.js"></script>

+ 1 - 0
test/unit/unittests_three-math.html

@@ -28,6 +28,7 @@
   <script src="math/Euler.js"></script>
   <script src="math/Line3.js"></script>
   <script src="math/Quaternion.js"></script>
+  <script src="math/Math.js"></script>
   <script src="math/Matrix3.js"></script>
   <script src="math/Matrix4.js"></script>
   <script src="math/Frustum.js"></script>

+ 1 - 0
test/unit/unittests_three.html

@@ -28,6 +28,7 @@
   <script src="math/Euler.js"></script>
   <script src="math/Line3.js"></script>
   <script src="math/Quaternion.js"></script>
+  <script src="math/Math.js"></script>
   <script src="math/Matrix3.js"></script>
   <script src="math/Matrix4.js"></script>
   <script src="math/Frustum.js"></script>

+ 1 - 0
test/unit/unittests_three.min.html

@@ -28,6 +28,7 @@
   <script src="math/Euler.js"></script>
   <script src="math/Line3.js"></script>
   <script src="math/Quaternion.js"></script>
+  <script src="math/Math.js"></script>
   <script src="math/Matrix3.js"></script>
   <script src="math/Matrix4.js"></script>
   <script src="math/Frustum.js"></script>

+ 29 - 2
utils/exporters/blender/addons/io_three/__init__.py

@@ -26,7 +26,8 @@ from bpy.props import (
     EnumProperty,
     BoolProperty,
     FloatProperty,
-    IntProperty
+    IntProperty,
+    StringProperty
 )
 
 from . import constants
@@ -41,7 +42,7 @@ SETTINGS_FILE_EXPORT = 'three_settings_export.js'
 bl_info = {
     'name': "Three.js Format",
     'author': "repsac, mrdoob, yomotsu, mpk, jpweeks",
-    'version': (1, 2, 3),
+    'version': (1, 3, 1),
     'blender': (2, 7, 3),
     'location': "File > Export",
     'description': "Export Three.js formatted JSON files.",
@@ -295,12 +296,14 @@ def save_settings_export(properties):
         constants.COMPRESSION: properties.option_compression,
         constants.INDENT: properties.option_indent,
         constants.COPY_TEXTURES: properties.option_copy_textures,
+        constants.TEXTURE_FOLDER: properties.option_texture_folder,
 
         constants.SCENE: properties.option_export_scene,
         #constants.EMBED_GEOMETRY: properties.option_embed_geometry,
         constants.EMBED_ANIMATION: properties.option_embed_animation,
         constants.LIGHTS: properties.option_lights,
         constants.CAMERAS: properties.option_cameras,
+        constants.HIERARCHY: properties.option_hierarchy,
 
         constants.MORPH_TARGETS: properties.option_animation_morph,
         constants.ANIMATION: properties.option_animation_skeletal,
@@ -420,6 +423,10 @@ def restore_settings_export(properties):
         constants.COPY_TEXTURES,
         constants.EXPORT_OPTIONS[constants.COPY_TEXTURES])
 
+    properties.option_texture_folder = settings.get(
+        constants.TEXTURE_FOLDER,
+        constants.EXPORT_OPTIONS[constants.TEXTURE_FOLDER])
+
     properties.option_embed_animation = settings.get(
         constants.EMBED_ANIMATION,
         constants.EXPORT_OPTIONS[constants.EMBED_ANIMATION])
@@ -441,6 +448,10 @@ def restore_settings_export(properties):
     properties.option_cameras = settings.get(
         constants.CAMERAS,
         constants.EXPORT_OPTIONS[constants.CAMERAS])
+
+    properties.option_hierarchy = settings.get(
+        constants.HIERARCHY,
+        constants.EXPORT_OPTIONS[constants.HIERARCHY])
     ## }
 
     ## Animation {
@@ -617,6 +628,11 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Copy textures",
         default=constants.EXPORT_OPTIONS[constants.COPY_TEXTURES])
 
+    option_texture_folder = StringProperty(
+        name="Texture folder",
+        description="add this folder to textures path",
+        default=constants.EXPORT_OPTIONS[constants.TEXTURE_FOLDER])
+
     option_lights = BoolProperty(
         name="Lights",
         description="Export default scene lights",
@@ -627,6 +643,11 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Export default scene cameras",
         default=False)
 
+    option_hierarchy = BoolProperty(
+        name="Hierarchy",
+        description="Export object hierarchy",
+        default=False)
+
     option_animation_morph = BoolProperty(
         name="Morph animation",
         description="Export animation (morphs)",
@@ -800,6 +821,9 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row.prop(self.properties, 'option_cameras')
         ## }
 
+        row = layout.row()
+        row.prop(self.properties, 'option_hierarchy')
+
         layout.separator()
 
         ## Settings {
@@ -812,6 +836,9 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row = layout.row()
         row.prop(self.properties, 'option_copy_textures')
 
+        row = layout.row()
+        row.prop(self.properties, 'option_texture_folder')
+
         row = layout.row()
         row.prop(self.properties, 'option_scale')
 

+ 10 - 4
utils/exporters/blender/addons/io_three/constants.py

@@ -60,9 +60,11 @@ SKIN_WEIGHTS = 'skinWeights'
 LOGGING = 'logging'
 CAMERAS = 'cameras'
 LIGHTS = 'lights'
+HIERARCHY = 'hierarchy'
 FACE_MATERIALS = 'faceMaterials'
 SKINNING = 'skinning'
 COPY_TEXTURES = 'copyTextures'
+TEXTURE_FOLDER = 'textureFolder'
 ENABLE_PRECISION = 'enablePrecision'
 PRECISION = 'precision'
 DEFAULT_PRECISION = 6
@@ -91,8 +93,8 @@ INFLUENCES_PER_VERTEX = 'influencesPerVertex'
 EXPORT_OPTIONS = {
     FACES: True,
     VERTICES: True,
-    NORMALS: False,
-    UVS: False,
+    NORMALS: True,
+    UVS: True,
     COLORS: False,
     MATERIALS: False,
     FACE_MATERIALS: False,
@@ -109,7 +111,9 @@ EXPORT_OPTIONS = {
     MORPH_TARGETS: False,
     CAMERAS: False,
     LIGHTS: False,
+    HIERARCHY: False,
     COPY_TEXTURES: True,
+    TEXTURE_FOLDER: '',
     LOGGING: DEBUG,
     ENABLE_PRECISION: True,
     PRECISION: DEFAULT_PRECISION,
@@ -158,6 +162,7 @@ POINT_LIGHT = 'PointLight'
 SPOT_LIGHT = 'SpotLight'
 HEMISPHERE_LIGHT = 'HemisphereLight'
 MESH = 'Mesh'
+EMPTY = 'Empty'
 SPRITE = 'Sprite'
 
 DEFAULT_METADATA = {
@@ -171,7 +176,7 @@ UUID = 'uuid'
 MATRIX = 'matrix'
 POSITION = 'position'
 QUATERNION = 'quaternion'
-ROTATION ='rotation'
+ROTATION = 'rotation'
 SCALE = 'scale'
 
 UV = 'uv'
@@ -319,7 +324,8 @@ BASIC = 'basic'
 
 NORMAL_BLENDING = 'NormalBlending'
 
-DBG_COLORS = (0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee)
+DBG_COLORS = (0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee,
+              0xeeee00, 0x00eeee, 0xee00ee)
 
 DOUBLE_SIDED = 'doubleSided'
 

+ 6 - 6
utils/exporters/blender/addons/io_three/exporter/__init__.py

@@ -71,12 +71,12 @@ def export_geometry(filepath, options, node=None):
             logger.error(msg)
             exception = exceptions.GeometryError
     
-    if exception is not None:
-        if api.batch_mode():
-            raise exception(msg)
-        else:
-            dialogs.error(msg)
-            return
+        if exception is not None:
+            if api.batch_mode():
+                raise exception(msg)
+            else:
+                dialogs.error(msg)
+                return
 
     mesh = api.object.mesh(node, options)
     parent = base_classes.BaseScene(filepath, options)

+ 12 - 0
utils/exporters/blender/addons/io_three/exporter/api/__init__.py

@@ -23,6 +23,18 @@ def batch_mode():
     return bpy.context.area is None
 
 
+def data(node):
+    """
+
+    :param node: name of an object node
+    :returns: the data block of the node
+
+    """
+    try:
+        return bpy.data.objects[node].data
+    except KeyError:
+        pass
+
 def init():
     """Initializing the api module. Required first step before
     initializing the actual export process.

+ 34 - 0
utils/exporters/blender/addons/io_three/exporter/api/animation.py

@@ -1,3 +1,4 @@
+import math
 import mathutils
 from bpy import data, context
 from .. import constants, logger, utilities
@@ -88,6 +89,7 @@ def _parse_rest_action(action, armature, options, round_off, round_val):
                                      action, armature.matrix_world)
             rot, rchange = _rotation(bone, computed_frame,
                                      action, rotation_matrix)
+            rot = _normalize_quaternion(rot)
 
             if round_off:
                 pos_x, pos_y, pos_z = utilities.round_off(
@@ -274,6 +276,7 @@ def _parse_pose_action(action, armature, options, round_off, round_val):
                 bone_matrix = parent_matrix.inverted() * bone_matrix
 
             pos, rot, scl = bone_matrix.decompose()
+            rot = _normalize_quaternion(rot)
 
             pchange = True or has_keyframe_at(
                 channels_location[bone_index], frame)
@@ -569,3 +572,34 @@ def _handle_position_channel(channel, frame, position):
             position.z = value
 
     return change
+
+
+def _quaternion_length(quat):
+    """Calculate the length of a quaternion
+
+    :param quat: Blender quaternion object
+    :rtype: float
+
+    """
+    return math.sqrt(quat.x * quat.x + quat.y * quat.y +
+                     quat.z * quat.z + quat.w * quat.w)
+
+
+def _normalize_quaternion(quat):
+    """Normalize a quaternion
+
+    :param quat: Blender quaternion object
+    :returns: generic quaternion enum object with normalized values
+    :rtype: object
+
+    """
+    enum = type('Enum', (), {'x': 0, 'y': 0, 'z': 0, 'w': 1})
+    length = _quaternion_length(quat)
+    if length is not 0:
+        length = 1 / length
+        enum.x = quat.x * length
+        enum.y = quat.y * length
+        enum.z = quat.z * length
+        enum.w = quat.w * length
+    return enum
+

+ 1 - 1
utils/exporters/blender/addons/io_three/exporter/api/material.py

@@ -380,7 +380,7 @@ def _valid_textures(material):
     for texture in material.texture_slots:
         if not texture:
             continue
-        if texture.texture.type != IMAGE:
+        if texture.texture.type != IMAGE or not texture.use:
             continue
         logger.debug("Valid texture found %s", texture)
         yield texture

+ 1 - 1
utils/exporters/blender/addons/io_three/exporter/api/mesh.py

@@ -340,7 +340,7 @@ def morph_targets(mesh, options):
             break
     else:
         logger.info("No valid morph data detected")
-        return
+        return []
 
     manifest = []
     for index, morph in enumerate(morphs):

+ 32 - 46
utils/exporters/blender/addons/io_three/exporter/api/object.py

@@ -2,6 +2,7 @@ import math
 import mathutils
 import bpy
 from bpy import data, context, types
+from bpy_extras.io_utils import axis_conversion
 from .. import constants, logger, utilities, exceptions
 from .constants import (
     MESH,
@@ -225,14 +226,8 @@ def position(obj, options):
 
     """
     logger.debug('object.position(%s)', obj)
-    vector = _decompose_matrix(obj)[0]
-    vector = (vector.x, vector.y, vector.z)
-
-    round_off, round_val = utilities.rounding(options)
-    if round_off:
-        vector = utilities.round_off(vector, round_val)
-
-    return vector
+    vector = matrix(obj, options).to_translation()
+    return (vector.x, vector.y, vector.z)
 
 
 @_object
@@ -250,23 +245,35 @@ def receive_shadow(obj):
             return False
 
 
+AXIS_CONVERSION = axis_conversion(to_forward='Z', to_up='Y').to_4x4()
+
 @_object
-def rotation(obj, options):
+def matrix(obj, options):
     """
 
     :param obj:
     :param options:
 
     """
-    logger.debug('object.rotation(%s)', obj)
-    vector = _decompose_matrix(obj)[1].to_euler(ZYX)
-    vector = (vector.x, vector.y, vector.z)
+    logger.debug('object.matrix(%s)', obj)
+    if options.get(constants.HIERARCHY, False) and obj.parent:
+        parent_inverted = obj.parent.matrix_world.inverted(mathutils.Matrix())
+        return parent_inverted * obj.matrix_world
+    else:
+        return AXIS_CONVERSION * obj.matrix_world
+
 
-    round_off, round_val = utilities.rounding(options)
-    if round_off:
-        vector = utilities.round_off(vector, round_val)
+@_object
+def rotation(obj, options):
+    """
 
-    return vector
+    :param obj:
+    :param options:
+
+    """
+    logger.debug('object.rotation(%s)', obj)
+    vector = matrix(obj, options).to_euler(ZYX)
+    return (vector.x, vector.y, vector.z)
 
 
 @_object
@@ -278,14 +285,8 @@ def scale(obj, options):
 
     """
     logger.debug('object.scale(%s)', obj)
-    vector = _decompose_matrix(obj)[2]
-    vector = (vector.x, vector.y, vector.z)
-
-    round_off, round_val = utilities.rounding(options)
-    if round_off:
-        vector = utilities.round_off(vector, round_val)
-
-    return vector
+    vector = matrix(obj, options).to_scale()
+    return (vector.x, vector.y, vector.z)
 
 
 @_object
@@ -477,24 +478,6 @@ def extracted_meshes():
     return [key for key in _MESH_MAP.keys()]
 
 
-def _decompose_matrix(obj, local=False):
-    """
-
-    :param obj:
-    :param local:  (Default value = False)
-
-    """
-    rotate_x_pi2 = mathutils.Quaternion((1.0, 0.0, 0.0),
-                                        math.radians(-90.0))
-    rotate_x_pi2 = rotate_x_pi2.to_matrix().to_4x4()
-
-    if local:
-        matrix = rotate_x_pi2 * obj.matrix_local
-    else:
-        matrix = rotate_x_pi2 * obj.matrix_world
-    return matrix.decompose()
-
-
 def _on_visible_layer(obj, visible_layers):
     """
 
@@ -502,12 +485,15 @@ def _on_visible_layer(obj, visible_layers):
     :param visible_layers:
 
     """
-    is_visible = True
+    is_visible = False
     for index, layer in enumerate(obj.layers):
-        if layer and index not in visible_layers:
-            logger.info('%s is on a hidden layer', obj.name)
-            is_visible = False
+        if layer and index in visible_layers:
+            is_visible = True
             break
+
+    if not is_visible:
+        logger.info('%s is on a hidden layer', obj.name)
+
     return is_visible
 
 

+ 6 - 3
utils/exporters/blender/addons/io_three/exporter/api/texture.py

@@ -170,6 +170,9 @@ def textures():
 
     """
     logger.debug("texture.textures()")
-    for texture in data.textures:
-        if texture.type == IMAGE:
-            yield texture.name
+    for mat in data.materials:
+        if mat.users == 0:
+            continue
+        for slot in mat.texture_slots:
+            if slot and slot.use and slot.texture.type == IMAGE:
+                yield slot.texture.name

+ 12 - 10
utils/exporters/blender/addons/io_three/exporter/geometry.py

@@ -115,7 +115,7 @@ class Geometry(base_classes.BaseNode):
     def copy(self, scene=True):
         """Copy the geometry definitions to a standard dictionary.
 
-        :param scene: toggle for scene formatting 
+        :param scene: toggle for scene formatting
                       (Default value = True)
         :type scene: bool
         :rtype: dict
@@ -135,16 +135,17 @@ class Geometry(base_classes.BaseNode):
 
         return data
 
-    def copy_textures(self):
+    def copy_textures(self, texture_folder=''):
         """Copy the textures to the destination directory."""
         logger.debug("Geometry().copy_textures()")
         if self.options.get(constants.COPY_TEXTURES):
             texture_registration = self.register_textures()
             if texture_registration:
                 logger.info("%s has registered textures", self.node)
+                dirname = os.path.dirname(self.scene.filepath)
+                full_path = os.path.join(dirname, texture_folder)
                 io.copy_registered_textures(
-                    os.path.dirname(self.scene.filepath),
-                    texture_registration)
+                    full_path, texture_registration)
 
     def parse(self):
         """Parse the current node"""
@@ -169,7 +170,7 @@ class Geometry(base_classes.BaseNode):
         """Write the geometry definitions to disk. Uses the
         desitnation path of the scene.
 
-        :param filepath: optional output file path 
+        :param filepath: optional output file path
                         (Default value = None)
         :type filepath: str
 
@@ -307,7 +308,8 @@ class Geometry(base_classes.BaseNode):
                     pass
                 continue
 
-            if key in skip: continue
+            if key in skip:
+                continue
 
             metadata[key] = len(self[key])
 
@@ -333,7 +335,7 @@ class Geometry(base_classes.BaseNode):
                 constants.METADATA: self.metadata
             })
         else:
-            if self.options[constants.EMBED_GEOMETRY]:
+            if self.options.get(constants.EMBED_GEOMETRY, True):
                 data[constants.DATA] = {
                     constants.ATTRIBUTES: component_data
                 }
@@ -430,12 +432,12 @@ class Geometry(base_classes.BaseNode):
 
             self[constants.INFLUENCES_PER_VERTEX] = influences
             self[constants.SKIN_INDICES] = api.mesh.skin_indices(
-                self.node, bone_map, influences)
+                self.node, bone_map, influences) or []
             self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights(
-                self.node, bone_map, influences)
+                self.node, bone_map, influences) or []
 
         if self.options.get(constants.MORPH_TARGETS):
             logger.info("Parsing %s", constants.MORPH_TARGETS)
             self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
-                self.node, self.options)
+                self.node, self.options) or []
 

+ 2 - 1
utils/exporters/blender/addons/io_three/exporter/image.py

@@ -11,7 +11,8 @@ class Image(base_classes.BaseNode):
         logger.debug("Image().__init__(%s)", node)
         base_classes.BaseNode.__init__(self, node, parent, constants.IMAGE)
 
-        self[constants.URL] = api.image.file_name(self.node)
+        texture_folder = self.scene.options.get(constants.TEXTURE_FOLDER, "")
+        self[constants.URL] = os.path.join(texture_folder, api.image.file_name(self.node))
 
     @property
     def destination(self):

+ 1 - 0
utils/exporters/blender/addons/io_three/exporter/io.py

@@ -14,6 +14,7 @@ def copy_registered_textures(dest, registration):
 
     """
     logger.debug("io.copy_registered_textures(%s, %s)", dest, registration)
+    os.makedirs(dest, exist_ok=True)
     for value in registration.values():
         copy(value['file_path'], dest)
 

+ 34 - 24
utils/exporters/blender/addons/io_three/exporter/object.py

@@ -13,34 +13,44 @@ class Object(base_classes.BaseNode):
         else:
             self._root_setup()
 
+    @property
+    def data(self):
+        """
+
+        :return: returns the data block of the node
+
+        """
+        return api.data(self.node)
+
+
     def _init_camera(self):
         """Initialize camera attributes"""
         logger.debug("Object()._init_camera()")
-        self[constants.FAR] = api.camera.far(self.node)
-        self[constants.NEAR] = api.camera.near(self.node)
+        self[constants.FAR] = api.camera.far(self.data)
+        self[constants.NEAR] = api.camera.near(self.data)
 
         if self[constants.TYPE] == constants.PERSPECTIVE_CAMERA:
-            self[constants.ASPECT] = api.camera.aspect(self.node)
-            self[constants.FOV] = api.camera.fov(self.node)
+            self[constants.ASPECT] = api.camera.aspect(self.data)
+            self[constants.FOV] = api.camera.fov(self.data)
         elif self[constants.TYPE] == constants.ORTHOGRAPHIC_CAMERA:
-            self[constants.LEFT] = api.camera.left(self.node)
-            self[constants.RIGHT] = api.camera.right(self.node)
-            self[constants.TOP] = api.camera.top(self.node)
-            self[constants.BOTTOM] = api.camera.bottom(self.node)
+            self[constants.LEFT] = api.camera.left(self.data)
+            self[constants.RIGHT] = api.camera.right(self.data)
+            self[constants.TOP] = api.camera.top(self.data)
+            self[constants.BOTTOM] = api.camera.bottom(self.data)
 
     #@TODO: need more light attributes. Some may have to come from
     #       custom blender attributes.
     def _init_light(self):
         """Initialize light attributes"""
         logger.debug("Object()._init_light()")
-        self[constants.COLOR] = api.light.color(self.node)
-        self[constants.INTENSITY] = api.light.intensity(self.node)
+        self[constants.COLOR] = api.light.color(self.data)
+        self[constants.INTENSITY] = api.light.intensity(self.data)
 
         if self[constants.TYPE] != constants.DIRECTIONAL_LIGHT:
-            self[constants.DISTANCE] = api.light.distance(self.node)
+            self[constants.DISTANCE] = api.light.distance(self.data)
 
         if self[constants.TYPE] == constants.SPOT_LIGHT:
-            self[constants.ANGLE] = api.light.angle(self.node)
+            self[constants.ANGLE] = api.light.angle(self.data)
 
     def _init_mesh(self):
         """Initialize mesh attributes"""
@@ -58,14 +68,13 @@ class Object(base_classes.BaseNode):
         logger.debug("Object()._node_setup()")
         self[constants.NAME] = api.object.name(self.node)
 
-        self[constants.POSITION] = api.object.position(
-            self.node, self.options)
-
-        self[constants.ROTATION] = api.object.rotation(
-            self.node, self.options)
+        transform = api.object.matrix(self.node, self.options)
+        matrix = []
+        for col in range(0, 4):
+            for row in range(0, 4):
+                matrix.append(transform[row][col])
 
-        self[constants.SCALE] = api.object.scale(
-            self.node, self.options)
+        self[constants.MATRIX] = matrix
 
         self[constants.VISIBLE] = api.object.visible(self.node)
 
@@ -110,11 +119,12 @@ class Object(base_classes.BaseNode):
         elif self[constants.TYPE] in lights:
             self._init_light()
 
-        #for child in api.object.children(self.node, self.scene.valid_types):
-        #    if not self.get(constants.CHILDREN):
-        #        self[constants.CHILDREN] = [Object(child, parent=self)]
-        #    else:
-        #        self[constants.CHILDREN].append(Object(child, parent=self))
+        if self.options.get(constants.HIERARCHY, False):
+            for child in api.object.children(self.node, self.scene.valid_types):
+                if not self.get(constants.CHILDREN):
+                    self[constants.CHILDREN] = [Object(child, parent=self)]
+                else:
+                    self[constants.CHILDREN].append(Object(child, parent=self))
 
     def _root_setup(self):
         """Applies to a root/scene object"""

+ 11 - 2
utils/exporters/blender/addons/io_three/exporter/scene.py

@@ -39,6 +39,9 @@ class Scene(base_classes.BaseScene):
         """
         valid_types = [api.constants.MESH]
 
+        if self.options.get(constants.HIERARCHY, False):
+            valid_types.append(api.constants.EMPTY)
+
         if self.options.get(constants.CAMERAS):
             logger.info("Adding cameras to valid object types")
             valid_types.append(api.constants.CAMERA)
@@ -159,9 +162,10 @@ class Scene(base_classes.BaseScene):
         io.dump(self.filepath, data, options=self.options)
 
         if self.options.get(constants.COPY_TEXTURES):
+            texture_folder = self.options.get(constants.TEXTURE_FOLDER)
             for geo in self[constants.GEOMETRIES]:
                 logger.info("Copying textures from %s", geo.node)
-                geo.copy_textures()
+                geo.copy_textures(texture_folder)
 
     def _parse_geometries(self):
         """Locate all geometry nodes and parse them"""
@@ -206,7 +210,12 @@ class Scene(base_classes.BaseScene):
         self[constants.UUID] = utilities.id_from_name(scene_name)
 
         objects = []
-        for node in api.object.nodes(self.valid_types, self.options):
+        if self.options.get(constants.HIERARCHY, False):
+            nodes = api.object.assemblies(self.valid_types, self.options)
+        else:
+            nodes = api.object.nodes(self.valid_types, self.options)
+
+        for node in nodes:
             logger.info("Parsing object %s", node)
             obj = object_.Object(node, parent=self[constants.OBJECT])
             objects.append(obj)

+ 11 - 10
utils/exporters/blender/addons/io_three/logger.py

@@ -32,20 +32,21 @@ def init(filename, level=constants.DEBUG):
     LOGGER = logging.getLogger('Three.Export')
     LOGGER.setLevel(LEVELS[level])
 
-    stream = logging.StreamHandler()
-    stream.setLevel(LEVELS[level])
+    if not LOGGER.handlers:
+        stream = logging.StreamHandler()
+        stream.setLevel(LEVELS[level])
 
-    format_ = '%(asctime)s - %(name)s - %(levelname)s: %(message)s'
-    formatter = logging.Formatter(format_)
+        format_ = '%(asctime)s - %(name)s - %(levelname)s: %(message)s'
+        formatter = logging.Formatter(format_)
 
-    stream.setFormatter(formatter)
+        stream.setFormatter(formatter)
 
-    file_handler = logging.FileHandler(LOG_FILE)
-    file_handler.setLevel(LEVELS[level])
-    file_handler.setFormatter(formatter)
+        file_handler = logging.FileHandler(LOG_FILE)
+        file_handler.setLevel(LEVELS[level])
+        file_handler.setFormatter(formatter)
 
-    LOGGER.addHandler(stream)
-    LOGGER.addHandler(file_handler)
+        LOGGER.addHandler(stream)
+        LOGGER.addHandler(file_handler)
 
 
 def info(*args):

BIN
utils/exporters/blender/tests/blend/scene_orthographic_camera.blend


BIN
utils/exporters/blender/tests/blend/scene_perspective_camera.blend


BIN
utils/exporters/blender/tests/blend/scene_spot_light.blend


Some files were not shown because too many files changed in this diff