Browse Source

Merge branch 'dev' of https://github.com/mrdoob/three.js into zh_doc

gogoend 6 years ago
parent
commit
d9d4fa7298
100 changed files with 2460 additions and 2068 deletions
  1. 1 1
      .github/ISSUE_TEMPLATE.md
  2. 30 43
      build/three.js
  3. 292 408
      build/three.min.js
  4. 30 43
      build/three.module.js
  5. 2 2
      docs/api/en/cameras/ArrayCamera.html
  6. 1 1
      docs/api/en/extras/core/Path.html
  7. 1 1
      docs/api/en/extras/core/Shape.html
  8. 1 1
      docs/api/en/extras/core/ShapePath.html
  9. 1 1
      docs/api/en/extras/curves/ArcCurve.html
  10. 1 1
      docs/api/en/geometries/ConeBufferGeometry.html
  11. 1 1
      docs/api/en/geometries/ConeGeometry.html
  12. 1 1
      docs/api/en/geometries/DodecahedronBufferGeometry.html
  13. 2 0
      docs/api/en/geometries/ExtrudeBufferGeometry.html
  14. 2 0
      docs/api/en/geometries/ExtrudeGeometry.html
  15. 1 1
      docs/api/en/geometries/IcosahedronBufferGeometry.html
  16. 1 1
      docs/api/en/geometries/OctahedronBufferGeometry.html
  17. 1 1
      docs/api/en/geometries/TetrahedronBufferGeometry.html
  18. 3 1
      docs/api/en/geometries/TextBufferGeometry.html
  19. 3 1
      docs/api/en/geometries/TextGeometry.html
  20. 1 1
      docs/api/en/helpers/AxesHelper.html
  21. 1 1
      docs/api/en/helpers/Box3Helper.html
  22. 1 1
      docs/api/en/helpers/BoxHelper.html
  23. 1 1
      docs/api/en/helpers/CameraHelper.html
  24. 1 1
      docs/api/en/helpers/FaceNormalsHelper.html
  25. 2 2
      docs/api/en/helpers/GridHelper.html
  26. 1 1
      docs/api/en/helpers/PlaneHelper.html
  27. 1 1
      docs/api/en/helpers/PointLightHelper.html
  28. 1 1
      docs/api/en/helpers/PolarGridHelper.html
  29. 1 1
      docs/api/en/helpers/VertexNormalsHelper.html
  30. 2 2
      docs/api/en/materials/MeshToonMaterial.html
  31. 1 1
      docs/api/en/materials/RawShaderMaterial.html
  32. 2 2
      docs/api/en/objects/LOD.html
  33. 10 1
      docs/api/en/renderers/WebGLRenderer.html
  34. 1 1
      docs/api/zh/cameras/ArrayCamera.html
  35. 2 2
      docs/api/zh/materials/MeshToonMaterial.html
  36. 2 2
      docs/api/zh/objects/LOD.html
  37. 9 0
      docs/api/zh/renderers/WebGLRenderer.html
  38. 1 1
      docs/examples/controls/OrbitControls.html
  39. 1 1
      docs/examples/loaders/MTLLoader.html
  40. 3 3
      docs/examples/loaders/OBJLoader.html
  41. 1 1
      docs/examples/loaders/OBJLoader2.html
  42. 78 62
      docs/examples/quickhull/QuickHull.html
  43. BIN
      docs/files/inconsolata.woff
  44. 0 213
      docs/index.css
  45. 28 32
      docs/index.html
  46. 65 4
      docs/manual/en/introduction/Import-via-modules.html
  47. 21 13
      docs/manual/en/introduction/Useful-links.html
  48. 20 12
      docs/manual/zh/introduction/Useful-links.html
  49. 58 25
      docs/page.css
  50. 1 2
      docs/page.js
  51. 4 2
      docs/prettify/threejs.css
  52. 5 16
      docs/scenes/bones-browser.html
  53. 5 16
      docs/scenes/geometry-browser.html
  54. 14 4
      docs/scenes/js/geometry.js
  55. 74 2
      docs/scenes/js/material.js
  56. 5 16
      docs/scenes/material-browser.html
  57. 4 0
      editor/index.html
  58. 2 2
      editor/js/History.js
  59. 161 98
      editor/js/Menubar.Add.js
  60. 62 0
      editor/js/Menubar.Edit.js
  61. 1 4
      editor/js/Menubar.File.js
  62. 22 19
      editor/js/Script.js
  63. 54 0
      editor/js/Sidebar.Geometry.OctahedronGeometry.js
  64. 95 0
      editor/js/Sidebar.Geometry.RingGeometry.js
  65. 55 0
      editor/js/Sidebar.Geometry.TetrahedronGeometry.js
  66. 25 3
      editor/js/Sidebar.Material.js
  67. 112 6
      editor/js/Sidebar.Object.js
  68. 3 3
      editor/js/Sidebar.Settings.js
  69. 44 0
      editor/js/Strings.js
  70. 11 3
      editor/js/Viewport.js
  71. 20 3
      editor/js/libs/ui.three.js
  72. 1 1
      editor/sw.js
  73. 2 2
      examples/css2d_label.html
  74. 2 2
      examples/css3d_orthographic.html
  75. 2 2
      examples/css3d_sandbox.html
  76. 1 1
      examples/css3d_youtube.html
  77. 5 3
      examples/files.js
  78. BIN
      examples/files/inconsolata.woff
  79. 26 221
      examples/index.html
  80. 102 0
      examples/js/QuickHull.js
  81. 1 1
      examples/js/controls/DragControls.js
  82. 23 23
      examples/js/controls/PointerLockControls.js
  83. 1 1
      examples/js/controls/TrackballControls.js
  84. 1 0
      examples/js/controls/TransformControls.js
  85. 6 6
      examples/js/effects/AnaglyphEffect.js
  86. 46 4
      examples/js/effects/OutlineEffect.js
  87. 32 10
      examples/js/exporters/GLTFExporter.js
  88. 163 159
      examples/js/geometries/LightningStrike.js
  89. 3 3
      examples/js/interactive/SelectionHelper.js
  90. 121 0
      examples/js/lights/LightProbeGenerator.js
  91. 201 75
      examples/js/loaders/LDrawLoader.js
  92. 11 8
      examples/js/loaders/LoaderSupport.js
  93. 117 74
      examples/js/loaders/OBJLoader2.js
  94. 1 1
      examples/js/loaders/PCDLoader.js
  95. 7 4
      examples/js/loaders/SVGLoader.js
  96. 21 5
      examples/js/loaders/VRMLLoader.js
  97. 3 3
      examples/js/math/ColorConverter.js
  98. 59 355
      examples/js/math/Lut.js
  99. 12 3
      examples/js/nodes/accessors/NormalNode.js
  100. 20 4
      examples/js/nodes/accessors/PositionNode.js

+ 1 - 1
.github/ISSUE_TEMPLATE.md

@@ -19,7 +19,7 @@ Please also include a live example if possible. You can start from these templat
 ##### Three.js version
 
 - [ ] Dev
-- [ ] r103
+- [ ] r104
 - [ ] ...
 
 ##### Browser

File diff suppressed because it is too large
+ 30 - 43
build/three.js


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


File diff suppressed because it is too large
+ 30 - 43
build/three.module.js


+ 2 - 2
docs/api/en/cameras/ArrayCamera.html

@@ -8,13 +8,13 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:PerspectiveCamera] &rarr;
+		[page:Object3D] &rarr; [page:Camera] &rarr; [page:PerspectiveCamera] &rarr;
 
 		<h1>[name]</h1>
 
 		<p class="desc">
 			[name] can be used in order to efficiently render a scene with a predefined set of cameras. This is an important performance aspect for rendering VR scenes.<br />
-			An instance of [name] always has an array of sub cameras. It's mandatory to define for each sub camera the *bounds* property which determines the part of the viewport that is rendered with this camera.
+			An instance of [name] always has an array of sub cameras. It's mandatory to define for each sub camera the *viewport* property which determines the part of the viewport that is rendered with this camera.
 		</p>
 
 		<h2>Example</h2>

+ 1 - 1
docs/api/en/extras/core/Path.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:CurvePath] &rarr;
+		[page:Curve] &rarr; [page:CurvePath] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/extras/core/Shape.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:Path] &rarr;
+		[page:Curve] &rarr; [page:CurvePath] &rarr; [page:Path] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/extras/core/ShapePath.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:CurvePath] &rarr;
+		[page:Curve] &rarr; [page:CurvePath] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/extras/curves/ArcCurve.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:EllipseCurve] &rarr;
+		[page:Curve] &rarr; [page:EllipseCurve] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/geometries/ConeBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:CylinderBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:CylinderBufferGeometry] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/geometries/ConeGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:CylinderGeometry] &rarr;
+		[page:Geometry] &rarr; [page:CylinderGeometry] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/geometries/DodecahedronBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:PolyhedronBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:PolyhedronBufferGeometry] &rarr;
 
 		<h1>[name]</h1>
 

+ 2 - 0
docs/api/en/geometries/ExtrudeBufferGeometry.html

@@ -51,6 +51,7 @@
 			bevelEnabled: true,
 			bevelThickness: 1,
 			bevelSize: 1,
+			bevelOffset: 0,
 			bevelSegments: 1
 		};
 
@@ -76,6 +77,7 @@
 				<li>bevelEnabled — bool. Apply beveling to the shape. Default is true.</li>
 				<li>bevelThickness — float. How deep into the original shape the bevel goes. Default is 6.</li>
 				<li>bevelSize — float. Distance from the shape outline that the bevel extends. Default is bevelThickness - 2.</li>
+				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelSegments — int. Number of bevel layers. Default is 3.</li>
 				<li>extrudePath — THREE.CurvePath. A 3D spline path along which the shape should be extruded.</li>
 				<li>UVGenerator —  Object. object that provides UV generator functions</li>

+ 2 - 0
docs/api/en/geometries/ExtrudeGeometry.html

@@ -51,6 +51,7 @@
 			bevelEnabled: true,
 			bevelThickness: 1,
 			bevelSize: 1,
+			bevelOffset: 0,
 			bevelSegments: 1
 		};
 
@@ -76,6 +77,7 @@
 				<li>bevelEnabled — bool. Apply beveling to the shape. Default is true.</li>
 				<li>bevelThickness — float. How deep into the original shape the bevel goes. Default is 6.</li>
 				<li>bevelSize — float. Distance from the shape outline that the bevel extends. Default is bevelThickness - 2.</li>
+				<li>bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0.</li>
 				<li>bevelSegments — int. Number of bevel layers. Default is 3.</li>
 				<li>extrudePath — THREE.CurvePath. A 3D spline path along which the shape should be extruded.</li>
 				<li>UVGenerator —  Object. object that provides UV generator functions</li>

+ 1 - 1
docs/api/en/geometries/IcosahedronBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:PolyhedronBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:PolyhedronBufferGeometry] &rarr;
 		<h1>[name]</h1>
 
 		<p class="desc">A class for generating an icosahedron geometry.</p>

+ 1 - 1
docs/api/en/geometries/OctahedronBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:PolyhedronBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:PolyhedronBufferGeometry] &rarr;
 		<h1>[name]</h1>
 
 		<p class="desc">A class for generating an octahedron geometry.</p>

+ 1 - 1
docs/api/en/geometries/TetrahedronBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:PolyhedronBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:PolyhedronBufferGeometry] &rarr;
 
 		<h1>[name]</h1>
 

+ 3 - 1
docs/api/en/geometries/TextBufferGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:ExtrudeBufferGeometry] &rarr;
+		[page:BufferGeometry] &rarr; [page:ExtrudeBufferGeometry] &rarr;
 
 		<h1>[name]</h1>
 
@@ -55,6 +55,7 @@
 				bevelEnabled: true,
 				bevelThickness: 10,
 				bevelSize: 8,
+				bevelOffset: 0,
 				bevelSegments: 5
 			} );
 		} );
@@ -74,6 +75,7 @@
 			<li>bevelEnabled — Boolean. Turn on bevel. Default is False.</li>
 			<li>bevelThickness — Float. How deep into text bevel goes. Default is 10.</li>
 			<li>bevelSize — Float. How far from text outline is bevel. Default is 8.</li>
+			<li>bevelOffset — Float. How far from text outline bevel starts. Default is 0.</li>
 			<li>bevelSegments — Integer. Number of bevel segments. Default is 3.</li>
 		</ul>
 		</p>

+ 3 - 1
docs/api/en/geometries/TextGeometry.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:ExtrudeGeometry] &rarr;
+		[page:Geometry] &rarr; [page:ExtrudeGeometry] &rarr;
 
 		<h1>[name]</h1>
 
@@ -55,6 +55,7 @@
 				bevelEnabled: true,
 				bevelThickness: 10,
 				bevelSize: 8,
+				bevelOffset: 0,
 				bevelSegments: 5
 			} );
 		} );
@@ -74,6 +75,7 @@
 			<li>bevelEnabled — Boolean. Turn on bevel. Default is False.</li>
 			<li>bevelThickness — Float. How deep into text bevel goes. Default is 10.</li>
 			<li>bevelSize — Float. How far from text outline is bevel. Default is 8.</li>
+			<li>bevelOffset — Float. How far from text outline bevel starts. Default is 0.</li>
 			<li>bevelSegments — Integer. Number of bevel segments. Default is 3.</li>
 		</ul>
 		</p>

+ 1 - 1
docs/api/en/helpers/AxesHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/Box3Helper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/BoxHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

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

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/FaceNormalsHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

+ 2 - 2
docs/api/en/helpers/GridHelper.html

@@ -8,8 +8,8 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:Line] &rarr;
-
+		[page:Object3D] &rarr; [page:Line] &rarr;
+		
 		<h1>[name]</h1>
 
 		<p class="desc">The GridHelper is an object to define grids. Grids are two-dimensional arrays of lines.</p>

+ 1 - 1
docs/api/en/helpers/PlaneHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:LineSegments] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr; [page:LineSegments] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/PointLightHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:Mesh] &rarr;
+		[page:Object3D] &rarr; [page:Mesh] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/PolarGridHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:Line] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr;
 
 		<h1>[name]</h1>
 

+ 1 - 1
docs/api/en/helpers/VertexNormalsHelper.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:Line] &rarr;
+		[page:Object3D] &rarr; [page:Line] &rarr;
 
 		<h1>[name]</h1>
 

+ 2 - 2
docs/api/en/materials/MeshToonMaterial.html

@@ -14,7 +14,7 @@
 
 		<div class="desc">An extension of the [page:MeshPhongMaterial] with toon shading.</div>
 
-		<!-- <iframe id="scene" src="scenes/material-browser.html#MeshStandardMaterial"></iframe>
+		<iframe id="scene" src="scenes/material-browser.html#MeshToonMaterial"></iframe>
 
 		<script>
 
@@ -30,7 +30,7 @@
 
 		}
 
-		</script> -->
+		</script>
 
 		<h2>Examples</h2>
 		[example:webgl_materials_variations_toon materials / variations / toon]<br />

+ 1 - 1
docs/api/en/materials/RawShaderMaterial.html

@@ -8,7 +8,7 @@
 		<link type="text/css" rel="stylesheet" href="page.css" />
 	</head>
 	<body>
-		[page:ShaderMaterial] &rarr;
+		[page:Material] &rarr; [page:ShaderMaterial] &rarr;
 
 		<h1>[name]</h1>
 

+ 2 - 2
docs/api/en/objects/LOD.html

@@ -72,7 +72,7 @@ scene.add( lod );
 		<h2>Methods</h2>
 		<p>See the base [page:Object3D] class for common methods.</p>
 
-		<h3>[method:null addLevel]( [param:Object3D object], [param:Float distance] )</h3>
+		<h3>[method:this addLevel]( [param:Object3D object], [param:Float distance] )</h3>
 		<p>
 		[page:Object3D object] - The [page:Object3D] to display at this level.<br />
 		[page:Float distance] - The distance at which to display this level of detail.<br /><br />
@@ -100,7 +100,7 @@ scene.add( lod );
 
 
 
-		<h3>[method:null toJSON]( meta )</h3>
+		<h3>[method:Object toJSON]( meta )</h3>
 		<p>
 		Create a JSON structure with details of this LOD object.
 		</p>

+ 10 - 1
docs/api/en/renderers/WebGLRenderer.html

@@ -92,6 +92,15 @@
 			Default is *true*.
 		</p>
 
+		<h3>[property:Boolean debug.checkShaderErrors]</h3>
+		<p>
+			If [page:.checkShaderErrors checkShaderErrors] is true, defines whether material shader programs are checked
+			for errors during compilation and linkage process. It may be useful to disable this check in production for performance gain.
+			It is strongly recommended to keep these checks enabled during development.
+			If the shader does not compile and link - it will not work and associated material will not render.
+			Default is *true*.
+		</p>
+
 		<h3>[property:Object capabilities]</h3>
 		<p>
 		An object containing details about the capabilities of the current [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext].<br />
@@ -416,7 +425,7 @@
 
 		<h3>[method:null setAnimationLoop]( [param:Function callback] )</h3>
 		<p>[page:Function callback] — The function will be called every available frame. If `null` is passed it will stop any already ongoing animation.</p>
-		<p>A build in function that can be used instead of [link:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame]. For WebVR projects this function must be used.</p>
+		<p>A built in function that can be used instead of [link:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame]. For WebVR projects this function must be used.</p>
 
 		<h3>[method:null setClearAlpha]( [param:Float alpha] )</h3>
 		<p>Sets the clear alpha. Valid input is a float between *0.0* and *1.0*.</p>

+ 1 - 1
docs/api/zh/cameras/ArrayCamera.html

@@ -15,7 +15,7 @@
 		<p class="desc">
 
 			[name] 用于更加高效地使用一组已经预定义的摄像机来渲染一个场景。这将能够更好地提升VR场景的渲染性能。<br />
-			一个 [name] 的实例中总是包含着一组子摄像机,应当为每一个子摄像机定义*bound*(边界)这个属性,这一属性决定了由该子摄像机所渲染的视口区域的大小。
+			一个 [name] 的实例中总是包含着一组子摄像机,应当为每一个子摄像机定义*viewport*(边界)这个属性,这一属性决定了由该子摄像机所渲染的视口区域的大小。
 		</p>
 
 		<h2>示例</h2>

+ 2 - 2
docs/api/zh/materials/MeshToonMaterial.html

@@ -14,7 +14,7 @@
 
 		<div class="desc">[page:MeshPhongMaterial]卡通着色的扩展。</div>
 
-		<!-- <iframe id="scene" src="scenes/material-browser.html#MeshStandardMaterial"></iframe>
+		<iframe id="scene" src="scenes/material-browser.html#MeshToonMaterial"></iframe>
 
 		<script>
 
@@ -30,7 +30,7 @@
 
 		}
 
-		</script> -->
+		</script>
 
 		<h2>例子(Examples)</h2>
 		[example:webgl_materials_variations_toon materials / variations / toon]<br />

+ 2 - 2
docs/api/zh/objects/LOD.html

@@ -69,7 +69,7 @@ scene.add( lod );
 		<h2>方法</h2>
 		<p>请参阅其基类[page:Object3D]来查看共有方法。</p>
 
-		<h3>[method:null addLevel]( [param:Object3D object], [param:Float distance] )</h3>
+		<h3>[method:this addLevel]( [param:Object3D object], [param:Float distance] )</h3>
 		<p>
 		[page:Object3D object] —— 在这个层次中将要显示的[page:Object3D]。<br />
 		[page:Float distance] —— 将显示这一细节层次的距离。<br /><br />
@@ -96,7 +96,7 @@ scene.add( lod );
 
 
 
-		<h3>[method:null toJSON]( meta )</h3>
+		<h3>[method:Object toJSON]( meta )</h3>
 		<p>
 			使用这个方法,为LOD对象中的每个细节层次创建一个JSON结构。
 		</p>

+ 9 - 0
docs/api/zh/renderers/WebGLRenderer.html

@@ -79,6 +79,15 @@
 			默认是*true*
 		</p>
 
+		<h3>[property:Boolean debug.checkShaderErrors]</h3>
+		<p>
+			如果[page:.checkShaderErrors checkShaderErrors]为true,定义是否检查材质着色器程序
+			编译和链接过程中的错误。 禁用此检查生产以获得性能增益可能很有用。
+			强烈建议在开发期间保持启用这些检查。
+			如果着色器没有编译和链接 - 它将无法工作,并且相关材料将不会呈现。
+			默认是*true*
+		</p>
+
 		<h3>[property:Object capabilities]</h3>
 		<p>
             一个包含当前渲染环境([link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext])的功能细节的对象。<br />

+ 1 - 1
docs/examples/controls/OrbitControls.html

@@ -32,7 +32,7 @@ var scene = new THREE.Scene();
 
 var camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
 
-var controls = new THREE.OrbitControls( camera );
+var controls = new THREE.OrbitControls( camera, renderer.domElement );
 
 //controls.update() must be called after any manual changes to the camera's transform
 camera.position.set( 0, 20, 100 );

+ 1 - 1
docs/examples/loaders/MTLLoader.html

@@ -94,7 +94,7 @@
 			[page:String path] — The path to the MTL file.
 		</p>
 		<p>
-			Parse a <em>mtl</em> text structure and return a [page:MTLLoaderMaterialCreator] instance.<br />
+			Parse a <em>mtl</em> text structure and return a [page:MTLLoaderMaterialCreator MTLLoader.MaterialCreator] instance.<br />
 		</p>
 
 

+ 3 - 3
docs/examples/loaders/OBJLoader.html

@@ -89,12 +89,12 @@
 		If an <em>obj</em> object or group uses multiple materials while declaring faces, geometry groups and an array of materials are used.
 		</p>
 
-		<h3>[method:OBJLoader setMaterials]( [param:Array materials] )</h3>
+		<h3>[method:OBJLoader setMaterials]( [param:MTLLoader.MaterialCreator materials] )</h3>
 		<p>
-		[page:Array materials] - Array of [page:Material Materials].
+		[page:MTLLoaderMaterialCreator MTLLoader.MaterialCreator materials] - A MaterialCreator instance.
 		</p>
 		<p>
-		Sets materials loaded by MTLLoader or any other supplier of an Array of [page:Material Materials].
+		Sets materials loaded by MTLLoader or any other supplier of a [page:MTLLoaderMaterialCreator MTLLoader.MaterialCreator].
 		</p>
 
 		<h3>[method:OBJLoader setPath]( [param:String path] )</h3>

+ 1 - 1
docs/examples/loaders/OBJLoader2.html

@@ -13,7 +13,7 @@
 
 		<p class="desc">A loader for loading a <em>.obj</em> resource.<br />
 			The <a href="https://en.wikipedia.org/wiki/Wavefront_.obj_file">OBJ file format</a> is a simple data-format
-			that represents 3D geometry in a human redeable format as, the position of each vertex, the UV position of
+			that represents 3D geometry in a human readable format as, the position of each vertex, the UV position of
 			each texture coordinate vertex, vertex normals, and the faces that make each polygon defined as a list of
 			vertices, and texture vertices.
 		</p>

+ 78 - 62
docs/examples/quickhull/QuickHull.html

@@ -24,9 +24,9 @@
 
 		<h2>Properties</h2>
 
-		<h3>[property:Float tolerance]</h3>
+		<h3>[property:VertexList assigned]</h3>
 		<p>
-			The epsilon value that is used for internal comparative operations. The calculation of this value depends on the size of the geometry. Default is -1.
+			This [page:VertexList vertex list] holds all vertices that are assigned to a face. Default is an empty vertex list.
 		</p>
 
 		<h3>[property:Array faces]</h3>
@@ -39,9 +39,9 @@
 			This array holds the faces that are generated within a single iteration. Default is an empty array.
 		</p>
 
-		<h3>[property:VertexList assigned]</h3>
+		<h3>[property:Float tolerance]</h3>
 		<p>
-			This [page:VertexList vertex list] holds all vertices that are assigned to a face. Default is an empty vertex list.
+			The epsilon value that is used for internal comparative operations. The calculation of this value depends on the size of the geometry. Default is -1.
 		</p>
 
 		<h3>[property:VertexList unassigned]</h3>
@@ -56,20 +56,18 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:QuickHull setFromPoints]( [param:Array points] )</h3>
-		[page:Array points] - Array of [page:Vector3 Vector3s] that the resulting convex hull will contain.<br /><br />
-
-		<p>Computes to convex hull for the given array of points.</p>
-
-		<h3>[method:QuickHull setFromObject]( [param:Object3D object] )</h3>
-		[page:Object3D object] - [page:Object3D] to compute the convex hull of.<br /><br />
+		<h3>[method:HalfEdge addAdjoiningFace]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )</h3>
+		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
+		[page:HalfEdge horizonEdge] - A single edge of the horizon.<br /><br />
 
-		<p>Computes the convex hull of an [page:Object3D] (including its children),
-		accounting for the world transforms of both the object and its childrens.</p>
+		<p>Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in CCW order.
+			All the half edges are created in CCW order thus the face is always pointing outside the hull</p>
 
-		<h3>[method:QuickHull makeEmpty]()</h3>
+		<h3>[method:QuickHull addNewFaces]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )</h3>
+		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
+		[page:HalfEdge horizon] - An array of half-edges that form the horizon.<br /><br />
 
-		<p>Makes this convex hull empty.</p>
+		<p>Adds 'horizon.length' faces to the hull, each face will be linked with the horizon opposite face and the face on the left/right.</p>
 
 		<h3>[method:QuickHull addVertexToFace]( [param:VertexNode vertex], [param:Face face]	)</h3>
 		[page:VertexNodeNode vertex] - The vertex to add.<br /><br />
@@ -77,16 +75,46 @@
 
 		<p>Adds a vertex to the 'assigned' list of vertices and assigns it to the given face.</p>
 
-		<h3>[method:QuickHull removeVertexFromFace]( [param:VertexNode vertex], [param:Face face]	)</h3>
-		[page:VertexNode vertex] - The vertex to remove.<br /><br />
-		[page:Face face] - The target face.<br /><br />
+		<h3>[method:QuickHull addVertexToHull]( [param:VertexNode eyeVertex] )</h3>
+		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
 
-		<p>Removes a vertex from the 'assigned' list of vertices and from the given face. It also makes sure that the link from 'face' to the first vertex it sees in 'assigned' is linked correctly after the removal.</p>
+		<p>Adds a vertex to the hull with the following algorithm
+			<ul>
+				<li>Compute the 'horizon' which is a chain of half edges. For an edge to belong to this group it must be the edge connecting a face that can see 'eyeVertex' and a face which cannot see 'eyeVertex'.</li>
+				<li>All the faces that can see 'eyeVertex' have its visible vertices removed from the assigned vertex list.</li>
+				<li>A new set of faces is created with each edge of the 'horizon' and 'eyeVertex'. Each face is connected with the opposite horizon face and the face on the left/right.</li>
+				<li>The vertices removed from all the visible faces are assigned to the new faces if possible.</li>
+			</ul>
+		</p>
 
