ソースを参照

Merge remote-tracking branch 'upstream/dev' into patch-4

webprofusion-chrisc 6 年 前
コミット
236dd2b8cd
70 ファイル変更2393 行追加1360 行削除
  1. 29 42
      build/three.js
  2. 407 405
      build/three.min.js
  3. 29 42
      build/three.module.js
  4. 2 2
      docs/api/en/materials/MeshToonMaterial.html
  5. 1 1
      docs/api/en/objects/LOD.html
  6. 9 0
      docs/api/en/renderers/WebGLRenderer.html
  7. 2 2
      docs/api/zh/materials/MeshToonMaterial.html
  8. 1 1
      docs/api/zh/objects/LOD.html
  9. 9 0
      docs/api/zh/renderers/WebGLRenderer.html
  10. 78 62
      docs/examples/quickhull/QuickHull.html
  11. 1 0
      docs/index.html
  12. 53 0
      docs/scenes/js/material.js
  13. 4 0
      editor/index.html
  14. 2 2
      editor/js/History.js
  15. 63 0
      editor/js/Menubar.Add.js
  16. 62 0
      editor/js/Menubar.Edit.js
  17. 22 19
      editor/js/Script.js
  18. 54 0
      editor/js/Sidebar.Geometry.OctahedronGeometry.js
  19. 95 0
      editor/js/Sidebar.Geometry.RingGeometry.js
  20. 55 0
      editor/js/Sidebar.Geometry.TetrahedronGeometry.js
  21. 25 3
      editor/js/Sidebar.Material.js
  22. 112 6
      editor/js/Sidebar.Object.js
  23. 3 3
      editor/js/Sidebar.Settings.js
  24. 43 0
      editor/js/Strings.js
  25. 11 3
      editor/js/Viewport.js
  26. 20 3
      editor/js/libs/ui.three.js
  27. 3 2
      examples/files.js
  28. 1 0
      examples/index.html
  29. 102 0
      examples/js/QuickHull.js
  30. 11 21
      examples/js/controls/PointerLockControls.js
  31. 44 2
      examples/js/effects/OutlineEffect.js
  32. 32 10
      examples/js/exporters/GLTFExporter.js
  33. 201 75
      examples/js/loaders/LDrawLoader.js
  34. 21 5
      examples/js/loaders/VRMLLoader.js
  35. 59 355
      examples/js/math/Lut.js
  36. 12 3
      examples/js/nodes/accessors/NormalNode.js
  37. 20 4
      examples/js/nodes/accessors/PositionNode.js
  38. 7 4
      examples/js/nodes/accessors/ResolutionNode.js
  39. 1 2
      examples/misc_controls_pointerlock.html
  40. 0 0
      examples/models/json/pressure.json
  41. BIN
      examples/models/sea3d/morph.sea
  42. BIN
      examples/models/sea3d/morph.tjs.sea
  43. BIN
      examples/textures/gradientMaps/fiveTone.jpg
  44. BIN
      examples/textures/gradientMaps/threeTone.jpg
  45. 79 185
      examples/webgl_geometry_colors_lookuptable.html
  46. 198 0
      examples/webgl_lightprobe.html
  47. 21 14
      examples/webgl_loader_gltf.html
  48. 10 2
      examples/webgl_loader_ldraw.html
  49. 4 3
      examples/webgl_materials_envmaps_parallax.html
  50. 5 3
      examples/webgl_materials_nodes.html
  51. BIN
      favicon.ico
  52. 5 5
      package.json
  53. 3 0
      src/Three.js
  54. 1 1
      src/core/BufferGeometry.js
  55. 3 1
      src/core/Object3D.js
  56. 152 0
      src/helpers/LightProbeHelper.js
  57. 44 3
      src/lights/LightProbe.js
  58. 29 0
      src/math/SphericalHarmonics3.js
  59. 5 12
      src/math/Vector2.js
  60. 6 12
      src/math/Vector3.js
  61. 1 1
      src/objects/LOD.d.ts
  62. 2 0
      src/objects/LOD.js
  63. 12 1
      src/renderers/WebGLRenderer.js
  64. 2 0
      src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js
  65. 38 0
      src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js
  66. 2 0
      src/renderers/shaders/UniformsLib.js
  67. 18 0
      src/renderers/webgl/WebGLLights.js
  68. 36 31
      src/renderers/webgl/WebGLProgram.js
  69. 1 1
      src/renderers/webgl/WebGLShader.d.ts
  70. 10 6
      src/renderers/webgl/WebGLShader.js

ファイルの差分が大きいため隠しています
+ 29 - 42
build/three.js


ファイルの差分が大きいため隠しています
+ 407 - 405
build/three.min.js


ファイルの差分が大きいため隠しています
+ 29 - 42
build/three.module.js


+ 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/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 />

+ 9 - 0
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 />

+ 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 />

+ 1 - 1
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 />

+ 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 />

+ 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>
 

+ 1 - 0
docs/index.html

@@ -5,6 +5,7 @@
 		<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="../favicon.ico" />
 	</head>
 	<body>
 

+ 53 - 0
docs/scenes/js/material.js

@@ -166,11 +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 ) {
 
@@ -452,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 = {
@@ -566,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 } );

+ 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="../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" ) ) {
 

+ 63 - 0
editor/js/Menubar.Add.js

@@ -86,6 +86,22 @@ Menubar.Add = function ( editor ) {
 	} );
 	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 );
+
 	// Cylinder
 
 	var option = new UI.Row();
@@ -134,6 +150,38 @@ Menubar.Add = function ( editor ) {
 	} );
 	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 );
+
+	// 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 = 'Tetrahedron';
+
+		editor.execute( new AddObjectCommand( mesh ) );
+
+	} );
+	options.add( option );
+
 	// Torus
 
 	var option = new UI.Row();
@@ -388,6 +436,21 @@ Menubar.Add = function ( editor ) {
 	} );
 	options.add( option );
 
+	// OrthographicCamera
+
+	var option = new UI.Row();
+	option.setClass( 'option' );
+	option.setTextContent( strings.getKey( 'menubar/add/orthographiccamera' ) );
+	option.onClick( function () {
+
+		var camera = new THREE.OrthographicCamera();
+		camera.name = 'OrthographicCamera';
+
+		editor.execute( new AddObjectCommand( camera ) );
+
+	} );
+	options.add( option );
+
 	return container;
 
 };