-		<h3>[method:VertexNode removeAllVerticesFromFace]( [param:Face face]	)</h3>
-		[page:Face face] - The given face.<br /><br />
+		<h3>[method:QuickHull cleanup]()</h3>
 
-		<p>Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertext list.</p>
+		<p>Cleans up internal properties after computing the convex hull.</p>
+
+		<h3>[method:QuickHull compute]()</h3>
+
+		<p>Starts the execution of the quick hull algorithm.</p>
+
+		<h3>[method:Object computeExtremes]()</h3>
+
+		<p>Computes the extremes values (min/max vectors) which will be used to compute the inital hull.</p>
+
+		<h3>[method:QuickHull computeHorizon]( [param:Vector3 eyePoint], [param:HalfEdge crossEdge], [param:Face face], [param:Array horizon]	)</h3>
+		[page:Vector3 eyePoint] - The 3D-coordinates of a point.<br /><br />
+		[page:HalfEdge crossEdge] - The edge used to jump to the current face.<br /><br />
+		[page:Face face] - The current face being tested.<br /><br />
+		[page:Array horizon] - The edges that form part of the horizon in CCW order.<br /><br />
+
+		<p>Computes a chain of half edges in CCW order called the 'horizon'. For an edge to be part of the horizon it must join a face that can see 'eyePoint' and a face that cannot see 'eyePoint'.</p>
+
+		<h3>[method:QuickHull computeInitialHull]()</h3>
+
+		<p>Computes the initial simplex assigning to its faces all the points that are candidates to form part of the hull.</p>
+
+		<h3>[method:QuickHull containsPoint]( [param:Vector3 point] )</h3>
+		[page:Vector3 point] - A point in 3D space.<br /><br />
+
+		<p>Returns *true* if the given point is inside this convex hull.</p>
 
 		<h3>[method:QuickHull deleteFaceVertices]( [param:Face face], [param:Face absorbingFace]	)</h3>
 		[page:Face face] - The given face.<br /><br />
@@ -100,22 +128,20 @@
 			</ul>
 		</p>
 
-		<h3>[method:QuickHull resolveUnassignedPoints]( [param:Array newFaces]	)</h3>
-		[page:Face newFaces] - An array of new faces.<br /><br />
+		<h3>[method:Vector3 intersectRay]( [param:Ray ray], [param:Vector3 target] )</h3>
+		[page:Ray ray] - The given ray.<br /><br />
+		[page:Vector3 target] - The target vector representing the intersection point.<br /><br />
 
-		<p>Reassigns as many vertices as possible from the unassigned list to the new faces.</p>
+		<p>Performs a ray intersection test with this convext hull. If no intersection is found, *null* is returned.</p>
 
-		<h3>[method:Object computeExtremes]()</h3>
+		<h3>[method:Boolean intersectsRay]( [param:Ray ray] )</h3>
+		[page:Ray ray] - The given ray.<br /><br />
 
-		<p>Computes the extremes values (min/max vectors) which will be used to compute the inital hull.</p>
+		<p>Returns *true* if the given ray intersects with this convex hull.</p>
 
-		<h3>[method:QuickHull computeInitialHull]()</h3>
-
-		<p>Computes the initial simplex assigning to its faces all the points that are candidates to form part of the hull.</p>
-
-		<h3>[method:QuickHull reindexFaces]()</h3>
+		<h3>[method:QuickHull makeEmpty]()</h3>
 
-		<p>Removes inactive (e.g. deleted) faces from the internal face list.</p>
+		<p>Makes this convex hull empty.</p>
 
 		<h3>[method:VertexNode nextVertexToAdd]()</h3>
 
@@ -127,46 +153,36 @@
 			</ul>
 		</p>
 
-		<h3>[method:QuickHull computeHorizon]( [param:Vector3 eyePoint], [param:HalfEdge crossEdge], [param:Face face], [param:Array horizon]	)</h3>
-		[page:Vector3 eyePoint] - The 3D-coordinates of a point.<br /><br />
-		[page:HalfEdge crossEdge] - The edge used to jump to the current face.<br /><br />
-		[page:Face face] - The current face being tested.<br /><br />
-		[page:Array horizon] - The edges that form part of the horizon in CCW order.<br /><br />
+		<h3>[method:QuickHull reindexFaces]()</h3>
 
-		<p>Computes a chain of half edges in CCW order called the 'horizon'. For an edge to be part of the horizon it must join a face that can see 'eyePoint' and a face that cannot see 'eyePoint'.</p>
+		<p>Removes inactive (e.g. deleted) faces from the internal face list.</p>
 
-		<h3>[method:HalfEdge addAdjoiningFace]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )</h3>
-		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
-		[page:HalfEdge horizonEdge] - A single edge of the horizon.<br /><br />
+		<h3>[method:VertexNode removeAllVerticesFromFace]( [param:Face face]	)</h3>
+		[page:Face face] - The given face.<br /><br />
 
-		<p>Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in CCW order.
-			All the half edges are created in CCW order thus the face is always pointing outside the hull</p>
+		<p>Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertext list.</p>
 
-		<h3>[method:QuickHull addNewFaces]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )</h3>
-		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
-		[page:HalfEdge horizon] - An array of half-edges that form the horizon.<br /><br />
+		<h3>[method:QuickHull removeVertexFromFace]( [param:VertexNode vertex], [param:Face face]	)</h3>
+		[page:VertexNode vertex] - The vertex to remove.<br /><br />
+		[page:Face face] - The target face.<br /><br />
 
-		<p>Adds 'horizon.length' faces to the hull, each face will be linked with the horizon opposite face and the face on the left/right.</p>
+		<p>Removes a vertex from the 'assigned' list of vertices and from the given face. It also makes sure that the link from 'face' to the first vertex it sees in 'assigned' is linked correctly after the removal.</p>
 
-		<h3>[method:QuickHull addVertexToHull]( [param:VertexNode eyeVertex] )</h3>
-		[page:VertexNode eyeVertex] - The vertex that is added to the hull.<br /><br />
+		<h3>[method:QuickHull resolveUnassignedPoints]( [param:Array newFaces]	)</h3>
+		[page:Face newFaces] - An array of new faces.<br /><br />
 
-		<p>Adds a vertex to the hull with the following algorithm
-			<ul>
-				<li>Compute the 'horizon' which is a chain of half edges. For an edge to belong to this group it must be the edge connecting a face that can see 'eyeVertex' and a face which cannot see 'eyeVertex'.</li>
-				<li>All the faces that can see 'eyeVertex' have its visible vertices removed from the assigned vertex list.</li>
-				<li>A new set of faces is created with each edge of the 'horizon' and 'eyeVertex'. Each face is connected with the opposite horizon face and the face on the left/right.</li>
-				<li>The vertices removed from all the visible faces are assigned to the new faces if possible.</li>
-			</ul>
-		</p>
+		<p>Reassigns as many vertices as possible from the unassigned list to the new faces.</p>
 
-		<h3>[method:QuickHull cleanup]()</h3>
+		<h3>[method:QuickHull setFromObject]( [param:Object3D object] )</h3>
+		[page:Object3D object] - [page:Object3D] to compute the convex hull of.<br /><br />
 
-		<p>Cleans up internal properties after computing the convex hull.</p>
+		<p>Computes the convex hull of an [page:Object3D] (including its children),
+		accounting for the world transforms of both the object and its childrens.</p>
 
-		<h3>[method:QuickHull compute]()</h3>
+		<h3>[method:QuickHull setFromPoints]( [param:Array points] )</h3>
+		[page:Array points] - Array of [page:Vector3 Vector3s] that the resulting convex hull will contain.<br /><br />
 
-		<p>Starts the execution of the quick hull algorithm.</p>
+		<p>Computes to convex hull for the given array of points.</p>
 
 		<h2>Source</h2>
 

BIN
docs/files/inconsolata.woff


+ 0 - 213
docs/index.css

@@ -1,213 +0,0 @@
-@font-face {
-	font-family: 'inconsolata';
-	src: url('files/inconsolata.woff') format('woff');
-	font-weight: normal;
-	font-style: normal;
-}
-
-* {
-	box-sizing: border-box;
-}
-
-html {
-	height: 100%;
-}
-
-body {
-	background-color: #ffffff;
-	margin: 0px;
-	height: 100%;
-	color: #555;
-	font-family: 'inconsolata';
-	font-size: 15px;
-	line-height: 18px;
-	overflow: hidden;
-}
-
-h1 {
-	margin-top: 30px;
-	margin-bottom: 40px;
-	font-size: 25px;
-	font-weight: normal;
-}
-
-h2 {
-	color: #454545;
-	font-size: 18px;
-	font-weight: normal;
-
-	margin-top: 20px;
-}
-
-h3 {
-	color: #666;
-	font-size: 16px;
-	font-weight: normal;
-
-	margin-top: 20px;
-}
-
-a {
-	color: #2194CE;
-	text-decoration: none;
-}
-
-#panel {
-	position: fixed;
-	left: 0px;
-	width: 260px;
-	height: 100%;
-	overflow: auto;
-	padding: 0px 20px 0px 20px;
-	background: #fafafa;
-}
-
-#panel ul {
-	list-style-type: none;
-	padding: 0px;
-}
-
-iframe {
-	position: absolute;
-	border: 0px;
-	left: 260px;
-	width: calc(100% - 260px);
-	height: 100%;
-	overflow: auto;
-}
-
-.filterBlock {
-	position: relative;
-}
-
-.filterBlock p {
-	margin: 0;
-}
-
-#filterInput {
-	width: 100%;
-	padding: 5px;
-	font-family: inherit;
-	font-size: 15px;
-	outline: none;
-	border: 1px solid #dedede;
-}
-
-#filterInput:focus{
-	border: 1px solid #2194CE;
-}
-
-#clearFilterButton {
-	position: absolute;
-	right: 6px;
-	top: 50%;
-	margin-top: -8px;
-	width: 16px;
-	height: 16px;
-	font-size: 14px;
-	color: grey;
-	text-align: center;
-	line-height: 0;
-	padding-top: 7px;
-	opacity: .5;
-}
-
-#clearFilterButton:hover {
-	opacity: 1;
-}
-
-.hidden {
-	display: none;
-}
-
-#panel li b {
-	font-weight: bold;
-}
-
-/* mobile */
-
-#expandButton {
-	display: none;
-	position: absolute;
-	right: 20px;
-	top: 12px;
-	width: 32px;
-	height: 32px;
-}
-
-#expandButton span {
-	height: 2px;
-	background-color: #2194CE;
-	width: 16px;
-	position: absolute;
-	left: 8px;
-	top: 10px;
-}
-
-#expandButton span:nth-child(1) {
-	top: 16px;
-}
-
-#expandButton span:nth-child(2) {
-	top: 22px;
-}
-
-@media all and ( max-width: 640px ) {
-
-	h1 {
-		margin-top: 20px;
-		margin-bottom: 20px;
-	}
-
-	#panel {
-		position: absolute;
-		left: 0;
-		top: 0;
-		height: 100%;
-		width: 100%;
-		right: 0;
-		z-index: 100;
-		overflow: hidden;
-		border-bottom: 1px solid #dedede;
-	}
-
-	#content {
-		position: absolute;
-		left: 0;
-		top: 120px;
-		right: 0;
-		bottom: 0;
-		font-size: 17px;
-		line-height: 22px;
-		padding-left: 20px;
-		overflow: auto;
-	}
-
-	#navigation {
-		position: absolute;
-		left: 0;
-		top: 90px;
-		right: 0;
-		bottom: 0;
-		font-size: 17px;
-		line-height: 22px;
-		overflow: auto;
-	}
-
-	iframe {
-		position: absolute;
-		left: 0;
-		top: 56px;
-		width: 100%;
-		height: calc(100% - 56px);
-	}
-
-	#expandButton {
-		display: block;
-	}
-
-	#panel.collapsed {
-		height: 56px;
-	}
-
-}

+ 28 - 32
docs/index.html

@@ -4,29 +4,32 @@
 		<meta charset="utf-8">
 		<title>three.js / documentation</title>
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<link type="text/css" rel="stylesheet" href="index.css">
+		<link rel="shortcut icon" href="../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../files/main.css">
+		<!-- console sandbox -->
+		<script src="../build/three.min.js" async defer></script>
 	</head>
 	<body>
 
 		<div id="panel" class="collapsed">
 
-			<h1><a href="http://threejs.org">three.js</a> / docs</h1>
+			<div id="header">
 
-			<a id="expandButton" href="#">
-				<span></span>
-				<span></span>
-				<span></span>
-			</a>
+				<h1><a href="http://threejs.org">three.js</a></h1>
 
-			<div class="filterBlock" >
-				<input type="text" id="filterInput" placeholder="Type to filter" autocorrect="off" autocapitalize="off" spellcheck="false">
-				<a href="#" id="clearFilterButton">x</a>
-			</div>
+				<img id="expandButton" src="../files/ic_menu_black_24dp.svg">
+
+				<div id="sections">
+					<span class="selected">docs</span> <a href="../examples/">examples</a>
+				</div>
+
+				<input type="text" id="filter" autocorrect="off" autocapitalize="off" spellcheck="false">
+
+				<select id="language">
+					<option value="en">en</option>
+					<option value="zh">zh</option>
+				</select>
 
-			<div style="text-align:center">
-				<br />
-				<a href="javascript:setLanguage('en')">en</a> |
-				<a href="javascript:setLanguage('zh')">zh</a>
 			</div>
 
 			<div id="content"></div>
@@ -65,9 +68,18 @@
 
 			//
 
+			var languageSelect = document.getElementById( 'language' );
+			languageSelect.value = language;
+			languageSelect.addEventListener( 'change', function ( event ) {
+
+				setLanguage( this.value );
+
+			} );
+
 			function setLanguage( value ) {
 
 				language = value;
+
 				createNavigation();
 				updateFilter();
 				autoChangeUrlLanguage( language );
@@ -76,9 +88,8 @@
 
 			var panel = document.getElementById( 'panel' );
 			var content = document.getElementById( 'content' );
-			var clearFilterButton = document.getElementById( 'clearFilterButton' );
 			var expandButton = document.getElementById( 'expandButton' );
-			var filterInput = document.getElementById( 'filterInput' );
+			var filterInput = document.getElementById( 'filter' );
 			var iframe = document.querySelector( 'iframe' );
 
 			var pageProperties = {};
@@ -106,18 +117,6 @@
 
 			};
 
-
-			// Functionality for filter clear button
-
-			clearFilterButton.onclick = function ( event ) {
-
-				event.preventDefault();
-
-				filterInput.value = '';
-				updateFilter();
-
-			};
-
 			// Activate content and title change on browser navigation
 
 			window.onpopstate = createNewIframe;
@@ -433,9 +432,6 @@
 
 		</script>
 
-		<!-- console sandbox -->
-		<script src="../build/three.min.js"></script>
-
 	</body>
 
 </html>

+ 65 - 4
docs/manual/en/introduction/Import-via-modules.html

@@ -62,11 +62,72 @@
 		...
 		</code>
 
-		<h2>Caveats</h2>
-
+		<h2>Importable Examples</h2>
+		<p>
+			The core of three.js is focused on the most important components of a 3D engine. Many other components like loaders or controls are part of the
+			examples directory. three.js ensures that these files are kept in sync with the core but users have to import them separately if they are required
+			for their project. However, most of these files are not modules which makes their usage in certain cases inconvenient. In order to address this issue,
+			we are working to provide all the examples as modules in the [link:https://github.com/mrdoob/three.js/tree/master/examples/jsm examples/jsm] directory.
+			If you install three.js via npm, you can import them like so:
+		</p>
+		<code>
+		import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+		</code>
+		<p>
+			The following examples files are already available as modules:
+			<ul>
+				<li>controls
+					<ul>
+						<li>MapControls</li>
+						<li>OrbitControls</li>
+						<li>TrackballControls</li>
+					</ul>
+				</li>
+				<li>exporters
+					<ul>
+						<li>ColladaExporter</li>
+						<li>GLTFExporter</li>
+						<li>MMDExporter</li>
+						<li>OBJExporter</li>
+						<li>PLYExporter</li>
+						<li>STLExporter</li>
+						<li>TypedGeometryExporter</li>
+					</ul>
+				</li>
+				<li>loaders
+					<ul>
+						<li>GLTFLoader</li>
+						<li>MTLLoader</li>
+						<li>OBJLoader</li>
+						<li>STLLoader</li>
+					</ul>
+				</li>
+				<li>pmrem
+					<ul>
+						<li>PMREMCubeUVPacker</li>
+						<li>PMREMGenerator</li>
+					</ul>
+				</li>
+				<li>utils
+					<ul>
+						<li>BufferGeometryUtils</li>
+						<li>GeometryUtils</li>
+						<li>MathUtils</li>
+						<li>SceneUtils</li>
+						<li>ShadowMapViewer</li>
+						<li>SkeletonUtils</li>
+						<li>TypedArrayUtils</li>
+						<li>UVsDebug</li>
+					</ul>
+				</li>
+			</ul>
+		</p>
 		<p>
-			Currently it's not possible to import the files within the "examples/js" directory in this way.
-			This is due to some of the files relying on global namespace pollution of THREE. For more information see <a href="https://github.com/mrdoob/three.js/issues/9562" target="_blank">Transform `examples/js` to support modules #9562</a>.
+			Note: When using code from the examples directory, it's important that all files match the version of
+			your three.js main file. For example it's no good approach to use *GLTFLoader* and *OrbitControls* from R96 together
+			with three.js R103. You can easily keep your files in sync by using the modules from the JSM directory. If the file
+			is not available as a module, you can still use third-party npm packages or convert the file to a module by yourself.
+			In both cases, ensure the code is compatible with your three.js main file.
 		</p>
 	</body>
 </html>

+ 21 - 13
docs/manual/en/introduction/Useful-links.html

@@ -25,7 +25,7 @@
 
 		<h2>Help forums</h2>
 		<p>
-			Three.js officially uses [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] for help requests.
+			Three.js officially uses the [link:https://discourse.threejs.org/ forum] and [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] for help requests.
 			If you need assistance with something, that's the place to go. Do NOT open an issue on Github for help requests.
 		</p>
 
@@ -33,6 +33,9 @@
 
 		<h3>Getting started with three.js</h3>
 		<ul>
+			<li>
+				[link:https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html Three.js Fundamentals starting lesson]
+			</li>
 			<li>
 				[link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith].
 			</li>
@@ -43,6 +46,12 @@
 
 		<h3>More extensive / advanced articles and courses</h3>
 		<ul>
+			<li>
+				[link:https://discoverthreejs.com/ Discover three.js]
+			</li>
+			<li>
+				[link:https://threejsfundamentals.org/ Three.js Fundamentals]
+			</li>
 			<li>
 				[link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon].
 			</li>
@@ -60,21 +69,16 @@
 			 [link:http://learningthreejs.com/ Learning Three.js] – a blog with articles dedicated to teaching three.js
 		 </li>
 		 <li>
-			 [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js]
-			 by [link:https://github.com/BKcore BKcore]
+			 [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general?
+			 Check out the selection of literature recommended by the community.
 		 </li>
 		</ul>
 
-		<h3>Tutorials in other languages</h3>
+		<h2>News and Updates</h2>
 		<ul>
 			<li>
-				[link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese
+				[link:https://twitter.com/hashtag/threejs Three.js on Twitter]
 			</li>
-
-		</ul>
-
-		<h2>News and Updates</h2>
-		<ul>
 			<li>
 				[link:http://www.reddit.com/r/threejs/ Three.js on reddit]
 			</li>
@@ -84,9 +88,6 @@
 			<li>
 				[link:http://learningwebgl.com/blog/ Learning WebGL Blog] – The authoritive news source for WebGL.
 			</li>
-			<li>
-				[link:https://plus.google.com/104300307601542851567/posts Three.js posts] on Google+ – frequent posts on Three.js
-			</li>
 		</ul>
 
 		<h2>Examples</h2>
@@ -173,6 +174,13 @@
 			[link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js]
 			by [link:http://github.com/nrocy Paul King]
 		</li>
+		<li>
+			[link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js]
+			by [link:https://github.com/BKcore BKcore]
+		</li>
+		<li>
+			[link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese
+		</li>
 	 </ul>
 
 	</body>

+ 20 - 12
docs/manual/zh/introduction/Useful-links.html

@@ -31,6 +31,9 @@
 
 		<h3>three.js入门</h3>
 		<ul>
+			<li>
+				[link:https://threejsfundamentals.org/threejs/lessons/threejs-fundamentals.html Three.js Fundamentals starting lesson]
+			</li>
 			<li>
 				[link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith].
 			</li>
@@ -41,6 +44,12 @@
 
 		<h3>更加广泛、高级的文章与教程</h3>
 		<ul>
+			<li>
+				[link:https://discoverthreejs.com/ Discover three.js]
+			</li>
+			<li>
+				[link:https://threejsfundamentals.org/ Three.js Fundamentals]
+			</li>
 			<li>
 				[link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon].
 			</li>
@@ -58,21 +67,16 @@
 			 [link:http://learningthreejs.com/ Learning Three.js] – a blog with articles dedicated to teaching three.js
 		 </li>
 		 <li>
-			 [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js]
-			 by [link:https://github.com/BKcore BKcore]
+			 [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general?
+			 Check out the selection of literature recommended by the community.
 		 </li>
 		</ul>
 
-		<h3>其它非英语的教程</h3>
+		<h2>新闻与更新</h2>
 		<ul>
 			<li>
-				[link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese
+				[link:https://twitter.com/hashtag/threejs Three.js on Twitter]
 			</li>
-
-		</ul>
-
-		<h2>新闻与更新</h2>
-		<ul>
 			<li>
 				[link:http://www.reddit.com/r/threejs/ Three.js on reddit]
 			</li>
@@ -82,9 +86,6 @@
 			<li>
 				[link:http://learningwebgl.com/blog/ Learning WebGL Blog] – The authoritive news source for WebGL.
 			</li>
-			<li>
-				[link:https://plus.google.com/104300307601542851567/posts Three.js posts] on Google+ – frequent posts on Three.js
-			</li>
 		</ul>
 		<h2>示例</h2>
 		<ul>
@@ -165,6 +166,13 @@
 			[link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js]
 			by [link:http://github.com/nrocy Paul King]
 		</li>
+		<li>
+			[link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js]
+			by [link:https://github.com/BKcore BKcore]
+		</li>
+		<li>
+			[link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese
+		</li>
 	 </ul>
 
 	</body>

+ 58 - 25
docs/page.css

@@ -1,16 +1,25 @@
 @font-face {
-	font-family: 'inconsolata';
-	src: url('files/inconsolata.woff') format('woff');
+	font-family: 'RobotoMono';
+	src: local('RobotoMono'), url('../files/RobotoMono-Regular.woff2') format('woff2');
+	font-weight: normal;
+	font-style: normal;
+}
+
+@font-face {
+	font-family: 'SF-Pro-Text';
+	src: local('SF-Pro-Text'), url('../files/SF-Pro-Text-Regular.otf');
 	font-weight: normal;
 	font-style: normal;
 }
 
 body {
-	margin: 30px 20px;
+	margin: 78px auto;
+	padding: 0px 24px;
+	max-width: 780px;
 	color: #555;
-	font-family: 'inconsolata';
-	font-size: 15px;
-	line-height: 18px;
+	font-family: 'SF-Pro-Text', sans-serif;
+	font-size: 16px;
+	line-height: 23px;
 	tab-size: 4;
 	overflow: auto;
 }
@@ -22,19 +31,18 @@ a {
 }
 
 h1 {
-	color: #333;
-	font-size: 25px;
+	color: #049EF4;
+	font-size: 32px;
 	font-weight: normal;
-
-	margin-top: 10px;
+	line-height: 42px;
 }
 
 h2 {
 	color: #4B0;
-	font-size: 18px;
-	font-weight: normal;
 
-	margin-top: 40px;
+	font-size: 22px;
+	font-weight: normal;
+	line-height: 31px;
 }
 
 h3 {
@@ -67,9 +75,6 @@ pre, code {
 
 code {
 	display: block;
-	width: -webkit-calc( 100% - 40px );
-	width: -moz-calc( 100% - 40px );
-	width: calc( 100% - 40px );
 	padding: 20px;
 	white-space: pre-wrap;
 	background-color: #f9f9f9;
@@ -104,19 +109,26 @@ strong {
 
 #button {
 	position: fixed;
-	top: 20px;
-	right: 20px;
+	bottom: 16px;
+	right: 16px;
+
 	padding: 8px;
-	color: #fff;
-	background-color: #555;
-	opacity: 0.5;
-}
+	border-radius: 50%;
+	margin-bottom: 0px; /* TODO div sets it to 20px */
 
-#button:hover {
-	cursor: pointer;
-	opacity: 1;
+	background-color: #dddddd;
+	opacity: 0.4;
 }
 
+	#button:hover {
+		cursor: pointer;
+		opacity: 1;
+	}
+
+	#button img {
+		display: block;
+	}
+
 a.permalink {
 	float: right;
 	margin-left: 5px;
@@ -141,3 +153,24 @@ sup, sub {
 sub {
 	top: 0.4em;
 }
+
+/* mobile */
+
+@media all and ( max-width: 640px ) {
+
+	body {
+		margin: 14px auto;
+		padding: 0px 14px;
+		font-size: 14px;
+		line-height: 22px;
+	}
+
+	h1 {
+		font-size: 26px;
+	}
+
+	h2 {
+		font-size: 18px;
+		line-height: 25px;
+	}
+}

+ 1 - 2
docs/page.js

@@ -97,8 +97,7 @@ function onDocumentLoad( event ) {
 
 	var button = document.createElement( 'div' );
 	button.id = 'button';
-	button.textContent = 'Edit';
-
+	button.innerHTML = '<img src="../files/ic_mode_edit_black_24dp.svg">';
 	button.addEventListener( 'click', function ( event ) {
 
 		window.open( 'https://github.com/mrdoob/three.js/blob/dev/docs/' + section + '/' + localizedPath + '.html' );

+ 4 - 2
docs/prettify/threejs.css

@@ -10,6 +10,8 @@ pre .dec, code .dec { color: #22c0c4; } /* decimal */
 
 
 pre.prettyprint, code.prettyprint {
-	background-color: #f9f9f9;
-	font-family: 'inconsolata';
+	background-color: #F5F5F5;
+	font-family: 'RobotoMono', monospace;
+	font-size: 14px;
+	line-height: 21px;
 }

+ 5 - 16
docs/scenes/bones-browser.html

@@ -3,22 +3,9 @@
 	<head>
 		<meta charset="utf-8">
 		<title>Three.js Bones Browser</title>
+		<link rel="shortcut icon" href="../../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../../files/main.css">
 		<style>
-			@font-face {
-				font-family: 'inconsolata';
-				src: url('../files/inconsolata.woff') format('woff');
-				font-weight: normal;
-				font-style: normal;
-			}
-
-			body {
-				margin:0;
-				font-family: 'inconsolata';
-				font-size: 15px;
-				line-height: 18px;
-				overflow: hidden;
-			}
-
 			canvas { width: 100%; height: 100% }
 
 			#newWindow {
@@ -49,7 +36,10 @@
 			function initScene() {
 
 				gui = new dat.GUI();
+
 				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x444444 );
+
 				camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 200 );
 				camera.position.z = 30;
 				camera.position.y = 30;
@@ -57,7 +47,6 @@
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setClearColor( 0x000000, 1 );
 				document.body.appendChild( renderer.domElement );
 
 				orbit = new THREE.OrbitControls( camera, renderer.domElement );

+ 5 - 16
docs/scenes/geometry-browser.html

@@ -3,22 +3,9 @@
 	<head>
 		<meta charset="utf-8">
 		<title>Three.js Geometry Browser</title>
+		<link rel="shortcut icon" href="../../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../../files/main.css">
 		<style>
-			@font-face {
-				font-family: 'inconsolata';
-				src: url('../files/inconsolata.woff') format('woff');
-				font-weight: normal;
-				font-style: normal;
-			}
-
-			body {
-				margin:0;
-				font-family: 'inconsolata';
-				font-size: 15px;
-				line-height: 18px;
-				overflow: hidden;
-			}
-
 			canvas { width: 100%; height: 100% }
 
 			#newWindow {
@@ -46,14 +33,16 @@
 			document.getElementById( 'newWindow' ).href += window.location.hash;
 
 			var gui = new dat.GUI();
+
 			var scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0x444444 );
+
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 50 );
 			camera.position.z = 30;
 
 			var renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
-			renderer.setClearColor( 0x000000, 1 );
 			document.body.appendChild( renderer.domElement );
 
 			var orbit = new THREE.OrbitControls( camera, renderer.domElement );

+ 14 - 4
docs/scenes/js/geometry.js

@@ -860,6 +860,7 @@ var guis = {
 			bevelEnabled: false,
 			bevelThickness: 1,
 			bevelSize: 0.5,
+			bevelOffset: 0.0,
 			bevelSegments: 3
 		};
 
@@ -887,6 +888,7 @@ var guis = {
 					bevelEnabled: data.bevelEnabled,
 					bevelThickness: data.bevelThickness,
 					bevelSize: data.bevelSize,
+					bevelOffset: data.bevelOffset,
 					bevelSegments: data.bevelSegments
 				} );
 				geometry.center();
@@ -910,7 +912,8 @@ var guis = {
 		folder.add( data, 'weight', weights ).onChange( generateGeometry );
 		folder.add( data, 'bevelEnabled' ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 0.1, 3 ).onChange( generateGeometry );
-		folder.add( data, 'bevelSize', 0.1, 3 ).onChange( generateGeometry );
+		folder.add( data, 'bevelSize', 0, 3 ).onChange( generateGeometry );
+		folder.add( data, 'bevelOffset', -0.5, 1.5 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSegments', 0, 8 ).step( 1 ).onChange( generateGeometry );
 
 		generateGeometry();
@@ -929,6 +932,7 @@ var guis = {
 			bevelEnabled: false,
 			bevelThickness: 1,
 			bevelSize: 0.5,
+			bevelOffset: 0.0,
 			bevelSegments: 3
 		};
 
@@ -956,6 +960,7 @@ var guis = {
 					bevelEnabled: data.bevelEnabled,
 					bevelThickness: data.bevelThickness,
 					bevelSize: data.bevelSize,
+					bevelOffset: data.bevelOffset,
 					bevelSegments: data.bevelSegments
 				} );
 				geometry.center();
@@ -979,7 +984,8 @@ var guis = {
 		folder.add( data, 'weight', weights ).onChange( generateGeometry );
 		folder.add( data, 'bevelEnabled' ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 0.1, 3 ).onChange( generateGeometry );
-		folder.add( data, 'bevelSize', 0.1, 3 ).onChange( generateGeometry );
+		folder.add( data, 'bevelSize', 0, 3 ).onChange( generateGeometry );
+		folder.add( data, 'bevelOffset', -0.5, 1.5 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSegments', 0, 8 ).step( 1 ).onChange( generateGeometry );
 
 		generateGeometry();
@@ -1276,6 +1282,7 @@ var guis = {
 			bevelEnabled: true,
 			bevelThickness: 1,
 			bevelSize: 1,
+			bevelOffset: 0,
 			bevelSegments: 1
 		};
 
@@ -1302,7 +1309,8 @@ var guis = {
 		folder.add( data, 'steps', 1, 10 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'depth', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 1, 5 ).step( 1 ).onChange( generateGeometry );
-		folder.add( data, 'bevelSize', 1, 5 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'bevelSize', 0, 5 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'bevelOffset', -4, 5 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSegments', 1, 5 ).step( 1 ).onChange( generateGeometry );
 
 		generateGeometry();
@@ -1317,6 +1325,7 @@ var guis = {
 			bevelEnabled: true,
 			bevelThickness: 1,
 			bevelSize: 1,
+			bevelOffset: 0,
 			bevelSegments: 1
 		};
 
@@ -1343,7 +1352,8 @@ var guis = {
 		folder.add( data, 'steps', 1, 10 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'depth', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 1, 5 ).step( 1 ).onChange( generateGeometry );
-		folder.add( data, 'bevelSize', 1, 5 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'bevelSize', 0, 5 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'bevelOffset', -4, 5 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSegments', 1, 5 ).step( 1 ).onChange( generateGeometry );
 
 		generateGeometry();

+ 74 - 2
docs/scenes/js/material.js

@@ -129,6 +129,20 @@ var diffuseMaps = ( function () {
 
 } )();
 
+var roughnessMaps = ( function () {
+
+	var bricks = textureLoader.load( '../../examples/textures/brick_roughness.jpg' );
+	bricks.wrapT = THREE.RepeatWrapping;
+	bricks.wrapS = THREE.RepeatWrapping;
+	bricks.repeat.set( 9, 1 );
+
+	return {
+		none: null,
+		bricks: bricks
+	};
+
+} )();
+
 var matcaps = ( function () {
 
 	return {
@@ -152,10 +166,30 @@ var alphaMaps = ( function () {
 
 } )();
 
+var gradientMaps = ( function () {
+
+	var threeTone = textureLoader.load( '../../examples/textures/gradientMaps/threeTone.jpg' );
+	threeTone.minFilter = THREE.NearestFilter;
+	threeTone.magFilter = THREE.NearestFilter;
+
+	var fiveTone = textureLoader.load( '../../examples/textures/gradientMaps/fiveTone.jpg' );
+	fiveTone.minFilter = THREE.NearestFilter;
+	fiveTone.magFilter = THREE.NearestFilter;
+
+	return {
+		none: null,
+		threeTone: threeTone,
+		fiveTone: fiveTone
+	};
+
+} )();
+
 var envMapKeys = getObjectsKeys( envMaps );
 var diffuseMapKeys = getObjectsKeys( diffuseMaps );
+var roughnessMapKeys = getObjectsKeys( roughnessMaps );
 var matcapKeys = getObjectsKeys( matcaps );
 var alphaMapKeys = getObjectsKeys( alphaMaps );
+var gradientMapKeys = getObjectsKeys( gradientMaps );
 
 function generateVertexColors( geometry ) {
 
@@ -437,6 +471,25 @@ function guiMeshPhongMaterial( gui, mesh, material, geometry ) {
 
 }
 
+function guiMeshToonMaterial( gui, mesh, material ) {
+
+	var data = {
+		color: material.color.getHex(),
+		map: diffuseMapKeys[ 0 ],
+		gradientMap: gradientMapKeys[ 1 ],
+		alphaMap: alphaMapKeys[ 0 ]
+	};
+
+	var folder = gui.addFolder( 'THREE.MeshToonMaterial' );
+
+	folder.addColor( data, 'color' ).onChange( handleColorChange( material.color ) );
+
+	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
+	folder.add( data, 'gradientMap', gradientMapKeys ).onChange( updateTexture( material, 'gradientMap', gradientMaps ) );
+	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
+
+}
+
 function guiMeshStandardMaterial( gui, mesh, material, geometry ) {
 
 	var data = {
@@ -444,6 +497,7 @@ function guiMeshStandardMaterial( gui, mesh, material, geometry ) {
 		emissive: material.emissive.getHex(),
 		envMaps: envMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
+		roughnessMap: roughnessMapKeys[ 0 ],
 		alphaMap: alphaMapKeys[ 0 ]
 	};
 
@@ -461,9 +515,10 @@ function guiMeshStandardMaterial( gui, mesh, material, geometry ) {
 	folder.add( material, 'fog' );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
+	folder.add( data, 'roughnessMap', roughnessMapKeys ).onChange( updateTexture( material, 'roughnessMap', roughnessMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 
-	// TODO roughnessMap and metalnessMap
+	// TODO metalnessMap
 
 }
 
@@ -474,6 +529,7 @@ function guiMeshPhysicalMaterial( gui, mesh, material, geometry ) {
 		emissive: material.emissive.getHex(),
 		envMaps: envMapKeys[ 0 ],
 		map: diffuseMapKeys[ 0 ],
+		roughnessMap: roughnessMapKeys[ 0 ],
 		alphaMap: alphaMapKeys[ 0 ]
 	};
 
@@ -494,9 +550,10 @@ function guiMeshPhysicalMaterial( gui, mesh, material, geometry ) {
 	folder.add( material, 'fog' );
 	folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
 	folder.add( data, 'map', diffuseMapKeys ).onChange( updateTexture( material, 'map', diffuseMaps ) );
+	folder.add( data, 'roughnessMap', roughnessMapKeys ).onChange( updateTexture( material, 'roughnessMap', roughnessMaps ) );
 	folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 
-	// TODO roughnessMap and metalnessMap
+	// TODO metalnessMap
 
 }
 
@@ -547,6 +604,21 @@ function chooseFromHash( gui, mesh, geometry ) {
 
 			break;
 
+		case 'MeshToonMaterial' :
+
+			material = new THREE.MeshToonMaterial( { color: 0x2194CE, gradientMap: gradientMaps.threeTone } );
+			guiMaterial( gui, mesh, material, geometry );
+			guiMeshToonMaterial( gui, mesh, material, geometry );
+
+			// only use a single point light
+
+			lights[ 0 ].visible = false;
+			lights[ 2 ].visible = false;
+
+			return material;
+
+			break;
+
 		case 'MeshStandardMaterial' :
 
 			material = new THREE.MeshStandardMaterial( { color: 0x2194CE } );

+ 5 - 16
docs/scenes/material-browser.html

@@ -3,22 +3,9 @@
 	<head>
 		<meta charset="utf-8">
 		<title>Three.js Material Browser</title>
+		<link rel="shortcut icon" href="../../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../../files/main.css">
 		<style>
-			@font-face {
-				font-family: 'inconsolata';
-				src: url('../files/inconsolata.woff') format('woff');
-				font-weight: normal;
-				font-style: normal;
-			}
-
-			body {
-				margin:0;
-				font-family: 'inconsolata';
-				font-size: 15px;
-				line-height: 18px;
-				overflow: hidden;
-			}
-
 			canvas { width: 100%; height: 100% }
 
 			#newWindow {
@@ -43,14 +30,16 @@
 			document.getElementById( 'newWindow' ).href += window.location.hash;
 
 			var gui = new dat.GUI();
+
 			var scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0x444444 );
+
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 10, 50 );
 			camera.position.z = 30;
 
 			var renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
-			renderer.setClearColor( 0x000000, 1 );
 			document.body.appendChild( renderer.domElement );
 
 			var ambientLight = new THREE.AmbientLight( 0x000000 );

+ 4 - 0
editor/index.html

@@ -6,6 +6,7 @@
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<link rel="apple-touch-icon" href="images/icon.png">
 		<link rel="manifest" href="manifest.json">
+		<link rel="shortcut icon" href="../files/favicon.ico" />
 	</head>
 	<body ontouchstart="">
 		<link rel="stylesheet" href="css/main.css">
@@ -126,8 +127,11 @@
 		<script src="js/Sidebar.Geometry.CircleGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CylinderGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.IcosahedronGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.OctahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.PlaneGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.RingGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.SphereGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.TetrahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TubeGeometry.js"></script>

+ 2 - 2
editor/js/History.js

@@ -173,7 +173,7 @@ History.prototype = {
 
 		// Append Undos to History
 
-		for ( var i = 0 ; i < this.undos.length; i ++ ) {
+		for ( var i = 0; i < this.undos.length; i ++ ) {
 
 			if ( this.undos[ i ].hasOwnProperty( "json" ) ) {
 
@@ -185,7 +185,7 @@ History.prototype = {
 
 		// Append Redos to History
 
-		for ( var i = 0 ; i < this.redos.length; i ++ ) {
+		for ( var i = 0; i < this.redos.length; i ++ ) {
 
 			if ( this.redos[ i ].hasOwnProperty( "json" ) ) {
 

+ 161 - 98
editor/js/Menubar.Add.js

@@ -37,23 +37,6 @@ Menubar.Add = function ( editor ) {
 
 	options.add( new UI.HorizontalRule() );
 
-	// Plane
-
-	var option = new UI.Row();
-	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/plane' ) );
-	option.onClick( function () {
-
-		var geometry = new THREE.PlaneBufferGeometry( 1, 1, 1, 1 );
-		var material = new THREE.MeshStandardMaterial();
-		var mesh = new THREE.Mesh( geometry, material );
-		mesh.name = 'Plane';
-
-		editor.execute( new AddObjectCommand( mesh ) );
-
-	} );
-	options.add( option );
-
 	// Box
 
 	var option = new UI.Row();
@@ -102,6 +85,101 @@ Menubar.Add = function ( editor ) {
 	} );
 	options.add( option );
 
+	// Icosahedron
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/icosahedron' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.IcosahedronBufferGeometry( 1, 0 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Icosahedron';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
+	// Lathe
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/lathe' ) );
+	option.onClick( function () {
+
+		var points = [
+			new THREE.Vector2( 0, 0 ),
+			new THREE.Vector2( 0.4, 0 ),
+			new THREE.Vector2( 0.35, 0.05 ),
+			new THREE.Vector2( 0.1, 0.075 ),
+			new THREE.Vector2( 0.08, 0.1 ),
+			new THREE.Vector2( 0.08, 0.4 ),
+			new THREE.Vector2( 0.1, 0.42 ),
+			new THREE.Vector2( 0.14, 0.48 ),
+			new THREE.Vector2( 0.2, 0.5 ),
+			new THREE.Vector2( 0.25, 0.54 ),
+			new THREE.Vector2( 0.3, 1.2 )
+		];
+
+		var geometry = new THREE.LatheBufferGeometry( points, 12, 0, Math.PI * 2 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
+		mesh.name = 'Lathe';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
+	// Octahedron
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/octahedron' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.OctahedronBufferGeometry( 1, 0 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Octahedron';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
+	// Plane
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/plane' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.PlaneBufferGeometry( 1, 1, 1, 1 );
+		var material = new THREE.MeshStandardMaterial();
+		var mesh = new THREE.Mesh( geometry, material );
+		mesh.name = 'Plane';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option )
+
+	// Ring
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/ring' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.RingBufferGeometry( 0.5, 1, 8, 1, 0, Math.PI * 2 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
+		mesh.name = 'Ring';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
 	// Sphere
 
 	var option = new UI.Row();
@@ -118,16 +196,31 @@ Menubar.Add = function ( editor ) {
 	} );
 	options.add( option );
 
-	// Icosahedron
+	// Sprite
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/icosahedron' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/sprite' ) );
 	option.onClick( function () {
 
-		var geometry = new THREE.IcosahedronBufferGeometry( 1, 0 );
+		var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
+		sprite.name = 'Sprite';
+
+		editor.execute( new AddObjectCommand( sprite ) );
+
+	} );
+	options.add( option );
+
+	// Tetrahedron
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/tetrahedron' ) );
+	option.onClick( function () {
+
+		var geometry = new THREE.TetrahedronBufferGeometry( 1, 0 );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-		mesh.name = 'Icosahedron';
+		mesh.name = 'Tetrahedron';
 
 		editor.execute( new AddObjectCommand( mesh ) );
 
@@ -218,161 +311,131 @@ Menubar.Add = function ( editor ) {
 	options.add( option );
 	*/
 
-	// Lathe
-
-	var option = new UI.Row();
-	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/lathe' ) );
-	option.onClick( function () {
-
-		var points = [
-			new THREE.Vector2( 0, 0 ),
-			new THREE.Vector2( 0.4, 0 ),
-			new THREE.Vector2( 0.35, 0.05 ),
-			new THREE.Vector2( 0.1, 0.075 ),
-			new THREE.Vector2( 0.08, 0.1 ),
-			new THREE.Vector2( 0.08, 0.4 ),
-			new THREE.Vector2( 0.1, 0.42 ),
-			new THREE.Vector2( 0.14, 0.48 ),
-			new THREE.Vector2( 0.2, 0.5 ),
-			new THREE.Vector2( 0.25, 0.54 ),
-			new THREE.Vector2( 0.3, 1.2 )
-		];
-
-		var geometry = new THREE.LatheBufferGeometry( points, 12, 0, Math.PI * 2 );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
-		mesh.name = 'Lathe';
-
-		editor.execute( new AddObjectCommand( mesh ) );
+	//
 
-	} );
-	options.add( option );
+	options.add( new UI.HorizontalRule() );
 
-	// Sprite
+	// AmbientLight
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/sprite' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/ambientlight' ) );
 	option.onClick( function () {
 
-		var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
-		sprite.name = 'Sprite';
+		var color = 0x222222;
 
-		editor.execute( new AddObjectCommand( sprite ) );
+		var light = new THREE.AmbientLight( color );
+		light.name = 'AmbientLight';
+
+		editor.execute( new AddObjectCommand( light ) );
 
 	} );
 	options.add( option );
 
-	//
-
-	options.add( new UI.HorizontalRule() );
-
-	// PointLight
+	// DirectionalLight
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/pointlight' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/directionallight' ) );
 	option.onClick( function () {
 
 		var color = 0xffffff;
 		var intensity = 1;
-		var distance = 0;
 
-		var light = new THREE.PointLight( color, intensity, distance );
-		light.name = 'PointLight';
+		var light = new THREE.DirectionalLight( color, intensity );
+		light.name = 'DirectionalLight';
+		light.target.name = 'DirectionalLight Target';
+
+		light.position.set( 5, 10, 7.5 );
 
 		editor.execute( new AddObjectCommand( light ) );
 
 	} );
 	options.add( option );
 
-	// SpotLight
+	// HemisphereLight
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/spotlight' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/hemispherelight' ) );
 	option.onClick( function () {
 
-		var color = 0xffffff;
+		var skyColor = 0x00aaff;
+		var groundColor = 0xffaa00;
 		var intensity = 1;
-		var distance = 0;
-		var angle = Math.PI * 0.1;
-		var penumbra = 0;
 
-		var light = new THREE.SpotLight( color, intensity, distance, angle, penumbra );
-		light.name = 'SpotLight';
-		light.target.name = 'SpotLight Target';
+		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
+		light.name = 'HemisphereLight';
 
-		light.position.set( 5, 10, 7.5 );
+		light.position.set( 0, 10, 0 );
 
 		editor.execute( new AddObjectCommand( light ) );
 
 	} );
 	options.add( option );
 
-	// DirectionalLight
+	// PointLight
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/directionallight' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/pointlight' ) );
 	option.onClick( function () {
 
 		var color = 0xffffff;
 		var intensity = 1;
+		var distance = 0;
 
-		var light = new THREE.DirectionalLight( color, intensity );
-		light.name = 'DirectionalLight';
-		light.target.name = 'DirectionalLight Target';
-
-		light.position.set( 5, 10, 7.5 );
+		var light = new THREE.PointLight( color, intensity, distance );
+		light.name = 'PointLight';
 
 		editor.execute( new AddObjectCommand( light ) );
 
 	} );
 	options.add( option );
 
-	// HemisphereLight
+	// SpotLight
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/hemispherelight' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/spotlight' ) );
 	option.onClick( function () {
 
-		var skyColor = 0x00aaff;
-		var groundColor = 0xffaa00;
+		var color = 0xffffff;
 		var intensity = 1;
+		var distance = 0;
+		var angle = Math.PI * 0.1;
+		var penumbra = 0;
 
-		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
-		light.name = 'HemisphereLight';
+		var light = new THREE.SpotLight( color, intensity, distance, angle, penumbra );
+		light.name = 'SpotLight';
+		light.target.name = 'SpotLight Target';
 
-		light.position.set( 0, 10, 0 );
+		light.position.set( 5, 10, 7.5 );
 
 		editor.execute( new AddObjectCommand( light ) );
 
 	} );
 	options.add( option );
 
-	// AmbientLight
+	//
+
+	options.add( new UI.HorizontalRule() );
+
+	// OrthographicCamera
 
 	var option = new UI.Row();
 	option.setClass( 'option' );
-	option.setTextContent( strings.getKey( 'menubar/add/ambientlight' ) );
+	option.setTextContent( strings.getKey( 'menubar/add/orthographiccamera' ) );
 	option.onClick( function () {
 
-		var color = 0x222222;
+		var camera = new THREE.OrthographicCamera();
+		camera.name = 'OrthographicCamera';
 
-		var light = new THREE.AmbientLight( color );
-		light.name = 'AmbientLight';
-
-		editor.execute( new AddObjectCommand( light ) );
+		editor.execute( new AddObjectCommand( camera ) );
 
 	} );
 	options.add( option );
 
-	//
-
-	options.add( new UI.HorizontalRule() );
-
 	// PerspectiveCamera
 
 	var option = new UI.Row();

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

@@ -199,6 +199,68 @@ Menubar.Edit = function ( editor ) {
 	} );
 	options.add( option );
 
+	options.add( new UI.HorizontalRule() );
+
+	// Set textures to sRGB. See #15903
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/edit/fixcolormaps' ) );
+	option.onClick( function () {
+
+		editor.scene.traverse( fixColorMap );
+
+	} );
+	options.add( option );
+
+	var colorMaps = [ 'map', 'envMap', 'emissiveMap' ];
+
+	function fixColorMap( obj ) {
+
+		var material = obj.material;
+
+		if ( material !== undefined ) {
+
+			if ( Array.isArray( material ) === true ) {
+
+				for ( var i = 0; i < material.length; i ++ ) {
+
+					fixMaterial( material[ i ] );
+
+				}
+
+			} else {
+
+				fixMaterial( material );
+
+			}
+
+			editor.signals.sceneGraphChanged.dispatch();
+
+		}
+
+	}
+
+	function fixMaterial( material ) {
+
+		var needsUpdate = material.needsUpdate;
+
+		for ( var i = 0; i < colorMaps.length; i ++ ) {
+
+			var map = material[ colorMaps[ i ] ];
+
+			if ( map ) {
+
+				map.encoding = THREE.sRGBEncoding;
+				needsUpdate = true;
+
+			}
+
+		}
+
+		material.needsUpdate = needsUpdate;
+
+	}
 
 	return container;
 

+ 1 - 4
editor/js/Menubar.File.js

@@ -393,14 +393,11 @@ Menubar.File = function ( editor ) {
 	//
 
 	var link = document.createElement( 'a' );
-	link.style.display = 'none';
-	document.body.appendChild( link ); // Firefox workaround, see #6594
-
 	function save( blob, filename ) {
 
 		link.href = URL.createObjectURL( blob );
 		link.download = filename || 'data.json';
-		link.click();
+		link.dispatchEvent( new MouseEvent( 'click' ) );
 
 		// URL.revokeObjectURL( url ); breaks Firefox...
 

+ 22 - 19
editor/js/Script.js

@@ -20,6 +20,7 @@ var Script = function ( editor ) {
 	header.add( title );
 
 	var buttonSVG = ( function () {
+
 		var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
 		svg.setAttribute( 'width', 32 );
 		svg.setAttribute( 'height', 32 );
@@ -28,6 +29,7 @@ var Script = function ( editor ) {
 		path.setAttribute( 'stroke', '#fff' );
 		svg.appendChild( path );
 		return svg;
+
 	} )();
 
 	var close = new UI.Element( buttonSVG );
@@ -80,7 +82,7 @@ var Script = function ( editor ) {
 
 			if ( ! validate( value ) ) return;
 
-			if ( typeof( currentScript ) === 'object' ) {
+			if ( typeof ( currentScript ) === 'object' ) {
 
 				if ( value !== currentScript.source ) {
 
@@ -88,6 +90,7 @@ var Script = function ( editor ) {
 
 				}
 				return;
+
 			}
 
 			if ( currentScript !== 'programInfo' ) return;
@@ -118,7 +121,7 @@ var Script = function ( editor ) {
 
 		}, 300 );
 
-	});
+	} );
 
 	// prevent backspace from deleting objects
 	var wrapper = codemirror.getWrapperElement();
@@ -177,7 +180,7 @@ var Script = function ( editor ) {
 					for ( var i = 0; i < errors.length; i ++ ) {
 
 						var error = errors[ i ];
-						error.message = error.message.replace(/Line [0-9]+: /, '');
+						error.message = error.message.replace( /Line [0-9]+: /, '' );
 
 					}
 
@@ -189,7 +192,7 @@ var Script = function ( editor ) {
 
 					jsonlint.parseError = function ( message, info ) {
 
-						message = message.split('\n')[3];
+						message = message.split( '\n' )[ 3 ];
 
 						errors.push( {
 
@@ -217,11 +220,11 @@ var Script = function ( editor ) {
 					try {
 
 						var shaderType = currentScript === 'vertexShader' ?
-								glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
+							glslprep.Shader.VERTEX : glslprep.Shader.FRAGMENT;
 
 						glslprep.parseGlsl( string, shaderType );
 
-					} catch( error ) {
+					} catch ( error ) {
 
 						if ( error instanceof glslprep.SyntaxError ) {
 
@@ -254,7 +257,7 @@ var Script = function ( editor ) {
 
 					for ( var i = 0, n = programs.length; i !== n; ++ i ) {
 
-						var diagnostics = programs[i].diagnostics;
+						var diagnostics = programs[ i ].diagnostics;
 
 						if ( diagnostics === undefined ||
 								diagnostics.material !== currentObject.material ) continue;
@@ -262,7 +265,7 @@ var Script = function ( editor ) {
 						if ( ! diagnostics.runnable ) valid = false;
 
 						var shaderInfo = diagnostics[ currentScript ];
-						var lineOffset = shaderInfo.prefix.split(/\r\n|\r|\n/).length;
+						var lineOffset = shaderInfo.prefix.split( /\r\n|\r|\n/ ).length;
 
 						while ( true ) {
 
@@ -305,7 +308,7 @@ var Script = function ( editor ) {
 
 			return valid !== undefined ? valid : errors.length === 0;
 
-		});
+		} );
 
 	};
 
@@ -317,23 +320,23 @@ var Script = function ( editor ) {
 	} );
 
 	codemirror.setOption( 'extraKeys', {
-		'Ctrl-Space': function(cm) { server.complete(cm); },
-		'Ctrl-I': function(cm) { server.showType(cm); },
-		'Ctrl-O': function(cm) { server.showDocs(cm); },
-		'Alt-.': function(cm) { server.jumpToDef(cm); },
-		'Alt-,': function(cm) { server.jumpBack(cm); },
-		'Ctrl-Q': function(cm) { server.rename(cm); },
-		'Ctrl-.': function(cm) { server.selectName(cm); }
+		'Ctrl-Space': function ( cm ) { server.complete( cm ); },
+		'Ctrl-I': function ( cm ) { server.showType( cm ); },
+		'Ctrl-O': function ( cm ) { server.showDocs( cm ); },
+		'Alt-.': function ( cm ) { server.jumpToDef( cm ); },
+		'Alt-,': function ( cm ) { server.jumpBack( cm ); },
+		'Ctrl-Q': function ( cm ) { server.rename( cm ); },
+		'Ctrl-.': function ( cm ) { server.selectName( cm ); }
 	} );
 
-	codemirror.on( 'cursorActivity', function( cm ) {
+	codemirror.on( 'cursorActivity', function ( cm ) {
 
 		if ( currentMode !== 'javascript' ) return;
 		server.updateArgHints( cm );
 
 	} );
 
-	codemirror.on( 'keypress', function( cm, kb ) {
+	codemirror.on( 'keypress', function ( cm, kb ) {
 
 		if ( currentMode !== 'javascript' ) return;
 		var typed = String.fromCharCode( kb.which || kb.keyCode );
@@ -358,7 +361,7 @@ var Script = function ( editor ) {
 
 		var mode, name, source;
 
-		if ( typeof( script ) === 'object' ) {
+		if ( typeof ( script ) === 'object' ) {
 
 			mode = 'javascript';
 			name = script.name;

+ 54 - 0
editor/js/Sidebar.Geometry.OctahedronGeometry.js

@@ -0,0 +1,54 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.OctahedronGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// radius
+
+	var radiusRow = new UI.Row();
+	var radius = new UI.Number( parameters.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/octahedron_geometry/radius' ) ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// detail
+
+	var detailRow = new UI.Row();
+	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
+
+	detailRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/octahedron_geometry/detail' ) ).setWidth( '90px' ) );
+	detailRow.add( detail );
+
+	container.add( detailRow );
+
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			radius.getValue(),
+			detail.getValue()
+		) ) );
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.OctahedronBufferGeometry = Sidebar.Geometry.OctahedronGeometry;

+ 95 - 0
editor/js/Sidebar.Geometry.RingGeometry.js

@@ -0,0 +1,95 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.RingGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// innerRadius
+
+	var innerRadiusRow = new UI.Row();
+	var innerRadius = new UI.Number( parameters.innerRadius ).onChange( update );
+
+	innerRadiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/innerRadius' ) ).setWidth( '90px' ) );
+	innerRadiusRow.add( innerRadius );
+
+	container.add( innerRadiusRow );
+
+	// outerRadius
+
+	var outerRadiusRow = new UI.Row();
+	var outerRadius = new UI.Number( parameters.outerRadius ).onChange( update );
+
+	outerRadiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/outerRadius' ) ).setWidth( '90px' ) );
+	outerRadiusRow.add( outerRadius );
+
+	container.add( outerRadiusRow );
+
+	// thetaSegments
+
+	var thetaSegmentsRow = new UI.Row();
+	var thetaSegments = new UI.Integer( parameters.thetaSegments ).setRange( 3, Infinity ).onChange( update );
+
+	thetaSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetaSegments' ) ).setWidth( '90px' ) );
+	thetaSegmentsRow.add( thetaSegments );
+
+	container.add( thetaSegmentsRow );
+
+	// phiSegments
+
+	var phiSegmentsRow = new UI.Row();
+	var phiSegments = new UI.Integer( parameters.phiSegments ).setRange( 3, Infinity ).onChange( update );
+
+	phiSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/phiSegments' ) ).setWidth( '90px' ) );
+	phiSegmentsRow.add( phiSegments );
+
+	container.add( phiSegmentsRow );
+
+	// thetaStart
+
+	var thetaStartRow = new UI.Row();
+	var thetaStart = new UI.Number( parameters.thetaStart * THREE.Math.RAD2DEG ).setStep( 10 ).onChange( update );
+
+	thetaStartRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetastart' ) ).setWidth( '90px' ) );
+	thetaStartRow.add( thetaStart );
+
+	container.add( thetaStartRow );
+
+	// thetaLength
+
+	var thetaLengthRow = new UI.Row();
+	var thetaLength = new UI.Number( parameters.thetaLength * THREE.Math.RAD2DEG ).setStep( 10 ).onChange( update );
+
+	thetaLengthRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/ring_geometry/thetalength' ) ).setWidth( '90px' ) );
+	thetaLengthRow.add( thetaLength );
+
+	container.add( thetaLengthRow );
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			innerRadius.getValue(),
+			outerRadius.getValue(),
+			thetaSegments.getValue(),
+			phiSegments.getValue(),
+			thetaStart.getValue() * THREE.Math.DEG2RAD,
+			thetaLength.getValue() * THREE.Math.DEG2RAD
+		) ) );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.RingBufferGeometry = Sidebar.Geometry.RingGeometry;

+ 55 - 0
editor/js/Sidebar.Geometry.TetrahedronGeometry.js

@@ -0,0 +1,55 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+
+Sidebar.Geometry.TetrahedronGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// radius
+
+	var radiusRow = new UI.Row();
+	var radius = new UI.Number( parameters.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/tetrahedron_geometry/radius' ) ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// detail
+
+	var detailRow = new UI.Row();
+	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
+
+	detailRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/tetrahedron_geometry/detail' ) ).setWidth( '90px' ) );
+	detailRow.add( detail );
+
+	container.add( detailRow );
+
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			radius.getValue(),
+			detail.getValue()
+		) ) );
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.TetrahedronBufferGeometry = Sidebar.Geometry.TetrahedronGeometry;

+ 25 - 3
editor/js/Sidebar.Material.js

@@ -289,7 +289,7 @@ Sidebar.Material = function ( editor ) {
 
 	var materialMapRow = new UI.Row();
 	var materialMapEnabled = new UI.Checkbox( false ).onChange( update );
-	var materialMap = new UI.Texture().onChange( update );
+	var materialMap = new UI.Texture().onChange( updateMaterial );
 
 	materialMapRow.add( new UI.Text( strings.getKey( 'sidebar/material/map' ) ).setWidth( '90px' ) );
 	materialMapRow.add( materialMapEnabled );
@@ -401,7 +401,7 @@ Sidebar.Material = function ( editor ) {
 
 	var materialEnvMapRow = new UI.Row();
 	var materialEnvMapEnabled = new UI.Checkbox( false ).onChange( update );
-	var materialEnvMap = new UI.Texture( THREE.SphericalReflectionMapping ).onChange( update );
+	var materialEnvMap = new UI.Texture( THREE.SphericalReflectionMapping ).onChange( updateMaterial );
 	var materialReflectivity = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
 
 	materialEnvMapRow.add( new UI.Text( strings.getKey( 'sidebar/material/envmap' ) ).setWidth( '90px' ) );
@@ -441,7 +441,7 @@ Sidebar.Material = function ( editor ) {
 
 	var materialEmissiveMapRow = new UI.Row();
 	var materialEmissiveMapEnabled = new UI.Checkbox( false ).onChange( update );
-	var materialEmissiveMap = new UI.Texture().onChange( update );
+	var materialEmissiveMap = new UI.Texture().onChange( updateMaterial );
 
 	materialEmissiveMapRow.add( new UI.Text( strings.getKey( 'sidebar/material/emissivemap' ) ).setWidth( '90px' ) );
 	materialEmissiveMapRow.add( materialEmissiveMapEnabled );
@@ -1061,6 +1061,28 @@ Sidebar.Material = function ( editor ) {
 
 	}
 
+	function updateMaterial( texture ) {
+
+		if ( texture !== null ) {
+
+			if ( texture.encoding !== THREE.sRGBEncoding ) {
+
+				texture.encoding = THREE.sRGBEncoding;
+				var object = currentObject;
+				if ( object !== null ) {
+
+					object.material.needsUpdate = true;
+
+				}
+
+			}
+
+		}
+
+		update();
+
+	}
+
 	//
 
 	function setRowVisibility() {

+ 112 - 6
editor/js/Sidebar.Object.js

@@ -146,6 +146,46 @@ Sidebar.Object = function ( editor ) {
 
 	container.add( objectFovRow );
 
+	// left
+
+	var objectLeftRow = new UI.Row();
+	var objectLeft = new UI.Number().onChange( update );
+
+	objectLeftRow.add( new UI.Text( strings.getKey( 'sidebar/object/left' ) ).setWidth( '90px' ) );
+	objectLeftRow.add( objectLeft );
+
+	container.add( objectLeftRow );
+
+	// right
+
+	var objectRightRow = new UI.Row();
+	var objectRight = new UI.Number().onChange( update );
+
+	objectRightRow.add( new UI.Text( strings.getKey( 'sidebar/object/right' ) ).setWidth( '90px' ) );
+	objectRightRow.add( objectRight );
+
+	container.add( objectRightRow );
+
+	// top
+
+	var objectTopRow = new UI.Row();
+	var objectTop = new UI.Number().onChange( update );
+
+	objectTopRow.add( new UI.Text( strings.getKey( 'sidebar/object/top' ) ).setWidth( '90px' ) );
+	objectTopRow.add( objectTop );
+
+	container.add( objectTopRow );
+
+	// bottom
+
+	var objectBottomRow = new UI.Row();
+	var objectBottom = new UI.Number().onChange( update );
+
+	objectBottomRow.add( new UI.Text( strings.getKey( 'sidebar/object/bottom' ) ).setWidth( '90px' ) );
+	objectBottomRow.add( objectBottom );
+
+	container.add( objectBottomRow );
+
 	// near
 
 	var objectNearRow = new UI.Row();
@@ -400,15 +440,53 @@ Sidebar.Object = function ( editor ) {
 
 			}
 
+			if ( object.left !== undefined && Math.abs( object.left - objectLeft.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'left', objectLeft.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.right !== undefined && Math.abs( object.right - objectRight.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'right', objectRight.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.top !== undefined && Math.abs( object.top - objectTop.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'top', objectTop.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.bottom !== undefined && Math.abs( object.bottom - objectBottom.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'bottom', objectBottom.getValue() ) );
+				object.updateProjectionMatrix();
+
+			}
+
 			if ( object.near !== undefined && Math.abs( object.near - objectNear.getValue() ) >= 0.01 ) {
 
 				editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
+				if ( object.isOrthographicCamera ) {
+
+					object.updateProjectionMatrix();
+
+				}
 
 			}
 
 			if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
 
 				editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
+				if ( object.isOrthographicCamera ) {
+
+					object.updateProjectionMatrix();
+
+				}
 
 			}
 
@@ -518,17 +596,21 @@ Sidebar.Object = function ( editor ) {
 
 		var properties = {
 			'fov': objectFovRow,
+			'left': objectLeftRow,
+			'right': objectRightRow,
+			'top': objectTopRow,
+			'bottom': objectBottomRow,
 			'near': objectNearRow,
 			'far': objectFarRow,
 			'intensity': objectIntensityRow,
 			'color': objectColorRow,
 			'groundColor': objectGroundColorRow,
-			'distance' : objectDistanceRow,
-			'angle' : objectAngleRow,
-			'penumbra' : objectPenumbraRow,
-			'decay' : objectDecayRow,
-			'castShadow' : objectShadowRow,
-			'receiveShadow' : objectReceiveShadow,
+			'distance': objectDistanceRow,
+			'angle': objectAngleRow,
+			'penumbra': objectPenumbraRow,
+			'decay': objectDecayRow,
+			'castShadow': objectShadowRow,
+			'receiveShadow': objectReceiveShadow,
 			'shadow': objectShadowRadius
 		};
 
@@ -617,6 +699,30 @@ Sidebar.Object = function ( editor ) {
 
 		}
 
+		if ( object.left !== undefined ) {
+
+			objectLeft.setValue( object.left );
+
+		}
+
+		if ( object.right !== undefined ) {
+
+			objectRight.setValue( object.right );
+
+		}
+
+		if ( object.top !== undefined ) {
+
+			objectTop.setValue( object.top );
+
+		}
+
+		if ( object.bottom !== undefined ) {
+
+			objectBottom.setValue( object.bottom );
+
+		}
+
 		if ( object.near !== undefined ) {
 
 			objectNear.setValue( object.near );

+ 3 - 3
editor/js/Sidebar.Settings.js

@@ -16,8 +16,8 @@ Sidebar.Settings = function ( editor ) {
 	// language
 
 	var options = {
-		'en': 'English',
-		'zh': '中文'
+		en: 'English',
+		zh: '中文'
 	};
 
 	var languageRow = new UI.Row();
@@ -72,7 +72,7 @@ Sidebar.Settings = function ( editor ) {
 	themeRow.add( new UI.Text( strings.getKey( 'sidebar/settings/theme' ) ).setWidth( '90px' ) );
 	themeRow.add( theme );
 
-	container.add( themeRow );	
+	container.add( themeRow );
 
 	container.add( new Sidebar.Settings.Shortcuts( editor ) );
 	container.add( new Sidebar.Settings.Viewport( editor ) );

+ 44 - 0
editor/js/Strings.js

@@ -31,6 +31,7 @@ var Strings = function ( config ) {
 			'menubar/edit/clone': 'Clone',
 			'menubar/edit/delete': 'Delete (Del)',
 			'menubar/edit/minify_shaders': 'Minify Shaders',
+			'menubar/edit/fixcolormaps': 'Fix Color Maps',
 
 			'menubar/add': 'Add',
 			'menubar/add/group': 'Group',
@@ -38,8 +39,11 @@ var Strings = function ( config ) {
 			'menubar/add/box': 'Box',
 			'menubar/add/circle': 'Circle',
 			'menubar/add/cylinder': 'Cylinder',
+			'menubar/add/ring': 'Ring',
 			'menubar/add/sphere': 'Sphere',
 			'menubar/add/icosahedron': 'Icosahedron',
+			'menubar/add/octahedron': 'Octahedron',
+			'menubar/add/tetrahedron': 'Tetrahedron',
 			'menubar/add/torus': 'Torus',
 			'menubar/add/tube': 'Tube',
 			'menubar/add/torusknot': 'TorusKnot',
@@ -51,6 +55,7 @@ var Strings = function ( config ) {
 			'menubar/add/hemispherelight': 'HemisphereLight',
 			'menubar/add/ambientlight': 'AmbientLight',
 			'menubar/add/perspectivecamera': 'PerspectiveCamera',
+			'menubar/add/orthographiccamera': 'OrthographicCamera',
 
 			'menubar/status/autosave': 'autosave',
 
@@ -81,6 +86,10 @@ var Strings = function ( config ) {
 			'sidebar/object/rotation': 'Rotation',
 			'sidebar/object/scale': 'Scale',
 			'sidebar/object/fov': 'Fov',
+			'sidebar/object/left': 'Left',
+			'sidebar/object/right': 'Right',
+			'sidebar/object/top': 'Top',
+			'sidebar/object/bottom': 'Bottom',
 			'sidebar/object/near': 'Near',
 			'sidebar/object/far': 'Far',
 			'sidebar/object/intensity': 'Intensity',
@@ -132,6 +141,12 @@ var Strings = function ( config ) {
 			'sidebar/geometry/icosahedron_geometry/radius': 'Radius',
 			'sidebar/geometry/icosahedron_geometry/detail': 'Detail',
 
+			'sidebar/geometry/octahedron_geometry/radius': 'Radius',
+			'sidebar/geometry/octahedron_geometry/detail': 'Detail',
+
+			'sidebar/geometry/tetrahedron_geometry/radius': 'Radius',
+			'sidebar/geometry/tetrahedron_geometry/detail': 'Detail',
+
 			'sidebar/geometry/lathe_geometry/segments': 'Segments',
 			'sidebar/geometry/lathe_geometry/phistart': 'Phi start (°)',
 			'sidebar/geometry/lathe_geometry/philength': 'Phi length (°)',
@@ -142,6 +157,13 @@ var Strings = function ( config ) {
 			'sidebar/geometry/plane_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/plane_geometry/heightsegments': 'Height segments',
 
+			'sidebar/geometry/ring_geometry/innerRadius': 'Inner radius',
+			'sidebar/geometry/ring_geometry/outerRadius': 'Outer radius',
+			'sidebar/geometry/ring_geometry/thetaSegments': 'Theta segments',
+			'sidebar/geometry/ring_geometry/phiSegments': 'Phi segments',
+			'sidebar/geometry/ring_geometry/thetastart': 'Theta start',
+			'sidebar/geometry/ring_geometry/thetalength': 'Theta length',
+
 			'sidebar/geometry/sphere_geometry/radius': 'Radius',
 			'sidebar/geometry/sphere_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/sphere_geometry/heightsegments': 'Height segments',
@@ -291,6 +313,7 @@ var Strings = function ( config ) {
 			'menubar/edit/clone': '拷贝',
 			'menubar/edit/delete': '删除 (Del)',
 			'menubar/edit/minify_shaders': '压缩着色器',
+			'menubar/edit/fixcolormaps': '修复颜色贴图',
 
 			'menubar/add': '添加',
 			'menubar/add/group': '组',
@@ -298,8 +321,11 @@ var Strings = function ( config ) {
 			'menubar/add/box': '正方体',
 			'menubar/add/circle': '圆',
 			'menubar/add/cylinder': '圆柱体',
+			'menubar/add/ring': '环',
 			'menubar/add/sphere': '球体',
 			'menubar/add/icosahedron': '二十面体',
+			'menubar/add/octahedron': '八面体',
+			'menubar/add/tetrahedron': '四面体',
 			'menubar/add/torus': '圆环体',
 			'menubar/add/torusknot': '环面纽结体',
 			'menubar/add/tube': '管',
@@ -311,6 +337,7 @@ var Strings = function ( config ) {
 			'menubar/add/hemispherelight': '半球光',
 			'menubar/add/ambientlight': '环境光',
 			'menubar/add/perspectivecamera': '透视相机',
+			'menubar/add/orthographiccamera': '正交相机',
 
 			'menubar/status/autosave': '自动保存',
 
@@ -341,6 +368,10 @@ var Strings = function ( config ) {
 			'sidebar/object/rotation': '旋转',
 			'sidebar/object/scale': '缩放',
 			'sidebar/object/fov': '视角',
+			'sidebar/object/left': '左',
+			'sidebar/object/right': '右',
+			'sidebar/object/top': '上',
+			'sidebar/object/bottom': '下',
 			'sidebar/object/near': '近点',
 			'sidebar/object/far': '远点',
 			'sidebar/object/intensity': '强度',
@@ -392,6 +423,12 @@ var Strings = function ( config ) {
 			'sidebar/geometry/icosahedron_geometry/radius': '半径',
 			'sidebar/geometry/icosahedron_geometry/detail': '面片分段',
 
+			'sidebar/geometry/octahedron_geometry/radius': '半径',
+			'sidebar/geometry/octahedron_geometry/detail': '面片分段',
+
+			'sidebar/geometry/tetrahedron_geometry/radius': '半径',
+			'sidebar/geometry/tetrahedron_geometry/detail': '面片分段',
+
 			'sidebar/geometry/lathe_geometry/segments': '分段',
 			'sidebar/geometry/lathe_geometry/phistart': '经度起点',
 			'sidebar/geometry/lathe_geometry/philength': '经度长度',
@@ -402,6 +439,13 @@ var Strings = function ( config ) {
 			'sidebar/geometry/plane_geometry/widthsegments': '宽度分段',
 			'sidebar/geometry/plane_geometry/heightsegments': '长度分段',
 
+			'sidebar/geometry/ring_geometry/innerRadius': '内半径',
+			'sidebar/geometry/ring_geometry/outerRadius': '外半径',
+			'sidebar/geometry/ring_geometry/thetaSegments': '弧度分段',
+			'sidebar/geometry/ring_geometry/phiSegments': '经度分段',
+			'sidebar/geometry/ring_geometry/thetastart': '弧度起点',
+			'sidebar/geometry/ring_geometry/thetalength': '弧度长度',
+
 			'sidebar/geometry/sphere_geometry/radius': '半径',
 			'sidebar/geometry/sphere_geometry/widthsegments': '宽度分段',
 			'sidebar/geometry/sphere_geometry/heightsegments': '长度分段',

+ 11 - 3
editor/js/Viewport.js

@@ -503,10 +503,18 @@ var Viewport = function ( editor ) {
 
 	signals.viewportCameraChanged.add( function ( viewportCamera ) {
 
-		camera = viewportCamera;
+		if ( viewportCamera.isPerspectiveCamera ) {
+
+			viewportCamera.aspect = editor.camera.aspect;
+			viewportCamera.projectionMatrix.copy( editor.camera.projectionMatrix );
+
+		} else if ( ! viewportCamera.isOrthographicCamera ) {
+
+			throw "Invalid camera set as viewport";
 
-		camera.aspect = editor.camera.aspect;
-		camera.projectionMatrix.copy( editor.camera.projectionMatrix );
+		}
+
+		camera = viewportCamera;
 
 		render();
 

+ 20 - 3
editor/js/libs/ui.three.js

@@ -64,7 +64,7 @@ UI.Texture = function ( mapping ) {
 
 					scope.setValue( texture );
 
-					if ( scope.onChangeCallback ) scope.onChangeCallback();
+					if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
 
 				}, false );
 
@@ -75,7 +75,7 @@ UI.Texture = function ( mapping ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 					var image = document.createElement( 'img' );
-					image.addEventListener( 'load', function( event ) {
+					image.addEventListener( 'load', function ( event ) {
 
 						var texture = new THREE.Texture( this, mapping );
 						texture.sourceFile = file.name;
@@ -84,7 +84,7 @@ UI.Texture = function ( mapping ) {
 
 						scope.setValue( texture );
 
-						if ( scope.onChangeCallback ) scope.onChangeCallback();
+						if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
 
 					}, false );
 
@@ -161,6 +161,19 @@ UI.Texture.prototype.setValue = function ( texture ) {
 
 };
 
+UI.Texture.prototype.setEncoding = function ( encoding ) {
+
+	var texture = this.getValue();
+	if ( texture !== null ) {
+
+		texture.encoding = encoding;
+
+	}
+
+	return this;
+
+};
+
 UI.Texture.prototype.onChange = function ( callback ) {
 
 	this.onChangeCallback = callback;
@@ -188,11 +201,13 @@ UI.Outliner = function ( editor ) {
 	dom.addEventListener( 'keydown', function ( event ) {
 
 		switch ( event.keyCode ) {
+
 			case 38: // up
 			case 40: // down
 				event.preventDefault();
 				event.stopPropagation();
 				break;
+
 		}
 
 	}, false );
@@ -201,12 +216,14 @@ UI.Outliner = function ( editor ) {
 	dom.addEventListener( 'keyup', function ( event ) {
 
 		switch ( event.keyCode ) {
+
 			case 38: // up
 				scope.selectIndex( scope.selectedIndex - 1 );
 				break;
 			case 40: // down
 				scope.selectIndex( scope.selectedIndex + 1 );
 				break;
+
 		}
 
 	}, false );

+ 1 - 1
editor/sw.js

@@ -1,4 +1,4 @@
-// r103
+// r104
 
 const staticAssets = [
 	'./',

+ 2 - 2
examples/css2d_label.html

@@ -64,8 +64,6 @@
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
 				camera.position.set( 10, 5, 20 );
 
-				var controls = new THREE.OrbitControls( camera );
-
 				scene = new THREE.Scene();
 
 				var dirLight = new THREE.DirectionalLight( 0xffffff );
@@ -128,6 +126,8 @@
 				labelRenderer.domElement.style.top = 0;
 				document.body.appendChild( labelRenderer.domElement );
 
+				var controls = new THREE.OrbitControls( camera, labelRenderer.domElement );
+
 			}
 
 			function animate() {

+ 2 - 2
examples/css3d_orthographic.html

@@ -53,8 +53,6 @@
 
 				camera.position.set( - 200, 200, 200 );
 
-				var controls = new THREE.OrbitControls( camera );
-
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0xf0f0f0 );
 
@@ -104,6 +102,8 @@
 				renderer2.domElement.style.top = 0;
 				document.body.appendChild( renderer2.domElement );
 
+				var controls = new THREE.OrbitControls( camera, renderer2.domElement );
+
 				function createPlane( width, height, cssColor, pos, rot ) {
 
 					var element = document.createElement( 'div' );

+ 2 - 2
examples/css3d_sandbox.html

@@ -52,8 +52,6 @@
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
 				camera.position.set( 200, 200, 200 );
 
-				controls = new THREE.TrackballControls( camera );
-
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0xf0f0f0 );
 
@@ -104,6 +102,8 @@
 				renderer2.domElement.style.top = 0;
 				document.body.appendChild( renderer2.domElement );
 
+				controls = new THREE.TrackballControls( camera, renderer2.domElement );
+
 			}
 
 			function animate() {

+ 1 - 1
examples/css3d_youtube.html

@@ -76,7 +76,7 @@
 				group.add( new Element( '9ubytEsCaS0', - 240, 0, 0, - Math.PI / 2 ) );
 				scene.add( group );
 
-				controls = new THREE.TrackballControls( camera );
+				controls = new THREE.TrackballControls( camera, renderer.domElement );
 				controls.rotateSpeed = 4;
 
 				window.addEventListener( 'resize', onWindowResize, false );

+ 5 - 3
examples/files.js

@@ -60,7 +60,7 @@ var files = {
 		"webgl_kinect",
 		"webgl_layers",
 		"webgl_lensflares",
-		"webgl_lightningstrike",
+		"webgl_lightprobe",
 		"webgl_lights_hemisphere",
 		"webgl_lights_physical",
 		"webgl_lights_pointlights",
@@ -68,7 +68,6 @@ var files = {
 		"webgl_lights_spotlight",
 		"webgl_lights_spotlights",
 		"webgl_lights_rectarealight",
-		"webgl_lightshafts",
 		"webgl_lines_colors",
 		"webgl_lines_dashed",
 		"webgl_lines_fat",
@@ -273,7 +272,8 @@ var files = {
 		"webgl_postprocessing_sobel",
 		"webgl_postprocessing_ssao",
 		"webgl_postprocessing_taa",
-		"webgl_postprocessing_unreal_bloom"
+		"webgl_postprocessing_unreal_bloom",
+		"webgl_postprocessing_unreal_bloom_selective"
 	],
 	"webgl / advanced": [
 		"webgl_buffergeometry",
@@ -305,6 +305,8 @@ var files = {
 		"webgl_gpgpu_water",
 		"webgl_gpgpu_protoplanet",
 		"webgl_gpu_particle_system",
+		"webgl_lightningstrike",
+		"webgl_lightshafts",
 		"webgl_materials_modified",
 		"webgl_raymarching_reflect",
 		"webgl_shadowmap_pcss",

BIN
examples/files/inconsolata.woff


+ 26 - 221
examples/index.html

@@ -1,90 +1,16 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js / examples</title>
 		<meta charset="utf-8">
+		<title>three.js / examples</title>
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link rel="shortcut icon" href="../files/favicon.ico" />
+		<link rel="stylesheet" type="text/css" href="../files/main.css">
 		<style>
-
-			@font-face {
-				font-family: 'inconsolata';
-				src: url('files/inconsolata.woff') format('woff');
-				font-weight: normal;
-				font-style: normal;
-			}
-
-			* {
-				box-sizing: border-box;
-			}
-
-			html {
-				height: 100%;
-			}
-
-			body {
-				background-color: #ffffff;
-				margin: 0px;
-				height: 100%;
-				color: #555;
-				font-family: 'inconsolata';
-				font-size: 15px;
-				line-height: 18px;
-				overflow: hidden;
-			}
-
-			h1 {
-				margin-top: 30px;
-				margin-bottom: 40px;
-				margin-left: 20px;
-				font-size: 25px;
-				font-weight: normal;
-			}
-
-			h2 {
-				font-size: 20px;
-				font-weight: normal;
-			}
-
-			a {
-				color: #2194CE;
-				text-decoration: none;
-			}
-
-			#panel {
-				position: fixed;
-				left: 0px;
-				width: 310px;
-				height: 100%;
-				overflow: auto;
-				background: #fafafa;
-			}
-
-			#panel #content {
-				padding: 0px 20px 20px 20px;
-			}
-
 			#panel #content .link {
-				color: #2194CE;
+				display: block;
 				text-decoration: none;
 				cursor: pointer;
-				display: block;
-			}
-
-			#panel #content .selected {
-				color: #ff0000;
-			}
-
-			#panel #content .link:hover {
-				text-decoration: underline;
-			}
-
-			#viewer {
-				position: absolute;
-				border: 0px;
-				left: 310px;
-				width: calc(100% - 310px);
-				height: 100%;
-				overflow: auto;
 			}
 
 			#viewSrcButton {
@@ -101,142 +27,30 @@
 				cursor: pointer;
 				opacity: 1;
 			}
+		</style>
+	</head>
+	<body>
 
-			.filterBlock{
-				margin: 20px;
-				position: relative;
-			}
-
-			.filterBlock p {
-				margin: 0;
-			}
-
-			#filterInput {
-				width: 100%;
-				padding: 5px;
-				font-family: inherit;
-				font-size: 15px;
-				outline: none;
-				border: 1px solid #dedede;
-			}
-
-			#filterInput:focus{
-				border: 1px solid #2194CE;
-			}
-
-			#clearFilterButton {
-				position: absolute;
-				right: 6px;
-				top: 50%;
-				margin-top: -8px;
-				width: 16px;
-				height: 16px;
-				font-size: 14px;
-				color: grey;
-				text-align: center;
-				line-height: 0;
-				padding-top: 7px;
-				opacity: .5;
-			}
-
-			#clearFilterButton:hover {
-				opacity: 1;
-			}
-
-			.filtered {
-				display: none !important;
-			}
-
-			#panel li b {
-				font-weight: bold;
-			}
-
-			/* mobile */
-
-			#expandButton {
-				display: none;
-				position: absolute;
-				right: 20px;
-				top: 12px;
-				width: 32px;
-				height: 32px;
-			}
+		<div id="panel">
 
-			#expandButton span {
-				height: 2px;
-				background-color: #2194CE;
-				width: 16px;
-				position: absolute;
-				left: 8px;
-				top: 10px;
-			}
+			<div id="header">
 
-			#expandButton span:nth-child(1) {
-				top: 16px;
-			}
+				<h1><a href="http://threejs.org">three.js</a></h1>
 
-			#expandButton span:nth-child(2) {
-				top: 22px;
-			}
+				<img id="expandButton" src="../files/ic_menu_black_24dp.svg">
 
-			@media all and ( max-width: 640px ) {
-				h1{
-					margin-top: 20px;
-					margin-bottom: 20px;
-				}
-				#panel{
-					position: absolute;
-					left: 0;
-					top: 0;
-					height: 480px;
-					width: 100%;
-					right: 0;
-					z-index: 100;
-					overflow: hidden;
-					border-bottom: 1px solid #dedede;
-				}
-				#content{
-					position: absolute;
-					left: 0;
-					top: 90px;
-					right: 0;
-					bottom: 0;
-					font-size: 17px;
-					line-height: 22px;
-					overflow: auto;
-				}
-				#viewer{
-					position: absolute;
-					left: 0;
-					top: 56px;
-					width: 100%;
-					height: calc(100% - 56px);
-				}
-				#expandButton{
-					display: block;
-				}
-				#panel.collapsed{
-					height: 56px;
-				}
-			}
+				<div id="sections">
+					<a href="../docs/">docs</a> <span class="selected">examples</span>
+				</div>
 
-		</style>
-	</head>
-	<body>
+				<input type="text" id="filter" autocorrect="off" autocapitalize="off" spellcheck="false"/>
 
-		<div id="panel">
-			<h1><a href="http://threejs.org">three.js</a> / examples</h1>
-			<a id="expandButton" href="#">
-				<span></span>
-				<span></span>
-				<span></span>
-			</a>
-			<div class="filterBlock" >
-				<input type="text" id="filterInput" placeholder="Type to filter" autocorrect="off" autocapitalize="off" spellcheck="false"/>
-				<a href="#" id="clearFilterButton" >x</a>
 			</div>
+
 			<div id="content"></div>
+
 		</div>
+
 		<iframe id="viewer" name="viewer" allowfullscreen allowvr onmousewheel=""></iframe>
 
 		<script src="files.js"></script>
@@ -255,8 +69,7 @@
 		var content = document.getElementById( 'content' );
 		var viewer = document.getElementById( 'viewer' );
 
-		var filterInput = document.getElementById( 'filterInput' );
-		var clearFilterButton = document.getElementById( 'clearFilterButton' );
+		var filterInput = document.getElementById( 'filter' );
 
 		var expandButton = document.getElementById( 'expandButton' );
 		expandButton.addEventListener( 'click', function ( event ) {
@@ -351,7 +164,7 @@
 			// Reveal "View source" button and set attributes to this example
 			viewSrcButton.style.display = '';
 			viewSrcButton.href = 'https://github.com/mrdoob/three.js/blob/master/examples/' + selected + '.html';
-			viewSrcButton.title = 'View source code for ' + getName(selected) + ' on GitHub';
+			viewSrcButton.title = 'View source code for ' + getName( selected ) + ' on GitHub';
 
 		}
 
@@ -369,14 +182,6 @@
 
 		} );
 
-		clearFilterButton.addEventListener( 'click', function( e ) {
-
-			filterInput.value = '';
-			updateFilter();
-			e.preventDefault();
-
-		} );
-
 		function updateFilter() {
 
 			var v = filterInput.value;
@@ -413,7 +218,7 @@
 
 			if ( res && res.length > 0 ) {
 
-				link.classList.remove( 'filtered' );
+				link.classList.remove( 'hidden' );
 
 				for( var i = 0; i < res.length; i++ ) {
 					text = name.replace( res[ i ], '<b>' + res[ i ] + '</b>' );
@@ -423,7 +228,7 @@
 
 			} else {
 
-				link.classList.add( 'filtered' );
+				link.classList.add( 'hidden' );
 				link.innerHTML = name;
 
 			}
@@ -449,7 +254,7 @@
 
 					var file = section[ i ];
 
-					if( !links[ file ].classList.contains( 'filtered' ) ){
+					if ( links[ file ].classList.contains( 'hidden' ) === false ){
 
 						collapsed = false;
 						break;
@@ -460,13 +265,13 @@
 
 				var element = document.querySelector( 'h2[data-category="' + key + '"]' );
 
-				if( collapsed ){
+				if ( collapsed ) {
 
-					element.classList.add( 'filtered' );
+					element.classList.add( 'hidden' );
 
 				} else {
 
-					element.classList.remove( 'filtered' );
+					element.classList.remove( 'hidden' );
 
 				}
 

+ 102 - 0
examples/js/QuickHull.js

@@ -10,6 +10,8 @@
 	var Visible = 0;
 	var Deleted = 1;
 
+	var v1 = new THREE.Vector3();
+
 	function QuickHull() {
 
 		this.tolerance = - 1;
@@ -119,6 +121,106 @@
 
 		},
 
+		containsPoint: function ( point ) {
+
+			var faces = this.faces;
+
+			for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+				var face = faces[ i ];
+
+				// compute signed distance and check on what half space the point lies
+
+				if ( face.distanceToPoint( point ) > this.tolerance ) return false;
+
+			}
+
+			return true;
+
+		},
+
+		intersectRay: function ( ray, target ) {
+
+			// based on "Fast Ray-Convex Polyhedron Intersection"  by Eric Haines, GRAPHICS GEMS II
+
+			var faces = this.faces;
+
+			var tNear = - Infinity;
+			var tFar = Infinity;
+
+			for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+				var face = faces[ i ];
+
+				// interpret faces as planes for the further computation
+
+				var vN = face.distanceToPoint( ray.origin );
+				var vD = face.normal.dot( ray.direction );
+
+				// if the origin is on the positive side of a plane (so the plane can "see" the origin) and
+				// the ray is turned away or parallel to the plane, there is no intersection
+
+				if ( vN > 0 && vD >= 0 ) return null;
+
+				// compute the distance from the ray’s origin to the intersection with the plane
+
+				var t = ( vD !== 0 ) ? ( - vN / vD ) : 0;
+
+				// only proceed if the distance is positive. a negative distance means the intersection point
+				// lies "behind" the origin
+
+				if ( t <= 0 ) continue;
+
+				// now categorized plane as front-facing or back-facing
+
+				if ( vD > 0 ) {
+
+					//  plane faces away from the ray, so this plane is a back-face
+
+					tFar = Math.min( t, tFar );
+
+				} else {
+
+					// front-face
+
+					tNear = Math.max( t, tNear );
+
+				}
+
+				if ( tNear > tFar ) {
+
+					// if tNear ever is greater than tFar, the ray must miss the convex hull
+
+					return null;
+
+				}
+
+			}
+
+			// evaluate intersection point
+
+			// always try tNear first since its the closer intersection point
+
+			if ( tNear !== - Infinity ) {
+
+				ray.at( tNear, target );
+
+			} else {
+
+				ray.at( tFar, target );
+
+			}
+
+			return target;
+
+		},
+
+		intersectsRay: function ( ray ) {
+
+			return this.intersectRay( ray, v1 ) !== null;
+
+		},
+
 		makeEmpty: function () {
 
 			this.faces = [];

+ 1 - 1
examples/js/controls/DragControls.js

@@ -21,7 +21,7 @@ THREE.DragControls = function ( _objects, _camera, _domElement ) {
 	var _intersection = new THREE.Vector3();
 	var _worldPosition = new THREE.Vector3();
 	var _inverseMatrix = new THREE.Matrix4();
-	
+
 	var _selected = null, _hovered = null;
 
 	//

+ 23 - 23
examples/js/controls/PointerLockControls.js

@@ -5,19 +5,20 @@
 
 THREE.PointerLockControls = function ( camera, domElement ) {
 
-	var scope = this;
-
 	this.domElement = domElement || document.body;
 	this.isLocked = false;
 
-	camera.rotation.set( 0, 0, 0 );
+	//
+	// internals
+	//
 
-	var pitchObject = new THREE.Object3D();
-	pitchObject.add( camera );
+	var scope = this;
 
-	var yawObject = new THREE.Object3D();
-	yawObject.position.y = 10;
-	yawObject.add( pitchObject );
+	var changeEvent = { type: 'change' };
+	var lockEvent = { type: 'lock' };
+	var unlockEvent = { type: 'unlock' };
+
+	var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
 
 	var PI_2 = Math.PI / 2;
 
@@ -28,10 +29,16 @@ THREE.PointerLockControls = function ( camera, domElement ) {
 		var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
 		var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
 
-		yawObject.rotation.y -= movementX * 0.002;
-		pitchObject.rotation.x -= movementY * 0.002;
+		euler.setFromQuaternion( camera.quaternion );
+
+		euler.y -= movementX * 0.002;
+		euler.x -= movementY * 0.002;
 
-		pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
+		euler.x = Math.max( - PI_2, Math.min( PI_2, euler.x ) );
+
+		camera.quaternion.setFromEuler( euler );
+
+		scope.dispatchEvent( changeEvent );
 
 	}
 
@@ -39,13 +46,13 @@ THREE.PointerLockControls = function ( camera, domElement ) {
 
 		if ( document.pointerLockElement === scope.domElement ) {
 
-			scope.dispatchEvent( { type: 'lock' } );
+			scope.dispatchEvent( lockEvent );
 
 			scope.isLocked = true;
 
 		} else {
 
-			scope.dispatchEvent( { type: 'unlock' } );
+			scope.dispatchEvent( unlockEvent );
 
 			scope.isLocked = false;
 
@@ -81,26 +88,19 @@ THREE.PointerLockControls = function ( camera, domElement ) {
 
 	};
 
-	this.getObject = function () {
+	this.getObject = function () { // retaining this method for backward compatibility
 
-		return yawObject;
+		return camera;
 
 	};
 
 	this.getDirection = function () {
 
-		// assumes the camera itself is not rotated
-
 		var direction = new THREE.Vector3( 0, 0, - 1 );
-		var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' );
 
 		return function ( v ) {
 
-			rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 );
-
-			v.copy( direction ).applyEuler( rotation );
-
-			return v;
+			return v.copy( direction ).applyQuaternion( camera.quaternion );
 
 		};
 

+ 1 - 1
examples/js/controls/TrackballControls.js

@@ -492,7 +492,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 	function touchstart( event ) {
 
 		if ( _this.enabled === false ) return;
-		
+
 		event.preventDefault();
 
 		switch ( event.touches.length ) {

+ 1 - 0
examples/js/controls/TransformControls.js

@@ -988,6 +988,7 @@ THREE.TransformControlsGizmo = function () {
 				var tempGeometry = object.geometry.clone();
 				tempGeometry.applyMatrix(object.matrix);
 				object.geometry = tempGeometry;
+				object.renderOrder = Infinity;
 
 				object.position.set( 0, 0, 0 );
 				object.rotation.set( 0, 0, 0 );

+ 6 - 6
examples/js/effects/AnaglyphEffect.js

@@ -12,9 +12,9 @@ THREE.AnaglyphEffect = function ( renderer, width, height ) {
 
 	this.colorMatrixLeft = new THREE.Matrix3().fromArray( [
 
-			1.0671679973602295, 	-0.0016435992438346148,		 0.0001777536963345483, // r out
-			-0.028107794001698494,	-0.00019593400065787137,	-0.0002875397040043026, // g out
-			-0.04279090091586113,	 0.000015809757314855233,	-0.00024287120322696865 // b out
+		1.0671679973602295, - 0.0016435992438346148, 0.0001777536963345483, // r out
+		- 0.028107794001698494, - 0.00019593400065787137, - 0.0002875397040043026, // g out
+		- 0.04279090091586113, 0.000015809757314855233, - 0.00024287120322696865 // b out
 
 	] );
 
@@ -22,9 +22,9 @@ THREE.AnaglyphEffect = function ( renderer, width, height ) {
 
 	this.colorMatrixRight = new THREE.Matrix3().fromArray( [
 
-			-0.0355340838432312,	-0.06440307199954987,		 0.018319187685847282,	// r out
-			-0.10269022732973099,	 0.8079727292060852,		-0.04835830628871918,	// g out
-			0.0001224992738571018,	-0.009558862075209618,		 0.567823588848114		// b out
+		- 0.0355340838432312, - 0.06440307199954987, 0.018319187685847282, // r out
+		- 0.10269022732973099, 0.8079727292060852, - 0.04835830628871918, // g out
+		0.0001224992738571018, - 0.009558862075209618, 0.567823588848114 // b out
 
 	] );
 

+ 46 - 4
examples/js/effects/OutlineEffect.js

@@ -3,6 +3,41 @@
  *
  * Reference: https://en.wikipedia.org/wiki/Cel_shading
  *
+ * API
+ *
+ * 1. Traditional
+ *
+ * var effect = new THREE.OutlineEffect( renderer );
+ *
+ * function render() {
+ *
+ * 	effect.render( scene, camera );
+ *
+ * }
+ *
+ * 2. VR compatible
+ *
+ * var effect = new THREE.OutlineEffect( renderer );
+ * var renderingOutline = false;
+ *
+ * scene.onAfterRender = function () {
+ *
+ * 	if ( renderingOutline ) return;
+ *
+ * 	renderingOutline = true;
+ *
+ * 	effect.renderOutline( scene, camera );
+ *
+ * 	renderingOutline = false;
+ *
+ * };
+ *
+ * function render() {
+ *
+ * 	renderer.render( scene, camera );
+ *
+ * }
+ *
  * // How to set default outline parameters
  * new THREE.OutlineEffect( renderer, {
  * 	defaultThickness: 0.01,
@@ -404,7 +439,7 @@ THREE.OutlineEffect = function ( renderer, parameters ) {
 
 	this.render = function ( scene, camera ) {
 
-		var renderTarget = null;
+		var renderTarget;
 		var forceClear = false;
 
 		if ( arguments[ 2 ] !== undefined ) {
@@ -421,7 +456,7 @@ THREE.OutlineEffect = function ( renderer, parameters ) {
 
 		}
 
-		renderer.setRenderTarget( renderTarget );
+		if ( renderTarget !== undefined ) renderer.setRenderTarget( renderTarget );
 
 		if ( forceClear ) renderer.clear();
 
@@ -435,10 +470,17 @@ THREE.OutlineEffect = function ( renderer, parameters ) {
 		var currentAutoClear = renderer.autoClear;
 		renderer.autoClear = this.autoClear;
 
-		// 1. render normally
 		renderer.render( scene, camera );
 
-		// 2. render outline
+		renderer.autoClear = currentAutoClear;
+
+		this.renderOutline( scene, camera );
+
+	};
+
+	this.renderOutline = function ( scene, camera ) {
+
+		var currentAutoClear = renderer.autoClear;
 		var currentSceneAutoUpdate = scene.autoUpdate;
 		var currentSceneBackground = scene.background;
 		var currentShadowMapEnabled = renderer.shadowMap.enabled;

+ 32 - 10
examples/js/exporters/GLTFExporter.js

@@ -123,9 +123,23 @@ THREE.GLTFExporter.prototype = {
 
 		var cachedCanvas;
 
+		var uids = new Map();
+		var uid = 0;
+
 		/**
-		 * Compare two arrays
+		 * Assign and return a temporal unique id for an object
+		 * especially which doesn't have .uuid
+		 * @param  {Object} object
+		 * @return {Integer}
 		 */
+		function getUID( object ) {
+
+			if ( ! uids.has( object ) ) uids.set( object, uid ++ );
+
+			return uids.get( object );
+
+		}
+
 		/**
 		 * Compare two arrays
 		 * @param  {Array} array1 Array 1 to compare
@@ -1178,9 +1192,9 @@ THREE.GLTFExporter.prototype = {
 
 				}
 
-				if ( cachedData.attributes.has( attribute ) ) {
+				if ( cachedData.attributes.has( getUID( attribute ) ) ) {
 
-					attributes[ attributeName ] = cachedData.attributes.get( attribute );
+					attributes[ attributeName ] = cachedData.attributes.get( getUID( attribute ) );
 					continue;
 
 				}
@@ -1201,7 +1215,7 @@ THREE.GLTFExporter.prototype = {
 				if ( accessor !== null ) {
 
 					attributes[ attributeName ] = accessor;
-					cachedData.attributes.set( attribute, accessor );
+					cachedData.attributes.set( getUID( attribute ), accessor );
 
 				}
 
@@ -1267,9 +1281,9 @@ THREE.GLTFExporter.prototype = {
 
 						var baseAttribute = geometry.attributes[ attributeName ];
 
-						if ( cachedData.attributes.has( attribute ) ) {
+						if ( cachedData.attributes.has( getUID( attribute ) ) ) {
 
-							target[ gltfAttributeName ] = cachedData.attributes.get( attribute );
+							target[ gltfAttributeName ] = cachedData.attributes.get( getUID( attribute ) );
 							continue;
 
 						}
@@ -1289,7 +1303,7 @@ THREE.GLTFExporter.prototype = {
 						}
 
 						target[ gltfAttributeName ] = processAccessor( relativeAttribute, geometry );
-						cachedData.attributes.set( baseAttribute, target[ gltfAttributeName ] );
+						cachedData.attributes.set( getUID( baseAttribute ), target[ gltfAttributeName ] );
 
 					}
 
@@ -1358,14 +1372,22 @@ THREE.GLTFExporter.prototype = {
 
 				if ( geometry.index !== null ) {
 
-					if ( cachedData.attributes.has( geometry.index ) ) {
+					var cacheKey = getUID( geometry.index );
+
+					if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) {
+
+						cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count;
+
+					}
+
+					if ( cachedData.attributes.has( cacheKey ) ) {
 
-						primitive.indices = cachedData.attributes.get( geometry.index );
+						primitive.indices = cachedData.attributes.get( cacheKey );
 
 					} else {
 
 						primitive.indices = processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );
-						cachedData.attributes.set( geometry.index, primitive.indices );
+						cachedData.attributes.set( cacheKey, primitive.indices );
 
 					}
 

+ 163 - 159
examples/js/geometries/LightningStrike.js

@@ -2,85 +2,85 @@
  * @author yomboprime https://github.com/yomboprime
  *
  * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs.
- * 
- * 
+ *
+ *
  * Usage
- * 
+ *
  * var myRay = new THREE.LightningStrike( paramsObject );
  * var myRayMesh = new THREE.Mesh( myRay, myMaterial );
  * scene.add( myRayMesh );
  * ...
  * myRay.update( currentTime );
- * 
+ *
  * The "currentTime" can vary its rate, go forwards, backwards or even jump, but it cannot be negative.
- * 
+ *
  * You should normally leave the ray position to (0, 0, 0). You should control it by changing the sourceOffset and destOffset parameters.
- * 
- * 
+ *
+ *
  * LightningStrike parameters
- * 
+ *
  * The paramsObject can contain any of the following parameters.
- * 
+ *
  * Legend:
  * 'LightningStrike' (also called 'ray'): An independent voltaic arc with its ramifications and defined with a set of parameters.
  * 'Subray': A ramification of the ray. It is not a LightningStrike object.
  * 'Segment': A linear segment piece of a subray.
  * 'Leaf segment': A ray segment which cannot be smaller.
- * 
- * 
+ *
+ *
  * The following parameters can be changed any time and if they vary smoothly, the ray form will also change smoothly:
- * 
+ *
  * @param {Vector3} sourceOffset The point where the ray starts.
- * 
+ *
  * @param {Vector3} destOffset The point where the ray ends.
- * 
+ *
  * @param {double} timeScale The rate at wich the ray form changes in time. Default: 1
- * 
+ *
  * @param {double} roughness From 0 to 1. The higher the value, the more wrinkled is the ray. Default: 0.9
- * 
+ *
  * @param {double} straightness From 0 to 1. The higher the value, the more straight will be a subray path. Default: 0.7
- * 
+ *
  * @param {Vector3} up0 Ray 'up' direction at the ray starting point. Must be normalized. It should be perpendicular to the ray forward direction but it doesn't matter much.
- * 
+ *
  * @param {Vector3} up1 Like the up0 parameter but at the end of the ray. Must be normalized.
- * 
+ *
  * @param {double} radius0 Radius of the main ray trunk at the start point. Default: 1
- * 
+ *
  * @param {double} radius1 Radius of the main ray trunk at the end point. Default: 1
- * 
+ *
  * @param {double} radius0Factor The radius0 of a subray is this factor times the radius0 of its parent subray. Default: 0.5
- * 
+ *
  * @param {double} radius1Factor The radius1 of a subray is this factor times the radius1 of its parent subray. Default: 0.2
- * 
+ *
  * @param {minRadius} Minimum value a subray radius0 or radius1 can get. Default: 0.1
- * 
- * 
+ *
+ *
  * The following parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly:
- * 
+ *
  * @param {boolean} isEternal If true the ray never extinguishes. Otherwise its life is controlled by the 'birthTime' and 'deathTime' parameters. Default: true if any of those two parameters is undefined.
- * 
+ *
  * @param {double} birthTime The time at which the ray starts its life and begins propagating. Only if isEternal is false. Default: None.
- * 
+ *
  * @param {double} deathTime The time at which the ray ends vanishing and its life. Only if isEternal is false. Default: None.
- * 
+ *
  * @param {double} propagationTimeFactor From 0 to 1. Lifetime factor at which the ray ends propagating and enters the steady phase. For example, 0.1 means it is propagating 1/10 of its lifetime. Default: 0.1
- * 
+ *
  * @param {double} vanishingTimeFactor From 0 to 1. Lifetime factor at which the ray ends the steady phase and begins vanishing. For example, 0.9 means it is vanishing 1/10 of its lifetime. Default: 0.9
- * 
+ *
  * @param {double} subrayPeriod Subrays cycle periodically. This is their time period. Default: 4
- * 
+ *
  * @param {double} subrayDutyCycle From 0 to 1. This is the fraction of time a subray is active. Default: 0.6
- * 
- * 
+ *
+ *
  * These parameters cannot change after lightning creation:
- * 
+ *
  * @param {integer} maxIterations: Greater than 0. The number of ray's leaf segments is 2**maxIterations. Default: 9
- * 
+ *
  * @param {boolean} isStatic Set to true only for rays which won't change over time and are not attached to moving objects (Rare case). It is used to set the vertex buffers non-dynamic. You can omit calling update() for these rays.
- *  
+ *
  * @param {integer} ramification Greater than 0. Maximum number of child subrays a subray can have. Default: 5
  *
- * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3 
+ * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3
  *
  * @param {double} recursionProbability From 0 to 1. The lower the value, the less chance each new generation of subrays has to generate new subrays. Default: 0.6
  *
@@ -91,13 +91,13 @@
  * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable.
  * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number.
  * The default value is an internal generator for some uses and Math.random for others (It is non-repeatable even if noiseSeed is supplied)
- * 
+ *
  * @param {double} noiseSeed Seed used to make repeatable rays (see the randomGenerator)
- * 
+ *
  * @param {function} onDecideSubrayCreation Set this to change the callback which decides subray creation. You can look at the default callback in the code (createDefaultSubrayCreationCallbacks)for more info.
- * 
+ *
  * @param {function} onSubrayCreation This is another callback, more simple than the previous one. It can be used to adapt the form of subrays or other parameters once a subray has been created and initialized. It is used in the examples to adapt subrays to a sphere or to a plane.
- * 
+ *
  *
 */
 
@@ -138,7 +138,7 @@ THREE.LightningStrike.createRandomGenerator = function () {
 	var numSeeds = 2053;
 	var seeds = [];
 
-	for ( var i = 0; i < numSeeds; i++ ) {
+	for ( var i = 0; i < numSeeds; i ++ ) {
 
 		seeds.push( Math.random() );
 
@@ -176,25 +176,24 @@ THREE.LightningStrike.createRandomGenerator = function () {
 
 };
 
-THREE.LightningStrike.copyParameters = function ( dest, source) {
+THREE.LightningStrike.copyParameters = function ( dest, source ) {
 
 	source = source || {};
 	dest = dest || {};
 
-	var vecCopy = function( v ) {
+	var vecCopy = function ( v ) {
 
 		if ( source === dest ) {
 
 			return v;
 
-		}
-		else {
+		} else {
 
 			return v.clone();
 
 		}
 
-	}
+	};
 
 	dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new THREE.Vector3( 0, 100, 0 ),
 	dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new THREE.Vector3( 0, 0, 0 ),
@@ -223,7 +222,7 @@ THREE.LightningStrike.copyParameters = function ( dest, source) {
 
 	// These parameters cannot change after lightning creation:
 
-	dest.maxIterations =  source.maxIterations !== undefined ? source.maxIterations : 9;
+	dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9;
 	dest.isStatic = source.isStatic !== undefined ? source.isStatic : false;
 	dest.ramification = source.ramification !== undefined ? source.ramification : 5;
 	dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3;
@@ -240,25 +239,21 @@ THREE.LightningStrike.copyParameters = function ( dest, source) {
 
 THREE.LightningStrike.prototype.update = function ( time ) {
 
-	if ( this.isStatic ) {
-		return;
-	}
-	
+	if ( this.isStatic ) return;
+
 	if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) {
 
 		this.updateMesh( time );
 
 		if ( time < this.subrays[ 0 ].endPropagationTime ) {
-		
+
 			this.state = THREE.LightningStrike.RAY_PROPAGATING;
 
-		}	
-		else if ( time > this.subrays[ 0 ].beginVanishingTime ) {
+		} else if ( time > this.subrays[ 0 ].beginVanishingTime ) {
 
 			this.state = THREE.LightningStrike.RAY_VANISHING;
 
-		}
-		else {
+		} else {
 
 			this.state = THREE.LightningStrike.RAY_STEADY;
 
@@ -266,8 +261,7 @@ THREE.LightningStrike.prototype.update = function ( time ) {
 
 		this.visible = true;
 
-	}
-	else {
+	} else {
 
 		this.visible = false;
 
@@ -275,8 +269,7 @@ THREE.LightningStrike.prototype.update = function ( time ) {
 
 			this.state = THREE.LightningStrike.RAY_UNBORN;
 
-		}
-		else {
+		} else {
 
 			this.state = THREE.LightningStrike.RAY_EXTINGUISHED;
 
@@ -294,7 +287,7 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 
 	// These parameters cannot change after lightning creation:
 
-	this.maxIterations =  rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9;
+	this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9;
 	rayParameters.maxIterations = this.maxIterations;
 	this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false;
 	rayParameters.isStatic = this.isStatic;
@@ -314,13 +307,12 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 		this.seedGenerator = rayParameters.randomGenerator;
 
 		if ( rayParameters.noiseSeed !== undefined ) {
-		
+
 			this.seedGenerator.setSeed( rayParameters.noiseSeed );
 
 		}
 
-	}
-	else {
+	} else {
 
 		this.randomGenerator = THREE.LightningStrike.createRandomGenerator();
 		this.seedGenerator = Math;
@@ -332,8 +324,7 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 
 		this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation;
 
-	}
-	else {
+	} else {
 
 		this.createDefaultSubrayCreationCallbacks();
 
@@ -351,12 +342,12 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 
 	this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) );
 	rayParameters.maxSubrays = this.maxSubrays;
-	
+
 	this.maxRaySegments = 2 * ( 1 << this.maxIterations );
 
 	this.subrays = [];
 
-	for ( var i = 0; i < this.maxSubrays; i++ ) {
+	for ( var i = 0; i < this.maxSubrays; i ++ ) {
 
 		this.subrays.push( this.createSubray() );
 
@@ -364,7 +355,7 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 
 	this.raySegments = [];
 
-	for ( var i = 0; i < this.maxRaySegments; i++ ) {
+	for ( var i = 0; i < this.maxRaySegments; i ++ ) {
 
 		this.raySegments.push( this.createSegment() );
 
@@ -389,7 +380,7 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 	this.indices = null;
 	this.positionAttribute = null;
 	this.uvsAttribute = null;
-	
+
 	this.simplexX = new SimplexNoise( this.seedGenerator );
 	this.simplexY = new SimplexNoise( this.seedGenerator );
 	this.simplexZ = new SimplexNoise( this.seedGenerator );
@@ -410,46 +401,56 @@ THREE.LightningStrike.prototype.init = function ( rayParameters ) {
 THREE.LightningStrike.prototype.createMesh = function () {
 
 	var maxDrawableSegmentsPerSubRay = 1 << this.maxIterations;
-	
+
 	var maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays;
 	var maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays;
 
 	this.vertices = new Float32Array( maxVerts * 3 );
 	this.indices = new Uint32Array( maxIndices );
 	if ( this.generateUVs ) {
+
 		this.uvs = new Float32Array( maxVerts * 2 );
+
 	}
 
 	// Populate the mesh
 	this.fillMesh( 0 );
-	
+
 	this.setIndex( new THREE.Uint32BufferAttribute( this.indices, 1 ) );
 
 	this.positionAttribute = new THREE.Float32BufferAttribute( this.vertices, 3 );
 	this.addAttribute( 'position', this.positionAttribute );
 
-	if ( this.generateUVs ) {1
+	if ( this.generateUVs ) {
+
 		this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 );
 		this.addAttribute( 'uv', this.uvsAttribute );
+
 	}
 
 	if ( ! this.isStatic ) {
+
 		this.index.dynamic = true;
 		this.positionAttribute.dynamic = true;
 		if ( this.generateUVs ) {
+
 			this.uvsAttribute.dynamic = true;
+
 		}
+
 	}
 
 	// Store buffers for later modification
 	this.vertices = this.positionAttribute.array;
 	this.indices = this.index.array;
 	if ( this.generateUVs ) {
+
 		this.uvs = this.uvsAttribute.array;
+
 	}
 
 };
-	
+
 THREE.LightningStrike.prototype.updateMesh = function ( time ) {
 
 	this.fillMesh( time );
@@ -461,7 +462,9 @@ THREE.LightningStrike.prototype.updateMesh = function ( time ) {
 	this.positionAttribute.needsUpdate = true;
 
 	if ( this.generateUVs ) {
+
 		this.uvsAttribute.needsUpdate = true;
+
 	}
 
 };
@@ -475,16 +478,15 @@ THREE.LightningStrike.prototype.fillMesh = function ( time ) {
 	this.currentCoordinate = 0;
 	this.currentUVCoordinate = 0;
 
-	this.fractalRay( time, function fillVertices ( segment ) {
+	this.fractalRay( time, function fillVertices( segment ) {
 
 		var subray = scope.currentSubray;
 
-		if ( time < subray.birthTime ) {//&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
+		if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
 
 			return;
 
-		}
-		else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
+		} else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
 
 			// Eternal rays don't propagate nor vanish, but its subrays do
 
@@ -492,8 +494,7 @@ THREE.LightningStrike.prototype.fillMesh = function ( time ) {
 
 			scope.onDecideSubrayCreation( segment, scope );
 
-		}
-		else if ( time < subray.endPropagationTime ) {
+		} else if ( time < subray.endPropagationTime ) {
 
 			if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) {
 
@@ -505,8 +506,7 @@ THREE.LightningStrike.prototype.fillMesh = function ( time ) {
 
 			}
 
-		}
-		else if ( time < subray.beginVanishingTime ) {
+		} else if ( time < subray.beginVanishingTime ) {
 
 			// Ray is steady (nor propagating nor vanishing)
 
@@ -514,10 +514,9 @@ THREE.LightningStrike.prototype.fillMesh = function ( time ) {
 
 			scope.onDecideSubrayCreation( segment, scope );
 
-		}
-		else {
+		} else {
 
-			if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1  * ( 1 - subray.vanishingTimeFactor ) ) {
+			if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) {
 
 				// Segment has not yet vanished
 
@@ -535,7 +534,7 @@ THREE.LightningStrike.prototype.fillMesh = function ( time ) {
 
 THREE.LightningStrike.prototype.addNewSubray = function ( rayParameters ) {
 
-	return this.subrays[ this.numSubrays++ ];
+	return this.subrays[ this.numSubrays ++ ];
 
 };
 
@@ -571,7 +570,7 @@ THREE.LightningStrike.prototype.fractalRay = function ( time, segmentCallback )
 	this.initSubray( this.addNewSubray(), this.rayParameters );
 
 	// Process all subrays that are being generated until consuming all of them
-	for ( var subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex++ ) {
+	for ( var subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) {
 
 		var subray = this.subrays[ subrayIndex ];
 		this.currentSubray = subray;
@@ -587,7 +586,7 @@ THREE.LightningStrike.prototype.fractalRay = function ( time, segmentCallback )
 
 		this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime );
 
-		this.currentSegmentIndex  = 0;
+		this.currentSegmentIndex = 0;
 		this.isInitialSegment = true;
 
 		var segment = this.getNewSegment();
@@ -630,9 +629,11 @@ THREE.LightningStrike.prototype.fractalRayRecursive = function ( segment ) {
 	this.forwards.subVectors( segment.pos1, segment.pos0 );
 	var lForwards = this.forwards.length();
 
-	if ( lForwards < 0.000001) {
+	if ( lForwards < 0.000001 ) {
+
 		this.forwards.set( 0, 0, 0.01 );
 		lForwards = this.forwards.length();
+
 	}
 
 	var middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5;
@@ -644,10 +645,10 @@ THREE.LightningStrike.prototype.fractalRayRecursive = function ( segment ) {
 	this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 );
 	var p = this.middleLinPos;
 
-	// Noise	
+	// Noise
 	this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ),
-						this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ),
-						this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) );
+		this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ),
+		this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) );
 
 	this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards );
 	this.newPos.add( this.middlePos );
@@ -698,7 +699,7 @@ THREE.LightningStrike.prototype.createPrism = function ( segment ) {
 	if ( this.isInitialSegment ) {
 
 		this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 );
-		
+
 		this.isInitialSegment = false;
 
 	}
@@ -712,7 +713,7 @@ THREE.LightningStrike.prototype.createPrism = function ( segment ) {
 THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( pos, up, forwards, radius ) {
 
 	// Create an equilateral triangle (only vertices)
-	
+
 	this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG );
 	this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG );
 
@@ -721,21 +722,21 @@ THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( po
 
 	p.copy( pos ).sub( this.side ).add( this.down );
 
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
 
 	p.copy( pos ).add( this.side ).add( this.down );
 
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
 
 	p.copy( up ).multiplyScalar( radius ).add( pos );
-	
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
+
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
 
 	this.currentVertex += 3;
 
@@ -744,7 +745,7 @@ THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( po
 THREE.LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos, up, forwards, radius, u ) {
 
 	// Create an equilateral triangle (only vertices)
-	
+
 	this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG );
 	this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG );
 
@@ -754,30 +755,30 @@ THREE.LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos,
 
 	p.copy( pos ).sub( this.side ).add( this.down );
 
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
 
-	uv[ this.currentUVCoordinate++ ] = u;
-	uv[ this.currentUVCoordinate++ ] = 0;
+	uv[ this.currentUVCoordinate ++ ] = u;
+	uv[ this.currentUVCoordinate ++ ] = 0;
 
 	p.copy( pos ).add( this.side ).add( this.down );
 
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
 
-	uv[ this.currentUVCoordinate++ ] = u;
-	uv[ this.currentUVCoordinate++ ] = 0.5;
+	uv[ this.currentUVCoordinate ++ ] = u;
+	uv[ this.currentUVCoordinate ++ ] = 0.5;
 
 	p.copy( up ).multiplyScalar( radius ).add( pos );
-	
-	v[ this.currentCoordinate++ ] = p.x;
-	v[ this.currentCoordinate++ ] = p.y;
-	v[ this.currentCoordinate++ ] = p.z;
 
-	uv[ this.currentUVCoordinate++ ] = u;
-	uv[ this.currentUVCoordinate++ ] = 1;
+	v[ this.currentCoordinate ++ ] = p.x;
+	v[ this.currentCoordinate ++ ] = p.y;
+	v[ this.currentCoordinate ++ ] = p.z;
+
+	uv[ this.currentUVCoordinate ++ ] = u;
+	uv[ this.currentUVCoordinate ++ ] = 1;
 
 	this.currentVertex += 3;
 
@@ -788,24 +789,24 @@ THREE.LightningStrike.prototype.createPrismFaces = function ( vertex, index ) {
 	var indices = this.indices;
 	var vertex = this.currentVertex - 6;
 
-	indices[ this.currentIndex++ ] = vertex + 1;
-	indices[ this.currentIndex++ ] = vertex + 2;
-	indices[ this.currentIndex++ ] = vertex + 5;
-	indices[ this.currentIndex++ ] = vertex + 1;
-	indices[ this.currentIndex++ ] = vertex + 5;
-	indices[ this.currentIndex++ ] = vertex + 4;
-	indices[ this.currentIndex++ ] = vertex + 0;
-	indices[ this.currentIndex++ ] = vertex + 1;
-	indices[ this.currentIndex++ ] = vertex + 4;
-	indices[ this.currentIndex++ ] = vertex + 0;
-	indices[ this.currentIndex++ ] = vertex + 4;
-	indices[ this.currentIndex++ ] = vertex + 3;
-	indices[ this.currentIndex++ ] = vertex + 2;
-	indices[ this.currentIndex++ ] = vertex + 0;
-	indices[ this.currentIndex++ ] = vertex + 3;
-	indices[ this.currentIndex++ ] = vertex + 2;
-	indices[ this.currentIndex++ ] = vertex + 3;
-	indices[ this.currentIndex++ ] = vertex + 5;
+	indices[ this.currentIndex ++ ] = vertex + 1;
+	indices[ this.currentIndex ++ ] = vertex + 2;
+	indices[ this.currentIndex ++ ] = vertex + 5;
+	indices[ this.currentIndex ++ ] = vertex + 1;
+	indices[ this.currentIndex ++ ] = vertex + 5;
+	indices[ this.currentIndex ++ ] = vertex + 4;
+	indices[ this.currentIndex ++ ] = vertex + 0;
+	indices[ this.currentIndex ++ ] = vertex + 1;
+	indices[ this.currentIndex ++ ] = vertex + 4;
+	indices[ this.currentIndex ++ ] = vertex + 0;
+	indices[ this.currentIndex ++ ] = vertex + 4;
+	indices[ this.currentIndex ++ ] = vertex + 3;
+	indices[ this.currentIndex ++ ] = vertex + 2;
+	indices[ this.currentIndex ++ ] = vertex + 0;
+	indices[ this.currentIndex ++ ] = vertex + 3;
+	indices[ this.currentIndex ++ ] = vertex + 2;
+	indices[ this.currentIndex ++ ] = vertex + 3;
+	indices[ this.currentIndex ++ ] = vertex + 5;
 
 };
 
@@ -821,9 +822,9 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 
 		var period = lightningStrike.rayParameters.subrayPeriod;
 		var dutyCycle = lightningStrike.rayParameters.subrayDutyCycle;
-		
+
 		var phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : THREE.Math.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period;
-		
+
 		var phase = lightningStrike.time - phase0;
 		var currentCycle = Math.floor( phase / period );
 
@@ -833,9 +834,12 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 
 		probability = lightningStrike.subrayProbability;
 		var probability = 0;
+
 		if ( isActive ) {
+
 			probability = lightningStrike.subrayProbability;
 			// Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0;
+
 		}
 
 		if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) {
@@ -850,12 +854,12 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 			childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 );
 
 			childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-			childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );;
+			childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
 			childSubray.up0.copy( subray.up0 );
 			childSubray.up1.copy( subray.up1 );
 			childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor;
 			childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor );
-			
+
 			childSubray.birthTime = phase0 + ( currentCycle ) * period;
 			childSubray.deathTime = childSubray.birthTime + period * dutyCycle;
 
@@ -884,7 +888,7 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 	var vec2Forward = new THREE.Vector3();
 	var vec3Side = new THREE.Vector3();
 	var vec4Up = new THREE.Vector3();
-	
+
 	this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) {
 
 		// Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray
@@ -895,9 +899,9 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 	};
 
 	this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
-		
+
 		// Sets childSubray pos0 and pos1 in a cone
-		
+
 		childSubray.pos0.copy( segment.pos0 );
 
 		vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 );
@@ -906,15 +910,15 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 		var length = vec1Pos.length();
 		vec3Side.crossVectors( parentSubray.up0, vec2Forward );
 		var angle = 2 * Math.PI * random1();
-		vec3Side.multiplyScalar( Math.cos ( angle ) );
-		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin ( angle ) );
+		vec3Side.multiplyScalar( Math.cos( angle ) );
+		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
 
 		childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
 
-	}
+	};
 
 	this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
-		
+
 		// Sets childSubray pos0 and pos1 in a cylinder
 
 		childSubray.pos0.copy( segment.pos0 );
@@ -925,12 +929,12 @@ THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function
 		var length = vec1Pos.length();
 		vec3Side.crossVectors( parentSubray.up0, vec2Forward );
 		var angle = 2 * Math.PI * random1();
-		vec3Side.multiplyScalar( Math.cos ( angle ) );
-		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin ( angle ) );
+		vec3Side.multiplyScalar( Math.cos( angle ) );
+		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
 
 		childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
 
-	}
+	};
 
 };
 
@@ -978,19 +982,19 @@ THREE.LightningStrike.prototype.createSegment = function () {
 		fraction0: 0,
 		fraction1: 0,
 		positionVariationFactor: 0
-	}
+	};
 
 };
 
 THREE.LightningStrike.prototype.getNewSegment = function () {
 
-	return this.raySegments[ this.currentSegmentIndex++ ];
+	return this.raySegments[ this.currentSegmentIndex ++ ];
 
 };
 
 THREE.LightningStrike.prototype.copy = function ( source ) {
-	
-	BufferGeometry.prototype.copy.call( this, source );
+
+	THREE.BufferGeometry.prototype.copy.call( this, source );
 
 	this.init( THREE.LightningStrike.copyParameters( {}, source.rayParameters ) );
 

+ 3 - 3
examples/js/interactive/SelectionHelper.js

@@ -12,9 +12,9 @@ THREE.SelectionHelper = ( function () {
 
 		this.renderer = renderer;
 
-		this.startPoint = { x: 0, y: 0 };
-		this.pointTopLeft = { x: 0, y: 0 };
-		this.pointBottomRight = { x: 0, y: 0 };
+		this.startPoint = new THREE.Vector2();
+		this.pointTopLeft = new THREE.Vector2();
+		this.pointBottomRight = new THREE.Vector2();
 
 		this.isDown = false;
 

+ 121 - 0
examples/js/lights/LightProbeGenerator.js

@@ -0,0 +1,121 @@
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.LightProbeGenerator = {
+
+	// https://www.ppsloan.org/publications/StupidSH36.pdf
+	fromCubeTexture: function ( cubeTexture ) {
+
+		var norm, lengthSq, weight, totalWeight = 0;
+
+		var coord = new THREE.Vector3();
+
+		var dir = new THREE.Vector3();
+
+		var color = new THREE.Color();
+
+		var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+
+		var sh = new THREE.SphericalHarmonics3();
+		var shCoefficients = sh.coefficients;
+
+		for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
+
+			var image = cubeTexture.image[ faceIndex ];
+
+			var width = image.width;
+			var height = image.height;
+
+			var canvas = document.createElement( 'canvas' );
+
+			canvas.width = width;
+			canvas.height = height;
+
+			var context = canvas.getContext( '2d' );
+
+			context.drawImage( image, 0, 0, width, height );
+
+			var imageData = context.getImageData( 0, 0, width, height );
+
+			var data = imageData.data;
+
+			var imageWidth = imageData.width; // assumed to be square
+
+			var pixelSize = 2 / imageWidth;
+
+			for ( var i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
+
+				// pixel color
+				color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 );
+
+				// convert to linear color space
+				color.copySRGBToLinear( color );
+
+				// pixel coordinate on unit cube
+
+				var pixelIndex = i / 4;
+
+				var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
+
+				var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
+
+				switch ( faceIndex ) {
+
+					case 0: coord.set( - 1, row, - col ); break;
+
+					case 1: coord.set( 1, row, col ); break;
+
+					case 2: coord.set( - col, 1, - row ); break;
+
+					case 3: coord.set( - col, - 1, row ); break;
+
+					case 4: coord.set( - col, row, 1 ); break;
+
+					case 5: coord.set( col, row, - 1 ); break;
+
+				}
+
+				// weight assigned to this pixel
+
+				lengthSq = coord.lengthSq();
+
+				weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
+
+				totalWeight += weight;
+
+				// direction vector to this pixel
+				dir.copy( coord ).normalize();
+
+				// evaluate SH basis functions in direction dir
+				THREE.SphericalHarmonics3.getBasisAt( dir, shBasis );
+
+				// accummuulate
+				for ( var j = 0; j < 9; j ++ ) {
+
+					shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
+					shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
+					shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
+
+				}
+
+			}
+
+		}
+
+		// normalize
+		norm = ( 4 * Math.PI ) / totalWeight;
+
+		for ( var j = 0; j < 9; j ++ ) {
+
+			shCoefficients[ j ].x *= norm;
+			shCoefficients[ j ].y *= norm;
+			shCoefficients[ j ].z *= norm;
+
+		}
+
+		return new THREE.LightProbe( sh );
+
+	}
+
+};

+ 201 - 75
examples/js/loaders/LDrawLoader.js

@@ -271,7 +271,7 @@ THREE.LDrawLoader = ( function () {
 
 			}, onProgress, onError );
 
-			function processObject( text, onProcessed ) {
+			function processObject( text, onProcessed, subobject ) {
 
 				var parseScope = scope.newParseScopeLevel();
 				parseScope.url = url;
@@ -280,13 +280,20 @@ THREE.LDrawLoader = ( function () {
 
 				// Add to cache
 				var currentFileName = parentParseScope.currentFileName;
+				if ( currentFileName !== null ) {
+
+					currentFileName = parentParseScope.currentFileName.toLowerCase();
+
+				}
+
 				if ( scope.subobjectCache[ currentFileName ] === undefined ) {
 
 					scope.subobjectCache[ currentFileName ] = text;
 
-
 				}
 
+				parseScope.inverted = subobject !== undefined ? subobject.inverted : false;
+
 				// Parse the object (returns a THREE.Group)
 				var objGroup = scope.parse( text );
 
@@ -295,37 +302,39 @@ THREE.LDrawLoader = ( function () {
 				parseScope.numSubobjects = parseScope.subobjects.length;
 				parseScope.subobjectIndex = 0;
 
-				if ( parseScope.numSubobjects > 0 ) {
-
-					// Load the first subobject
-					var subobjectGroup = loadSubobject( parseScope.subobjects[ 0 ], true );
+				var finishedCount = 0;
+				onSubobjectFinish();
 
-					// Optimization for loading pack: If subobjects are obtained from cache, keep loading them iteratively rather than recursively
-					if ( subobjectGroup ) {
+				return objGroup;
 
-						while ( subobjectGroup && parseScope.subobjectIndex < parseScope.numSubobjects - 1 ) {
+				function onSubobjectFinish() {
 
-							subobjectGroup = loadSubobject( parseScope.subobjects[ ++ parseScope.subobjectIndex ], true );
+					finishedCount ++;
 
-						}
+					if ( finishedCount === parseScope.subobjects.length + 1 ) {
 
-						if ( subobjectGroup ) {
+						finalizeObject();
 
-							finalizeObject();
+					} else {
 
-						}
+						// Once the previous subobject has finished we can start processing the next one in the list.
+						// The subobject processing shares scope in processing so it's important that they be loaded serially
+						// to avoid race conditions.
+						// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
+						// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
+						// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
+						var subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
+						Promise.resolve().then( function () {
 
-					}
+							loadSubobject( subobject );
 
-				} else {
+						} );
+						parseScope.subobjectIndex ++;
 
-					// No subobjects, finish object
-					finalizeObject();
+					}
 
 				}
 
-				return objGroup;
-
 				function finalizeObject() {
 
 					if ( ! scope.separateObjects && ! parentParseScope.isFromParse ) {
@@ -355,7 +364,7 @@ THREE.LDrawLoader = ( function () {
 
 				}
 
-				function loadSubobject( subobject, sync ) {
+				function loadSubobject( subobject ) {
 
 					parseScope.mainColourCode = subobject.material.userData.code;
 					parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
@@ -369,16 +378,15 @@ THREE.LDrawLoader = ( function () {
 					}
 
 					// If subobject was cached previously, use the cached one
-					var cached = scope.subobjectCache[ subobject.originalFileName ];
+					var cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
 					if ( cached ) {
 
-						var subobjectGroup = processObject( cached, sync ? undefined : onSubobjectLoaded );
-						if ( sync ) {
+						processObject( cached, function ( subobjectGroup ) {
 
-							addSubobject( subobject, subobjectGroup );
-							return subobjectGroup;
+							onSubobjectLoaded( subobjectGroup, subobject );
+							onSubobjectFinish();
 
-						}
+						}, subobject );
 
 						return;
 
@@ -438,37 +446,36 @@ THREE.LDrawLoader = ( function () {
 							// All location possibilities have been tried, give up loading this object
 							console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
 
-							// Try to read the next subobject
-							parseScope.subobjectIndex ++;
+							return;
 
-							if ( parseScope.subobjectIndex >= parseScope.numSubobjects ) {
+					}
 
-								// All subojects have been loaded. Finish parent object
-								scope.removeScopeLevel();
-								onProcessed( objGroup );
+					subobject.locationState = newLocationState;
+					subobject.url = subobjectURL;
 
-							} else {
+					// Load the subobject
+					// Use another file loader here so we can keep track of the subobject information
+					// and use it when processing the next model.
+					var fileLoader = new THREE.FileLoader( scope.manager );
+					fileLoader.setPath( scope.path );
+					fileLoader.load( subobjectURL, function ( text ) {
 
-								// Load next subobject
-								loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
+						processObject( text, function ( subobjectGroup ) {
 
-							}
+							onSubobjectLoaded( subobjectGroup, subobject );
+							onSubobjectFinish();
 
-							return;
+						}, subobject );
 
-					}
+					}, undefined, function ( err ) {
 
-					subobject.locationState = newLocationState;
-					subobject.url = subobjectURL;
+						onSubobjectError( err, subobject );
 
-					// Load the subobject
-					scope.load( subobjectURL, onSubobjectLoaded, undefined, onSubobjectError );
+					}, subobject );
 
 				}
 
-				function onSubobjectLoaded( subobjectGroup ) {
-
-					var subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
+				function onSubobjectLoaded( subobjectGroup, subobject ) {
 
 					if ( subobjectGroup === null ) {
 
@@ -481,20 +488,6 @@ THREE.LDrawLoader = ( function () {
 					// Add the subobject just loaded
 					addSubobject( subobject, subobjectGroup );
 
-					// Proceed to load the next subobject, or finish the parent object
-
-					parseScope.subobjectIndex ++;
-
-					if ( parseScope.subobjectIndex < parseScope.numSubobjects ) {
-
-						loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
-
-					} else {
-
-						finalizeObject();
-
-					}
-
 				}
 
 				function addSubobject( subobject, subobjectGroup ) {
@@ -512,10 +505,10 @@ THREE.LDrawLoader = ( function () {
 
 				}
 
-				function onSubobjectError( err ) {
+				function onSubobjectError( err, subobject ) {
 
 					// Retry download from a different default possible location
-					loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
+					loadSubobject( subobject );
 
 				}
 
@@ -587,6 +580,7 @@ THREE.LDrawLoader = ( function () {
 				subobjects: null,
 				numSubobjects: 0,
 				subobjectIndex: 0,
+				inverted: false,
 
 				// Current subobject
 				currentFileName: null,
@@ -897,9 +891,6 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
-			// BFC (Back Face Culling) LDraw language meta extension is not implemented, so set all materials double-sided:
-			material.side = THREE.DoubleSide;
-
 			material.transparent = isTransparent;
 			material.opacity = alpha;
 
@@ -1035,6 +1026,11 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
+			var bfcCertified = false;
+			var bfcCCW = true;
+			var bfcInverted = false;
+			var bfcCull = true;
+
 			// Parse all line commands
 			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 
@@ -1047,7 +1043,7 @@ THREE.LDrawLoader = ( function () {
 					if ( line.startsWith( '0 FILE ' ) ) {
 
 						// Save previous embedded file in the cache
-						this.subobjectCache[ currentEmbeddedFileName ] = currentEmbeddedText;
+						this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
 
 						// New embedded text file
 						currentEmbeddedFileName = line.substring( 7 );
@@ -1137,6 +1133,58 @@ THREE.LDrawLoader = ( function () {
 										currentEmbeddedFileName = lp.getRemainingString();
 										currentEmbeddedText = '';
 
+										bfcCertified = false;
+										bfcCCW = true;
+
+									}
+
+									break;
+
+								case 'BFC':
+
+									// Changes to the backface culling state
+									while ( ! lp.isAtTheEnd() ) {
+
+										var token = lp.getToken();
+
+										switch ( token ) {
+
+											case 'CERTIFY':
+											case 'NOCERTIFY':
+
+												bfcCertified = token === 'CERTIFY';
+												bfcCCW = true;
+
+												break;
+
+											case 'CW':
+											case 'CCW':
+
+												bfcCCW = token === 'CCW';
+
+												break;
+
+											case 'INVERTNEXT':
+
+												bfcInverted = true;
+
+												break;
+
+											case 'CLIP':
+											case 'NOCLIP':
+
+											  bfcCull = token === 'CLIP';
+
+												break;
+
+											default:
+
+												console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
+
+												break;
+
+										}
+
 									}
 
 									break;
@@ -1198,6 +1246,14 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
+						// If the scale of the object is negated then the triangle winding order
+						// needs to be flipped.
+						if ( scope.separateObjects === false && matrix.determinant() < 0 ) {
+
+							bfcInverted = ! bfcInverted;
+
+						}
+
 						subobjects.push( {
 							material: material,
 							matrix: matrix,
@@ -1205,9 +1261,12 @@ THREE.LDrawLoader = ( function () {
 							originalFileName: fileName,
 							locationState: LDrawLoader.FILE_LOCATION_AS_IS,
 							url: null,
-							triedLowerCase: false
+							triedLowerCase: false,
+							inverted: bfcInverted !== currentParseScope.inverted
 						} );
 
+						bfcInverted = false;
+
 						break;
 
 					// Line type 2: Line segment
@@ -1229,14 +1288,45 @@ THREE.LDrawLoader = ( function () {
 
 						var material = parseColourCode( lp );
 
+						var inverted = currentParseScope.inverted;
+						var ccw = bfcCCW !== inverted;
+						var doubleSided = ! bfcCertified || ! bfcCull;
+						var v0, v1, v2;
+
+						if ( ccw === true ) {
+
+							v0 = parseVector( lp );
+							v1 = parseVector( lp );
+							v2 = parseVector( lp );
+
+						} else {
+
+							v2 = parseVector( lp );
+							v1 = parseVector( lp );
+							v0 = parseVector( lp );
+
+						}
+
 						triangles.push( {
 							material: material,
 							colourCode: material.userData.code,
-							v0: parseVector( lp ),
-							v1: parseVector( lp ),
-							v2: parseVector( lp )
+							v0: v0,
+							v1: v1,
+							v2: v2
 						} );
 
+						if ( doubleSided === true ) {
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v2,
+								v2: v1
+							} );
+
+						}
+
 						break;
 
 					// Line type 4: Quadrilateral
@@ -1244,10 +1334,26 @@ THREE.LDrawLoader = ( function () {
 
 						var material = parseColourCode( lp );
 
-						var v0 = parseVector( lp );
-						var v1 = parseVector( lp );
-						var v2 = parseVector( lp );
-						var v3 = parseVector( lp );
+						var inverted = currentParseScope.inverted;
+						var ccw = bfcCCW !== inverted;
+						var doubleSided = ! bfcCertified || ! bfcCull;
+						var v0, v1, v2, v3;
+
+						if ( ccw === true ) {
+
+							v0 = parseVector( lp );
+							v1 = parseVector( lp );
+							v2 = parseVector( lp );
+							v3 = parseVector( lp );
+
+						} else {
+
+							v3 = parseVector( lp );
+							v2 = parseVector( lp );
+							v1 = parseVector( lp );
+							v0 = parseVector( lp );
+
+						}
 
 						triangles.push( {
 							material: material,
@@ -1265,6 +1371,26 @@ THREE.LDrawLoader = ( function () {
 							v2: v3
 						} );
 
+						if ( doubleSided === true ) {
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v2,
+								v2: v1
+							} );
+
+							triangles.push( {
+								material: material,
+								colourCode: material.userData.code,
+								v0: v0,
+								v1: v3,
+								v2: v2
+							} );
+
+						}
+
 						break;
 
 					// Line type 5: Optional line
@@ -1282,7 +1408,7 @@ THREE.LDrawLoader = ( function () {
 
 			if ( parsingEmbeddedFiles ) {
 
-				this.subobjectCache[ currentEmbeddedFileName ] = currentEmbeddedText;
+				this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
 
 			}
 

+ 11 - 8
examples/js/loaders/LoaderSupport.js

@@ -347,7 +347,6 @@ THREE.LoaderSupport.PrepData.prototype = {
  * @class
  */
 THREE.LoaderSupport.MeshBuilder = function() {
-	console.info( 'Using THREE.LoaderSupport.MeshBuilder version: ' + THREE.LoaderSupport.MeshBuilder.LOADER_MESH_BUILDER_VERSION );
 	this.validator = THREE.LoaderSupport.Validator;
 
 	this.logging = {
@@ -356,9 +355,11 @@ THREE.LoaderSupport.MeshBuilder = function() {
 	};
 
 	this.callbacks = new THREE.LoaderSupport.Callbacks();
-	this.materials = [];
+	this.materials = {};
 };
-THREE.LoaderSupport.MeshBuilder.LOADER_MESH_BUILDER_VERSION = '1.3.0';
+THREE.LoaderSupport.MeshBuilder.LOADER_MESH_BUILDER_VERSION = '1.3.1';
+console.info( 'Using THREE.LoaderSupport.MeshBuilder version: ' + THREE.LoaderSupport.MeshBuilder.LOADER_MESH_BUILDER_VERSION );
+
 
 THREE.LoaderSupport.MeshBuilder.prototype = {
 
@@ -715,7 +716,6 @@ THREE.LoaderSupport.MeshBuilder.prototype = {
  * @class
  */
 THREE.LoaderSupport.WorkerSupport = function () {
-	console.info( 'Using THREE.LoaderSupport.WorkerSupport version: ' + THREE.LoaderSupport.WorkerSupport.WORKER_SUPPORT_VERSION );
 	this.logging = {
 		enabled: true,
 		debug: false
@@ -726,6 +726,8 @@ THREE.LoaderSupport.WorkerSupport = function () {
 };
 
 THREE.LoaderSupport.WorkerSupport.WORKER_SUPPORT_VERSION = '2.3.0';
+console.info( 'Using THREE.LoaderSupport.WorkerSupport version: ' + THREE.LoaderSupport.WorkerSupport.WORKER_SUPPORT_VERSION );
+
 
 THREE.LoaderSupport.WorkerSupport.prototype = {
 
@@ -1285,10 +1287,10 @@ THREE.LoaderSupport.WorkerRunnerRefImpl.prototype = {
 
 			var self = this.getParentScope();
 			var callbacks = {
-				callbackMeshBuilder: function ( payload ) {
+				callbackOnAssetAvailable: function ( payload ) {
 					self.postMessage( payload );
 				},
-				callbackProgress: function ( text ) {
+				callbackOnProgress: function ( text ) {
 					if ( payload.logging.enabled && payload.logging.debug ) console.debug( 'WorkerRunner: progress: ' + text );
 				}
 			};
@@ -1304,7 +1306,7 @@ THREE.LoaderSupport.WorkerRunnerRefImpl.prototype = {
 
 			if ( payload.logging.enabled ) console.log( 'WorkerRunner: Run complete!' );
 
-			callbacks.callbackMeshBuilder( {
+			callbacks.callbackOnAssetAvailable( {
 				cmd: 'complete',
 				msg: 'WorkerRunner completed run.'
 			} );
@@ -1414,7 +1416,6 @@ THREE.LoaderSupport.WorkerSupport.NodeLoaderWorker.prototype.initWorker = functi
  * @param {string} classDef Class definition to be used for construction
  */
 THREE.LoaderSupport.WorkerDirector = function ( classDef ) {
-	console.info( 'Using THREE.LoaderSupport.WorkerDirector version: ' + THREE.LoaderSupport.WorkerDirector.LOADER_WORKER_DIRECTOR_VERSION );
 	this.logging = {
 		enabled: true,
 		debug: false
@@ -1443,6 +1444,8 @@ THREE.LoaderSupport.WorkerDirector = function ( classDef ) {
 THREE.LoaderSupport.WorkerDirector.LOADER_WORKER_DIRECTOR_VERSION = '2.3.0';
 THREE.LoaderSupport.WorkerDirector.MAX_WEB_WORKER = 16;
 THREE.LoaderSupport.WorkerDirector.MAX_QUEUE_SIZE = 2048;
+console.info( 'Using THREE.LoaderSupport.WorkerDirector version: ' + THREE.LoaderSupport.WorkerDirector.LOADER_WORKER_DIRECTOR_VERSION );
+
 
 THREE.LoaderSupport.WorkerDirector.prototype = {
 

+ 117 - 74
examples/js/loaders/OBJLoader2.js

@@ -17,8 +17,6 @@ if ( THREE.LoaderSupport === undefined ) console.error( '"THREE.LoaderSupport" i
  */
 
 THREE.OBJLoader2 = function ( manager ) {
-	console.info( 'Using THREE.OBJLoader2 version: ' + THREE.OBJLoader2.OBJLOADER2_VERSION );
-
 	this.manager = THREE.LoaderSupport.Validator.verifyInput( manager, THREE.DefaultLoadingManager );
 	this.logging = {
 		enabled: true,
@@ -40,8 +38,9 @@ THREE.OBJLoader2 = function ( manager ) {
 	this.workerSupport = new THREE.LoaderSupport.WorkerSupport();
 	this.terminateWorkerOnLoad = true;
 };
+THREE.OBJLoader2.OBJLOADER2_VERSION = '2.5.1';
+console.info( 'Using THREE.OBJLoader2 version: ' + THREE.OBJLoader2.OBJLOADER2_VERSION );
 
-THREE.OBJLoader2.OBJLOADER2_VERSION = '2.5.0';
 
 THREE.OBJLoader2.prototype = {
 
@@ -174,19 +173,12 @@ THREE.OBJLoader2.prototype = {
 		if ( this.logging.enabled && this.logging.debug ) console.debug( content );
 	},
 
-	_onError: function ( event ) {
-		var output = 'Error occurred while downloading!';
-
-		if ( event.currentTarget && event.currentTarget.statusText !== null ) {
+	_onError: function ( errorMessage ) {
+		if ( this.logging.enabled && this.logging.debug ) {
 
-			output += '\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText;
+			console.log( errorMessage );
 
 		}
-		this.onProgress( 'error', output, -1 );
-		this._throwError( output );
-	},
-
-	_throwError: function ( errorMessage ) {
 		if ( THREE.LoaderSupport.Validator.isValid( this.callbacks.onReportError ) )  {
 
 			this.callbacks.onReportError( errorMessage );
@@ -216,9 +208,18 @@ THREE.OBJLoader2.prototype = {
 	_loadObj: function ( resource, onLoad, onProgress, onError, onMeshAlter, useAsync ) {
 		var scope = this;
 		if ( ! THREE.LoaderSupport.Validator.isValid( onError ) ) {
+
 			onError = function ( event ) {
-				scope._onError( event );
-			}
+
+				var errorMessage = event;
+				if ( event.currentTarget && event.currentTarget.statusText !== null ) {
+
+					errorMessage = 'Error occurred while downloading!\nurl: ' + event.currentTarget.responseURL + '\nstatus: ' + event.currentTarget.statusText;
+
+				}
+				scope._onError( errorMessage );
+
+			};
 		}
 
 		// fast-fail
@@ -339,10 +340,9 @@ THREE.OBJLoader2.prototype = {
 	 */
 	parse: function ( content ) {
 		// fast-fail in case of illegal data
-		if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) {
+		if ( content === null || content === undefined ) {
 
-			console.warn( 'Provided content is not a valid ArrayBuffer or String.' );
-			return this.loaderRootNode;
+			throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing';
 
 		}
 		if ( this.logging.enabled ) console.time( 'OBJLoader2 parse: ' + this.modelName );
@@ -366,11 +366,15 @@ THREE.OBJLoader2.prototype = {
 				scope.loaderRootNode.add( mesh );
 			}
 		};
-		parser.setCallbackMeshBuilder( onMeshLoaded );
+		parser.setCallbackOnAssetAvailable( onMeshLoaded );
 		var onProgressScoped = function ( text, numericalValue ) {
 			scope.onProgress( 'progressParse', text, numericalValue );
 		};
-		parser.setCallbackProgress( onProgressScoped );
+		parser.setCallbackOnProgress( onProgressScoped );
+		var onErrorScoped = function ( message ) {
+			scope._onError( message );
+		};
+		parser.setCallbackOnError( onErrorScoped );
 
 		if ( content instanceof ArrayBuffer || content instanceof Uint8Array ) {
 
@@ -384,7 +388,7 @@ THREE.OBJLoader2.prototype = {
 
 		} else {
 
-			this._throwError( 'Provided content was neither of type String nor Uint8Array! Aborting...' );
+			this._onError( 'Provided content was neither of type String nor Uint8Array! Aborting...' );
 
 		}
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2 parse: ' + this.modelName );
@@ -414,10 +418,9 @@ THREE.OBJLoader2.prototype = {
 			if ( measureTime && scope.logging.enabled ) console.timeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
 		};
 		// fast-fail in case of illegal data
-		if ( ! THREE.LoaderSupport.Validator.isValid( content ) ) {
+		if ( content === null || content === undefined ) {
 
-			console.warn( 'Provided content is not a valid ArrayBuffer.' );
-			scopedOnLoad()
+			throw 'Provided content is not a valid ArrayBuffer or String. Unable to continue parsing';
 
 		} else {
 
@@ -441,7 +444,6 @@ THREE.OBJLoader2.prototype = {
 			workerCode += '  * This code was constructed by OBJLoader2 buildCode.\n';
 			workerCode += '  */\n\n';
 			workerCode += 'THREE = { LoaderSupport: {}, OBJLoader2: {} };\n\n';
-			workerCode += codeSerializer.serializeObject( 'THREE.LoaderSupport.Validator', THREE.LoaderSupport.Validator );
 			workerCode += codeSerializer.serializeClass( 'THREE.OBJLoader2.Parser', THREE.OBJLoader2.Parser );
 
 			return workerCode;
@@ -503,7 +505,7 @@ THREE.OBJLoader2.prototype = {
 		if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' );
 		if ( THREE.LoaderSupport.Validator.isValid( resource ) && this.logging.enabled ) console.time( 'Loading MTL: ' + resource.name );
 
-		var materials = [];
+		var materials = {};
 		var scope = this;
 		var processMaterials = function ( materialCreator ) {
 			var materialCreatorMaterials = [];
@@ -548,7 +550,7 @@ THREE.OBJLoader2.prototype = {
 
 					} else {
 
-						this._throwError( 'Unable to parse mtl as it it seems to be neither a String, an Array or an ArrayBuffer!' );
+						scope._onError( 'Unable to parse mtl as it it seems to be neither a String, an Array or an ArrayBuffer!' );
 					}
 
 				}
@@ -597,8 +599,11 @@ THREE.OBJLoader2.prototype = {
  * @class
  */
 THREE.OBJLoader2.Parser = function () {
-	this.callbackProgress = null;
-	this.callbackMeshBuilder = null;
+	this.callbacks = {
+		onProgress: null,
+		onAssetAvailable: null,
+		onError: null
+	};
 	this.contentRef = null;
 	this.legacyMode = false;
 
@@ -696,29 +701,61 @@ THREE.OBJLoader2.Parser.prototype = {
 	},
 
 	setMaterials: function ( materials ) {
-		this.materials = THREE.LoaderSupport.Validator.verifyInput( materials, this.materials );
-		this.materials = THREE.LoaderSupport.Validator.verifyInput( this.materials, {} );
+		if ( materials === undefined || materials === null ) return;
+
+		for ( var materialName in materials ) {
+			if ( materials.hasOwnProperty( materialName ) ) {
+
+				this.materials[ materialName ] = materials[ materialName ];
+
+			}
+		}
 	},
 
-	setCallbackMeshBuilder: function ( callbackMeshBuilder ) {
-		if ( ! THREE.LoaderSupport.Validator.isValid( callbackMeshBuilder ) ) {
+	setCallbackOnAssetAvailable: function ( onAssetAvailable ) {
+		if ( onAssetAvailable !== null && onAssetAvailable !== undefined ) {
 
-			this._throwError( 'Unable to run as no "MeshBuilder" callback is set.' );
+			this.callbacks.onAssetAvailable = onAssetAvailable;
 
 		}
-		this.callbackMeshBuilder = callbackMeshBuilder;
 	},
 
-	setCallbackProgress: function ( callbackProgress ) {
-		this.callbackProgress = callbackProgress;
+	setCallbackOnProgress: function ( onProgress ) {
+		if ( onProgress !== null && onProgress !== undefined ) {
+
+			this.callbacks.onProgress = onProgress;
+
+		}
+	},
+
+	setCallbackOnError: function ( onError ) {
+		if ( onError !== null && onError !== undefined ) {
+
+			this.callbacks.onError = onError;
+
+		}
 	},
 
+
 	setLogging: function ( enabled, debug ) {
 		this.logging.enabled = enabled === true;
 		this.logging.debug = debug === true;
 	},
 
 	configure: function () {
+		if ( this.callbacks.onAssetAvailable === null ) {
+
+			var errorMessage = 'Unable to run as no callback for building meshes is set.';
+			if ( this.callbacks.onError !== null ) {
+
+				this.callbacks.onError( errorMessage );
+
+			} else {
+
+				throw errorMessage;
+			}
+
+		}
 		this.pushSmoothingGroup( 1 );
 
 		if ( this.logging.enabled ) {
@@ -731,9 +768,16 @@ THREE.OBJLoader2.Parser.prototype = {
 				+ '\n\tmaterialPerSmoothingGroup: ' + this.materialPerSmoothingGroup
 				+ '\n\tuseOAsMesh: ' + this.useOAsMesh
 				+ '\n\tuseIndices: ' + this.useIndices
-				+ '\n\tdisregardNormals: ' + this.disregardNormals
-				+ '\n\tcallbackMeshBuilderName: ' + this.callbackMeshBuilder.name
-				+ '\n\tcallbackProgressName: ' + this.callbackProgress.name;
+				+ '\n\tdisregardNormals: ' + this.disregardNormals;
+			if ( this.callbacks.onProgress !== null ) {
+				printedConfig += '\n\tcallbacks.onProgress: ' + this.callbacks.onProgress.name;
+			}
+			if ( this.callbacks.onAssetAvailable !== null ) {
+				printedConfig += '\n\tcallbacks.onAssetAvailable: ' + this.callbacks.onAssetAvailable.name;
+			}
+			if ( this.callbacks.onError !== null ) {
+				printedConfig += '\n\tcallbacks.onError: ' + this.callbacks.onError.name;
+			}
 			console.info( printedConfig );
 		}
 	},
@@ -1042,7 +1086,7 @@ THREE.OBJLoader2.Parser.prototype = {
 		var index = this.rawMesh.activeMtlName + '|' + this.rawMesh.smoothingGroup.normalized;
 		this.rawMesh.subGroupInUse = this.rawMesh.subGroups[ index ];
 
-		if ( ! THREE.LoaderSupport.Validator.isValid( this.rawMesh.subGroupInUse ) ) {
+		if ( this.rawMesh.subGroupInUse === undefined || this.rawMesh.subGroupInUse === null ) {
 
 			this.rawMesh.subGroupInUse = {
 				index: index,
@@ -1110,17 +1154,17 @@ THREE.OBJLoader2.Parser.prototype = {
 
 			var mappingName = faceIndexV + ( faceIndexU ? '_' + faceIndexU : '_n' ) + ( faceIndexN ? '_' + faceIndexN : '_n' );
 			var indicesPointer = this.rawMesh.subGroupInUse.indexMappings[ mappingName ];
-			if ( THREE.LoaderSupport.Validator.isValid( indicesPointer ) ) {
-
-				this.rawMesh.counts.doubleIndicesCount++;
-
-			} else {
+			if ( indicesPointer === undefined || indicesPointer === null ) {
 
 				indicesPointer = this.rawMesh.subGroupInUse.vertices.length / 3;
 				updateSubGroupInUse();
 				this.rawMesh.subGroupInUse.indexMappings[ mappingName ] = indicesPointer;
 				this.rawMesh.subGroupInUse.indexMappingsCount++;
 
+			} else {
+
+				this.rawMesh.counts.doubleIndicesCount++;
+
 			}
 			this.rawMesh.subGroupInUse.indices.push( indicesPointer );
 
@@ -1202,11 +1246,12 @@ THREE.OBJLoader2.Parser.prototype = {
 
 	processCompletedMesh: function () {
 		var result = this.finalizeRawMesh();
-		if ( THREE.LoaderSupport.Validator.isValid( result ) ) {
+		var haveMesh = result !== null;
+		if ( haveMesh ) {
 
-			if ( this.colors.length > 0 && this.colors.length !== this.vertices.length ) {
+			if ( this.colors.length > 0 && this.colors.length !== this.vertices.length && this.callbacks.onError !== null ) {
 
-				this._throwError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
+				this.callbacks.onError( 'Vertex Colors were detected, but vertex count and color count do not match!' );
 
 			}
 			if ( this.logging.enabled && this.logging.debug ) console.debug( this.createRawMeshReport( this.inputObjectCount ) );
@@ -1214,14 +1259,17 @@ THREE.OBJLoader2.Parser.prototype = {
 
 			this.buildMesh( result );
 			var progressBytesPercent = this.globalCounts.currentByte / this.globalCounts.totalBytes;
-			this.callbackProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName + '] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
+			if ( this.callbacks.onProgress !== null ) {
+
+				this.callbacks.onProgress( 'Completed [o: ' + this.rawMesh.objectName + ' g:' + this.rawMesh.groupName +
+					'] Total progress: ' + ( progressBytesPercent * 100 ).toFixed( 2 ) + '%', progressBytesPercent );
+
+			}
 			this.resetRawMesh();
 			return true;
 
-		} else {
-
-			return false;
 		}
+		return haveMesh;
 	},
 
 	/**
@@ -1241,7 +1289,7 @@ THREE.OBJLoader2.Parser.prototype = {
 		var colorFA = ( result.absoluteColorCount > 0 ) ? new Float32Array( result.absoluteColorCount ) : null;
 		var normalFA = ( result.absoluteNormalCount > 0 ) ? new Float32Array( result.absoluteNormalCount ) : null;
 		var uvFA = ( result.absoluteUvCount > 0 ) ? new Float32Array( result.absoluteUvCount ) : null;
-		var haveVertexColors = THREE.LoaderSupport.Validator.isValid( colorFA );
+		var haveVertexColors = colorFA !== null;
 
 		var meshOutputGroup;
 		var materialNames = [];
@@ -1283,25 +1331,20 @@ THREE.OBJLoader2.Parser.prototype = {
 			material = this.materials[ materialName ];
 
 			// both original and derived names do not lead to an existing material => need to use a default material
-			if ( ! THREE.LoaderSupport.Validator.isValid( materialOrg ) && ! THREE.LoaderSupport.Validator.isValid( material ) ) {
-
-				var defaultMaterialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
-				materialOrg = this.materials[ defaultMaterialName ];
-				if ( this.logging.enabled ) console.warn( 'object_group "' + meshOutputGroup.objectName + '_' +
-					meshOutputGroup.groupName + '" was defined with unresolvable material "' +
-					materialNameOrg + '"! Assigning "' + defaultMaterialName + '".' );
-				materialNameOrg = defaultMaterialName;
+			if ( ( materialOrg === undefined || materialOrg === null ) && ( material === undefined || material === null ) ) {
 
-				// if names are identical then there is no need for later manipulation
-				if ( materialNameOrg === materialName ) {
+				materialName = haveVertexColors ? 'defaultVertexColorMaterial' : 'defaultMaterial';
+				material = this.materials[ materialName ];
+				if ( this.logging.enabled ) {
 
-					material = materialOrg;
-					materialName = defaultMaterialName;
+					console.info( 'object_group "' + meshOutputGroup.objectName + '_' +
+						meshOutputGroup.groupName + '" was defined with unresolvable material "' +
+						materialNameOrg + '"! Assigning "' + materialName + '".' );
 
 				}
 
 			}
-			if ( ! THREE.LoaderSupport.Validator.isValid( material ) ) {
+			if ( material === undefined || material === null ) {
 
 				var materialCloneInstructions = {
 					materialNameOrg: materialNameOrg,
@@ -1317,7 +1360,7 @@ THREE.OBJLoader2.Parser.prototype = {
 						materialCloneInstructions: materialCloneInstructions
 					}
 				};
-				this.callbackMeshBuilder( payload );
+				this.callbacks.onAssetAvailable( payload );
 
 				// fake entry for async; sync Parser always works on material references (Builder update directly visible here)
 				if ( this.useAsync ) this.materials[ materialName ] = materialCloneInstructions;
@@ -1382,7 +1425,7 @@ THREE.OBJLoader2.Parser.prototype = {
 			}
 
 			if ( this.logging.enabled && this.logging.debug ) {
-				var materialIndexLine = THREE.LoaderSupport.Validator.isValid( selectedMaterialIndex ) ? '\n\t\tmaterialIndex: ' + selectedMaterialIndex : '';
+				var materialIndexLine = ( selectedMaterialIndex === undefined || selectedMaterialIndex === null ) ? '' : '\n\t\tmaterialIndex: ' + selectedMaterialIndex;
 				var createdReport = '\tOutput Object no.: ' + this.outputObjectCount +
 					'\n\t\tgroupName: ' + meshOutputGroup.groupName +
 					'\n\t\tIndex: ' + meshOutputGroup.index +
@@ -1402,7 +1445,7 @@ THREE.OBJLoader2.Parser.prototype = {
 		}
 
 		this.outputObjectCount++;
-		this.callbackMeshBuilder(
+		this.callbacks.onAssetAvailable(
 			{
 				cmd: 'meshData',
 				progress: {
@@ -1427,10 +1470,10 @@ THREE.OBJLoader2.Parser.prototype = {
 				geometryType: this.rawMesh.faceType < 4 ? 0 : ( this.rawMesh.faceType === 6 ) ? 2 : 1
 			},
 			[ vertexFA.buffer ],
-			THREE.LoaderSupport.Validator.isValid( indexUA ) ? [ indexUA.buffer ] : null,
-			THREE.LoaderSupport.Validator.isValid( colorFA ) ? [ colorFA.buffer ] : null,
-			THREE.LoaderSupport.Validator.isValid( normalFA ) ? [ normalFA.buffer ] : null,
-			THREE.LoaderSupport.Validator.isValid( uvFA ) ? [ uvFA.buffer ] : null
+			indexUA !== null ?  [ indexUA.buffer ] : null,
+			colorFA !== null ? [ colorFA.buffer ] : null,
+			normalFA !== null ? [ normalFA.buffer ] : null,
+			uvFA !== null ? [ uvFA.buffer ] : null
 		);
 	},
 

+ 1 - 1
examples/js/loaders/PCDLoader.js

@@ -169,7 +169,7 @@ THREE.PCDLoader.prototype = {
 
 		}
 
-		var textData = THREE.LoaderUtils.decodeText( data );
+		var textData = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
 
 		// parse header (always ascii format)
 

+ 7 - 4
examples/js/loaders/SVGLoader.js

@@ -804,13 +804,16 @@ THREE.SVGLoader.prototype = {
 
 			var transform = new THREE.Matrix3();
 			var currentTransform = tempTransform0;
-			var transformsTexts = node.getAttribute( 'transform' ).split( ' ' );
+			var transformsTexts = node.getAttribute( 'transform' ).split( ')' );
 
 			for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) {
 
-				var transformText = transformsTexts[ tIndex ];
-				var openParPos = transformText.indexOf( "(" );
-				var closeParPos = transformText.indexOf( ")" );
+				var transformText = transformsTexts[ tIndex ].trim();
+
+				if ( transformText === '' ) continue;
+
+				var openParPos = transformText.indexOf( '(' );
+				var closeParPos = transformText.length;
 
 				if ( openParPos > 0 && openParPos < closeParPos ) {
 

+ 21 - 5
examples/js/loaders/VRMLLoader.js

@@ -512,7 +512,9 @@ THREE.VRMLLoader.prototype = {
 
 					}
 
-					node[ fieldName ] = property;
+					// VRMLLoader does not support text so it can't process the "string" property yet
+
+					if ( fieldName !== 'string' ) node[ fieldName ] = property;
 
 				}
 
@@ -825,6 +827,16 @@ THREE.VRMLLoader.prototype = {
 
 						parent.geometry = new THREE.SphereBufferGeometry( data.radius );
 
+					} else if ( data.nodeType === 'IndexedLineSet' ) {
+
+						console.warn( 'THREE.VRMLLoader: IndexedLineSet not supported yet.' );
+						parent.parent.remove( parent ); // since the loader is not able to parse the geometry, remove the respective 3D object
+
+					} else if ( data.nodeType === 'Text' ) {
+
+						console.warn( 'THREE.VRMLLoader: Text not supported yet.' );
+						parent.parent.remove( parent );
+
 					} else if ( data.nodeType === 'IndexedFaceSet' ) {
 
 						var geometry = new THREE.BufferGeometry();
@@ -1244,13 +1256,13 @@ THREE.VRMLLoader.prototype = {
 
 		// some lines do not have breaks
 
-		for ( var i = lines.length - 1; i > - 1; i -- ) {
-
-			var line = lines[ i ];
+		for ( var i = lines.length - 1; i > 0; i -- ) {
 
 			// The # symbol indicates that all subsequent text, until the end of the line is a comment,
 			// and should be ignored. (see http://gun.teipir.gr/VRML-amgem/spec/part1/grammar.html)
-			line = line.replace( /(#.*)/, '' );
+			lines[ i ] = lines[ i ].replace( /(#.*)/, '' );
+
+			var line = lines[ i ];
 
 			// split lines with {..{ or {..[ - some have both
 			if ( /{.*[{\[]/.test( line ) ) {
@@ -1270,6 +1282,8 @@ THREE.VRMLLoader.prototype = {
 
 			}
 
+			line = lines[ i ];
+
 			if ( /}.*}/.test( line ) ) {
 
 				// split lines with }..}
@@ -1280,6 +1294,8 @@ THREE.VRMLLoader.prototype = {
 
 			}
 
+			line = lines[ i ];
+
 			if ( /^\b[^\s]+\b$/.test( line.trim() ) ) {
 
 				// prevent lines with single words like "coord" or "geometry", see #12209

+ 3 - 3
examples/js/math/ColorConverter.js

@@ -17,7 +17,7 @@ THREE.ColorConverter = {
 
 	},
 
-	getHSV: function() {
+	getHSV: function () {
 
 		var hsl = {};
 
@@ -46,7 +46,7 @@ THREE.ColorConverter = {
 	}(),
 
 	// where c, m, y, k is between 0 and 1
-	
+
 	setCMYK: function ( color, c, m, y, k ) {
 
 		var r = ( 1 - c ) * ( 1 - k );
@@ -62,7 +62,7 @@ THREE.ColorConverter = {
 		if ( target === undefined ) {
 
 			console.warn( 'THREE.ColorConverter: .getCMYK() target is now required' );
-			target = { c: 0, m: 0, y: 0, k:0 };
+			target = { c: 0, m: 0, y: 0, k: 0 };
 
 		}
 

+ 59 - 355
examples/js/math/Lut.js

@@ -5,43 +5,30 @@
 THREE.Lut = function ( colormap, numberofcolors ) {
 
 	this.lut = [];
-	this.map = THREE.ColorMapKeywords[ colormap ];
-	this.n = numberofcolors;
-	this.mapname = colormap;
+	this.setColorMap( colormap, numberofcolors );
+	return this;
 
-	var step = 1.0 / this.n;
-
-	for ( var i = 0; i <= 1; i += step ) {
-
-		for ( var j = 0; j < this.map.length - 1; j ++ ) {
-
-			if ( i >= this.map[ j ][ 0 ] && i < this.map[ j + 1 ][ 0 ] ) {
-
-				var min = this.map[ j ][ 0 ];
-				var max = this.map[ j + 1 ][ 0 ];
-
-				var minColor = new THREE.Color( 0xffffff ).setHex( this.map[ j ][ 1 ] );
-				var maxColor = new THREE.Color( 0xffffff ).setHex( this.map[ j + 1 ][ 1 ] );
-
-				var color = minColor.lerp( maxColor, ( i - min ) / ( max - min ) );
-
-				this.lut.push( color );
-
-			}
-
-		}
-
-	}
-
-	return this.set( this );
+};
 
+var defaultLabelParameters = {
+	fontsize: 24,
+	fontface: 'Arial',
+	title: '',
+	um: '',
+	ticks: 0,
+	decimal: 2,
+	notation: 'standard'
 };
 
+var defaultBackgroundColor = { r: 255, g: 100, b: 100, a: 0.8 };
+var defaultBorderColor = { r: 255, g: 0, b: 0, a: 1.0 };
+var defaultBorderThickness = 4;
+
 THREE.Lut.prototype = {
 
 	constructor: THREE.Lut,
 
-	lut: [], map: [], mapname: 'rainbow', n: 256, minV: 0, maxV: 1, legend: null,
+	lut: [], map: [], n: 256, minV: 0, maxV: 1,
 
 	set: function ( value ) {
 
@@ -71,26 +58,43 @@ THREE.Lut.prototype = {
 
 	},
 
-	changeNumberOfColors: function ( numberofcolors ) {
+	setColorMap: function ( colormap, numberofcolors ) {
 
-		this.n = numberofcolors;
+		this.map = THREE.ColorMapKeywords[ colormap ] || THREE.ColorMapKeywords.rainbow;
+		this.n = numberofcolors || 32;
 
-		return new THREE.Lut( this.mapname, this.n );
+		var step = 1.0 / this.n;
 
-	},
+		this.lut.length = 0;
+		for ( var i = 0; i <= 1; i += step ) {
+
+			for ( var j = 0; j < this.map.length - 1; j ++ ) {
+
+				if ( i >= this.map[ j ][ 0 ] && i < this.map[ j + 1 ][ 0 ] ) {
 
-	changeColorMap: function ( colormap ) {
+					var min = this.map[ j ][ 0 ];
+					var max = this.map[ j + 1 ][ 0 ];
 
-		this.mapname = colormap;
+					var minColor = new THREE.Color( this.map[ j ][ 1 ] );
+					var maxColor = new THREE.Color( this.map[ j + 1 ][ 1 ] );
 
-		return new THREE.Lut( this.mapname, this.n );
+					var color = minColor.lerp( maxColor, ( i - min ) / ( max - min ) );
+
+					this.lut.push( color );
+
+				}
+
+			}
+
+		}
+
+		return this;
 
 	},
 
 	copy: function ( lut ) {
 
 		this.lut = lut.lut;
-		this.mapname = lut.mapname;
 		this.map = lut.map;
 		this.n = lut.n;
 		this.minV = lut.minV;
@@ -127,42 +131,26 @@ THREE.Lut.prototype = {
 
 	},
 
-	setLegendOn: function ( parameters ) {
+	createCanvas: function () {
 
-		if ( parameters === undefined ) {
-
-			parameters = {};
-
-		}
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = 1;
+		canvas.height = this.n;
 
-		this.legend = {};
+		this.updateCanvas( canvas );
 
-		this.legend.layout = parameters.hasOwnProperty( 'layout' ) ? parameters[ 'layout' ] : 'vertical';
+		return canvas;
 
-		this.legend.position = parameters.hasOwnProperty( 'position' ) ? parameters[ 'position' ] : { 'x': 4, 'y': 0, 'z': 0 };
-
-		this.legend.dimensions = parameters.hasOwnProperty( 'dimensions' ) ? parameters[ 'dimensions' ] : { 'width': 0.5, 'height': 3 };
-
-		this.legend.canvas = document.createElement( 'canvas' );
-
-		this.legend.canvas.setAttribute( 'id', 'legend' );
-		this.legend.canvas.setAttribute( 'hidden', true );
-
-		document.body.appendChild( this.legend.canvas );
-
-		this.legend.ctx = this.legend.canvas.getContext( '2d' );
+	},
 
-		this.legend.canvas.setAttribute( 'width', 1 );
-		this.legend.canvas.setAttribute( 'height', this.n );
+	updateCanvas: function ( canvas ) {
 
-		this.legend.texture = new THREE.Texture( this.legend.canvas );
+		var ctx = canvas.getContext( '2d', { alpha: false } );
 
-		var imageData = this.legend.ctx.getImageData( 0, 0, 1, this.n );
+		var imageData = ctx.getImageData( 0, 0, 1, this.n );
 
 		var data = imageData.data;
 
-		this.map = THREE.ColorMapKeywords[ this.mapname ];
-
 		var k = 0;
 
 		var step = 1.0 / this.n;
@@ -176,8 +164,8 @@ THREE.Lut.prototype = {
 					var min = this.map[ j - 1 ][ 0 ];
 					var max = this.map[ j ][ 0 ];
 
-					var minColor = new THREE.Color( 0xffffff ).setHex( this.map[ j - 1 ][ 1 ] );
-					var maxColor = new THREE.Color( 0xffffff ).setHex( this.map[ j ][ 1 ] );
+					var minColor = new THREE.Color( this.map[ j - 1 ][ 1 ] );
+					var maxColor = new THREE.Color( this.map[ j ][ 1 ] );
 
 					var color = minColor.lerp( maxColor, ( i - min ) / ( max - min ) );
 
@@ -194,303 +182,19 @@ THREE.Lut.prototype = {
 
 		}
 
-		this.legend.ctx.putImageData( imageData, 0, 0 );
-		this.legend.texture.needsUpdate = true;
-
-		this.legend.legendGeometry = new THREE.PlaneBufferGeometry( this.legend.dimensions.width, this.legend.dimensions.height );
-		this.legend.legendMaterial = new THREE.MeshBasicMaterial( { map: this.legend.texture, side: THREE.DoubleSide } );
-
-		this.legend.mesh = new THREE.Mesh( this.legend.legendGeometry, this.legend.legendMaterial );
-
-		if ( this.legend.layout == 'horizontal' ) {
-
-			this.legend.mesh.rotation.z = - 90 * ( Math.PI / 180 );
-
-		}
-
-		this.legend.mesh.position.copy( this.legend.position );
-
-		return this.legend.mesh;
-
-	},
-
-	setLegendOff: function () {
-
-		this.legend = null;
-
-		return this.legend;
-
-	},
-
-	setLegendLayout: function ( layout ) {
-
-		if ( ! this.legend ) {
-
-			return false;
-
-		}
-
-		if ( this.legend.layout == layout ) {
-
-			return false;
-
-		}
-
-		if ( layout != 'horizontal' && layout != 'vertical' ) {
-
-			return false;
-
-		}
-
-		this.layout = layout;
-
-		if ( layout == 'horizontal' ) {
-
-			this.legend.mesh.rotation.z = 90 * ( Math.PI / 180 );
-
-		}
-
-		if ( layout == 'vertical' ) {
-
-			this.legend.mesh.rotation.z = - 90 * ( Math.PI / 180 );
-
-		}
-
-		return this.legend.mesh;
-
-	},
-
-	setLegendPosition: function ( position ) {
-
-		this.legend.position = new THREE.Vector3( position.x, position.y, position.z );
-
-		return this.legend;
-
-	},
-
-	setLegendLabels: function ( parameters, callback ) {
-
-		if ( ! this.legend ) {
-
-			return false;
-
-		}
-
-		if ( typeof parameters === 'function' ) {
-
-			callback = parameters;
-
-		}
-
-		if ( parameters === undefined ) {
-
-			parameters = {};
-
-		}
-
-		this.legend.labels = {};
-
-		this.legend.labels.fontsize = parameters.hasOwnProperty( 'fontsize' ) ? parameters[ 'fontsize' ] : 24;
-
-		this.legend.labels.fontface = parameters.hasOwnProperty( 'fontface' ) ? parameters[ 'fontface' ] : 'Arial';
+		ctx.putImageData( imageData, 0, 0 );
 
-		this.legend.labels.title = parameters.hasOwnProperty( 'title' ) ? parameters[ 'title' ] : '';
-
-		this.legend.labels.um = parameters.hasOwnProperty( 'um' ) ? ' [ ' + parameters[ 'um' ] + ' ]' : '';
-
-		this.legend.labels.ticks = parameters.hasOwnProperty( 'ticks' ) ? parameters[ 'ticks' ] : 0;
-
-		this.legend.labels.decimal = parameters.hasOwnProperty( 'decimal' ) ? parameters[ 'decimal' ] : 2;
-
-		this.legend.labels.notation = parameters.hasOwnProperty( 'notation' ) ? parameters[ 'notation' ] : 'standard';
-
-		var backgroundColor = { r: 255, g: 100, b: 100, a: 0.8 };
-		var borderColor = { r: 255, g: 0, b: 0, a: 1.0 };
-		var borderThickness = 4;
-
-		var canvasTitle = document.createElement( 'canvas' );
-		var contextTitle = canvasTitle.getContext( '2d' );
-
-		contextTitle.font = 'Normal ' + this.legend.labels.fontsize * 1.2 + 'px ' + this.legend.labels.fontface;
-
-		contextTitle.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')';
-
-		contextTitle.strokeStyle = 'rgba(' + borderColor.r + ',' + borderColor.g + ',' + borderColor.b + ',' + borderColor.a + ')';
-
-		contextTitle.lineWidth = borderThickness;
-
-		contextTitle.fillStyle = 'rgba( 0, 0, 0, 1.0 )';
-
-		contextTitle.fillText( this.legend.labels.title.toString() + this.legend.labels.um.toString(), borderThickness, this.legend.labels.fontsize + borderThickness );
-
-		var txtTitle = new THREE.CanvasTexture( canvasTitle );
-		txtTitle.minFilter = THREE.LinearFilter;
-
-		var spriteMaterialTitle = new THREE.SpriteMaterial( { map: txtTitle } );
-
-		var spriteTitle = new THREE.Sprite( spriteMaterialTitle );
-
-		spriteTitle.scale.set( 2, 1, 1.0 );
-
-		if ( this.legend.layout == 'vertical' ) {
-
-			spriteTitle.position.set( this.legend.position.x + this.legend.dimensions.width, this.legend.position.y + ( this.legend.dimensions.height * 0.45 ), this.legend.position.z );
-
-		}
-
-		if ( this.legend.layout == 'horizontal' ) {
-
-			spriteTitle.position.set( this.legend.position.x * 1.015, this.legend.position.y + ( this.legend.dimensions.height * 0.03 ), this.legend.position.z );
-
-		}
-
-		if ( this.legend.labels.ticks > 0 ) {
-
-			var ticks = {};
-			var lines = {};
-
-			if ( this.legend.layout == 'vertical' ) {
-
-				var topPositionY = this.legend.position.y + ( this.legend.dimensions.height * 0.36 );
-				var bottomPositionY = this.legend.position.y - ( this.legend.dimensions.height * 0.61 );
-
-			}
-
-			if ( this.legend.layout == 'horizontal' ) {
-
-				var topPositionX = this.legend.position.x + ( this.legend.dimensions.height * 0.75 );
-				var bottomPositionX = this.legend.position.x - ( this.legend.dimensions.width * 1.2 );
-
-			}
-
-			for ( var i = 0; i < this.legend.labels.ticks; i ++ ) {
-
-				var value = ( this.maxV - this.minV ) / ( this.legend.labels.ticks - 1 ) * i + this.minV;
-
-				if ( callback ) {
-
-					value = callback( value );
-
-				} else {
-
-					if ( this.legend.labels.notation == 'scientific' ) {
-
-						value = value.toExponential( this.legend.labels.decimal );
-
-					} else {
-
-						value = value.toFixed( this.legend.labels.decimal );
-
-					}
-
-				}
-
-				var canvasTick = document.createElement( 'canvas' );
-				var contextTick = canvasTick.getContext( '2d' );
-
-				contextTick.font = 'Normal ' + this.legend.labels.fontsize + 'px ' + this.legend.labels.fontface;
-
-				contextTick.fillStyle = 'rgba(' + backgroundColor.r + ',' + backgroundColor.g + ',' + backgroundColor.b + ',' + backgroundColor.a + ')';
-
-				contextTick.strokeStyle = 'rgba(' + borderColor.r + ',' + borderColor.g + ',' + borderColor.b + ',' + borderColor.a + ')';
-
-				contextTick.lineWidth = borderThickness;
-
-				contextTick.fillStyle = 'rgba( 0, 0, 0, 1.0 )';
-
-				contextTick.fillText( value.toString(), borderThickness, this.legend.labels.fontsize + borderThickness );
-
-				var txtTick = new THREE.CanvasTexture( canvasTick );
-				txtTick.minFilter = THREE.LinearFilter;
-
-				var spriteMaterialTick = new THREE.SpriteMaterial( { map: txtTick } );
-
-				var spriteTick = new THREE.Sprite( spriteMaterialTick );
-
-				spriteTick.scale.set( 2, 1, 1.0 );
-
-				if ( this.legend.layout == 'vertical' ) {
-
-					var position = bottomPositionY + ( topPositionY - bottomPositionY ) * ( ( value - this.minV ) / ( this.maxV - this.minV ) );
-
-					spriteTick.position.set( this.legend.position.x + ( this.legend.dimensions.width * 2.7 ), position, this.legend.position.z );
-
-				}
-
-				if ( this.legend.layout == 'horizontal' ) {
-
-					var position = bottomPositionX + ( topPositionX - bottomPositionX ) * ( ( value - this.minV ) / ( this.maxV - this.minV ) );
-
-					if ( this.legend.labels.ticks > 5 ) {
-
-						if ( i % 2 === 0 ) {
-
-							var offset = 1.7;
-
-						} else {
-
-							var offset = 2.1;
-
-						}
-
-					} else {
-
-						var offset = 1.7;
-
-					}
-
-					spriteTick.position.set( position, this.legend.position.y - this.legend.dimensions.width * offset, this.legend.position.z );
-
-				}
-
-				var material = new THREE.LineBasicMaterial( { color: 0x000000, linewidth: 2 } );
-
-				var points = [];
-
-
-				if ( this.legend.layout == 'vertical' ) {
-
-					var linePosition = ( this.legend.position.y - ( this.legend.dimensions.height * 0.5 ) + 0.01 ) + ( this.legend.dimensions.height ) * ( ( value - this.minV ) / ( this.maxV - this.minV ) * 0.99 );
-
-					points.push( new THREE.Vector3( this.legend.position.x + this.legend.dimensions.width * 0.55, linePosition, this.legend.position.z ) );
-
-					points.push( new THREE.Vector3( this.legend.position.x + this.legend.dimensions.width * 0.7, linePosition, this.legend.position.z ) );
-
-				}
-
-				if ( this.legend.layout == 'horizontal' ) {
-
-					var linePosition = ( this.legend.position.x - ( this.legend.dimensions.height * 0.5 ) + 0.01 ) + ( this.legend.dimensions.height ) * ( ( value - this.minV ) / ( this.maxV - this.minV ) * 0.99 );
-
-					points.push( new THREE.Vector3( linePosition, this.legend.position.y - this.legend.dimensions.width * 0.55, this.legend.position.z ) );
-
-					points.push( new THREE.Vector3( linePosition, this.legend.position.y - this.legend.dimensions.width * 0.7, this.legend.position.z ) );
-
-				}
-
-				var geometry = new THREE.BufferGeometry().setFromPoints( points );
-
-				var line = new THREE.Line( geometry, material );
-
-				lines[ i ] = line;
-				ticks[ i ] = spriteTick;
-
-			}
-
-		}
-
-		return { 'title': spriteTitle, 'ticks': ticks, 'lines': lines };
+		return canvas;
 
 	}
-
 };
 
 
 THREE.ColorMapKeywords = {
 
-	"rainbow": [[ 0.0, '0x0000FF' ], [ 0.2, '0x00FFFF' ], [ 0.5, '0x00FF00' ], [ 0.8, '0xFFFF00' ], [ 1.0, '0xFF0000' ]],
-	"cooltowarm": [[ 0.0, '0x3C4EC2' ], [ 0.2, '0x9BBCFF' ], [ 0.5, '0xDCDCDC' ], [ 0.8, '0xF6A385' ], [ 1.0, '0xB40426' ]],
-	"blackbody": [[ 0.0, '0x000000' ], [ 0.2, '0x780000' ], [ 0.5, '0xE63200' ], [ 0.8, '0xFFFF00' ], [ 1.0, '0xFFFFFF' ]],
-	"grayscale": [[ 0.0, '0x000000' ], [ 0.2, '0x404040' ], [ 0.5, '0x7F7F80' ], [ 0.8, '0xBFBFBF' ], [ 1.0, '0xFFFFFF' ]]
+	"rainbow": [[ 0.0, 0x0000FF ], [ 0.2, 0x00FFFF ], [ 0.5, 0x00FF00 ], [ 0.8, 0xFFFF00 ], [ 1.0, 0xFF0000 ]],
+	"cooltowarm": [[ 0.0, 0x3C4EC2 ], [ 0.2, 0x9BBCFF ], [ 0.5, 0xDCDCDC ], [ 0.8, 0xF6A385 ], [ 1.0, 0xB40426 ]],
+	"blackbody": [[ 0.0, 0x000000 ], [ 0.2, 0x780000 ], [ 0.5, 0xE63200 ], [ 0.8, 0xFFFF00 ], [ 1.0, 0xFFFFFF ]],
+	"grayscale": [[ 0.0, 0x000000 ], [ 0.2, 0x404040 ], [ 0.5, 0x7F7F80 ], [ 0.8, 0xBFBFBF ], [ 1.0, 0xFFFFFF ]]
 
 };

+ 12 - 3
examples/js/nodes/accessors/NormalNode.js

@@ -43,7 +43,8 @@ NormalNode.prototype.generate = function ( builder, output ) {
 
 		case NormalNode.LOCAL:
 
-			builder.requires.normal = true;
+			// to use vObjectNormal as vertex normal
+			//builder.requires.normal = true;
 
 			result = 'normal';
 
@@ -51,9 +52,17 @@ NormalNode.prototype.generate = function ( builder, output ) {
 
 		case NormalNode.WORLD:
 
-			builder.requires.worldNormal = true;
+			if ( builder.isShader( 'vertex' ) ) {
 
-			result = builder.isShader( 'vertex' ) ? '( modelMatrix * vec4( objectNormal, 0.0 ) ).xyz' : 'vWNormal';
+				return '( modelMatrix * vec4( objectNormal, 0.0 ) ).xyz';
+
+			} else {
+
+				builder.requires.worldNormal = true;
+
+				result = 'vWNormal';
+
+			}
 
 			break;
 

+ 20 - 4
examples/js/nodes/accessors/PositionNode.js

@@ -59,17 +59,33 @@ PositionNode.prototype.generate = function ( builder, output ) {
 
 		case PositionNode.LOCAL:
 
-			builder.requires.position = true;
+			if ( builder.isShader( 'vertex' ) ) {
 
-			result = builder.isShader( 'vertex' ) ? 'transformed' : 'vPosition';
+				result = 'transformed';
+
+			} else {
+
+				builder.requires.position = true;
+
+				result = 'vPosition';
+
+			}
 
 			break;
 
 		case PositionNode.WORLD:
 
-			builder.requires.worldPosition = true;
+			if ( builder.isShader( 'vertex' ) ) {
+
+				return '( modelMatrix * vec4( transformed, 1.0 ) ).xyz';
+
+			} else {
+
+				builder.requires.worldPosition = true;
+
+				result = 'vWPosition';
 
-			result = 'vWPosition';
+			}
 
 			break;
 

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