+ 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;
 

+ 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 ) );

+ 43 - 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',
@@ -298,8 +320,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 +336,7 @@ var Strings = function ( config ) {
 			'menubar/add/hemispherelight': '半球光',
 			'menubar/add/ambientlight': '环境光',
 			'menubar/add/perspectivecamera': '透视相机',
+			'menubar/add/orthographiccamera': '正交相机',
 
 			'menubar/status/autosave': '自动保存',
 
@@ -341,6 +367,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 +422,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 +438,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 );

+ 3 - 2
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",
@@ -305,6 +304,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",

+ 1 - 0
examples/index.html

@@ -4,6 +4,7 @@
 		<title>three.js / examples</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link rel="shortcut icon" href="../favicon.ico" />
 		<style>
 
 			@font-face {

+ 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 = [];

+ 11 - 21
examples/js/controls/PointerLockControls.js

@@ -10,14 +10,7 @@ THREE.PointerLockControls = function ( camera, domElement ) {
 	this.domElement = domElement || document.body;
 	this.isLocked = false;
 
-	camera.rotation.set( 0, 0, 0 );
-
-	var pitchObject = new THREE.Object3D();
-	pitchObject.add( camera );
-
-	var yawObject = new THREE.Object3D();
-	yawObject.position.y = 10;
-	yawObject.add( pitchObject );
+	var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
 
 	var PI_2 = Math.PI / 2;
 
@@ -28,10 +21,14 @@ 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;
+
+		euler.x = Math.max( - PI_2, Math.min( PI_2, euler.x ) );
 
-		pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) );
+		camera.quaternion.setFromEuler( euler );
 
 	}
 
@@ -81,26 +78,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 );
 
 		};
 

+ 44 - 2
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,
@@ -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 );
 
 					}
 

+ 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;
 
 			}
 

+ 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

+ 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;
 

+ 7 - 4
examples/js/nodes/accessors/ResolutionNode.js

@@ -8,6 +8,8 @@ function ResolutionNode() {
 
 	Vector2Node.call( this );
 
+	this.size = new THREE.Vector2();
+
 }
 
 ResolutionNode.prototype = Object.create( Vector2Node.prototype );
@@ -18,11 +20,12 @@ ResolutionNode.prototype.updateFrame = function ( frame ) {
 
 	if ( frame.renderer ) {
 
-		var size = frame.renderer.getSize(),
-			pixelRatio = frame.renderer.getPixelRatio();
+		frame.renderer.getSize( this.size );
+
+		var pixelRatio = frame.renderer.getPixelRatio();
 
-		this.x = size.width * pixelRatio;
-		this.y = size.height * pixelRatio;
+		this.x = this.size.width * pixelRatio;
+		this.y = this.size.height * pixelRatio;
 
 	} else {
 

+ 1 - 2
examples/misc_controls_pointerlock.html

@@ -328,9 +328,8 @@
 						canJump = true;
 
 					}
-
 					controls.getObject().translateX( velocity.x * delta );
-					controls.getObject().translateY( velocity.y * delta );
+					controls.getObject().position.y += ( velocity.y * delta ); // new behavior
 					controls.getObject().translateZ( velocity.z * delta );
 
 					if ( controls.getObject().position.y < 10 ) {

ファイルの差分が大きいため隠しています
+ 0 - 0
examples/models/json/pressure.json


BIN
examples/models/sea3d/morph.sea


BIN
examples/models/sea3d/morph.tjs.sea


BIN
examples/textures/gradientMaps/fiveTone.jpg


BIN
examples/textures/gradientMaps/threeTone.jpg


+ 79 - 185
examples/webgl_geometry_colors_lookuptable.html

@@ -34,7 +34,6 @@
 	<body>
 
 		<div id="info"><a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - lookuptable - vertex color values from a range of data values.<br />
-		press A: change color map, press S: change numberOfColors, press D: toggle Legend on/off, press F: change Legend layout<br />
 		</div>
 
 		<div id="container"></div>
@@ -42,276 +41,171 @@
 		<script src="../build/three.js"></script>
 		<script src="js/math/Lut.js"></script>
 		<script src="js/WebGL.js"></script>
-		<script src="js/libs/stats.min.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
 
 		<script>
-
 			if ( WEBGL.isWebGLAvailable() === false ) {
 
 				document.body.appendChild( WEBGL.getWebGLErrorMessage() );
 
 			}
 
-			var container, stats;
-
-			var camera, scene, renderer, lut, legendLayout;
+			var container;
 
-			var mesh, material;
+			var perpCamera, orthoCamera, renderer, lut;
 
-			var colorMap;
-			var numberOfColors;
+			var mesh, sprite;
+			var scene, uiScene;
 
-			var loader = new THREE.BufferGeometryLoader();
+			var params;
 
 			init();
-			animate();
 
 			function init() {
 
 				container = document.getElementById( 'container' );
 
-				// SCENE
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0xffffff );
 
-				// CAMERA
-				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 100 );
-				camera.position.set( 0, 0, 10 );
+				uiScene = new THREE.Scene();
+
+				lut = new THREE.Lut();
 
-				stats = new Stats();
-				container.appendChild( stats.dom );
+				var width = window.innerWidth;
+				var height = window.innerHeight;
 
-				// LIGHT
-				var ambientLight = new THREE.AmbientLight( 0x444444 );
-				scene.add( ambientLight );
+				perpCamera = new THREE.PerspectiveCamera( 60, width / height, 1, 100 );
+				perpCamera.position.set( 0, 0, 10 );
+				scene.add( perpCamera );
 
-				colorMap = 'rainbow';
-				numberOfColors = 512;
+				orthoCamera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 1, 2 );
+				orthoCamera.position.set( 0.5, 0, 1 );
 
-				legendLayout = 'vertical';
+				sprite = new THREE.Sprite( new THREE.SpriteMaterial( {
+					map: new THREE.CanvasTexture( lut.createCanvas() )
+				} ) );
+				sprite.scale.x = 0.125;
+				uiScene.add( sprite );
 
-				material = new THREE.MeshLambertMaterial( {
+				mesh = new THREE.Mesh( undefined, new THREE.MeshLambertMaterial( {
 					side: THREE.DoubleSide,
 					color: 0xF5F5F5,
 					vertexColors: THREE.VertexColors
-				} );
+				} ) );
+				scene.add( mesh );
 
-				loadModel( colorMap, numberOfColors, legendLayout );
+				params	= {
+					colorMap: 'rainbow',
+				};
+				loadModel( );
 
-				var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.7 );
-				directionalLight.position.set( 17, 9, 30 );
-				scene.add( directionalLight );
+				var pointLight = new THREE.PointLight( 0xffffff, 1 );
+				perpCamera.add( pointLight );
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.autoClear = false;
 				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setSize( width, height );
 				container.appendChild( renderer.domElement );
 
 				window.addEventListener( 'resize', onWindowResize, false );
-				window.addEventListener( 'keydown', onKeyDown, true );
 
-			}
+				var controls = new THREE.OrbitControls( perpCamera, renderer.domElement );
+				controls.addEventListener( 'change', render );
 
-			function onWindowResize() {
+				var gui = new dat.GUI();
 
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
+				gui.add( params, 'colorMap', [ 'rainbow', 'cooltowarm', 'blackbody', 'grayscale' ] ).onChange( function () {
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				render();
+					updateColors();
+					render();
+
+				} );
 
 			}
 
-			function animate() {
+			function onWindowResize() {
+
+				var width = window.innerWidth;
+				var height = window.innerHeight;
 
-				requestAnimationFrame( animate );
+				perpCamera.aspect = width / height;
+				perpCamera.updateProjectionMatrix();
 
+				renderer.setSize( width, height );
 				render();
-				stats.update();
 
 			}
 
 			function render() {
 
-				if ( mesh !== undefined ) mesh.rotation.y += 0.01;
-
-				renderer.render( scene, camera );
+				renderer.clear();
+				renderer.render( scene, perpCamera );
+				renderer.render( uiScene, orthoCamera );
 
 			}
 
-			function loadModel( colorMap, numberOfColors, legendLayout ) {
+			function loadModel( ) {
 
+				var loader = new THREE.BufferGeometryLoader();
 				loader.load( 'models/json/pressure.json', function ( geometry ) {
 
 					geometry.center();
 					geometry.computeVertexNormals();
 
-					var lutColors = [];
-
-					lut = new THREE.Lut( colorMap, numberOfColors );
-
-					lut.setMax( 2000 );
-					lut.setMin( 0 );
-
-					for ( var i = 0; i < geometry.attributes.pressure.array.length; i ++ ) {
-
-						var colorValue = geometry.attributes.pressure.array[ i ];
-
-						var color = lut.getColor( colorValue );
-
-						if ( color === undefined ) {
-
-							console.log( 'Unable to determine color for value:', colorValue );
-
-						} else {
+					// default color attribute
+					var colors = [];
+					for ( var i = 0, n = geometry.attributes.position.count; i < n; ++ i ) {
 
-							lutColors[ 3 * i ] = color.r;
-							lutColors[ 3 * i + 1 ] = color.g;
-							lutColors[ 3 * i + 2 ] = color.b;
-
-						}
+						colors.push( 1, 1, 1 );
 
 					}
+					geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
 
-					geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( lutColors, 3 ) );
-					geometry.removeAttribute( 'pressure' );
-
-					mesh = new THREE.Mesh( geometry, material );
-
-					scene.add( mesh );
-
-					if ( legendLayout ) {
-
-						var legend;
-
-						if ( legendLayout === 'horizontal' ) {
-
-							legend = lut.setLegendOn( { 'layout': 'horizontal', 'position': { 'x': 4, 'y': 0, 'z': 0 } } );
-
-						} else {
-
-							legend = lut.setLegendOn();
-
-						}
+					mesh.geometry = geometry;
+					updateColors();
 
-						scene.add( legend );
-
-						var labels = lut.setLegendLabels( { 'title': 'Pressure', 'um': 'Pa', 'ticks': 5 } );
-
-						scene.add( labels[ 'title' ] );
-
-						for ( var i = 0; i < Object.keys( labels[ 'ticks' ] ).length; i ++ ) {
-
-							scene.add( labels[ 'ticks' ][ i ] );
-							scene.add( labels[ 'lines' ][ i ] );
-
-						}
-
-					}
+					render();
 
 				} );
 
 			}
 
-			function cleanScene() {
-
-				var elementsInTheScene = scene.children.length;
-
-				for ( var i = elementsInTheScene - 1; i > 0; i -- ) {
-
-					var child = scene.children[ i ];
+			function updateColors() {
 
-					if ( ! child.isLight ) {
+				lut.setColorMap( params.colorMap );
 
-						scene.remove( child );
+				lut.setMax( 2000 );
+				lut.setMin( 0 );
 
-						if ( child.isMesh || child.isLine ) {
+				var geometry = mesh.geometry;
+				var pressures = geometry.attributes.pressure;
+				var colors = geometry.attributes.color;
+				for ( var i = 0; i < pressures.array.length; i ++ ) {
 
-							child.geometry.dispose();
-							child.material.dispose();
-							if ( child.material.map ) child.material.map.dispose();
+					var colorValue = pressures.array[ i ];
 
+					var color = lut.getColor( colorValue );
 
-						} else if ( child.isSprite ) {
+					if ( color === undefined ) {
 
-							child.material.dispose();
-							if ( child.material.map ) child.material.map.dispose();
-
-						}
-
-					}
-
-				}
-
-			}
-
-			function onKeyDown( e ) {
-
-				var maps = [ 'rainbow', 'cooltowarm', 'blackbody', 'grayscale' ];
-
-				var colorNumbers = [ '16', '128', '256', '512' ];
-
-				if ( e.keyCode === 65 ) {
-
-					cleanScene();
-
-					var index = maps.indexOf( colorMap ) >= maps.length - 1 ? 0 : maps.indexOf( colorMap ) + 1;
-
-					colorMap = maps[ index ];
-
-					loadModel( colorMap, numberOfColors, legendLayout );
-
-				} else if ( e.keyCode === 83 ) {
-
-					cleanScene();
-
-					var index = colorNumbers.indexOf( numberOfColors ) >= colorNumbers.length - 1 ? 0 : colorNumbers.indexOf( numberOfColors ) + 1;
-
-					numberOfColors = colorNumbers[ index ];
-
-					loadModel( colorMap, numberOfColors, legendLayout );
-
-				} else if ( e.keyCode === 68 ) {
-
-					if ( ! legendLayout ) {
-
-						cleanScene();
-
-						legendLayout = 'vertical';
-
-						loadModel( colorMap, numberOfColors, legendLayout );
+						console.log( 'Unable to determine color for value:', colorValue );
 
 					} else {
 
-						cleanScene();
-
-						legendLayout = lut.setLegendOff();
-
-						loadModel( colorMap, numberOfColors, legendLayout );
+						colors.setXYZ( i, color.r, color.g, color.b );
 
 					}
 
-				} else if ( e.keyCode === 70 ) {
-
-					cleanScene();
-
-					if ( ! legendLayout ) return false;
-
-					lut.setLegendOff();
-
-					if ( legendLayout === 'horizontal' ) {
-
-						legendLayout = 'vertical';
-
-					} else {
-
-						legendLayout = 'horizontal';
-
-					}
+				}
 
-					loadModel( colorMap, numberOfColors, legendLayout );
+				colors.needsUpdate = true;
 
-				}
+				var map = sprite.material.map;
+				lut.updateCanvas( map.image );
+				map.needsUpdate = true;
 
 			}
 

+ 198 - 0
examples/webgl_lightprobe.html

@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - lights - light probe</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				font-family: Monospace;
+				background-color: #000;
+				color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			a {
+				color: #ffa;
+				font-weight: bold;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				z-index: 0; /* to not conflict with dat.gui */
+				display:block;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl lights - light probe
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/WebGL.js"></script>
+
+		<script>
+
+			if ( WEBGL.isWebGLAvailable() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGLErrorMessage() );
+
+			}
+
+			var mesh, renderer, scene, camera;
+
+			var gui;
+
+			var lightProbe;
+			var directionalLight;
+
+			// linear color space
+			var API = {
+				lightProbeIntensity: 1.0,
+				directionalLightIntensity: 0.2,
+				envMapIntensity: 1
+			};
+
+			init();
+
+			function init() {
+
+				// renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				// tone mapping
+				//renderer.toneMapping = THREE.LinearToneMapping;
+			 	//renderer.toneMappingExposure = API.exposure;
+
+				// gamma
+				renderer.gammaOutput = true;
+				renderer.gammaFactor = 2.2; // approximate sRGB
+
+				// scene
+				scene = new THREE.Scene();
+
+				// camera
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.set( 0, 0, 30 );
+
+				// controls
+				var controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
+				controls.minDistance = 10;
+				controls.maxDistance = 50;
+				controls.enablePan = false;
+
+				// probe
+				lightProbe = new THREE.LightProbe( 0xffffff, API.lightProbeIntensity );
+				scene.add( lightProbe );
+
+				// light
+				directionalLight = new THREE.DirectionalLight( 0xffffff, API.directionalLightIntensity );
+				directionalLight.position.set( 10, 10, 10 );
+				scene.add( directionalLight );
+
+				// envmap
+				var genCubeUrls = function ( prefix, postfix ) {
+
+					return [
+						prefix + 'px' + postfix, prefix + 'nx' + postfix,
+						prefix + 'py' + postfix, prefix + 'ny' + postfix,
+						prefix + 'pz' + postfix, prefix + 'nz' + postfix
+					];
+
+				};
+
+				var urls = genCubeUrls( 'textures/cube/pisa/', '.png' );
+
+				new THREE.CubeTextureLoader().load( urls, function ( cubeTexture ) {
+
+					cubeTexture.encoding = THREE.sRGBEncoding;
+
+					scene.background = cubeTexture;
+
+					lightProbe.setFromCubeTexture( cubeTexture );
+
+					var geometry = new THREE.SphereBufferGeometry( 5, 64, 32 );
+					//var geometry = new THREE.TorusKnotBufferGeometry( 4, 1.5, 256, 32, 2, 3 );
+
+					var material = new THREE.MeshStandardMaterial( {
+						color: 0xffffff,
+						metalness: 0,
+						roughness: 0,
+						envMap: cubeTexture,
+						envMapIntensity: API.envMapIntensity,
+					} );
+
+					// mesh
+					mesh = new THREE.Mesh( geometry, material );
+					scene.add( mesh );
+
+					render();
+
+				} );
+
+
+				// gui
+				gui = new dat.GUI();
+
+				gui.width = 300;
+
+				gui.domElement.style.userSelect = 'none';
+
+				var fl = gui.addFolder( 'Intensity' );
+
+				fl.add( API, 'lightProbeIntensity', 0, 1, 0.02 )
+					.name( 'light probe')
+					.onChange( function() { lightProbe.intensity = API.lightProbeIntensity; render(); } );
+
+				fl.add( API, 'directionalLightIntensity', 0, 1, 0.02 )
+					.name( 'directional light')
+					.onChange( function() { directionalLight.intensity = API.directionalLightIntensity; render(); } );
+
+				fl.add( API, 'envMapIntensity', 0, 1, 0.02 )
+					.name( 'envMap')
+					.onChange( function() { mesh.material.envMapIntensity = API.envMapIntensity; render(); } );
+
+				fl.open();
+
+				// listener
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				render();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 21 - 14
examples/webgl_loader_gltf.html

@@ -40,9 +40,6 @@
 		<script src="js/controls/OrbitControls.js"></script>
 		<script src="js/loaders/GLTFLoader.js"></script>
 
-		<script src="js/pmrem/PMREMGenerator.js"></script>
-		<script src="js/pmrem/PMREMCubeUVPacker.js"></script>
-
 		<script src="js/WebGL.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 
@@ -78,13 +75,25 @@
 				var loader = new THREE.CubeTextureLoader().setPath( 'textures/cube/Bridge2/' );
 				loader.load( urls, function ( texture ) {
 
-					var pmremGenerator = new THREE.PMREMGenerator( texture );
-					pmremGenerator.update( renderer );
-
-					var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker( pmremGenerator.cubeLods );
-					pmremCubeUVPacker.update( renderer );
-
-					var envMap = pmremCubeUVPacker.CubeUVRenderTarget.texture;
+					texture.encoding = THREE.sRGBEncoding;
+
+					// light probe
+
+					var lightProbe = new THREE.LightProbe();
+					// Coefficients computed with setFromCubeTexture( texture ) and sh.toArray()
+					lightProbe.sh.fromArray(
+						[ 0.30350402186576847, 0.4695020609740584, 0.6617666153025029,
+							0.08320329629560637, 0.17400245533281114, 0.3453152275957874,
+							0.12158824672933624, 0.10353622444396401, 0.06530153583524678,
+							0.013607857556048842, 0.019188302420841814, 0.01874813525865349,
+							0.010822144860690035, 0.01574198289465548, 0.01742654587405097,
+							0.06310934215371257, 0.061061933521237545, 0.044428745834389806,
+							0.19958014705624538, 0.22020936865062024, 0.19468224569437417,
+							0.019647224115989702, 0.032414009820739324, 0.043555315974879015,
+							0.13316051440231733, 0.1964793374921572, 0.2189213184804167
+						]
+					);
+					scene.add( lightProbe );
 
 					// model
 
@@ -95,7 +104,7 @@
 
 							if ( child.isMesh ) {
 
-								child.material.envMap = envMap;
+								child.material.envMap = texture;
 
 							}
 
@@ -105,9 +114,6 @@
 
 					} );
 
-					pmremGenerator.dispose();
-					pmremCubeUVPacker.dispose();
-
 					scene.background = texture;
 
 				} );
@@ -116,6 +122,7 @@
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.gammaOutput = true;
+				renderer.gammaFactor = 2.2; // approximate sRGB
 				container.appendChild( renderer.domElement );
 
 				window.addEventListener( 'resize', onWindowResize, false );

+ 10 - 2
examples/webgl_loader_ldraw.html

@@ -97,7 +97,7 @@
 
 				//
 
-				renderer = new THREE.WebGLRenderer();
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				container.appendChild( renderer.domElement );
@@ -108,7 +108,8 @@
 
 				guiData = {
 					modelFileName: modelFileList[ 'Car' ],
-					envMapActivated: false
+					envMapActivated: false,
+					separateObjects: false
 				};
 
 				gui = new dat.GUI();
@@ -127,6 +128,12 @@
 
 				} );
 
+				gui.add( guiData, 'separateObjects' ).name( 'Separate Objects' ).onChange( function ( value ) {
+
+					reloadObject( false );
+
+				} );
+
 				window.addEventListener( 'resize', onWindowResize, false );
 
 				progressBarDiv = document.createElement( 'div' );
@@ -160,6 +167,7 @@
 				showProgressBar();
 
 				var lDrawLoader = new THREE.LDrawLoader();
+				lDrawLoader.separateObjects = guiData.separateObjects;
 				lDrawLoader
 					.setPath( ldrawPath )
 					.load( guiData.modelFileName, function ( group2 ) {

+ 4 - 3
examples/webgl_materials_envmaps_parallax.html

@@ -79,8 +79,8 @@
 					vec3 parallaxCorrectNormal( vec3 v, vec3 cubeSize, vec3 cubePos ) {
 
 						vec3 nDir = normalize( v );
-						vec3 rbmax = (   .5 * ( cubeSize - cubePos ) - vWorldPosition ) / nDir;
-						vec3 rbmin = ( - .5 * ( cubeSize - cubePos ) - vWorldPosition ) / nDir;
+						vec3 rbmax = ( .5 * cubeSize + cubePos - vWorldPosition ) / nDir;
+						vec3 rbmin = ( -.5 * cubeSize + cubePos - vWorldPosition ) / nDir;
 
 						vec3 rbminmax;
 						rbminmax.x = ( nDir.x > 0. ) ? rbmax.x : rbmin.x;
@@ -356,7 +356,8 @@
 
 				boxProjectedMat.onBeforeCompile = function ( shader ) {
 
-					shader.uniforms.cubeMapSize = { value: new THREE.Vector3( 200, 100, 100 ) };
+					//these parameters are for the cubeCamera texture
+					shader.uniforms.cubeMapSize = { value: new THREE.Vector3( 200, 200, 100 ) };
 					shader.uniforms.cubeMapPos = { value: new THREE.Vector3( 0, - 50, 0 ) };
 					shader.uniforms.flipEnvMap.value = true;
 

+ 5 - 3
examples/webgl_materials_nodes.html

@@ -455,7 +455,7 @@
 
 							// apply material
 
-							mtl.side = THREE.DoubleSide;
+							mtl.side = defaultSide;
 							mtl.needsUpdate = true;
 
 							mesh.material = mtl;
@@ -766,6 +766,8 @@
 
 						mtl = new THREE.PhongNodeMaterial();
 
+						defaultSide = THREE.FrontSide;
+
 						var intensity = 1.3;
 						var power = new THREE.FloatNode( 3 );
 						var color = new THREE.ColorNode( 0xFFFFFF );
@@ -1538,6 +1540,8 @@
 
 						mtl = new THREE.PhongNodeMaterial();
 
+						defaultSide = THREE.FrontSide;
+
 						var time = new THREE.TimerNode();
 						var uv = new THREE.UVNode();
 
@@ -1577,8 +1581,6 @@
 						mtl.environment = new THREE.ColorNode( 0xFFFFFF );
 						mtl.alpha = clouds;
 
-						defaultSide = THREE.FrontSide;
-
 						// GUI
 
 						addGui( 'color', mtl.environment.value.getHex(), function ( val ) {

BIN
favicon.ico


+ 5 - 5
package.json

@@ -8,16 +8,16 @@
   "module": "build/three.module.js",
   "types": "src/Three.d.ts",
   "files": [
-    "package.json",
-    "LICENSE",
-    "README.md",
     "build/three.js",
     "build/three.min.js",
     "build/three.module.js",
-    "src",
     "examples/js",
     "examples/jsm",
-    "examples/fonts"
+    "examples/fonts",
+    "LICENSE",
+    "package.json",
+    "README.md",
+    "src"
   ],
   "directories": {
     "doc": "docs",

+ 3 - 0
src/Three.js

@@ -60,6 +60,7 @@ export { DirectionalLight } from './lights/DirectionalLight.js';
 export { AmbientLight } from './lights/AmbientLight.js';
 export { LightShadow } from './lights/LightShadow.js';
 export { Light } from './lights/Light.js';
+export { LightProbe } from './lights/LightProbe.js';
 export { StereoCamera } from './cameras/StereoCamera.js';
 export { PerspectiveCamera } from './cameras/PerspectiveCamera.js';
 export { OrthographicCamera } from './cameras/OrthographicCamera.js';
@@ -123,6 +124,7 @@ export { Vector3 } from './math/Vector3.js';
 export { Vector2 } from './math/Vector2.js';
 export { Quaternion } from './math/Quaternion.js';
 export { Color } from './math/Color.js';
+export { SphericalHarmonics3 } from './math/SphericalHarmonics3.js';
 export { ImmediateRenderObject } from './extras/objects/ImmediateRenderObject.js';
 export { VertexNormalsHelper } from './helpers/VertexNormalsHelper.js';
 export { SpotLightHelper } from './helpers/SpotLightHelper.js';
@@ -130,6 +132,7 @@ export { SkeletonHelper } from './helpers/SkeletonHelper.js';
 export { PointLightHelper } from './helpers/PointLightHelper.js';
 export { RectAreaLightHelper } from './helpers/RectAreaLightHelper.js';
 export { HemisphereLightHelper } from './helpers/HemisphereLightHelper.js';
+export { LightProbeHelper } from './helpers/LightProbeHelper.js';
 export { GridHelper } from './helpers/GridHelper.js';
 export { PolarGridHelper } from './helpers/PolarGridHelper.js';
 export { PositionalAudioHelper } from './helpers/PositionalAudioHelper.js';

+ 1 - 1
src/core/BufferGeometry.js

@@ -716,7 +716,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
 						for ( var j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
 
-							vector.fromBufferAttribute( morphAttribute, i );
+							vector.fromBufferAttribute( morphAttribute, j );
 
 							maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 

+ 3 - 1
src/core/Object3D.js

@@ -114,7 +114,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 	applyMatrix: function ( matrix ) {
 
-		this.matrix.multiplyMatrices( matrix, this.matrix );
+		if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+		this.matrix.premultiply( matrix );
 
 		this.matrix.decompose( this.position, this.quaternion, this.scale );
 

+ 152 - 0
src/helpers/LightProbeHelper.js

@@ -0,0 +1,152 @@
+import { Mesh } from '../objects/Mesh.js';
+import { ShaderMaterial } from '../materials/ShaderMaterial.js';
+import { SphereBufferGeometry } from '../geometries/SphereGeometry.js';
+
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+function LightProbeHelper( lightProbe, size ) {
+
+	this.lightProbe = lightProbe;
+
+	this.size = size;
+
+	var defines = {};
+	defines[ 'GAMMA_OUTPUT' ] = "";
+
+	// material
+	var material = new ShaderMaterial( {
+
+		defines: defines,
+
+		uniforms: {
+
+			sh: { value: this.lightProbe.sh.coefficients }, // by reference
+
+			intensity: { value: this.lightProbe.intensity }
+
+		},
+
+		vertexShader: `
+
+			varying vec3 vNormal;
+
+			void main() {
+
+				vNormal = normalize( normalMatrix * normal );
+
+				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+			}`,
+
+		fragmentShader: `
+
+			#define RECIPROCAL_PI 0.318309886
+
+			vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {
+
+				// matrix is assumed to be orthogonal
+
+				return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );
+
+			}
+
+			vec3 linearToOutput( in vec3 a ) {
+
+				#ifdef GAMMA_OUTPUT
+
+					return pow( a, vec3( 1.0 / float( GAMMA_FACTOR ) ) );
+
+				#else
+
+					return a;
+
+				#endif
+
+			}
+
+			// get the irradiance (radiance convolved with cosine lobe) at the point 'normal' on the unit sphere
+			// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf
+			vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {
+
+				// normal is assumed to have unit length
+
+				float x = normal.x, y = normal.y, z = normal.z;
+
+				// band 0
+				vec3 result = shCoefficients[ 0 ] * 0.886227;
+
+				// band 1
+				result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;
+				result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;
+				result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;
+
+				// band 2
+				result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;
+				result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;
+				result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );
+				result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;
+				result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );
+
+				return result;
+
+			}
+
+			uniform vec3 sh[ 9 ]; // sh coefficients
+
+			uniform float intensity; // light probe intensity
+
+			varying vec3 vNormal;
+
+			void main() {
+
+				vec3 normal = normalize( vNormal );
+
+				vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
+
+				vec3 irradiance = shGetIrradianceAt( worldNormal, sh );
+
+				vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;
+
+				outgoingLight = linearToOutput( outgoingLight );
+
+				gl_FragColor = vec4( outgoingLight, 1.0 );
+
+			}`
+
+	} );
+
+	var geometry = new SphereBufferGeometry( 1, 32, 16 );
+
+	Mesh.call( this, geometry, material );
+
+	this.onBeforeRender();
+
+}
+
+LightProbeHelper.prototype = Object.create( Mesh.prototype );
+LightProbeHelper.prototype.constructor = LightProbeHelper;
+
+LightProbeHelper.prototype.dispose = function () {
+
+	this.geometry.dispose();
+	this.material.dispose();
+
+};
+
+LightProbeHelper.prototype.onBeforeRender = function () {
+
+	return function update() {
+
+		this.position.copy( this.lightProbe.position );
+
+		this.scale.set( 1, 1, 1 ).multiplyScalar( this.size );
+
+		this.material.uniforms.intensity.value = this.lightProbe.intensity;
+
+	};
+
+}();
+
+export { LightProbeHelper };

+ 44 - 3
src/lights/LightProbe.js

@@ -10,11 +10,13 @@ import { Light } from './Light.js';
 
 // A LightProbe is a source of indirect-diffuse light
 
-function LightProbe( sh, intensity ) {
+function LightProbe( color, intensity ) {
 
-	Light.call( this, 0xffffff, intensity );
+	Light.call( this, color, intensity );
 
-	this.sh = ( sh !== undefined ) ? sh : new SphericalHarmonics3();
+	this.sh = new SphericalHarmonics3();
+
+	this.sh.coefficients[ 0 ].set( 1, 1, 1 );
 
 	this.type = 'LightProbe';
 
@@ -26,6 +28,45 @@ LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
 
 	isLightProbe: true,
 
+	setAmbientProbe: function ( color, intensity ) {
+
+		this.color.set( color );
+
+		this.intensity = intensity !== undefined ? intensity : 1;
+
+		this.sh.zero();
+
+		// without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI );
+		this.sh.coefficients[ 0 ].set( 1, 1, 1 ).multiplyScalar( 2 * Math.sqrt( Math.PI ) );
+
+	},
+
+	setHemisphereProbe: function ( skyColor, groundColor, intensity ) {
+
+		// up-direction hardwired
+
+		this.color.setHex( 0xffffff );
+
+		this.intensity = intensity !== undefined ? intensity : 1;
+
+		var sky = new Color( skyColor );
+		var ground = new Color( groundColor );
+
+		/* cough */
+		sky = new Vector3( sky.r, sky.g, sky.b );
+		ground = new Vector3( ground.r, ground.g, ground.b );
+
+		// without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI );
+		var c0 = Math.sqrt( Math.PI );
+		var c1 = c0 * Math.sqrt( 0.75 );
+
+		this.sh.zero();
+
+		this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 );
+		this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 );
+
+	},
+
 	// https://www.ppsloan.org/publications/StupidSH36.pdf
 	setFromCubeTexture: function ( cubeTexture ) {
 

+ 29 - 0
src/math/SphericalHarmonics3.js

@@ -175,6 +175,35 @@ Object.assign( SphericalHarmonics3.prototype, {
 
 		return new this.constructor().copy( this );
 
+	},
+
+	fromArray: function ( array ) {
+
+		var coefficients = this.coefficients;
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			coefficients[ i ].fromArray( array, i * 3 );
+
+		}
+
+		return this;
+
+	},
+
+	toArray: function () {
+
+		var array = [];
+		var coefficients = this.coefficients;
+
+		for ( var i = 0; i < 9; i ++ ) {
+
+			coefficients[ i ].toArray( array, i * 3 );
+
+		}
+
+		return array;
+
 	}
 
 } );

+ 5 - 12
src/math/Vector2.js

@@ -278,21 +278,14 @@ Object.assign( Vector2.prototype, {
 
 	},
 
-	clampScalar: function () {
+	clampScalar: function ( minVal, maxVal ) {
 
-		var min = new Vector2();
-		var max = new Vector2();
+		this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+		this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
 
-		return function clampScalar( minVal, maxVal ) {
-
-			min.set( minVal, minVal );
-			max.set( maxVal, maxVal );
-
-			return this.clamp( min, max );
-
-		};
+		return this;
 
-	}(),
+	},
 
 	clampLength: function ( min, max ) {
 

+ 6 - 12
src/math/Vector3.js

@@ -387,21 +387,15 @@ Object.assign( Vector3.prototype, {
 
 	},
 
-	clampScalar: function () {
+	clampScalar: function ( minVal, maxVal ) {
 
-		var min = new Vector3();
-		var max = new Vector3();
+		this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+		this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+		this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
 
-		return function clampScalar( minVal, maxVal ) {
-
-			min.set( minVal, minVal, minVal );
-			max.set( maxVal, maxVal, maxVal );
-
-			return this.clamp( min, max );
-
-		};
+		return this;
 
-	}(),
+	},
 
 	clampLength: function ( min, max ) {
 

+ 1 - 1
src/objects/LOD.d.ts

@@ -10,7 +10,7 @@ export class LOD extends Object3D {
 
   levels: { distance: number; object: Object3D }[];
 
-  addLevel(object: Object3D, distance?: number): void;
+  addLevel(object: Object3D, distance?: number): this;
   getObjectForDistance(distance: number): Object3D;
   raycast(raycaster: Raycaster, intersects: Intersection[]): void;
   update(camera: Camera): void;

+ 2 - 0
src/objects/LOD.js

@@ -68,6 +68,8 @@ LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
 		this.add( object );
 
+		return this;
+
 	},
 
 	getObjectForDistance: function ( distance ) {

+ 12 - 1
src/renderers/WebGLRenderer.js

@@ -77,6 +77,16 @@ function WebGLRenderer( parameters ) {
 	this.domElement = _canvas;
 	this.context = null;
 
+	// Debug configuration container
+	this.debug = {
+
+		/**
+		 * Enables error checking and reporting when shader programs are being compiled
+		 * @type {boolean}
+		 */
+		checkShaderErrors: false
+	};
+
 	// clearing
 
 	this.autoClear = true;
@@ -1599,6 +1609,7 @@ function WebGLRenderer( parameters ) {
 			// wire up the material to this renderer's lighting state
 
 			uniforms.ambientLightColor.value = lights.state.ambient;
+			uniforms.lightProbe.value = lights.state.probe;
 			uniforms.directionalLights.value = lights.state.directional;
 			uniforms.spotLights.value = lights.state.spot;
 			uniforms.rectAreaLights.value = lights.state.rectArea;
@@ -2402,6 +2413,7 @@ function WebGLRenderer( parameters ) {
 	function markUniformsLightsNeedsUpdate( uniforms, value ) {
 
 		uniforms.ambientLightColor.needsUpdate = value;
+		uniforms.lightProbe.needsUpdate = value;
 
 		uniforms.directionalLights.needsUpdate = value;
 		uniforms.pointLights.needsUpdate = value;
@@ -2599,5 +2611,4 @@ function WebGLRenderer( parameters ) {
 
 }
 
-
 export { WebGLRenderer };

+ 2 - 0
src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js

@@ -103,6 +103,8 @@ IncidentLight directLight;
 
 	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
 
+	irradiance += getLightProbeIrradiance( lightProbe, geometry );
+
 	#if ( NUM_HEMI_LIGHTS > 0 )
 
 		#pragma unroll_loop

+ 38 - 0
src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js

@@ -1,5 +1,43 @@
 export default /* glsl */`
 uniform vec3 ambientLightColor;
+uniform vec3 lightProbe[ 9 ];
+
+// get the irradiance (radiance convolved with cosine lobe) at the point 'normal' on the unit sphere
+// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf
+vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {
+
+	// normal is assumed to have unit length
+
+	float x = normal.x, y = normal.y, z = normal.z;
+
+	// band 0
+	vec3 result = shCoefficients[ 0 ] * 0.886227;
+
+	// band 1
+	result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;
+	result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;
+	result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;
+
+	// band 2
+	result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;
+	result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;
+	result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );
+	result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;
+	result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );
+
+	return result;
+
+}
+
+vec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {
+
+	vec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );
+
+	vec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );
+
+	return irradiance;
+
+}
 
 vec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {
 

+ 2 - 0
src/renderers/shaders/UniformsLib.js

@@ -109,6 +109,8 @@ var UniformsLib = {
 
 		ambientLightColor: { value: [] },
 
+		lightProbe: { value: [] },
+
 		directionalLights: { value: [], properties: {
 			direction: {},
 			color: {},

+ 18 - 0
src/renderers/webgl/WebGLLights.js

@@ -121,6 +121,7 @@ function WebGLLights() {
 		},
 
 		ambient: [ 0, 0, 0 ],
+		probe: [],
 		directional: [],
 		directionalShadowMap: [],
 		directionalShadowMatrix: [],
@@ -135,6 +136,8 @@ function WebGLLights() {
 
 	};
 
+	for ( var i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+
 	var vector3 = new Vector3();
 	var matrix4 = new Matrix4();
 	var matrix42 = new Matrix4();
@@ -143,6 +146,8 @@ function WebGLLights() {
 
 		var r = 0, g = 0, b = 0;
 
+		for ( var i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
+
 		var directionalLength = 0;
 		var pointLength = 0;
 		var spotLength = 0;
@@ -167,6 +172,19 @@ function WebGLLights() {
 				g += color.g * intensity;
 				b += color.b * intensity;
 
+			} else if ( light.isLightProbe ) {
+
+				for ( var j = 0; j < 9; j ++ ) {
+
+					var probe = state.probe[ j ];
+					var coeff = light.sh.coefficients[ j ];
+
+					probe.x += coeff.x * color.r * intensity;
+					probe.y += coeff.y * color.g * intensity;
+					probe.z += coeff.z * color.b * intensity;
+
+				}
+
 			} else if ( light.isDirectionalLight ) {
 
 				var uniforms = cache.get( light );

+ 36 - 31
src/renderers/webgl/WebGLProgram.js

@@ -583,8 +583,8 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 	// console.log( '*VERTEX*', vertexGlsl );
 	// console.log( '*FRAGMENT*', fragmentGlsl );
 
-	var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
-	var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
+	var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl, renderer.debug.checkShaderErrors );
+	var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl, renderer.debug.checkShaderErrors );
 
 	gl.attachShader( program, glVertexShader );
 	gl.attachShader( program, glFragmentShader );
@@ -604,56 +604,61 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters,
 
 	gl.linkProgram( program );
 
-	var programLog = gl.getProgramInfoLog( program ).trim();
-	var vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
-	var fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
+	// check for link errors
+	if ( renderer.debug.checkShaderErrors ) {
 
-	var runnable = true;
-	var haveDiagnostics = true;
+		var programLog = gl.getProgramInfoLog( program ).trim();
+		var vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
+		var fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
 
-	// console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) );
-	// console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) );
+		var runnable = true;
+		var haveDiagnostics = true;
 
-	if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {
+		// console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) );
+		// console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) );
 
-		runnable = false;
+		if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {
 
-		console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );
+			runnable = false;
 
-	} else if ( programLog !== '' ) {
+			console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );
 
-		console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+		} else if ( programLog !== '' ) {
 
-	} else if ( vertexLog === '' || fragmentLog === '' ) {
+			console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
 
-		haveDiagnostics = false;
+		} else if ( vertexLog === '' || fragmentLog === '' ) {
 
-	}
+			haveDiagnostics = false;
 
-	if ( haveDiagnostics ) {
+		}
 
-		this.diagnostics = {
+		if ( haveDiagnostics ) {
 
-			runnable: runnable,
-			material: material,
+			this.diagnostics = {
 
-			programLog: programLog,
+				runnable: runnable,
+				material: material,
 
-			vertexShader: {
+				programLog: programLog,
 
-				log: vertexLog,
-				prefix: prefixVertex
+				vertexShader: {
 
-			},
+					log: vertexLog,
+					prefix: prefixVertex
 
-			fragmentShader: {
+				},
 
-				log: fragmentLog,
-				prefix: prefixFragment
+				fragmentShader: {
 
-			}
+					log: fragmentLog,
+					prefix: prefixFragment
 
-		};
+				}
+
+			};
+
+		}
 
 	}
 

+ 1 - 1
src/renderers/webgl/WebGLShader.d.ts

@@ -1,3 +1,3 @@
 export class WebGLShader {
-  constructor(gl: any, type: string, string: string);
+  constructor(gl: any, type: string, string: string, debug: boolean);
 }

+ 10 - 6
src/renderers/webgl/WebGLShader.js

@@ -16,22 +16,26 @@ function addLineNumbers( string ) {
 
 }
 
-function WebGLShader( gl, type, string ) {
+function WebGLShader( gl, type, string, debug ) {
 
 	var shader = gl.createShader( type );
 
 	gl.shaderSource( shader, string );
 	gl.compileShader( shader );
 
-	if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {
+	if ( debug === true ) {
 
-		console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' );
+		if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {
 
-	}
+			console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' );
+
+		}
+
+		if ( gl.getShaderInfoLog( shader ) !== '' ) {
 
-	if ( gl.getShaderInfoLog( shader ) !== '' ) {
+			console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) );
 
-		console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) );
+		}
 
 	}
 

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません