Mr.doob 3 years ago
parent
commit
3151ec625c
100 changed files with 2505 additions and 4183 deletions
  1. 315 225
      build/three.cjs
  2. 315 225
      build/three.js
  3. 0 0
      build/three.min.js
  4. 257 247
      build/three.module.js
  5. 2 1
      docs/api/en/core/BufferGeometry.html
  6. 1 1
      docs/api/en/core/Raycaster.html
  7. 1 1
      docs/api/en/extras/core/Path.html
  8. 5 0
      docs/api/en/helpers/CameraHelper.html
  9. 0 5
      docs/api/en/lights/AmbientLight.html
  10. 4 1
      docs/api/en/materials/MeshBasicMaterial.html
  11. 4 1
      docs/api/en/materials/MeshDepthMaterial.html
  12. 4 1
      docs/api/en/materials/MeshDistanceMaterial.html
  13. 4 1
      docs/api/en/materials/MeshLambertMaterial.html
  14. 5 1
      docs/api/en/materials/MeshMatcapMaterial.html
  15. 5 1
      docs/api/en/materials/MeshPhongMaterial.html
  16. 5 1
      docs/api/en/materials/MeshStandardMaterial.html
  17. 5 1
      docs/api/en/materials/MeshToonMaterial.html
  18. 5 1
      docs/api/en/materials/PointsMaterial.html
  19. 5 2
      docs/api/en/materials/SpriteMaterial.html
  20. 1 1
      docs/api/en/math/Matrix4.html
  21. 1 1
      docs/api/en/renderers/WebGLRenderTarget.html
  22. 1 2
      docs/api/en/renderers/WebGLRenderer.html
  23. 2 1
      docs/api/ko/core/BufferGeometry.html
  24. 1 1
      docs/api/ko/core/Raycaster.html
  25. 1 1
      docs/api/ko/extras/core/Path.html
  26. 2 1
      docs/api/zh/core/BufferGeometry.html
  27. 1 1
      docs/api/zh/core/Raycaster.html
  28. 1 1
      docs/api/zh/extras/core/Path.html
  29. 9 3
      docs/api/zh/helpers/CameraHelper.html
  30. 0 5
      docs/api/zh/lights/AmbientLight.html
  31. 4 1
      docs/api/zh/materials/MeshBasicMaterial.html
  32. 4 1
      docs/api/zh/materials/MeshDepthMaterial.html
  33. 4 1
      docs/api/zh/materials/MeshDistanceMaterial.html
  34. 4 1
      docs/api/zh/materials/MeshLambertMaterial.html
  35. 5 1
      docs/api/zh/materials/MeshMatcapMaterial.html
  36. 5 1
      docs/api/zh/materials/MeshPhongMaterial.html
  37. 5 1
      docs/api/zh/materials/MeshStandardMaterial.html
  38. 5 1
      docs/api/zh/materials/MeshToonMaterial.html
  39. 4 1
      docs/api/zh/materials/PointsMaterial.html
  40. 5 2
      docs/api/zh/materials/SpriteMaterial.html
  41. 1 1
      docs/api/zh/math/Matrix4.html
  42. 1 1
      docs/api/zh/renderers/WebGLRenderer.html
  43. 8 8
      docs/api/zh/renderers/webgl/WebGLProgram.html
  44. 1 1
      docs/manual/ar/introduction/How-to-update-things.html
  45. 1 1
      docs/manual/en/introduction/How-to-update-things.html
  46. 1 1
      docs/manual/ja/introduction/How-to-update-things.html
  47. 1 1
      docs/manual/ko/introduction/How-to-update-things.html
  48. 2 2
      docs/manual/zh/introduction/FAQ.html
  49. 1 1
      docs/manual/zh/introduction/How-to-update-things.html
  50. 12 10
      docs/scenes/ccdiksolver-browser.html
  51. 202 199
      editor/js/EditorControls.js
  52. 36 37
      editor/js/History.js
  53. 1 1
      editor/sw.js
  54. 25 105
      examples/css3d_molecules.html
  55. 6 2
      examples/files.json
  56. 9 16
      examples/index.html
  57. 16 3
      examples/js/controls/ArcballControls.js
  58. 11 1
      examples/js/controls/TransformControls.js
  59. 0 1048
      examples/js/controls/experimental/CameraControls.js
  60. 28 16
      examples/js/exporters/GLTFExporter.js
  61. 6 0
      examples/js/exporters/USDZExporter.js
  62. 0 0
      examples/js/libs/ktx-parse.umd.js
  63. 2 13
      examples/js/loaders/EXRLoader.js
  64. 1 1
      examples/js/loaders/FontLoader.js
  65. 27 27
      examples/js/loaders/GLTFLoader.js
  66. 119 22
      examples/js/loaders/KTX2Loader.js
  67. 139 74
      examples/js/loaders/LDrawLoader.js
  68. 7 13
      examples/js/loaders/OBJLoader.js
  69. 16 9
      examples/js/loaders/PCDLoader.js
  70. 3 3
      examples/js/loaders/PLYLoader.js
  71. 1 0
      examples/js/loaders/SVGLoader.js
  72. 33 24
      examples/js/loaders/VRMLLoader.js
  73. 1 1
      examples/js/loaders/VTKLoader.js
  74. 8 7
      examples/js/loaders/lwo/LWO2Parser.js
  75. 8 7
      examples/js/loaders/lwo/LWO3Parser.js
  76. 198 190
      examples/js/misc/Volume.js
  77. 90 88
      examples/js/misc/VolumeSlice.js
  78. 1 1
      examples/js/modifiers/SimplifyModifier.js
  79. 5 1
      examples/js/objects/ShadowMesh.js
  80. 4 2
      examples/js/postprocessing/OutlinePass.js
  81. 6 2
      examples/js/shaders/MMDToonShader.js
  82. 1 1
      examples/js/utils/BufferGeometryUtils.js
  83. 2 18
      examples/js/utils/GeometryUtils.js
  84. 1 1
      examples/jsm/animation/MMDPhysics.js
  85. 17 2
      examples/jsm/controls/ArcballControls.js
  86. 9 1
      examples/jsm/controls/TransformControls.js
  87. 0 1248
      examples/jsm/controls/experimental/CameraControls.js
  88. 29 16
      examples/jsm/exporters/GLTFExporter.js
  89. 29 29
      examples/jsm/exporters/KTX2Exporter.js
  90. 7 0
      examples/jsm/exporters/USDZExporter.js
  91. 9 9
      examples/jsm/interactive/HTMLMesh.js
  92. 3 13
      examples/jsm/loaders/EXRLoader.js
  93. 1 1
      examples/jsm/loaders/FontLoader.js
  94. 28 28
      examples/jsm/loaders/GLTFLoader.js
  95. 162 30
      examples/jsm/loaders/KTX2Loader.js
  96. 136 74
      examples/jsm/loaders/LDrawLoader.js
  97. 6 14
      examples/jsm/loaders/OBJLoader.js
  98. 16 9
      examples/jsm/loaders/PCDLoader.js
  99. 3 3
      examples/jsm/loaders/PLYLoader.js
  100. 1 1
      examples/jsm/loaders/RGBELoader.js

File diff suppressed because it is too large
+ 315 - 225
build/three.cjs


File diff suppressed because it is too large
+ 315 - 225
build/three.js


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


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


+ 2 - 1
docs/api/en/core/BufferGeometry.html

@@ -138,7 +138,8 @@
 
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
-			Hashmap of [page:BufferAttribute]s holding details of the geometry's morph targets.
+			Hashmap of [page:BufferAttribute]s holding details of the geometry's morph targets.<br />
+			Note: Once the geometry has been rendered, the morph attribute data cannot be changed. You will have to call [page:.dispose](), and create a new instance of [name].
 		</p>
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>

+ 1 - 1
docs/api/en/core/Raycaster.html

@@ -65,7 +65,7 @@
 			[example:webgl_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<br />
-			[example:webgl_raycast_texture Raycast to a Texture]
+			[example:webgl_raycaster_texture Raycast to a Texture]
 		</p>
 
 		<h2>Constructor</h2>

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

@@ -52,7 +52,7 @@
 		<h2>Properties</h2>
 		<p>See the base [page:CurvePath] class for common properties.</p>
 
-		<h3>[property:Array currentPoint]</h3>
+		<h3>[property:Vector2 currentPoint]</h3>
 		<p>The current offset of the path. Any new [page:Curve] added will start here.</p>
 
 

+ 5 - 0
docs/api/en/helpers/CameraHelper.html

@@ -64,6 +64,11 @@ scene.add( helper );
 			Disposes of the internally-created [page:Line.material material] and [page:Line.geometry geometry] used by this helper.
 		</p>
 
+		<h3>[method:this setColors]( [param:Color frustum], [param:Color cone], [param:Color up], [param:Color target], [param:Color cross] )</h3>
+		<p>
+			Defines the colors of the helper.
+		</p>
+
 		<h3>[method:undefined update]()</h3>
 		<p>Updates the helper based on the projectionMatrix of the camera.</p>
 

+ 0 - 5
docs/api/en/lights/AmbientLight.html

@@ -24,11 +24,6 @@
 		scene.add( light );
 		</code>
 
-		<h2>Examples</h2>
-		<p>
-			[example:webgl_animation_skinning_blending animation / skinning / blending ]
-		</p>
-
 		<h2>Constructor</h2>
 
 		<h3>[name]( [param:Integer color], [param:Float intensity] )</h3>

+ 4 - 1
docs/api/en/materials/MeshBasicMaterial.html

@@ -93,7 +93,10 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null.</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+		</p>
 
 		<h3>[property:Float reflectivity]</h3>
 		<p>

+ 4 - 1
docs/api/en/materials/MeshDepthMaterial.html

@@ -82,7 +82,10 @@
 		<p>Whether the material is affected by fog. Default is `false`.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null.</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+		</p>
 
 		<h3>[property:Boolean wireframe]</h3>
 		<p>Render geometry as wireframe. Default is false (i.e. render as smooth shaded).</p>

+ 4 - 1
docs/api/en/materials/MeshDistanceMaterial.html

@@ -93,7 +93,10 @@
 		<p>Whether the material is affected by fog. Default is `false`.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null.</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+		</p>
 
 		<h3>[property:Float nearDistance]</h3>
 		<p>

+ 4 - 1
docs/api/en/materials/MeshLambertMaterial.html

@@ -120,7 +120,10 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null.</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+		</p>
 
 		<h3>[property:Float reflectivity]</h3>
 		<p>How much the environment map affects the surface; also see [page:.combine].</p>

+ 5 - 1
docs/api/en/materials/MeshMatcapMaterial.html

@@ -104,7 +104,11 @@
 		<p>Whether the material is affected by fog. Default is `true`.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null. The texture map color is modulated by the diffuse [page:.color].</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+			The texture map color is modulated by the diffuse [page:.color].
+		</p>
 
 		<h3>[property:Texture matcap]</h3>
 		<p>The matcap map. Default is null.</p>

+ 5 - 1
docs/api/en/materials/MeshPhongMaterial.html

@@ -157,7 +157,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null. The texture map color is modulated by the diffuse [page:.color].</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+			The texture map color is modulated by the diffuse [page:.color].
+		</p>
 
 		<h3>[property:Texture normalMap]</h3>
 		<p>

+ 5 - 1
docs/api/en/materials/MeshStandardMaterial.html

@@ -192,7 +192,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null. The texture map color is modulated by the diffuse [page:.color].</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+			The texture map color is modulated by the diffuse [page:.color].
+		</p>
 
 		<h3>[property:Float metalness]</h3>
 		<p>

+ 5 - 1
docs/api/en/materials/MeshToonMaterial.html

@@ -134,7 +134,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null. The texture map color is modulated by the diffuse [page:.color].</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+			The texture map color is modulated by the diffuse [page:.color].
+		</p>
 
 		<h3>[property:Texture normalMap]</h3>
 		<p>

+ 5 - 1
docs/api/en/materials/PointsMaterial.html

@@ -85,7 +85,11 @@
 		<p>Whether the material is affected by fog. Default is `true`.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>Sets the color of the points using data from a [page:Texture].</p>
+		<p>
+			Sets the color of the points using data from a [page:Texture].
+			May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest].
+		</p>
 
 		<h3>[property:Number size]</h3>
 		<p>Defines the size of the points in pixels. Default is 1.0.<br/>

+ 5 - 2
docs/api/en/materials/SpriteMaterial.html

@@ -26,7 +26,7 @@
 
 		<h2>Examples</h2>
 		<p>
-			[example:webgl_raycast_sprite WebGL / raycast / sprite]<br />
+			[example:webgl_raycaster_sprite WebGL / raycast / sprite]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:svg_sandbox SVG / sandbox]
 		</p>
@@ -71,7 +71,10 @@
 		</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The texture map. Default is null.</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+		</p>
 
 		<h3>[property:Radians rotation]</h3>
 		<p>The rotation of the sprite in radians. Default is 0.</p>

+ 1 - 1
docs/api/en/math/Matrix4.html

@@ -215,7 +215,7 @@ zAxis = (c, g, k)
 
 		Sets this matrix as rotation transform around [page:Vector3 axis] by [page:Float theta] radians.<br />
 
-		This is a somewhat controversial but mathematically sound alternative to rotating via [page:Quaternions].
+		This is a somewhat controversial but mathematically sound alternative to rotating via [page:Quaternion Quaternions].
 		See the discussion [link:https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199 here].
 		</p>
 

+ 1 - 1
docs/api/en/renderers/WebGLRenderTarget.html

@@ -25,7 +25,7 @@
 		<p>
 		[page:Float width] - The width of the renderTarget. <br />
 		[page:Float height] - The height of the renderTarget.<br />
-		options - (optional object that holds texture parameters for an auto-generated target
+		options - optional object that holds texture parameters for an auto-generated target
 		texture and depthBuffer/stencilBuffer booleans.
 
 		For an explanation of the texture parameters see [page:Texture Texture]. The following are

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

@@ -33,8 +33,7 @@
 		Default is null.<br />
 
 		[page:String precision] - Shader precision. Can be `"highp"`, `"mediump"` or `"lowp"`.
-		Defaults to `"highp"` if supported by the device. See the note in "Things to Avoid"
-		[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices here].<br />
+		Defaults to `"highp"` if supported by the device.<br />
 
 		[page:Boolean alpha] - controls the default clear alpha value. When set to `true`, the value is `0`. Otherwise it's `1`. Default is `false`.<br />
 

+ 2 - 1
docs/api/ko/core/BufferGeometry.html

@@ -134,7 +134,8 @@
 
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
-			[page:BufferAttribute]의 해쉬맵은 기하학의 모프 타겟에 대한 세부정보를 담고 있습니다.
+			[page:BufferAttribute]의 해쉬맵은 기하학의 모프 타겟에 대한 세부정보를 담고 있습니다.<br />
+			Note: Once the geometry has been rendered, the morph attribute data cannot be changed. You will have to call [page:.dispose](), and create a new instance of [name].
 		</p>
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>

+ 1 - 1
docs/api/ko/core/Raycaster.html

@@ -64,7 +64,7 @@
 			[example:webgl_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<br />
-			[example:webgl_raycast_texture Raycast to a Texture]
+			[example:webgl_raycaster_texture Raycast to a Texture]
 		</p>
 
 		<h2>생성자</h2>

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

@@ -51,7 +51,7 @@
 		<h2>프로퍼티</h2>
 		<p>일반 프로퍼티는 기본 [page:CurvePath] 클래스를 참고하세요.</p>
 
-		<h3>[property:Array currentPoint]</h3>
+		<h3>[property:Vector2 currentPoint]</h3>
 		<p>path의 현재 오프셋입니다. 새 [page:Curve]들은 여기서부터 시작될 것입니다.</p>
 
 

+ 2 - 1
docs/api/zh/core/BufferGeometry.html

@@ -130,7 +130,8 @@
 
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
-			存储 [page:BufferAttribute] 的 Hashmap,存储了几何体 morph targets 的细节信息。
+			存储 [page:BufferAttribute] 的 Hashmap,存储了几何体 morph targets 的细节信息。<br />
+			Note: Once the geometry has been rendered, the morph attribute data cannot be changed. You will have to call [page:.dispose](), and create a new instance of [name].
 		</p>
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>

+ 1 - 1
docs/api/zh/core/Raycaster.html

@@ -62,7 +62,7 @@
 			[example:webgl_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<br />
-			[example:webgl_raycast_texture Raycast to a Texture]
+			[example:webgl_raycaster_texture Raycast to a Texture]
 		</p>
 
 

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

@@ -51,7 +51,7 @@
 		<h2>属性</h2>
 		<p>共有属性请参见其基类[page:CurvePath]。</p>
 
-		<h3>[property:Array currentPoint]</h3>
+		<h3>[property:Vector2 currentPoint]</h3>
 		<p>路径当前的偏移量,任何新被加入的[page:Curve]将会从这里开始。</p>
 
 

+ 9 - 3
docs/api/zh/helpers/CameraHelper.html

@@ -63,12 +63,18 @@
 		</p>
 
 
-
-
-
 		<h2>方法</h2>
 		<p>请到基类 [page:LineSegments] 页面查看公共方法.</p>
 
+		<h3>[method:undefined dispose]()</h3>
+		<p>
+			用于辅助对象销毁内部创建的 [page:Line.material material] 和 [page:Line.geometry geometry] 。
+		</p>
+
+		<h3>[method:this setColors]( [param:Color frustum], [param:Color cone], [param:Color up], [param:Color target], [param:Color cross] )</h3>
+		<p>
+			定义辅助对象的颜色。
+		</p>
 
 		<h3>[method:undefined update]()</h3>
 		<p>基于相机的投影矩阵更新辅助对象.</p>

+ 0 - 5
docs/api/zh/lights/AmbientLight.html

@@ -24,11 +24,6 @@
 		scene.add( light );
 		</code>
 
-		<h2>例子</h2>
-		<p>
-			[example:webgl_animation_skinning_blending animation / skinning / blending ]
-		</p>
-
 		<h2>构造函数</h2>
 
 		<h3>[name]( [param:Integer color], [param:Float intensity] )</h3>

+ 4 - 1
docs/api/zh/materials/MeshBasicMaterial.html

@@ -83,7 +83,10 @@
 		<p>烘焙光的强度。默认值为1。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p> 颜色贴图。默认为null。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+		</p>
 
 		<h3>[property:Float reflectivity]</h3>
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。

+ 4 - 1
docs/api/zh/materials/MeshDepthMaterial.html

@@ -68,7 +68,10 @@
 		</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+		</p>
 
 		<h3>[property:Boolean wireframe]</h3>
 		<p> 将几何体渲染为线框。默认值为*false*(即渲染为平滑着色)。</p>

+ 4 - 1
docs/api/zh/materials/MeshDistanceMaterial.html

@@ -79,7 +79,10 @@
 		</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+		</p>
 
 		<h3>[property:Float nearDistance]</h3>
 		<p>

+ 4 - 1
docs/api/zh/materials/MeshLambertMaterial.html

@@ -102,7 +102,10 @@
 		<p>烘焙光的强度。默认值为1。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+		</p>
 
 		<h3>[property:Float reflectivity]</h3>
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。</p>

+ 5 - 1
docs/api/zh/materials/MeshMatcapMaterial.html

@@ -88,7 +88,11 @@
 		<p>材质是否受雾影响。默认为*true*。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。纹理贴图颜色由漫反射颜色[page:.color]调节。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+			纹理贴图颜色由漫反射颜色[page:.color]调节。
+		</p>
 
 		<h3>[property:Texture matcap]</h3>
 		<p>matcap贴图,默认为null。</p>

+ 5 - 1
docs/api/zh/materials/MeshPhongMaterial.html

@@ -126,7 +126,11 @@
 		<p>烘焙光的强度。默认值为1。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。纹理贴图颜色由漫反射颜色[page:.color]调节。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+			纹理贴图颜色由漫反射颜色[page:.color]调节。
+		</p>
 
 		<h3>[property:Texture normalMap]</h3>
 		<p> 用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。

+ 5 - 1
docs/api/zh/materials/MeshStandardMaterial.html

@@ -159,7 +159,11 @@
 		<p>烘焙光的强度。默认值为1。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。纹理贴图颜色由漫反射颜色[page:.color]调节。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+			纹理贴图颜色由漫反射颜色[page:.color]调节。
+		</p>
 
 		<h3>[property:Float metalness]</h3>
 		<p> 材质与金属的相似度。非金属材质,如木材或石材,使用0.0,金属使用1.0,通常没有中间值。

+ 5 - 1
docs/api/zh/materials/MeshToonMaterial.html

@@ -134,7 +134,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>The color map. Default is null. The texture map color is modulated by the diffuse [page:.color].</p>
+		<p>
+			The color map. May optionally include an alpha channel, typically combined with
+			[page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.
+			The texture map color is modulated by the diffuse [page:.color].
+		</p>
 
 		<h3>[property:Texture normalMap]</h3>
 		<p>

+ 4 - 1
docs/api/zh/materials/PointsMaterial.html

@@ -82,7 +82,10 @@
 		<p>材质是否受雾影响。默认为*true*。</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>使用[page:Texture]中的数据设置点的颜色。</p>
+		<p>
+			使用来自[page:Texture]的数据设置点的颜色。可以选择包括一个alpha通道,通常与
+			[page:Material.transparent .transparent]或[page:Material.alphaTest .alphaTest]。
+		</p>
 
 		<h3>[property:Number size]</h3>
 		<p>设置点的大小。默认值为1.0。<br/>

+ 5 - 2
docs/api/zh/materials/SpriteMaterial.html

@@ -26,7 +26,7 @@
 
 		<h2>例子</h2>
 		<p>
-			[example:webgl_raycast_sprite WebGL / raycast / sprite]<br />
+			[example:webgl_raycaster_sprite WebGL / raycast / sprite]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:svg_sandbox SVG / sandbox]
 		</p>
@@ -69,7 +69,10 @@
 		</p>
 
 		<h3>[property:Texture map]</h3>
-		<p>颜色贴图。默认为null。</p>
+		<p>
+			颜色贴图。可以选择包括一个alpha通道,通常与[page:Material.transparent .transparent]
+			或[page:Material.alphaTest .alphaTest]。默认为null。
+		</p>
 
 		<h3>[property:Radians rotation]</h3>
 		<p> sprite的转动,以弧度为单位。默认值为0。</p>

+ 1 - 1
docs/api/zh/math/Matrix4.html

@@ -199,7 +199,7 @@ zAxis = (c, g, k)
 
 		设置当前矩阵为围绕轴 [page:Vector3 axis] 旋转量为 [page:Float theta]弧度。<br />
 
-		这是一种有点争议但在数学上可以替代通过四元数[page:Quaternions]旋转的办法。 请参阅此处[link:https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199 here]的讨论。
+		这是一种有点争议但在数学上可以替代通过四元数[page:Quaternion Quaternions]旋转的办法。 请参阅此处[link:https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199 here]的讨论。
 		</p>
 
 		<h3>[method:this makeBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )</h3>

+ 1 - 1
docs/api/zh/renderers/WebGLRenderer.html

@@ -28,7 +28,7 @@
 		[page:WebGLRenderingContext context] - 可用于将渲染器附加到已有的渲染环境([link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext])中。默认值是null<br />
 
 		[page:String precision] - 着色器精度. 可以是 *"highp"*, *"mediump"* 或者 *"lowp"*.
-		如果设备支持,默认为*"highp"* . 点击[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices here] 查看"应该避免的事"<br />
+		如果设备支持,默认为*"highp"* .<br />
 
 		[page:Boolean alpha] - controls the default clear alpha value. When set to *true*, the value is *0*. Otherwise it's *1*. Default is *false*.<br />
 

+ 8 - 8
docs/api/zh/renderers/webgl/WebGLProgram.html

@@ -113,25 +113,25 @@
 		<h2>属性</h2>
 
 		<h3>[property:String name]</h3>
-		<p>The name of the respective shader program.</p>
+		<p>相应着色器程序的名称。</p>
 
 		<h3>[property:String id]</h3>
-		<p>The identifier of this instance.</p>
+		<p>该实例的 id 标识。</p>
 
 		<h3>[property:String cacheKey]</h3>
-		<p>This key enables the reusability of a single [name] for different materials.</p>
+		<p>启用这个 key 之后,能够实现单个 WebGLProgram 不同材料的可重用性。</p>
 
 		<h3>[property:Integer usedTimes]</h3>
-		<p>How many times this instance is used for rendering render items.</p>
+		<p>此实例用于渲染渲染项的次数。</p>
 
 		<h3>[property:Object program]</h3>
-		<p>The actual shader program.</p>
+		<p>实际的着色器程序。</p>
 
 		<h3>[property:WebGLShader vertexShader]</h3>
-		<p>The vertex shader.</p>
+		<p>顶点着色器。</p>
 
 		<h3>[property:WebGLShader fragmentShader]</h3>
-		<p>The fragment shader.</p>
+		<p>片元着色器。</p>
 
 		<h2>方法</h2>
 
@@ -147,7 +147,7 @@
 
 		<h3>[method:undefined destroy]()</h3>
 		<p>
-		Destroys an instance of [name].
+		销毁 WebGLProgram 的实例。
 		</p>
 
 		<h2>源码</h2>

+ 1 - 1
docs/manual/ar/introduction/How-to-update-things.html

@@ -111,7 +111,7 @@ line.geometry.computeBoundingSphere();
 
 			<p>
 				هنا مثال يعرض خطًا متحركًا يمكن تكييفه مع حالة الاستخدام الخاصة بك.
-				[link:https://jsfiddle.net/xvnctbL0/2/ Here is a fiddle]
+				[link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle]
 			</p>
 
 			<h3>أمثلة</h3>

+ 1 - 1
docs/manual/en/introduction/How-to-update-things.html

@@ -121,7 +121,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 
 			<p>
-				[link:https://jsfiddle.net/xvnctbL0/2/ Here is a fiddle] showing an animated line which you can adapt to your use case.
+				[link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case.
 			</p>
 
 			<h3>Examples</h3>

+ 1 - 1
docs/manual/ja/introduction/How-to-update-things.html

@@ -115,7 +115,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 
         <p>
-            [link:http://jsfiddle.net/w67tzfhx/ Here is a fiddle] showing an animated line which you can adapt to your use case. ここ([link:http://jsfiddle.net/w67tzfhx/ link])では、あなたのユースケースに合わせることができるアニメーションを表示しています。
+           ここ([link:https://jsfiddle.net/t4m85pLr/1/ link])では、あなたのユースケースに合わせることができるアニメーションを表示しています。
         </p>
 
         <h3>Examples</h3>

+ 1 - 1
docs/manual/ko/introduction/How-to-update-things.html

@@ -117,7 +117,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 
 			<p>
-				[link:https://jsfiddle.net/xvnctbL0/2/ Here is a fiddle] showing an animated line which you can adapt to your use case.
+				[link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case.
 			</p>
 
 			<h3>Examples</h3>

+ 2 - 2
docs/manual/zh/introduction/FAQ.html

@@ -50,9 +50,9 @@ visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_f
 			<code>material.side = THREE.DoubleSide</code>
 		</p>
 
-		<h2>Why does three.js sometimes return strange results for invalid inputs?</h2>
+		<h2>为什么有时候无效的输入会让three.js返回奇怪的结果?</h2>
 		<p>
-			For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid.
+			出于性能考虑,大多数情况下 three.js 不验证输入。确保所有输入均有效是你的应用的责任。
 		</p>
 	</body>
 </html>

+ 1 - 1
docs/manual/zh/introduction/How-to-update-things.html

@@ -107,7 +107,7 @@ line.geometry.attributes.position.needsUpdate = true; // 需要加在第一次
 			</code>
 
 			<p>
-				<a href="https://jsfiddle.net/xvnctbL0/2/">这个fiddle</a>展示了一个你可以参考的运动的line。
+				<a href="https://jsfiddle.net/t4m85pLr/1/">这个fiddle</a>展示了一个你可以参考的运动的line。
 			</p>
 
 			<h3>例子</h3>

+ 12 - 10
docs/scenes/ccdiksolver-browser.html

@@ -58,7 +58,7 @@
 				Uint16BufferAttribute,
 				WebGLRenderer
 			} from 'three';
-			import { CCDIKSolver, CCDIKHelper } from "../../examples/jsm/animation/CCDIKSolver.js";
+			import { CCDIKSolver, CCDIKHelper } from '../../examples/jsm/animation/CCDIKSolver.js';
 
 			import { GUI } from '../../examples/jsm/libs/lil-gui.module.min.js';
 			import { OrbitControls } from '../../examples/jsm/controls/OrbitControls.js';
@@ -146,9 +146,9 @@
 				bones = [];
 
 				// "root bone"
-				let rootBone = new Bone();
-				rootBone.name = "root";
-				rootBone.position.y = -sizing.halfHeight;
+				const rootBone = new Bone();
+				rootBone.name = 'root';
+				rootBone.position.y = - sizing.halfHeight;
 				bones.push( rootBone );
 
 				//
@@ -158,7 +158,7 @@
 				// "bone0"
 				let prevBone = new Bone();
 				prevBone.position.y = 0;
-				rootBone.add(prevBone);
+				rootBone.add( prevBone );
 				bones.push( prevBone );
 
 				// "bone1", "bone2", "bone3"
@@ -175,7 +175,7 @@
 
 				// "target"
 				const targetBone = new Bone();
-				targetBone.name = "target";
+				targetBone.name = 'target';
 				targetBone.position.y = sizing.height + sizing.segmentHeight; // relative to parent: rootBone
 				rootBone.add( targetBone );
 				bones.push( targetBone );
@@ -214,16 +214,18 @@
 				gui.add( mesh, 'pose' ).name( 'mesh.pose()' );
 
 				mesh.skeleton.bones
-					.filter( ( bone ) => bone.name === "target" )
-					.forEach( function (bone) {
+					.filter( ( bone ) => bone.name === 'target' )
+					.forEach( function ( bone ) {
+
 						const folder = gui.addFolder( bone.name );
 
 						const delta = 20;
 						folder.add( bone.position, 'x', - delta + bone.position.x, delta + bone.position.x );
 						folder.add( bone.position, 'y', - bone.position.y, bone.position.y );
 						folder.add( bone.position, 'z', - delta + bone.position.z, delta + bone.position.z );
-					} );
-				
+			
+		} );
+			
 				gui.add( ikSolver, 'update' ).name( 'ikSolver.update()' );
 				gui.add( state, 'ikSolverAutoUpdate' );
 

+ 202 - 199
editor/js/EditorControls.js

@@ -1,354 +1,357 @@
 import * as THREE from 'three';
 
-function EditorControls( object, domElement ) {
+class EditorControls extends THREE.EventDispatcher {
 
-	// API
+	constructor( object, domElement ) {
 
-	this.enabled = true;
-	this.center = new THREE.Vector3();
-	this.panSpeed = 0.002;
-	this.zoomSpeed = 0.1;
-	this.rotationSpeed = 0.005;
+		super();
 
-	// internals
+		// API
 
-	var scope = this;
-	var vector = new THREE.Vector3();
-	var delta = new THREE.Vector3();
-	var box = new THREE.Box3();
+		this.enabled = true;
+		this.center = new THREE.Vector3();
+		this.panSpeed = 0.002;
+		this.zoomSpeed = 0.1;
+		this.rotationSpeed = 0.005;
 
-	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };
-	var state = STATE.NONE;
+		// internals
 
-	var center = this.center;
-	var normalMatrix = new THREE.Matrix3();
-	var pointer = new THREE.Vector2();
-	var pointerOld = new THREE.Vector2();
-	var spherical = new THREE.Spherical();
-	var sphere = new THREE.Sphere();
+		var scope = this;
+		var vector = new THREE.Vector3();
+		var delta = new THREE.Vector3();
+		var box = new THREE.Box3();
 
-	// events
+		var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };
+		var state = STATE.NONE;
 
-	var changeEvent = { type: 'change' };
+		var center = this.center;
+		var normalMatrix = new THREE.Matrix3();
+		var pointer = new THREE.Vector2();
+		var pointerOld = new THREE.Vector2();
+		var spherical = new THREE.Spherical();
+		var sphere = new THREE.Sphere();
 
-	this.focus = function ( target ) {
+		// events
 
-		var distance;
+		var changeEvent = { type: 'change' };
 
-		box.setFromObject( target );
+		this.focus = function ( target ) {
 
-		if ( box.isEmpty() === false ) {
+			var distance;
 
-			box.getCenter( center );
-			distance = box.getBoundingSphere( sphere ).radius;
+			box.setFromObject( target );
 
-		} else {
+			if ( box.isEmpty() === false ) {
 
-			// Focusing on an Group, AmbientLight, etc
+				box.getCenter( center );
+				distance = box.getBoundingSphere( sphere ).radius;
 
-			center.setFromMatrixPosition( target.matrixWorld );
-			distance = 0.1;
+			} else {
 
-		}
+				// Focusing on an Group, AmbientLight, etc
 
-		delta.set( 0, 0, 1 );
-		delta.applyQuaternion( object.quaternion );
-		delta.multiplyScalar( distance * 4 );
+				center.setFromMatrixPosition( target.matrixWorld );
+				distance = 0.1;
 
-		object.position.copy( center ).add( delta );
+			}
 
-		scope.dispatchEvent( changeEvent );
+			delta.set( 0, 0, 1 );
+			delta.applyQuaternion( object.quaternion );
+			delta.multiplyScalar( distance * 4 );
 
-	};
+			object.position.copy( center ).add( delta );
 
-	this.pan = function ( delta ) {
+			scope.dispatchEvent( changeEvent );
 
-		var distance = object.position.distanceTo( center );
+		};
 
-		delta.multiplyScalar( distance * scope.panSpeed );
-		delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+		this.pan = function ( delta ) {
 
-		object.position.add( delta );
-		center.add( delta );
+			var distance = object.position.distanceTo( center );
 
-		scope.dispatchEvent( changeEvent );
+			delta.multiplyScalar( distance * scope.panSpeed );
+			delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
 
-	};
+			object.position.add( delta );
+			center.add( delta );
 
-	this.zoom = function ( delta ) {
+			scope.dispatchEvent( changeEvent );
 
-		var distance = object.position.distanceTo( center );
+		};
 
-		delta.multiplyScalar( distance * scope.zoomSpeed );
+		this.zoom = function ( delta ) {
 
-		if ( delta.length() > distance ) return;
+			var distance = object.position.distanceTo( center );
 
-		delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+			delta.multiplyScalar( distance * scope.zoomSpeed );
 
-		object.position.add( delta );
+			if ( delta.length() > distance ) return;
 
-		scope.dispatchEvent( changeEvent );
+			delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
 
-	};
+			object.position.add( delta );
 
-	this.rotate = function ( delta ) {
+			scope.dispatchEvent( changeEvent );
 
-		vector.copy( object.position ).sub( center );
+		};
 
-		spherical.setFromVector3( vector );
+		this.rotate = function ( delta ) {
 
-		spherical.theta += delta.x * scope.rotationSpeed;
-		spherical.phi += delta.y * scope.rotationSpeed;
+			vector.copy( object.position ).sub( center );
 
-		spherical.makeSafe();
+			spherical.setFromVector3( vector );
 
-		vector.setFromSpherical( spherical );
+			spherical.theta += delta.x * scope.rotationSpeed;
+			spherical.phi += delta.y * scope.rotationSpeed;
 
-		object.position.copy( center ).add( vector );
+			spherical.makeSafe();
 
-		object.lookAt( center );
+			vector.setFromSpherical( spherical );
 
-		scope.dispatchEvent( changeEvent );
+			object.position.copy( center ).add( vector );
 
-	};
+			object.lookAt( center );
 
-	//
+			scope.dispatchEvent( changeEvent );
 
-	function onPointerDown( event ) {
+		};
 
-		if ( scope.enabled === false ) return;
+		//
 
-		switch ( event.pointerType ) {
+		function onPointerDown( event ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseDown( event );
-				break;
+			if ( scope.enabled === false ) return;
 
-			// TODO touch
+			switch ( event.pointerType ) {
 
-		}
+				case 'mouse':
+				case 'pen':
+					onMouseDown( event );
+					break;
 
-		domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
-		domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+				// TODO touch
 
-	}
+			}
+
+			domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+			domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+
+		}
 
-	function onPointerMove( event ) {
+		function onPointerMove( event ) {
 
-		if ( scope.enabled === false ) return;
+			if ( scope.enabled === false ) return;
 
-		switch ( event.pointerType ) {
+			switch ( event.pointerType ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseMove( event );
-				break;
+				case 'mouse':
+				case 'pen':
+					onMouseMove( event );
+					break;
 
-			// TODO touch
+				// TODO touch
+
+			}
 
 		}
 
-	}
+		function onPointerUp( event ) {
 
-	function onPointerUp( event ) {
+			switch ( event.pointerType ) {
 
-		switch ( event.pointerType ) {
+				case 'mouse':
+				case 'pen':
+					onMouseUp();
+					break;
 
-			case 'mouse':
-			case 'pen':
-				onMouseUp();
-				break;
+				// TODO touch
 
-			// TODO touch
+			}
+
+			domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
 
 		}
 
-		domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+		// mouse
 
-	}
+		function onMouseDown( event ) {
 
-	// mouse
+			if ( event.button === 0 ) {
 
-	function onMouseDown( event ) {
+				state = STATE.ROTATE;
 
-		if ( event.button === 0 ) {
+			} else if ( event.button === 1 ) {
 
-			state = STATE.ROTATE;
+				state = STATE.ZOOM;
 
-		} else if ( event.button === 1 ) {
+			} else if ( event.button === 2 ) {
 
-			state = STATE.ZOOM;
+				state = STATE.PAN;
 
-		} else if ( event.button === 2 ) {
+			}
 
-			state = STATE.PAN;
+			pointerOld.set( event.clientX, event.clientY );
 
 		}
 
-		pointerOld.set( event.clientX, event.clientY );
+		function onMouseMove( event ) {
 
-	}
+			pointer.set( event.clientX, event.clientY );
 
-	function onMouseMove( event ) {
+			var movementX = pointer.x - pointerOld.x;
+			var movementY = pointer.y - pointerOld.y;
 
-		pointer.set( event.clientX, event.clientY );
+			if ( state === STATE.ROTATE ) {
 
-		var movementX = pointer.x - pointerOld.x;
-		var movementY = pointer.y - pointerOld.y;
+				scope.rotate( delta.set( - movementX, - movementY, 0 ) );
 
-		if ( state === STATE.ROTATE ) {
+			} else if ( state === STATE.ZOOM ) {
 
-			scope.rotate( delta.set( - movementX, - movementY, 0 ) );
+				scope.zoom( delta.set( 0, 0, movementY ) );
 
-		} else if ( state === STATE.ZOOM ) {
+			} else if ( state === STATE.PAN ) {
 
-			scope.zoom( delta.set( 0, 0, movementY ) );
+				scope.pan( delta.set( - movementX, movementY, 0 ) );
 
-		} else if ( state === STATE.PAN ) {
+			}
 
-			scope.pan( delta.set( - movementX, movementY, 0 ) );
+			pointerOld.set( event.clientX, event.clientY );
 
 		}
 
-		pointerOld.set( event.clientX, event.clientY );
+		function onMouseUp() {
 
-	}
+			state = STATE.NONE;
 
-	function onMouseUp() {
+		}
 
-		state = STATE.NONE;
+		function onMouseWheel( event ) {
 
-	}
+			if ( scope.enabled === false ) return;
 
-	function onMouseWheel( event ) {
+			event.preventDefault();
 
-		if ( scope.enabled === false ) return;
+			// Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460
+			scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) );
 
-		event.preventDefault();
+		}
 
-		// Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460
-		scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) );
+		function contextmenu( event ) {
 
-	}
+			event.preventDefault();
 
-	function contextmenu( event ) {
+		}
 
-		event.preventDefault();
+		this.dispose = function () {
 
-	}
+			domElement.removeEventListener( 'contextmenu', contextmenu );
+			domElement.removeEventListener( 'dblclick', onMouseUp );
+			domElement.removeEventListener( 'wheel', onMouseWheel );
 
-	this.dispose = function () {
+			domElement.removeEventListener( 'pointerdown', onPointerDown );
 
-		domElement.removeEventListener( 'contextmenu', contextmenu );
-		domElement.removeEventListener( 'dblclick', onMouseUp );
-		domElement.removeEventListener( 'wheel', onMouseWheel );
+			domElement.removeEventListener( 'touchstart', touchStart );
+			domElement.removeEventListener( 'touchmove', touchMove );
 
-		domElement.removeEventListener( 'pointerdown', onPointerDown );
+		};
 
-		domElement.removeEventListener( 'touchstart', touchStart );
-		domElement.removeEventListener( 'touchmove', touchMove );
+		domElement.addEventListener( 'contextmenu', contextmenu );
+		domElement.addEventListener( 'dblclick', onMouseUp );
+		domElement.addEventListener( 'wheel', onMouseWheel );
 
-	};
+		domElement.addEventListener( 'pointerdown', onPointerDown );
 
-	domElement.addEventListener( 'contextmenu', contextmenu );
-	domElement.addEventListener( 'dblclick', onMouseUp );
-	domElement.addEventListener( 'wheel', onMouseWheel );
+		// touch
 
-	domElement.addEventListener( 'pointerdown', onPointerDown );
+		var touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+		var prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
 
-	// touch
+		var prevDistance = null;
 
-	var touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
-	var prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+		function touchStart( event ) {
 
-	var prevDistance = null;
+			if ( scope.enabled === false ) return;
 
-	function touchStart( event ) {
+			switch ( event.touches.length ) {
 
-		if ( scope.enabled === false ) return;
+				case 1:
+					touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					break;
 
-		switch ( event.touches.length ) {
+				case 2:
+					touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] );
+					break;
 
-			case 1:
-				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				break;
+			}
 
-			case 2:
-				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] );
-				break;
+			prevTouches[ 0 ].copy( touches[ 0 ] );
+			prevTouches[ 1 ].copy( touches[ 1 ] );
 
 		}
 
-		prevTouches[ 0 ].copy( touches[ 0 ] );
-		prevTouches[ 1 ].copy( touches[ 1 ] );
 
-	}
+		function touchMove( event ) {
 
+			if ( scope.enabled === false ) return;
 
-	function touchMove( event ) {
+			event.preventDefault();
+			event.stopPropagation();
 
-		if ( scope.enabled === false ) return;
+			function getClosest( touch, touches ) {
 
-		event.preventDefault();
-		event.stopPropagation();
+				var closest = touches[ 0 ];
 
-		function getClosest( touch, touches ) {
+				for ( var touch2 of touches ) {
 
-			var closest = touches[ 0 ];
+					if ( closest.distanceTo( touch ) > touch2.distanceTo( touch ) ) closest = touch2;
 
-			for ( var touch2 of touches ) {
+				}
 
-				if ( closest.distanceTo( touch ) > touch2.distanceTo( touch ) ) closest = touch2;
+				return closest;
 
 			}
 
-			return closest;
+			switch ( event.touches.length ) {
 
-		}
+				case 1:
+					touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - 1 ) );
+					break;
 
-		switch ( event.touches.length ) {
+				case 2:
+					touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+					var distance = touches[ 0 ].distanceTo( touches[ 1 ] );
+					scope.zoom( delta.set( 0, 0, prevDistance - distance ) );
+					prevDistance = distance;
 
-			case 1:
-				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - 1 ) );
-				break;
 
-			case 2:
-				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
-				var distance = touches[ 0 ].distanceTo( touches[ 1 ] );
-				scope.zoom( delta.set( 0, 0, prevDistance - distance ) );
-				prevDistance = distance;
+					var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) );
+					var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) );
+					offset0.x = - offset0.x;
+					offset1.x = - offset1.x;
 
+					scope.pan( offset0.add( offset1 ) );
 
-				var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) );
-				var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) );
-				offset0.x = - offset0.x;
-				offset1.x = - offset1.x;
+					break;
 
-				scope.pan( offset0.add( offset1 ) );
+			}
 
-				break;
+			prevTouches[ 0 ].copy( touches[ 0 ] );
+			prevTouches[ 1 ].copy( touches[ 1 ] );
 
 		}
 
-		prevTouches[ 0 ].copy( touches[ 0 ] );
-		prevTouches[ 1 ].copy( touches[ 1 ] );
+		domElement.addEventListener( 'touchstart', touchStart );
+		domElement.addEventListener( 'touchmove', touchMove );
 
 	}
 
-	domElement.addEventListener( 'touchstart', touchStart );
-	domElement.addEventListener( 'touchmove', touchMove );
-
 }
 
-EditorControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-EditorControls.prototype.constructor = EditorControls;
-
 export { EditorControls };

+ 36 - 37
editor/js/History.js

@@ -1,41 +1,40 @@
-
 import * as Commands from './commands/Commands.js';
 
-function History( editor ) {
+class History {
 
-	this.editor = editor;
-	this.undos = [];
-	this.redos = [];
-	this.lastCmdTime = new Date();
-	this.idCounter = 0;
+	constructor( editor ) {
 
-	this.historyDisabled = false;
-	this.config = editor.config;
+		this.editor = editor;
+		this.undos = [];
+		this.redos = [];
+		this.lastCmdTime = Date.now();
+		this.idCounter = 0;
 
-	// signals
+		this.historyDisabled = false;
+		this.config = editor.config;
 
-	const scope = this;
+		// signals
 
-	this.editor.signals.startPlayer.add( function () {
+		const scope = this;
 
-		scope.historyDisabled = true;
+		this.editor.signals.startPlayer.add( function () {
 
-	} );
+			scope.historyDisabled = true;
 
-	this.editor.signals.stopPlayer.add( function () {
+		} );
 
-		scope.historyDisabled = false;
+		this.editor.signals.stopPlayer.add( function () {
 
-	} );
+			scope.historyDisabled = false;
 
-}
+		} );
 
-History.prototype = {
+	}
 
-	execute: function ( cmd, optionalName ) {
+	execute( cmd, optionalName ) {
 
 		const lastCmd = this.undos[ this.undos.length - 1 ];
-		const timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
+		const timeDifference = Date.now() - this.lastCmdTime;
 
 		const isUpdatableCmd = lastCmd &&
 			lastCmd.updatable &&
@@ -76,16 +75,16 @@ History.prototype = {
 
 		}
 
-		this.lastCmdTime = new Date();
+		this.lastCmdTime = Date.now();
 
 		// clearing all the redo-commands
 
 		this.redos = [];
 		this.editor.signals.historyChanged.dispatch( cmd );
 
-	},
+	}
 
-	undo: function () {
+	undo() {
 
 		if ( this.historyDisabled ) {
 
@@ -118,9 +117,9 @@ History.prototype = {
 
 		return cmd;
 
-	},
+	}
 
-	redo: function () {
+	redo() {
 
 		if ( this.historyDisabled ) {
 
@@ -153,9 +152,9 @@ History.prototype = {
 
 		return cmd;
 
-	},
+	}
 
-	toJSON: function () {
+	toJSON() {
 
 		const history = {};
 		history.undos = [];
@@ -193,9 +192,9 @@ History.prototype = {
 
 		return history;
 
-	},
+	}
 
-	fromJSON: function ( json ) {
+	fromJSON( json ) {
 
 		if ( json === undefined ) return;
 
@@ -226,9 +225,9 @@ History.prototype = {
 		// Select the last executed undo-command
 		this.editor.signals.historyChanged.dispatch( this.undos[ this.undos.length - 1 ] );
 
-	},
+	}
 
-	clear: function () {
+	clear() {
 
 		this.undos = [];
 		this.redos = [];
@@ -236,9 +235,9 @@ History.prototype = {
 
 		this.editor.signals.historyChanged.dispatch();
 
-	},
+	}
 
-	goToState: function ( id ) {
+	goToState( id ) {
 
 		if ( this.historyDisabled ) {
 
@@ -281,9 +280,9 @@ History.prototype = {
 		this.editor.signals.sceneGraphChanged.dispatch();
 		this.editor.signals.historyChanged.dispatch( cmd );
 
-	},
+	}
 
-	enableSerialization: function ( id ) {
+	enableSerialization( id ) {
 
 		/**
 		 * because there might be commands in this.undos and this.redos
@@ -317,6 +316,6 @@ History.prototype = {
 
 	}
 
-};
+}
 
 export { History };

+ 1 - 1
editor/sw.js

@@ -1,4 +1,4 @@
-// r141
+// r142
 
 const cacheName = 'threejs-editor';
 

+ 25 - 105
examples/css3d_molecules.html

@@ -10,44 +10,6 @@
 				background-color: #050505;
 				background: radial-gradient(ellipse at center,  rgba(43,45,48,1) 0%,rgba(0,0,0,1) 100%);
 			}
-
-			#topmenu {
-				position: absolute;
-				top: 50px;
-				width: 100%;
-				padding: 10px;
-				box-sizing: border-box;
-				text-align: center;
-			}
-
-			#menu {
-				position: absolute;
-				bottom: 20px;
-				width: 100%;
-				padding: 10px;
-				box-sizing: border-box;
-				text-align: center;
-			}
-
-			button {
-				color: rgb(255,255,255);
-				background: rgb(255,255,255,0.1);
-				border: 0px;
-				padding: 5px 10px;
-				margin: 2px;
-				font-size: 14px;
-				cursor: pointer;
-			}
-
-				button:hover {
-					background-color: rgba(0,255,255,0.5);
-				}
-
-				button:active {
-					color: #000000;
-					background-color: rgba(0,255,255,1);
-				}
-
 			.bond {
 				width: 5px;
 				height: 10px;
@@ -59,10 +21,6 @@
 	<body>
 		<div id="container"></div>
 		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - molecules</div>
-		<div id="topmenu">
-			<button id="b_a">Atoms</button><button id="b_b">Bonds</button><button id="b_ab">Atoms + Bonds</button>
-		</div>
-		<div id="menu"></div>
 
 		<!-- Import maps polyfill -->
 		<!-- Remove this when import maps will be widely supported -->
@@ -83,6 +41,7 @@
 			import { TrackballControls } from './jsm/controls/TrackballControls.js';
 			import { PDBLoader } from './jsm/loaders/PDBLoader.js';
 			import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from './jsm/renderers/CSS3DRenderer.js';
+			import { GUI } from './jsm/libs/lil-gui.module.min.js';
 
 			let camera, scene, renderer;
 			let controls;
@@ -95,7 +54,11 @@
 			const tmpVec4 = new THREE.Vector3();
 			const offset = new THREE.Vector3();
 
-			let visualizationType = 2;
+			const VIZ_TYPE = {
+				'Atoms': 0,
+				'Bonds': 1,
+				'Atoms + Bonds': 2
+			};
 
 			const MOLECULES = {
 				'Ethanol': 'ethanol.pdb',
@@ -118,12 +81,15 @@
 				'Graphite': 'graphite.pdb'
 			};
 
+			const params = {
+				vizType: 2,
+				molecule: 'caffeine.pdb'
+			};
+
 			const loader = new PDBLoader();
 			const colorSpriteMap = {};
 			const baseSprite = document.createElement( 'img' );
 
-			const menu = document.getElementById( 'menu' );
-
 			init();
 			animate();
 
@@ -152,8 +118,7 @@
 
 				baseSprite.onload = function () {
 
-					loadMolecule( 'models/pdb/caffeine.pdb' );
-					createMenu();
+					loadMolecule( params.molecule );
 
 				};
 
@@ -163,56 +128,21 @@
 
 				window.addEventListener( 'resize', onWindowResize );
 
-			}
-
-			//
-
-			function generateButtonCallback( url ) {
-
-				return function () {
+				//
 
-					loadMolecule( url );
+				const gui = new GUI();
 
-				};
+				gui.add( params, 'vizType', VIZ_TYPE ).onChange( changeVizType );
+				gui.add( params, 'molecule', MOLECULES ).onChange( loadMolecule );
+				gui.open();
 
 			}
 
-			function createMenu() {
-
-				for ( const m in MOLECULES ) {
-
-					const button = document.createElement( 'button' );
-					button.innerHTML = m;
-					menu.appendChild( button );
+			function changeVizType( value ) {
 
-					const url = 'models/pdb/' + MOLECULES[ m ];
-
-					button.addEventListener( 'click', generateButtonCallback( url ) );
-
-				}
-
-				const b_a = document.getElementById( 'b_a' );
-				const b_b = document.getElementById( 'b_b' );
-				const b_ab = document.getElementById( 'b_ab' );
-
-				b_a.addEventListener( 'click', function () {
-
-					visualizationType = 0;
-					showAtoms();
-
-				} );
-				b_b.addEventListener( 'click', function () {
-
-					visualizationType = 1;
-					showBonds();
-
-				} );
-				b_ab.addEventListener( 'click', function () {
-
-					visualizationType = 2;
-					showAtomsBonds();
-
-				} );
+				if ( value === 0 ) showAtoms();
+				else if ( value === 1 ) showBonds();
+				else showAtomsBonds();
 
 			}
 
@@ -322,7 +252,9 @@
 
 			//
 
-			function loadMolecule( url ) {
+			function loadMolecule( model ) {
+
+				const url = 'models/pdb/' + model;
 
 				for ( let i = 0; i < objects.length; i ++ ) {
 
@@ -471,19 +403,7 @@
 
 					//console.log( "CSS3DObjects:", objects.length );
 
-					switch ( visualizationType ) {
-
-						case 0:
-							showAtoms();
-							break;
-						case 1:
-							showBonds();
-							break;
-						case 2:
-							showAtomsBonds();
-							break;
-
-					}
+					changeVizType( params.vizType );
 
 				} );
 

+ 6 - 2
examples/files.json

@@ -196,8 +196,9 @@
 		"webgl_points_sprites",
 		"webgl_points_waves",
 		"webgl_portal",
-		"webgl_raycast_sprite",
-		"webgl_raycast_texture",
+		"webgl_raycaster_bvh",
+		"webgl_raycaster_sprite",
+		"webgl_raycaster_texture",
 		"webgl_read_float_buffer",
 		"webgl_refraction",
 		"webgl_rtt",
@@ -309,6 +310,7 @@
 	],
 	"webgpu": [
 		"webgpu_compute",
+		"webgpu_cubemap_adjustments",
 		"webgpu_cubemap_mix",
 		"webgpu_depth_texture",
 		"webgpu_instance_mesh",
@@ -318,6 +320,7 @@
 		"webgpu_loader_gltf",
 		"webgpu_materials",
 		"webgpu_nodes_playground",
+		"webgpu_particles",
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_skinning",
@@ -333,6 +336,7 @@
 	],
 	"webxr": [
 		"webxr_ar_cones",
+		"webxr_ar_dragging",
 		"webxr_ar_hittest",
 		"webxr_ar_lighting",
 		"webxr_ar_paint",

+ 9 - 16
examples/index.html

@@ -90,18 +90,18 @@
 
 			for ( const key in files ) {
 
-				const section = files[ key ];
+				const category = files[ key ];
 
 				const header = document.createElement( 'h2' );
 				header.textContent = key;
 				header.setAttribute( 'data-category', key );
 				container.appendChild( header );
 
-				for ( let i = 0; i < section.length; i ++ ) {
+				for ( let i = 0; i < category.length; i ++ ) {
 
-					const file = section[ i ];
+					const file = category[ i ];
 
-					const link = createLink( file );
+					const link = createLink( file, tags[ file ] );
 					container.appendChild( link );
 
 					links[ file ] = link;
@@ -214,15 +214,17 @@
 
 		}
 
-		function createLink( file ) {
+		function createLink( file, tags ) {
+
+			const external = Array.isArray( tags ) && tags.includes( 'external' ) ? ' <span class="tag">external</span>' : '';
 
 			const template = `
 				<div class="card">
-					<a href="${file}.html" target="viewer">
+					<a href="${ file }.html" target="viewer">
 						<div class="cover">
 							<img src="screenshots/${ file }.jpg" loading="lazy" width="400" />
 						</div>
-						<div class="title">${getName( file )}</div>
+						<div class="title">${ getName( file ) }${ external }</div>
 					</a>
 				</div>
 			`;
@@ -332,18 +334,9 @@
 
 				link.classList.remove( 'hidden' );
 
-				for ( let i = 0; i < res.length; i ++ ) {
-
-					text = name.replace( res[ i ], '<b>' + res[ i ] + '</b>' );
-
-				}
-
-				link.querySelector( '.title' ).innerHTML = text;
-
 			} else {
 
 				link.classList.add( 'hidden' );
-				link.querySelector( '.title' ).innerHTML = name;
 
 			}
 

+ 16 - 3
examples/js/controls/ArcballControls.js

@@ -1850,7 +1850,7 @@
 
 				this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
 
-				if ( this.camera.zoom != 1 ) {
+				if ( this.camera.zoom !== 1 ) {
 
 					//adapt gizmos size to camera zoom
 					const size = 1 / this.camera.zoom;
@@ -1867,9 +1867,22 @@
 
 				}
 
-				this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
+				this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); //
+
+
+				this._gizmos.traverse( function ( object ) {
+
+					if ( object.isLine ) {
+
+						object.geometry.dispose();
+						object.material.dispose();
+
+					}
+
+				} );
+
+				this._gizmos.clear(); //
 
-				this._gizmos.clear();
 
 				this._gizmos.add( gizmoX );
 

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

@@ -181,7 +181,17 @@
 
 			this.camera.updateMatrixWorld();
 			this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
-			this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
+
+			if ( this.camera.isOrthographicCamera ) {
+
+				this.camera.getWorldDirection( this.eye );
+
+			} else {
+
+				this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
+
+			}
+
 			super.updateMatrixWorld( this );
 
 		}

+ 0 - 1048
examples/js/controls/experimental/CameraControls.js

@@ -1,1048 +0,0 @@
-( function () {
-
-	var CameraControls = function ( object, domElement ) {
-
-		if ( domElement === undefined ) console.warn( 'THREE.CameraControls: The second parameter "domElement" is now mandatory.' );
-		if ( domElement === document ) console.error( 'THREE.CameraControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
-		this.object = object;
-		this.domElement = domElement; // Set to false to disable this control
-
-		this.enabled = true; // "target" sets the location of focus, where the object orbits around
-
-		this.target = new THREE.Vector3(); // Set to true to enable trackball behavior
-
-		this.trackball = false; // How far you can dolly in and out ( PerspectiveCamera only )
-
-		this.minDistance = 0;
-		this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
-
-		this.minZoom = 0;
-		this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
-		// Range is 0 to Math.PI radians.
-
-		this.minPolarAngle = 0; // radians
-
-		this.maxPolarAngle = Math.PI; // radians
-		// How far you can orbit horizontally, upper and lower limits.
-		// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
-
-		this.minAzimuthAngle = - Infinity; // radians
-
-		this.maxAzimuthAngle = Infinity; // radians
-		// Set to true to enable damping (inertia)
-		// If damping is enabled, you must call controls.update() in your animation loop
-
-		this.enableDamping = false;
-		this.dampingFactor = 0.05; // This option enables dollying in and out; property named as "zoom" for backwards compatibility
-		// Set to false to disable zooming
-
-		this.enableZoom = true;
-		this.zoomSpeed = 1.0; // Set to false to disable rotating
-
-		this.enableRotate = true;
-		this.rotateSpeed = 1.0; // Set to false to disable panning
-
-		this.enablePan = true;
-		this.panSpeed = 1.0;
-		this.screenSpacePanning = false; // if true, pan in screen-space
-
-		this.keyPanSpeed = 7.0; // pixels moved per arrow key push
-		// Set to true to automatically rotate around the target
-		// If auto-rotate is enabled, you must call controls.update() in your animation loop
-		// auto-rotate is not supported for trackball behavior
-
-		this.autoRotate = false;
-		this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
-		// Set to false to disable use of the keys
-
-		this.enableKeys = true; // The four arrow keys
-
-		this.keys = {
-			LEFT: 37,
-			UP: 38,
-			RIGHT: 39,
-			BOTTOM: 40
-		}; // Mouse buttons
-
-		this.mouseButtons = {
-			LEFT: THREE.MOUSE.ROTATE,
-			MIDDLE: THREE.MOUSE.DOLLY,
-			RIGHT: THREE.MOUSE.PAN
-		}; // Touch fingers
-
-		this.touches = {
-			ONE: THREE.TOUCH.ROTATE,
-			TWO: THREE.TOUCH.DOLLY_PAN
-		}; // for reset
-
-		this.target0 = this.target.clone();
-		this.position0 = this.object.position.clone();
-		this.quaternion0 = this.object.quaternion.clone();
-		this.zoom0 = this.object.zoom; //
-		// public methods
-		//
-
-		this.getPolarAngle = function () {
-
-			return spherical.phi;
-
-		};
-
-		this.getAzimuthalAngle = function () {
-
-			return spherical.theta;
-
-		};
-
-		this.saveState = function () {
-
-			scope.target0.copy( scope.target );
-			scope.position0.copy( scope.object.position );
-			scope.quaternion0.copy( scope.object.quaternion );
-			scope.zoom0 = scope.object.zoom;
-
-		};
-
-		this.reset = function () {
-
-			scope.target.copy( scope.target0 );
-			scope.object.position.copy( scope.position0 );
-			scope.object.quaternion.copy( scope.quaternion0 );
-			scope.object.zoom = scope.zoom0;
-			scope.object.updateProjectionMatrix();
-			scope.dispatchEvent( changeEvent );
-			scope.update();
-			state = STATE.NONE;
-
-		}; // this method is exposed, but perhaps it would be better if we can make it private...
-
-
-		this.update = function () {
-
-			var offset = new THREE.Vector3(); // so camera.up is the orbit axis
-
-			var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
-			var quatInverse = quat.clone().invert();
-			var lastPosition = new THREE.Vector3();
-			var lastQuaternion = new THREE.Quaternion();
-			var q = new THREE.Quaternion();
-			var vec = new THREE.Vector3();
-			return function update() {
-
-				var position = scope.object.position;
-				offset.copy( position ).sub( scope.target );
-
-				if ( scope.trackball ) {
-
-					// rotate around screen-space y-axis
-					if ( sphericalDelta.theta ) {
-
-						vec.set( 0, 1, 0 ).applyQuaternion( scope.object.quaternion );
-						const factor = scope.enableDamping ? scope.dampingFactor : 1;
-						q.setFromAxisAngle( vec, sphericalDelta.theta * factor );
-						scope.object.quaternion.premultiply( q );
-						offset.applyQuaternion( q );
-
-					} // rotate around screen-space x-axis
-
-
-					if ( sphericalDelta.phi ) {
-
-						vec.set( 1, 0, 0 ).applyQuaternion( scope.object.quaternion );
-						const factor = scope.enableDamping ? scope.dampingFactor : 1;
-						q.setFromAxisAngle( vec, sphericalDelta.phi * factor );
-						scope.object.quaternion.premultiply( q );
-						offset.applyQuaternion( q );
-
-					}
-
-					offset.multiplyScalar( scale );
-					offset.clampLength( scope.minDistance, scope.maxDistance );
-
-				} else {
-
-					// rotate offset to "y-axis-is-up" space
-					offset.applyQuaternion( quat );
-
-					if ( scope.autoRotate && state === STATE.NONE ) {
-
-						rotateLeft( getAutoRotationAngle() );
-
-					}
-
-					spherical.setFromVector3( offset );
-
-					if ( scope.enableDamping ) {
-
-						spherical.theta += sphericalDelta.theta * scope.dampingFactor;
-						spherical.phi += sphericalDelta.phi * scope.dampingFactor;
-
-					} else {
-
-						spherical.theta += sphericalDelta.theta;
-						spherical.phi += sphericalDelta.phi;
-
-					} // restrict theta to be between desired limits
-
-
-					spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); // restrict phi to be between desired limits
-
-					spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
-					spherical.makeSafe();
-					spherical.radius *= scale; // restrict radius to be between desired limits
-
-					spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
-					offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
-
-					offset.applyQuaternion( quatInverse );
-
-				} // move target to panned location
-
-
-				if ( scope.enableDamping === true ) {
-
-					scope.target.addScaledVector( panOffset, scope.dampingFactor );
-
-				} else {
-
-					scope.target.add( panOffset );
-
-				}
-
-				position.copy( scope.target ).add( offset );
-
-				if ( scope.trackball === false ) {
-
-					scope.object.lookAt( scope.target );
-
-				}
-
-				if ( scope.enableDamping === true ) {
-
-					sphericalDelta.theta *= 1 - scope.dampingFactor;
-					sphericalDelta.phi *= 1 - scope.dampingFactor;
-					panOffset.multiplyScalar( 1 - scope.dampingFactor );
-
-				} else {
-
-					sphericalDelta.set( 0, 0, 0 );
-					panOffset.set( 0, 0, 0 );
-
-				}
-
-				scale = 1; // update condition is:
-				// min(camera displacement, camera rotation in radians)^2 > EPS
-				// using small-angle approximation cos(x/2) = 1 - x^2 / 8
-
-				if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
-
-					scope.dispatchEvent( changeEvent );
-					lastPosition.copy( scope.object.position );
-					lastQuaternion.copy( scope.object.quaternion );
-					zoomChanged = false;
-					return true;
-
-				}
-
-				return false;
-
-			};
-
-		}();
-
-		this.dispose = function () {
-
-			scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
-			scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
-			scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
-			scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
-			scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
-			scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
-			document.removeEventListener( 'mousemove', onMouseMove, false );
-			document.removeEventListener( 'mouseup', onMouseUp, false );
-			scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
-
-		}; //
-		// internals
-		//
-
-
-		var scope = this;
-		var changeEvent = {
-			type: 'change'
-		};
-		var startEvent = {
-			type: 'start'
-		};
-		var endEvent = {
-			type: 'end'
-		};
-		var STATE = {
-			NONE: - 1,
-			ROTATE: 0,
-			DOLLY: 1,
-			PAN: 2,
-			TOUCH_ROTATE: 3,
-			TOUCH_PAN: 4,
-			TOUCH_DOLLY_PAN: 5,
-			TOUCH_DOLLY_ROTATE: 6
-		};
-		var state = STATE.NONE;
-		var EPS = 0.000001; // current position in spherical coordinates
-
-		var spherical = new THREE.Spherical();
-		var sphericalDelta = new THREE.Spherical();
-		var scale = 1;
-		var panOffset = new THREE.Vector3();
-		var zoomChanged = false;
-		var rotateStart = new THREE.Vector2();
-		var rotateEnd = new THREE.Vector2();
-		var rotateDelta = new THREE.Vector2();
-		var panStart = new THREE.Vector2();
-		var panEnd = new THREE.Vector2();
-		var panDelta = new THREE.Vector2();
-		var dollyStart = new THREE.Vector2();
-		var dollyEnd = new THREE.Vector2();
-		var dollyDelta = new THREE.Vector2();
-
-		function getAutoRotationAngle() {
-
-			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
-
-		}
-
-		function getZoomScale() {
-
-			return Math.pow( 0.95, scope.zoomSpeed );
-
-		}
-
-		function rotateLeft( angle ) {
-
-			sphericalDelta.theta -= angle;
-
-		}
-
-		function rotateUp( angle ) {
-
-			sphericalDelta.phi -= angle;
-
-		}
-
-		var panLeft = function () {
-
-			var v = new THREE.Vector3();
-			return function panLeft( distance, objectMatrix ) {
-
-				v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
-
-				v.multiplyScalar( - distance );
-				panOffset.add( v );
-
-			};
-
-		}();
-
-		var panUp = function () {
-
-			var v = new THREE.Vector3();
-			return function panUp( distance, objectMatrix ) {
-
-				if ( scope.screenSpacePanning === true ) {
-
-					v.setFromMatrixColumn( objectMatrix, 1 );
-
-				} else {
-
-					v.setFromMatrixColumn( objectMatrix, 0 );
-					v.crossVectors( scope.object.up, v );
-
-				}
-
-				v.multiplyScalar( distance );
-				panOffset.add( v );
-
-			};
-
-		}(); // deltaX and deltaY are in pixels; right and down are positive
-
-
-		var pan = function () {
-
-			var offset = new THREE.Vector3();
-			return function pan( deltaX, deltaY ) {
-
-				var element = scope.domElement;
-
-				if ( scope.object.isPerspectiveCamera ) {
-
-					// perspective
-					var position = scope.object.position;
-					offset.copy( position ).sub( scope.target );
-					var targetDistance = offset.length(); // half of the fov is center to top of screen
-
-					targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
-
-					panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
-					panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
-
-				} else if ( scope.object.isOrthographicCamera ) {
-
-					// orthographic
-					panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
-					panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
-
-				} else {
-
-					// camera neither orthographic nor perspective
-					console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - pan disabled.' );
-					scope.enablePan = false;
-
-				}
-
-			};
-
-		}();
-
-		function dollyIn( dollyScale ) {
-
-			if ( scope.object.isPerspectiveCamera ) {
-
-				scale /= dollyScale;
-
-			} else if ( scope.object.isOrthographicCamera ) {
-
-				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
-				scope.object.updateProjectionMatrix();
-				zoomChanged = true;
-
-			} else {
-
-				console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-				scope.enableZoom = false;
-
-			}
-
-		}
-
-		function dollyOut( dollyScale ) {
-
-			if ( scope.object.isPerspectiveCamera ) {
-
-				scale *= dollyScale;
-
-			} else if ( scope.object.isOrthographicCamera ) {
-
-				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
-				scope.object.updateProjectionMatrix();
-				zoomChanged = true;
-
-			} else {
-
-				console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-				scope.enableZoom = false;
-
-			}
-
-		} //
-		// event callbacks - update the object state
-		//
-
-
-		function handleMouseDownRotate( event ) {
-
-			rotateStart.set( event.clientX, event.clientY );
-
-		}
-
-		function handleMouseDownDolly( event ) {
-
-			dollyStart.set( event.clientX, event.clientY );
-
-		}
-
-		function handleMouseDownPan( event ) {
-
-			panStart.set( event.clientX, event.clientY );
-
-		}
-
-		function handleMouseMoveRotate( event ) {
-
-			rotateEnd.set( event.clientX, event.clientY );
-			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-			var element = scope.domElement;
-			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-			rotateStart.copy( rotateEnd );
-			scope.update();
-
-		}
-
-		function handleMouseMoveDolly( event ) {
-
-			dollyEnd.set( event.clientX, event.clientY );
-			dollyDelta.subVectors( dollyEnd, dollyStart );
-
-			if ( dollyDelta.y > 0 ) {
-
-				dollyIn( getZoomScale() );
-
-			} else if ( dollyDelta.y < 0 ) {
-
-				dollyOut( getZoomScale() );
-
-			}
-
-			dollyStart.copy( dollyEnd );
-			scope.update();
-
-		}
-
-		function handleMouseMovePan( event ) {
-
-			panEnd.set( event.clientX, event.clientY );
-			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-			pan( panDelta.x, panDelta.y );
-			panStart.copy( panEnd );
-			scope.update();
-
-		}
-
-		function
-		/*event*/
-		handleMouseUp() { // no-op
-		}
-
-		function handleMouseWheel( event ) {
-
-			if ( event.deltaY < 0 ) {
-
-				dollyOut( getZoomScale() );
-
-			} else if ( event.deltaY > 0 ) {
-
-				dollyIn( getZoomScale() );
-
-			}
-
-			scope.update();
-
-		}
-
-		function handleKeyDown( event ) {
-
-			var needsUpdate = false;
-
-			switch ( event.keyCode ) {
-
-				case scope.keys.UP:
-					pan( 0, scope.keyPanSpeed );
-					needsUpdate = true;
-					break;
-
-				case scope.keys.BOTTOM:
-					pan( 0, - scope.keyPanSpeed );
-					needsUpdate = true;
-					break;
-
-				case scope.keys.LEFT:
-					pan( scope.keyPanSpeed, 0 );
-					needsUpdate = true;
-					break;
-
-				case scope.keys.RIGHT:
-					pan( - scope.keyPanSpeed, 0 );
-					needsUpdate = true;
-					break;
-
-			}
-
-			if ( needsUpdate ) {
-
-				// prevent the browser from scrolling on cursor keys
-				event.preventDefault();
-				scope.update();
-
-			}
-
-		}
-
-		function handleTouchStartRotate( event ) {
-
-			if ( event.touches.length == 1 ) {
-
-				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-			} else {
-
-				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-				rotateStart.set( x, y );
-
-			}
-
-		}
-
-		function handleTouchStartPan( event ) {
-
-			if ( event.touches.length == 1 ) {
-
-				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-			} else {
-
-				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-				panStart.set( x, y );
-
-			}
-
-		}
-
-		function handleTouchStartDolly( event ) {
-
-			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-			var distance = Math.sqrt( dx * dx + dy * dy );
-			dollyStart.set( 0, distance );
-
-		}
-
-		function handleTouchStartDollyPan( event ) {
-
-			if ( scope.enableZoom ) handleTouchStartDolly( event );
-			if ( scope.enablePan ) handleTouchStartPan( event );
-
-		}
-
-		function handleTouchStartDollyRotate( event ) {
-
-			if ( scope.enableZoom ) handleTouchStartDolly( event );
-			if ( scope.enableRotate ) handleTouchStartRotate( event );
-
-		}
-
-		function handleTouchMoveRotate( event ) {
-
-			if ( event.touches.length == 1 ) {
-
-				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-			} else {
-
-				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-				rotateEnd.set( x, y );
-
-			}
-
-			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-			var element = scope.domElement;
-			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-			rotateStart.copy( rotateEnd );
-
-		}
-
-		function handleTouchMovePan( event ) {
-
-			if ( event.touches.length == 1 ) {
-
-				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-			} else {
-
-				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-				panEnd.set( x, y );
-
-			}
-
-			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-			pan( panDelta.x, panDelta.y );
-			panStart.copy( panEnd );
-
-		}
-
-		function handleTouchMoveDolly( event ) {
-
-			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-			var distance = Math.sqrt( dx * dx + dy * dy );
-			dollyEnd.set( 0, distance );
-			dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
-			dollyIn( dollyDelta.y );
-			dollyStart.copy( dollyEnd );
-
-		}
-
-		function handleTouchMoveDollyPan( event ) {
-
-			if ( scope.enableZoom ) handleTouchMoveDolly( event );
-			if ( scope.enablePan ) handleTouchMovePan( event );
-
-		}
-
-		function handleTouchMoveDollyRotate( event ) {
-
-			if ( scope.enableZoom ) handleTouchMoveDolly( event );
-			if ( scope.enableRotate ) handleTouchMoveRotate( event );
-
-		}
-
-		function
-		/*event*/
-		handleTouchEnd() { // no-op
-		} //
-		// event handlers - FSM: listen for events and reset state
-		//
-
-
-		function onMouseDown( event ) {
-
-			if ( scope.enabled === false ) return; // Prevent the browser from scrolling.
-
-			event.preventDefault(); // Manually set the focus since calling preventDefault above
-			// prevents the browser from setting it automatically.
-
-			scope.domElement.focus ? scope.domElement.focus() : window.focus();
-			var mouseAction;
-
-			switch ( event.button ) {
-
-				case 0:
-					mouseAction = scope.mouseButtons.LEFT;
-					break;
-
-				case 1:
-					mouseAction = scope.mouseButtons.MIDDLE;
-					break;
-
-				case 2:
-					mouseAction = scope.mouseButtons.RIGHT;
-					break;
-
-				default:
-					mouseAction = - 1;
-
-			}
-
-			switch ( mouseAction ) {
-
-				case THREE.MOUSE.DOLLY:
-					if ( scope.enableZoom === false ) return;
-					handleMouseDownDolly( event );
-					state = STATE.DOLLY;
-					break;
-
-				case THREE.MOUSE.ROTATE:
-					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
-						if ( scope.enablePan === false ) return;
-						handleMouseDownPan( event );
-						state = STATE.PAN;
-
-					} else {
-
-						if ( scope.enableRotate === false ) return;
-						handleMouseDownRotate( event );
-						state = STATE.ROTATE;
-
-					}
-
-					break;
-
-				case THREE.MOUSE.PAN:
-					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
-						if ( scope.enableRotate === false ) return;
-						handleMouseDownRotate( event );
-						state = STATE.ROTATE;
-
-					} else {
-
-						if ( scope.enablePan === false ) return;
-						handleMouseDownPan( event );
-						state = STATE.PAN;
-
-					}
-
-					break;
-
-				default:
-					state = STATE.NONE;
-
-			}
-
-			if ( state !== STATE.NONE ) {
-
-				document.addEventListener( 'mousemove', onMouseMove, false );
-				document.addEventListener( 'mouseup', onMouseUp, false );
-				scope.dispatchEvent( startEvent );
-
-			}
-
-		}
-
-		function onMouseMove( event ) {
-
-			if ( scope.enabled === false ) return;
-			event.preventDefault();
-
-			switch ( state ) {
-
-				case STATE.ROTATE:
-					if ( scope.enableRotate === false ) return;
-					handleMouseMoveRotate( event );
-					break;
-
-				case STATE.DOLLY:
-					if ( scope.enableZoom === false ) return;
-					handleMouseMoveDolly( event );
-					break;
-
-				case STATE.PAN:
-					if ( scope.enablePan === false ) return;
-					handleMouseMovePan( event );
-					break;
-
-			}
-
-		}
-
-		function onMouseUp( event ) {
-
-			if ( scope.enabled === false ) return;
-			handleMouseUp( event );
-			document.removeEventListener( 'mousemove', onMouseMove, false );
-			document.removeEventListener( 'mouseup', onMouseUp, false );
-			scope.dispatchEvent( endEvent );
-			state = STATE.NONE;
-
-		}
-
-		function onMouseWheel( event ) {
-
-			if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
-			event.preventDefault();
-			event.stopPropagation();
-			scope.dispatchEvent( startEvent );
-			handleMouseWheel( event );
-			scope.dispatchEvent( endEvent );
-
-		}
-
-		function onKeyDown( event ) {
-
-			if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
-			handleKeyDown( event );
-
-		}
-
-		function onTouchStart( event ) {
-
-			if ( scope.enabled === false ) return;
-			event.preventDefault();
-
-			switch ( event.touches.length ) {
-
-				case 1:
-					switch ( scope.touches.ONE ) {
-
-						case THREE.TOUCH.ROTATE:
-							if ( scope.enableRotate === false ) return;
-							handleTouchStartRotate( event );
-							state = STATE.TOUCH_ROTATE;
-							break;
-
-						case THREE.TOUCH.PAN:
-							if ( scope.enablePan === false ) return;
-							handleTouchStartPan( event );
-							state = STATE.TOUCH_PAN;
-							break;
-
-						default:
-							state = STATE.NONE;
-
-					}
-
-					break;
-
-				case 2:
-					switch ( scope.touches.TWO ) {
-
-						case THREE.TOUCH.DOLLY_PAN:
-							if ( scope.enableZoom === false && scope.enablePan === false ) return;
-							handleTouchStartDollyPan( event );
-							state = STATE.TOUCH_DOLLY_PAN;
-							break;
-
-						case THREE.TOUCH.DOLLY_ROTATE:
-							if ( scope.enableZoom === false && scope.enableRotate === false ) return;
-							handleTouchStartDollyRotate( event );
-							state = STATE.TOUCH_DOLLY_ROTATE;
-							break;
-
-						default:
-							state = STATE.NONE;
-
-					}
-
-					break;
-
-				default:
-					state = STATE.NONE;
-
-			}
-
-			if ( state !== STATE.NONE ) {
-
-				scope.dispatchEvent( startEvent );
-
-			}
-
-		}
-
-		function onTouchMove( event ) {
-
-			if ( scope.enabled === false ) return;
-			event.preventDefault();
-			event.stopPropagation();
-
-			switch ( state ) {
-
-				case STATE.TOUCH_ROTATE:
-					if ( scope.enableRotate === false ) return;
-					handleTouchMoveRotate( event );
-					scope.update();
-					break;
-
-				case STATE.TOUCH_PAN:
-					if ( scope.enablePan === false ) return;
-					handleTouchMovePan( event );
-					scope.update();
-					break;
-
-				case STATE.TOUCH_DOLLY_PAN:
-					if ( scope.enableZoom === false && scope.enablePan === false ) return;
-					handleTouchMoveDollyPan( event );
-					scope.update();
-					break;
-
-				case STATE.TOUCH_DOLLY_ROTATE:
-					if ( scope.enableZoom === false && scope.enableRotate === false ) return;
-					handleTouchMoveDollyRotate( event );
-					scope.update();
-					break;
-
-				default:
-					state = STATE.NONE;
-
-			}
-
-		}
-
-		function onTouchEnd( event ) {
-
-			if ( scope.enabled === false ) return;
-			handleTouchEnd( event );
-			scope.dispatchEvent( endEvent );
-			state = STATE.NONE;
-
-		}
-
-		function onContextMenu( event ) {
-
-			if ( scope.enabled === false ) return;
-			event.preventDefault();
-
-		} //
-
-
-		scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
-		scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
-		scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
-		scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
-		scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
-		scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
-		scope.domElement.addEventListener( 'keydown', onKeyDown, false ); // make sure element can receive keys.
-
-		if ( scope.domElement.tabIndex === - 1 ) {
-
-			scope.domElement.tabIndex = 0;
-
-		} // force an update at start
-
-
-		this.object.lookAt( scope.target );
-		this.update();
-		this.saveState();
-
-	};
-
-	CameraControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-	CameraControls.prototype.constructor = CameraControls; // OrbitControls maintains the "up" direction, camera.up (+Y by default).
-	//
-	//    Orbit - left mouse / touch: one-finger move
-	//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-	//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
-
-	var OrbitControls = function ( object, domElement ) {
-
-		CameraControls.call( this, object, domElement );
-		this.mouseButtons.LEFT = THREE.MOUSE.ROTATE;
-		this.mouseButtons.RIGHT = THREE.MOUSE.PAN;
-		this.touches.ONE = THREE.TOUCH.ROTATE;
-		this.touches.TWO = THREE.TOUCH.DOLLY_PAN;
-
-	};
-
-	OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-	OrbitControls.prototype.constructor = OrbitControls; // MapControls maintains the "up" direction, camera.up (+Y by default)
-	//
-	//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
-	//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-	//    Pan - left mouse, or left right + ctrl/meta/shiftKey, or arrow keys / touch: one-finger move
-
-	var MapControls = function ( object, domElement ) {
-
-		CameraControls.call( this, object, domElement );
-		this.mouseButtons.LEFT = THREE.MOUSE.PAN;
-		this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
-		this.touches.ONE = THREE.TOUCH.PAN;
-		this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
-
-	};
-
-	MapControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-	MapControls.prototype.constructor = MapControls; // TrackballControls allows the camera to rotate over the polls and does not maintain camera.up
-	//
-	//    Orbit - left mouse / touch: one-finger move
-	//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-	//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
-
-	var TrackballControls = function ( object, domElement ) {
-
-		CameraControls.call( this, object, domElement );
-		this.trackball = true;
-		this.screenSpacePanning = true;
-		this.autoRotate = false;
-		this.mouseButtons.LEFT = THREE.MOUSE.ROTATE;
-		this.mouseButtons.RIGHT = THREE.MOUSE.PAN;
-		this.touches.ONE = THREE.TOUCH.ROTATE;
-		this.touches.TWO = THREE.TOUCH.DOLLY_PAN;
-
-	};
-
-	TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-	TrackballControls.prototype.constructor = TrackballControls;
-
-	THREE.CameraControls = CameraControls;
-	THREE.MapControls = MapControls;
-	THREE.OrbitControls = OrbitControls;
-	THREE.TrackballControls = TrackballControls;
-
-} )();

+ 28 - 16
examples/js/exporters/GLTFExporter.js

@@ -300,27 +300,15 @@
 
 	}
 
-	let cachedCanvas = null;
-
 	function getCanvas() {
 
-		if ( cachedCanvas ) {
-
-			return cachedCanvas;
-
-		}
-
 		if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
 
-			cachedCanvas = new OffscreenCanvas( 1, 1 );
-
-		} else {
-
-			cachedCanvas = document.createElement( 'canvas' );
+			return new OffscreenCanvas( 1, 1 );
 
 		}
 
-		return cachedCanvas;
+		return document.createElement( 'canvas' );
 
 	}
 
@@ -676,6 +664,27 @@
 		buildMetalRoughTexture( metalnessMap, roughnessMap ) {
 
 			if ( metalnessMap === roughnessMap ) return metalnessMap;
+
+			function getEncodingConversion( map ) {
+
+				if ( map.encoding === THREE.sRGBEncoding ) {
+
+					return function SRGBToLinear( c ) {
+
+						return c < 0.04045 ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+					};
+
+				}
+
+				return function LinearToLinear( c ) {
+
+					return c;
+
+				};
+
+			}
+
 			console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 			const metalness = metalnessMap?.image;
 			const roughness = roughnessMap?.image;
@@ -692,11 +701,12 @@
 			if ( metalness ) {
 
 				context.drawImage( metalness, 0, 0, width, height );
+				const convert = getEncodingConversion( metalnessMap );
 				const data = context.getImageData( 0, 0, width, height ).data;
 
 				for ( let i = 2; i < data.length; i += 4 ) {
 
-					composite.data[ i ] = data[ i ];
+					composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
 
 				}
 
@@ -705,11 +715,12 @@
 			if ( roughness ) {
 
 				context.drawImage( roughness, 0, 0, width, height );
+				const convert = getEncodingConversion( roughnessMap );
 				const data = context.getImageData( 0, 0, width, height ).data;
 
 				for ( let i = 1; i < data.length; i += 4 ) {
 
-					composite.data[ i ] = data[ i ];
+					composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
 
 				}
 
@@ -720,6 +731,7 @@
 			const reference = metalnessMap || roughnessMap;
 			const texture = reference.clone();
 			texture.source = new THREE.Source( canvas );
+			texture.encoding = THREE.LinearEncoding;
 			return texture;
 
 		}

+ 6 - 0
examples/js/exporters/USDZExporter.js

@@ -380,6 +380,12 @@ ${array.join( '' )}
 
 		}
 
+		if ( material.side === THREE.DoubleSide ) {
+
+			console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material );
+
+		}
+
 		if ( material.map !== null ) {
 
 			inputs.push( `${pad}color3f inputs:diffuseColor.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:rgb>` );

File diff suppressed because it is too large
+ 0 - 0
examples/js/libs/ktx-parse.umd.js


+ 2 - 13
examples/js/loaders/EXRLoader.js

@@ -1610,20 +1610,9 @@
 
 			const parseInt64 = function ( dataView, offset ) {
 
-				let int;
-
-				if ( 'getBigInt64' in DataView.prototype ) {
-
-					int = Number( dataView.getBigInt64( offset.value, true ) );
-
-				} else {
-
-					int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 );
-
-				}
-
+				const Int64 = Number( dataView.getBigInt64( offset.value, true ) );
 				offset.value += ULONG_SIZE;
-				return int;
+				return Int64;
 
 			};
 

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

@@ -63,7 +63,7 @@
 
 			for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
 
-				Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+				shapes.push( ...paths[ p ].toShapes() );
 
 			}
 

+ 27 - 27
examples/js/loaders/GLTFLoader.js

@@ -1733,44 +1733,44 @@
 
 		}
 
-	}
+		interpolate_( i1, t0, t, t1 ) {
 
-	GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
+			const result = this.resultBuffer;
+			const values = this.sampleValues;
+			const stride = this.valueSize;
+			const stride2 = stride * 2;
+			const stride3 = stride * 3;
+			const td = t1 - t0;
+			const p = ( t - t0 ) / td;
+			const pp = p * p;
+			const ppp = pp * p;
+			const offset1 = i1 * stride3;
+			const offset0 = offset1 - stride3;
+			const s2 = - 2 * ppp + 3 * pp;
+			const s3 = ppp - pp;
+			const s0 = 1 - s2;
+			const s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations:
+			//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
 
-		const result = this.resultBuffer;
-		const values = this.sampleValues;
-		const stride = this.valueSize;
-		const stride2 = stride * 2;
-		const stride3 = stride * 3;
-		const td = t1 - t0;
-		const p = ( t - t0 ) / td;
-		const pp = p * p;
-		const ppp = pp * p;
-		const offset1 = i1 * stride3;
-		const offset0 = offset1 - stride3;
-		const s2 = - 2 * ppp + 3 * pp;
-		const s3 = ppp - pp;
-		const s0 = 1 - s2;
-		const s1 = s3 - pp + p; // Layout of keyframe output values for CUBICSPLINE animations:
-		//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
+			for ( let i = 0; i !== stride; i ++ ) {
 
-		for ( let i = 0; i !== stride; i ++ ) {
+				const p0 = values[ offset0 + i + stride ]; // splineVertex_k
 
-			const p0 = values[ offset0 + i + stride ]; // splineVertex_k
+				const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
 
-			const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
+				const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
 
-			const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
+				const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
 
-			const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
+				result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
 
-			result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
+			}
 
-		}
+			return result;
 
-		return result;
+		}
 
-	};
+	}
 
 	const _q = new THREE.Quaternion();
 

+ 119 - 22
examples/js/loaders/KTX2Loader.js

@@ -5,15 +5,31 @@
  *
  * KTX 2.0 is a container format for various GPU texture formats. The loader
  * supports Basis Universal GPU textures, which can be quickly transcoded to
- * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
- * other hardware-specific formats, this loader does not yet parse them.
+ * a wide variety of GPU texture compression formats, as well as some
+ * uncompressed THREE.DataTexture and THREE.Data3DTexture formats.
  *
  * References:
  * - KTX: http://github.khronos.org/KTX-Specification/
  * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
  */
-	const KTX2TransferSRGB = 2;
-	const KTX2_ALPHA_PREMULTIPLIED = 1;
+	const {
+		read,
+		KHR_DF_FLAG_ALPHA_PREMULTIPLIED,
+		KHR_DF_TRANSFER_SRGB,
+		VK_FORMAT_UNDEFINED,
+		VK_FORMAT_R16_SFLOAT,
+		VK_FORMAT_R16G16_SFLOAT,
+		VK_FORMAT_R16G16B16A16_SFLOAT,
+		VK_FORMAT_R32_SFLOAT,
+		VK_FORMAT_R32G32_SFLOAT,
+		VK_FORMAT_R32G32B32A32_SFLOAT,
+		VK_FORMAT_R8_SRGB,
+		VK_FORMAT_R8_UNORM,
+		VK_FORMAT_R8G8_SRGB,
+		VK_FORMAT_R8G8_UNORM,
+		VK_FORMAT_R8G8B8A8_SRGB,
+		VK_FORMAT_R8G8B8A8_UNORM
+	} = KTX; // eslint-disable-line no-undef
 
 	const _taskCache = new WeakMap();
 
@@ -137,7 +153,6 @@
 			const loader = new THREE.FileLoader( this.manager );
 			loader.setResponseType( 'arraybuffer' );
 			loader.setWithCredentials( this.withCredentials );
-			const texture = new THREE.CompressedTexture();
 			loader.load( url, buffer => {
 
 				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
@@ -150,16 +165,9 @@
 
 				}
 
-				this._createTexture( [ buffer ] ).then( function ( _texture ) {
-
-					texture.copy( _texture );
-					texture.needsUpdate = true;
-					if ( onLoad ) onLoad( texture );
-
-				} ).catch( onError );
+				this._createTexture( buffer ).then( texture => onLoad ? onLoad( texture ) : null ).catch( onError );
 
 			}, onProgress, onError );
-			return texture;
 
 		}
 
@@ -181,32 +189,41 @@
 			texture.magFilter = THREE.LinearFilter;
 			texture.generateMipmaps = false;
 			texture.needsUpdate = true;
-			texture.encoding = dfdTransferFn === KTX2TransferSRGB ? THREE.sRGBEncoding : THREE.LinearEncoding;
-			texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED );
+			texture.encoding = dfdTransferFn === KHR_DF_TRANSFER_SRGB ? THREE.sRGBEncoding : THREE.LinearEncoding;
+			texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED );
 			return texture;
 
 		}
 		/**
-   * @param {ArrayBuffer[]} buffers
+   * @param {ArrayBuffer} buffer
    * @param {object?} config
-   * @return {Promise<CompressedTexture>}
+   * @return {Promise<CompressedTexture|DataTexture|Data3DTexture>}
    */
 
 
-		_createTexture( buffers, config = {} ) {
+		_createTexture( buffer, config = {} ) {
+
+			const container = read( new Uint8Array( buffer ) );
+
+			if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) {
+
+				return createDataTexture( container );
+
+			} //
+
 
 			const taskConfig = config;
 			const texturePending = this.init().then( () => {
 
 				return this.workerPool.postMessage( {
 					type: 'transcode',
-					buffers,
+					buffer,
 					taskConfig: taskConfig
-				}, buffers );
+				}, [ buffer ] );
 
 			} ).then( e => this._createTextureFrom( e.data ) ); // Cache the task result.
 
-			_taskCache.set( buffers[ 0 ], {
+			_taskCache.set( buffer, {
 				promise: texturePending
 			} );
 
@@ -299,7 +316,7 @@
 								format,
 								dfdTransferFn,
 								dfdFlags
-							} = transcode( message.buffers[ 0 ] );
+							} = transcode( message.buffer );
 							const buffers = [];
 
 							for ( let i = 0; i < mipmaps.length; ++ i ) {
@@ -551,7 +568,87 @@
 
 		}
 
+	}; //
+	// THREE.DataTexture and THREE.Data3DTexture parsing.
+
+
+	const FORMAT_MAP = {
+		[ VK_FORMAT_R32G32B32A32_SFLOAT ]: THREE.RGBAFormat,
+		[ VK_FORMAT_R16G16B16A16_SFLOAT ]: THREE.RGBAFormat,
+		[ VK_FORMAT_R8G8B8A8_UNORM ]: THREE.RGBAFormat,
+		[ VK_FORMAT_R8G8B8A8_SRGB ]: THREE.RGBAFormat,
+		[ VK_FORMAT_R32G32_SFLOAT ]: THREE.RGFormat,
+		[ VK_FORMAT_R16G16_SFLOAT ]: THREE.RGFormat,
+		[ VK_FORMAT_R8G8_UNORM ]: THREE.RGFormat,
+		[ VK_FORMAT_R8G8_SRGB ]: THREE.RGFormat,
+		[ VK_FORMAT_R32_SFLOAT ]: THREE.RedFormat,
+		[ VK_FORMAT_R16_SFLOAT ]: THREE.RedFormat,
+		[ VK_FORMAT_R8_SRGB ]: THREE.RedFormat,
+		[ VK_FORMAT_R8_UNORM ]: THREE.RedFormat
 	};
+	const TYPE_MAP = {
+		[ VK_FORMAT_R32G32B32A32_SFLOAT ]: THREE.FloatType,
+		[ VK_FORMAT_R16G16B16A16_SFLOAT ]: THREE.HalfFloatType,
+		[ VK_FORMAT_R8G8B8A8_UNORM ]: THREE.UnsignedByteType,
+		[ VK_FORMAT_R8G8B8A8_SRGB ]: THREE.UnsignedByteType,
+		[ VK_FORMAT_R32G32_SFLOAT ]: THREE.FloatType,
+		[ VK_FORMAT_R16G16_SFLOAT ]: THREE.HalfFloatType,
+		[ VK_FORMAT_R8G8_UNORM ]: THREE.UnsignedByteType,
+		[ VK_FORMAT_R8G8_SRGB ]: THREE.UnsignedByteType,
+		[ VK_FORMAT_R32_SFLOAT ]: THREE.FloatType,
+		[ VK_FORMAT_R16_SFLOAT ]: THREE.HalfFloatType,
+		[ VK_FORMAT_R8_SRGB ]: THREE.UnsignedByteType,
+		[ VK_FORMAT_R8_UNORM ]: THREE.UnsignedByteType
+	};
+	const ENCODING_MAP = {
+		[ VK_FORMAT_R8G8B8A8_SRGB ]: THREE.sRGBEncoding,
+		[ VK_FORMAT_R8G8_SRGB ]: THREE.sRGBEncoding,
+		[ VK_FORMAT_R8_SRGB ]: THREE.sRGBEncoding
+	};
+
+	function createDataTexture( container ) {
+
+		const {
+			vkFormat,
+			pixelWidth,
+			pixelHeight,
+			pixelDepth
+		} = container;
+
+		if ( FORMAT_MAP[ vkFormat ] === undefined ) {
+
+			throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' );
+
+		} //
+
+
+		let view;
+		const levelData = container.levels[ 0 ].levelData;
+
+		if ( TYPE_MAP[ vkFormat ] === THREE.FloatType ) {
+
+			view = new Float32Array( levelData.buffer, levelData.byteOffset, levelData.byteLength / Float32Array.BYTES_PER_ELEMENT );
+
+		} else if ( TYPE_MAP[ vkFormat ] === THREE.HalfFloatType ) {
+
+			view = new Uint16Array( levelData.buffer, levelData.byteOffset, levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT );
+
+		} else {
+
+			view = levelData;
+
+		} //
+
+
+		const texture = pixelDepth === 0 ? new THREE.DataTexture( view, pixelWidth, pixelHeight ) : new THREE.Data3DTexture( view, pixelWidth, pixelHeight, pixelDepth );
+		texture.type = TYPE_MAP[ vkFormat ];
+		texture.format = FORMAT_MAP[ vkFormat ];
+		texture.encoding = ENCODING_MAP[ vkFormat ] || THREE.LinearEncoding;
+		texture.needsUpdate = true; //
+
+		return Promise.resolve( texture );
+
+	}
 
 	THREE.KTX2Loader = KTX2Loader;
 

+ 139 - 74
examples/js/loaders/LDrawLoader.js

@@ -10,10 +10,10 @@
 	const FINISH_TYPE_METAL = 5; // State machine to search a subobject path.
 	// The LDraw standard establishes these various possible subfolders.
 
-	const FILE_LOCATION_AS_IS = 0;
-	const FILE_LOCATION_TRY_PARTS = 1;
-	const FILE_LOCATION_TRY_P = 2;
-	const FILE_LOCATION_TRY_MODELS = 3;
+	const FILE_LOCATION_TRY_PARTS = 0;
+	const FILE_LOCATION_TRY_P = 1;
+	const FILE_LOCATION_TRY_MODELS = 2;
+	const FILE_LOCATION_AS_IS = 3;
 	const FILE_LOCATION_TRY_RELATIVE = 4;
 	const FILE_LOCATION_TRY_ABSOLUTE = 5;
 	const FILE_LOCATION_NOT_FOUND = 6;
@@ -655,7 +655,9 @@
 			result.type = original.type;
 			result.category = original.category;
 			result.keywords = original.keywords;
+			result.author = original.author;
 			result.subobjects = original.subobjects;
+			result.fileName = original.fileName;
 			result.totalFaces = original.totalFaces;
 			result.startingConstructionStep = original.startingConstructionStep;
 			result.materials = original.materials;
@@ -667,7 +669,7 @@
 		async fetchData( fileName ) {
 
 			let triedLowerCase = false;
-			let locationState = FILE_LOCATION_AS_IS;
+			let locationState = FILE_LOCATION_TRY_PARTS;
 
 			while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 
@@ -711,7 +713,7 @@
 							fileName = fileName.toLowerCase();
 							subobjectURL = fileName;
 							triedLowerCase = true;
-							locationState = FILE_LOCATION_AS_IS;
+							locationState = FILE_LOCATION_TRY_PARTS;
 
 						}
 
@@ -761,6 +763,7 @@
 			let type = 'Model';
 			let category = null;
 			let keywords = null;
+			let author = null;
 			let totalFaces = 0; // split into lines
 
 			if ( text.indexOf( '\r\n' ) !== - 1 ) {
@@ -936,6 +939,10 @@
 									startingConstructionStep = true;
 									break;
 
+								case 'Author:':
+									author = lp.getToken();
+									break;
+
 								default:
 									// Other meta directives are not implemented
 									break;
@@ -1141,6 +1148,7 @@
 				type,
 				category,
 				keywords,
+				author,
 				subobjects,
 				totalFaces,
 				startingConstructionStep,
@@ -1277,6 +1285,9 @@
 				const group = new THREE.Group();
 				group.userData.category = info.category;
 				group.userData.keywords = info.keywords;
+				group.userData.author = info.author;
+				group.userData.type = info.type;
+				group.userData.fileName = info.fileName;
 				info.group = group;
 				const subobjectInfos = await Promise.all( promises );
 
@@ -1300,6 +1311,7 @@
 						subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
 						subobjectGroup.name = subobject.fileName;
 						loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
+						subobjectGroup.userData.colorCode = subobject.colorCode;
 						group.add( subobjectGroup );
 						continue;
 
@@ -1388,6 +1400,7 @@
 				if ( subobject ) {
 
 					loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
+					group.userData.colorCode = subobject.colorCode;
 
 				}
 
@@ -1790,7 +1803,25 @@
 
 			this.smoothNormals = true; // The path to load parts from the LDraw parts library from.
 
-			this.partsLibraryPath = '';
+			this.partsLibraryPath = ''; // Material assigned to not available colors for meshes and edges
+
+			this.missingColorMaterial = new THREE.MeshStandardMaterial( {
+				color: 0xFF00FF,
+				roughness: 0.3,
+				metalness: 0
+			} );
+			this.missingColorMaterial.name = 'Missing material';
+			this.missingEdgeColorMaterial = new THREE.LineBasicMaterial( {
+				color: 0xFF00FF
+			} );
+			this.missingEdgeColorMaterial.name = 'Missing material - Edge';
+			this.missingConditionalEdgeColorMaterial = new LDrawConditionalLineMaterial( {
+				fog: true,
+				color: 0xFF00FF
+			} );
+			this.missingConditionalEdgeColorMaterial.name = 'Missing material - Conditional Edge';
+			this.missingColorMaterial.userData.edgeMaterial = this.missingEdgeColorMaterial;
+			this.missingEdgeColorMaterial.userData.conditionalEdgeMaterial = this.missingConditionalEdgeColorMaterial;
 
 		}
 
@@ -1842,6 +1873,7 @@
 
 					this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 					this.computeConstructionSteps( group );
+					group.userData.fileName = url;
 					onLoad( group );
 
 				} ).catch( onError );
@@ -1854,7 +1886,9 @@
 
 			this.partsCache.parseModel( text, this.materialLibrary ).then( group => {
 
+				this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 				this.computeConstructionSteps( group );
+				group.userData.fileName = '';
 				onLoad( group );
 
 			} );
@@ -1984,8 +2018,10 @@
 
 					if ( material === null ) {
 
-						// otherwise throw an error if this is final opportunity to set the material
-						throw new Error( `LDrawLoader: Material properties for code ${colorCode} not available.` );
+						// otherwise throw a warning if this is final opportunity to set the material
+						console.warn( `LDrawLoader: Material properties for code ${colorCode} not available.` ); // And return the 'missing color' material
+
+						material = loader.missingColorMaterial;
 
 					}
 
@@ -2021,8 +2057,8 @@
 
 		getMainEdgeMaterial() {
 
-			const mainMat = this.getMainMaterial();
-			return mainMat && mainMat.userData ? mainMat.userData.edgeMaterial : null;
+			const mat = this.getMaterial( MAIN_EDGE_COLOUR_CODE );
+			return mat ? mat.userData.edgeMaterial : null;
 
 		}
 
@@ -2061,110 +2097,111 @@
 
 				}
 
-				switch ( token.toUpperCase() ) {
+				if ( ! parseLuminance( token ) ) {
 
-					case 'CODE':
-						code = lineParser.getToken();
-						break;
+					switch ( token.toUpperCase() ) {
 
-					case 'VALUE':
-						color = lineParser.getToken();
+						case 'CODE':
+							code = lineParser.getToken();
+							break;
 
-						if ( color.startsWith( '0x' ) ) {
+						case 'VALUE':
+							color = lineParser.getToken();
 
-							color = '#' + color.substring( 2 );
+							if ( color.startsWith( '0x' ) ) {
 
-						} else if ( ! color.startsWith( '#' ) ) {
+								color = '#' + color.substring( 2 );
 
-							throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
+							} else if ( ! color.startsWith( '#' ) ) {
 
-						}
+								throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
 
-						break;
+							}
 
-					case 'EDGE':
-						edgeColor = lineParser.getToken();
+							break;
 
-						if ( edgeColor.startsWith( '0x' ) ) {
+						case 'EDGE':
+							edgeColor = lineParser.getToken();
 
-							edgeColor = '#' + edgeColor.substring( 2 );
+							if ( edgeColor.startsWith( '0x' ) ) {
 
-						} else if ( ! edgeColor.startsWith( '#' ) ) {
+								edgeColor = '#' + edgeColor.substring( 2 );
 
-							// Try to see if edge color is a color code
-							edgeMaterial = this.getMaterial( edgeColor );
+							} else if ( ! edgeColor.startsWith( '#' ) ) {
 
-							if ( ! edgeMaterial ) {
+								// Try to see if edge color is a color code
+								edgeMaterial = this.getMaterial( edgeColor );
 
-								throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
+								if ( ! edgeMaterial ) {
 
-							} // Get the edge material for this triangle material
+									throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
 
+								} // Get the edge material for this triangle material
 
-							edgeMaterial = edgeMaterial.userData.edgeMaterial;
 
-						}
+								edgeMaterial = edgeMaterial.userData.edgeMaterial;
 
-						break;
+							}
 
-					case 'ALPHA':
-						alpha = parseInt( lineParser.getToken() );
+							break;
 
-						if ( isNaN( alpha ) ) {
+						case 'ALPHA':
+							alpha = parseInt( lineParser.getToken() );
 
-							throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
+							if ( isNaN( alpha ) ) {
 
-						}
+								throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
 
-						alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
+							}
 
-						if ( alpha < 1 ) {
+							alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
 
-							isTransparent = true;
+							if ( alpha < 1 ) {
 
-						}
+								isTransparent = true;
 
-						break;
+							}
 
-					case 'LUMINANCE':
-						luminance = parseInt( lineParser.getToken() );
+							break;
 
-						if ( isNaN( luminance ) ) {
+						case 'LUMINANCE':
+							if ( ! parseLuminance( lineParser.getToken() ) ) {
 
-							throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
+								throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
 
-						}
+							}
 
-						luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
-						break;
+							break;
 
-					case 'CHROME':
-						finishType = FINISH_TYPE_CHROME;
-						break;
+						case 'CHROME':
+							finishType = FINISH_TYPE_CHROME;
+							break;
 
-					case 'PEARLESCENT':
-						finishType = FINISH_TYPE_PEARLESCENT;
-						break;
+						case 'PEARLESCENT':
+							finishType = FINISH_TYPE_PEARLESCENT;
+							break;
 
-					case 'RUBBER':
-						finishType = FINISH_TYPE_RUBBER;
-						break;
+						case 'RUBBER':
+							finishType = FINISH_TYPE_RUBBER;
+							break;
 
-					case 'MATTE_METALLIC':
-						finishType = FINISH_TYPE_MATTE_METALLIC;
-						break;
+						case 'MATTE_METALLIC':
+							finishType = FINISH_TYPE_MATTE_METALLIC;
+							break;
 
-					case 'METAL':
-						finishType = FINISH_TYPE_METAL;
-						break;
+						case 'METAL':
+							finishType = FINISH_TYPE_METAL;
+							break;
 
-					case 'MATERIAL':
-						// Not implemented
-						lineParser.setToEnd();
-						break;
+						case 'MATERIAL':
+							// Not implemented
+							lineParser.setToEnd();
+							break;
 
-					default:
-						throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
+						default:
+							throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
+
+					}
 
 				}
 
@@ -2268,6 +2305,8 @@
 					opacity: alpha
 				} );
 				edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear();
+				edgeMaterial.userData.conditionalEdgeMaterial.userData.code = code;
+				edgeMaterial.userData.conditionalEdgeMaterial.name = name + ' - Conditional Edge';
 
 			}
 
@@ -2277,6 +2316,32 @@
 			this.addMaterial( material );
 			return material;
 
+			function parseLuminance( token ) {
+
+				// Returns success
+				let lum;
+
+				if ( token.startsWith( 'LUMINANCE' ) ) {
+
+					lum = parseInt( token.substring( 9 ) );
+
+				} else {
+
+					lum = parseInt( token );
+
+				}
+
+				if ( isNaN( lum ) ) {
+
+					return false;
+
+				}
+
+				luminance = Math.max( 0, Math.min( 1, lum / 255 ) );
+				return true;
+
+			}
+
 		}
 
 		computeConstructionSteps( model ) {

+ 7 - 13
examples/js/loaders/OBJLoader.js

@@ -7,6 +7,7 @@
 	const _material_use_pattern = /^usemtl /; // usemap map_name
 
 	const _map_use_pattern = /^usemap /;
+	const _face_vertex_data_separator_pattern = /\s+/;
 
 	const _vA = new THREE.Vector3();
 
@@ -438,26 +439,19 @@
 			}
 
 			const lines = text.split( '\n' );
-			let line = '',
-				lineFirstChar = '';
-			let lineLength = 0;
-			let result = []; // Faster to just trim left side of the line. Use if available.
-
-			const trimLeft = typeof ''.trimLeft === 'function';
+			let result = [];
 
 			for ( let i = 0, l = lines.length; i < l; i ++ ) {
 
-				line = lines[ i ];
-				line = trimLeft ? line.trimLeft() : line.trim();
-				lineLength = line.length;
-				if ( lineLength === 0 ) continue;
-				lineFirstChar = line.charAt( 0 ); // @todo invoke passed in handler if any
+				const line = lines[ i ].trimStart();
+				if ( line.length === 0 ) continue;
+				const lineFirstChar = line.charAt( 0 ); // @todo invoke passed in handler if any
 
 				if ( lineFirstChar === '#' ) continue;
 
 				if ( lineFirstChar === 'v' ) {
 
-					const data = line.split( /\s+/ );
+					const data = line.split( _face_vertex_data_separator_pattern );
 
 					switch ( data[ 0 ] ) {
 
@@ -492,7 +486,7 @@
 				} else if ( lineFirstChar === 'f' ) {
 
 					const lineData = line.slice( 1 ).trim();
-					const vertexData = lineData.split( /\s+/ );
+					const vertexData = lineData.split( _face_vertex_data_separator_pattern );
 					const faceVertices = []; // Parse the face vertex data into an easy to work with format
 
 					for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {

+ 16 - 9
examples/js/loaders/PCDLoader.js

@@ -268,25 +268,32 @@
 
 					if ( offset.x !== undefined ) {
 
-						position.push( dataview.getFloat32( PCDheader.points * offset.x + PCDheader.size[ 0 ] * i, this.littleEndian ) );
-						position.push( dataview.getFloat32( PCDheader.points * offset.y + PCDheader.size[ 1 ] * i, this.littleEndian ) );
-						position.push( dataview.getFloat32( PCDheader.points * offset.z + PCDheader.size[ 2 ] * i, this.littleEndian ) );
+						const xIndex = PCDheader.fields.indexOf( 'x' );
+						const yIndex = PCDheader.fields.indexOf( 'y' );
+						const zIndex = PCDheader.fields.indexOf( 'z' );
+						position.push( dataview.getFloat32( PCDheader.points * offset.x + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
+						position.push( dataview.getFloat32( PCDheader.points * offset.y + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
+						position.push( dataview.getFloat32( PCDheader.points * offset.z + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
 
 					}
 
 					if ( offset.rgb !== undefined ) {
 
-						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 2 ) / 255.0 );
-						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 1 ) / 255.0 );
-						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 0 ) / 255.0 );
+						const rgbIndex = PCDheader.fields.indexOf( 'rgb' );
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0 );
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0 );
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0 );
 
 					}
 
 					if ( offset.normal_x !== undefined ) {
 
-						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_x + PCDheader.size[ 4 ] * i, this.littleEndian ) );
-						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_y + PCDheader.size[ 5 ] * i, this.littleEndian ) );
-						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_z + PCDheader.size[ 6 ] * i, this.littleEndian ) );
+						const xIndex = PCDheader.fields.indexOf( 'normal_x' );
+						const yIndex = PCDheader.fields.indexOf( 'normal_y' );
+						const zIndex = PCDheader.fields.indexOf( 'normal_z' );
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_x + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_y + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_z + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
 
 					}
 

+ 3 - 3
examples/js/loaders/PLYLoader.js

@@ -81,7 +81,7 @@
 
 			function parseHeader( data ) {
 
-				const patternHeader = /^ply([\s\S]*)end_header\r?\n/;
+				const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/;
 				let headerText = '';
 				let headerLength = 0;
 				const result = patternHeader.exec( data );
@@ -99,7 +99,7 @@
 					headerLength: headerLength,
 					objInfo: ''
 				};
-				const lines = headerText.split( '\n' );
+				const lines = headerText.split( /\r\n|\r|\n/ );
 				let currentElement;
 
 				function make_ply_element_property( propertValues, propertyNameMapping ) {
@@ -269,7 +269,7 @@
 
 				}
 
-				const lines = body.split( '\n' );
+				const lines = body.split( /\r\n|\r|\n/ );
 				let currentElement = 0;
 				let currentElementCount = 0;
 

+ 1 - 0
examples/js/loaders/SVGLoader.js

@@ -59,6 +59,7 @@
 				switch ( node.nodeName ) {
 
 					case 'svg':
+						style = parseStyle( node, style );
 						break;
 
 					case 'style':

+ 33 - 24
examples/js/loaders/VRMLLoader.js

@@ -212,16 +212,16 @@
 			function createVisitor( BaseVRMLVisitor ) {
 
 				// the visitor is created dynmaically based on the given base class
-				function VRMLToASTVisitor() {
+				class VRMLToASTVisitor extends BaseVRMLVisitor {
 
-					BaseVRMLVisitor.call( this );
-					this.validateVisitor();
+					constructor() {
 
-				}
+						super();
+						this.validateVisitor();
+
+					}
 
-				VRMLToASTVisitor.prototype = Object.assign( Object.create( BaseVRMLVisitor.prototype ), {
-					constructor: VRMLToASTVisitor,
-					vrml: function ( ctx ) {
+					vrml( ctx ) {
 
 						const data = {
 							version: this.visit( ctx.version ),
@@ -249,13 +249,15 @@
 
 						return data;
 
-					},
-					version: function ( ctx ) {
+					}
+
+					version( ctx ) {
 
 						return ctx.Version[ 0 ].image;
 
-					},
-					node: function ( ctx ) {
+					}
+
+					node( ctx ) {
 
 						const data = {
 							name: ctx.NodeName[ 0 ].image,
@@ -282,8 +284,9 @@
 
 						return data;
 
-					},
-					field: function ( ctx ) {
+					}
+
+					field( ctx ) {
 
 						const data = {
 							name: ctx.Identifier[ 0 ].image,
@@ -309,30 +312,35 @@
 						data.values = result.values;
 						return data;
 
-					},
-					def: function ( ctx ) {
+					}
+
+					def( ctx ) {
 
 						return ( ctx.Identifier || ctx.NodeName )[ 0 ].image;
 
-					},
-					use: function ( ctx ) {
+					}
+
+					use( ctx ) {
 
 						return {
 							USE: ( ctx.Identifier || ctx.NodeName )[ 0 ].image
 						};
 
-					},
-					singleFieldValue: function ( ctx ) {
+					}
+
+					singleFieldValue( ctx ) {
 
 						return processField( this, ctx );
 
-					},
-					multiFieldValue: function ( ctx ) {
+					}
+
+					multiFieldValue( ctx ) {
 
 						return processField( this, ctx );
 
-					},
-					route: function ( ctx ) {
+					}
+
+					route( ctx ) {
 
 						const data = {
 							FROM: ctx.RouteIdentifier[ 0 ].image,
@@ -341,7 +349,8 @@
 						return data;
 
 					}
-				} );
+
+				}
 
 				function processField( scope, ctx ) {
 

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

@@ -855,7 +855,7 @@
 							// Depending on the number of DataArrays
 							let arr;
 
-							if ( Object.prototype.toString.call( section.DataArray ) === '[object Array]' ) {
+							if ( Array.isArray( section.DataArray ) ) {
 
 								arr = section.DataArray;
 

+ 8 - 7
examples/js/loaders/lwo/LWO2Parser.js

@@ -1,14 +1,14 @@
 ( function () {
 
-	function LWO2Parser( IFFParser ) {
+	class LWO2Parser {
 
-		this.IFF = IFFParser;
+		constructor( IFFParser ) {
 
-	}
+			this.IFF = IFFParser;
+
+		}
 
-	LWO2Parser.prototype = {
-		constructor: LWO2Parser,
-		parseBlock: function () {
+		parseBlock() {
 
 			this.IFF.debugger.offset = this.IFF.reader.offset;
 			this.IFF.debugger.closeForms();
@@ -440,7 +440,8 @@
 			}
 
 		}
-	};
+
+	}
 
 	THREE.LWO2Parser = LWO2Parser;
 

+ 8 - 7
examples/js/loaders/lwo/LWO3Parser.js

@@ -1,14 +1,14 @@
 ( function () {
 
-	function LWO3Parser( IFFParser ) {
+	class LWO3Parser {
 
-		this.IFF = IFFParser;
+		constructor( IFFParser ) {
 
-	}
+			this.IFF = IFFParser;
+
+		}
 
-	LWO3Parser.prototype = {
-		constructor: LWO3Parser,
-		parseBlock: function () {
+		parseBlock() {
 
 			this.IFF.debugger.offset = this.IFF.reader.offset;
 			this.IFF.debugger.closeForms();
@@ -394,7 +394,8 @@
 			}
 
 		}
-	};
+
+	}
 
 	THREE.LWO3Parser = LWO3Parser;
 

+ 198 - 190
examples/js/misc/Volume.js

@@ -13,205 +13,203 @@
  * @param   {ArrayBuffer}   arrayBuffer     The buffer with volume data
  */
 
-	function Volume( xLength, yLength, zLength, type, arrayBuffer ) {
+	class Volume {
+
+		constructor( xLength, yLength, zLength, type, arrayBuffer ) {
+
+			if ( xLength !== undefined ) {
+
+				/**
+       * @member {number} xLength Width of the volume in the IJK coordinate system
+       */
+				this.xLength = Number( xLength ) || 1;
+				/**
+       * @member {number} yLength Height of the volume in the IJK coordinate system
+       */
+
+				this.yLength = Number( yLength ) || 1;
+				/**
+       * @member {number} zLength Depth of the volume in the IJK coordinate system
+       */
+
+				this.zLength = Number( zLength ) || 1;
+				/**
+       * @member {Array<string>} The order of the Axis dictated by the NRRD header
+       */
+
+				this.axisOrder = [ 'x', 'y', 'z' ];
+				/**
+       * @member {TypedArray} data Data of the volume
+       */
+
+				switch ( type ) {
+
+					case 'Uint8':
+					case 'uint8':
+					case 'uchar':
+					case 'unsigned char':
+					case 'uint8_t':
+						this.data = new Uint8Array( arrayBuffer );
+						break;
+
+					case 'Int8':
+					case 'int8':
+					case 'signed char':
+					case 'int8_t':
+						this.data = new Int8Array( arrayBuffer );
+						break;
+
+					case 'Int16':
+					case 'int16':
+					case 'short':
+					case 'short int':
+					case 'signed short':
+					case 'signed short int':
+					case 'int16_t':
+						this.data = new Int16Array( arrayBuffer );
+						break;
+
+					case 'Uint16':
+					case 'uint16':
+					case 'ushort':
+					case 'unsigned short':
+					case 'unsigned short int':
+					case 'uint16_t':
+						this.data = new Uint16Array( arrayBuffer );
+						break;
+
+					case 'Int32':
+					case 'int32':
+					case 'int':
+					case 'signed int':
+					case 'int32_t':
+						this.data = new Int32Array( arrayBuffer );
+						break;
+
+					case 'Uint32':
+					case 'uint32':
+					case 'uint':
+					case 'unsigned int':
+					case 'uint32_t':
+						this.data = new Uint32Array( arrayBuffer );
+						break;
+
+					case 'longlong':
+					case 'long long':
+					case 'long long int':
+					case 'signed long long':
+					case 'signed long long int':
+					case 'int64':
+					case 'int64_t':
+					case 'ulonglong':
+					case 'unsigned long long':
+					case 'unsigned long long int':
+					case 'uint64':
+					case 'uint64_t':
+						throw new Error( 'Error in Volume constructor : this type is not supported in JavaScript' );
+						break;
+
+					case 'Float32':
+					case 'float32':
+					case 'float':
+						this.data = new Float32Array( arrayBuffer );
+						break;
+
+					case 'Float64':
+					case 'float64':
+					case 'double':
+						this.data = new Float64Array( arrayBuffer );
+						break;
+
+					default:
+						this.data = new Uint8Array( arrayBuffer );
 
-		if ( arguments.length > 0 ) {
+				}
+
+				if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
 
+					throw new Error( 'Error in Volume constructor, lengths are not matching arrayBuffer size' );
+
+				}
+
+			}
 			/**
-     * @member {number} xLength Width of the volume in the IJK coordinate system
+     * @member {Array}  spacing Spacing to apply to the volume from IJK to RAS coordinate system
      */
-			this.xLength = Number( xLength ) || 1;
+
+
+			this.spacing = [ 1, 1, 1 ];
 			/**
-     * @member {number} yLength Height of the volume in the IJK coordinate system
+     * @member {Array}  offset Offset of the volume in the RAS coordinate system
      */
 
-			this.yLength = Number( yLength ) || 1;
+			this.offset = [ 0, 0, 0 ];
 			/**
-     * @member {number} zLength Depth of the volume in the IJK coordinate system
+     * @member {Martrix3} matrix The IJK to RAS matrix
      */
 
-			this.zLength = Number( zLength ) || 1;
+			this.matrix = new THREE.Matrix3();
+			this.matrix.identity();
 			/**
-     * @member {Array<string>} The order of the Axis dictated by the NRRD header
+     * @member {Martrix3} inverseMatrix The RAS to IJK matrix
      */
 
-			this.axisOrder = [ 'x', 'y', 'z' ];
 			/**
-     * @member {TypedArray} data Data of the volume
+     * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
+     *                      If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
      */
 
-			switch ( type ) {
+			let lowerThreshold = - Infinity;
+			Object.defineProperty( this, 'lowerThreshold', {
+				get: function () {
 
-				case 'Uint8':
-				case 'uint8':
-				case 'uchar':
-				case 'unsigned char':
-				case 'uint8_t':
-					this.data = new Uint8Array( arrayBuffer );
-					break;
+					return lowerThreshold;
 
-				case 'Int8':
-				case 'int8':
-				case 'signed char':
-				case 'int8_t':
-					this.data = new Int8Array( arrayBuffer );
-					break;
+				},
+				set: function ( value ) {
 
-				case 'Int16':
-				case 'int16':
-				case 'short':
-				case 'short int':
-				case 'signed short':
-				case 'signed short int':
-				case 'int16_t':
-					this.data = new Int16Array( arrayBuffer );
-					break;
+					lowerThreshold = value;
+					this.sliceList.forEach( function ( slice ) {
 
-				case 'Uint16':
-				case 'uint16':
-				case 'ushort':
-				case 'unsigned short':
-				case 'unsigned short int':
-				case 'uint16_t':
-					this.data = new Uint16Array( arrayBuffer );
-					break;
+						slice.geometryNeedsUpdate = true;
 
-				case 'Int32':
-				case 'int32':
-				case 'int':
-				case 'signed int':
-				case 'int32_t':
-					this.data = new Int32Array( arrayBuffer );
-					break;
+					} );
 
-				case 'Uint32':
-				case 'uint32':
-				case 'uint':
-				case 'unsigned int':
-				case 'uint32_t':
-					this.data = new Uint32Array( arrayBuffer );
-					break;
+				}
+			} );
+			/**
+     * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
+     *                      If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
+     */
 
-				case 'longlong':
-				case 'long long':
-				case 'long long int':
-				case 'signed long long':
-				case 'signed long long int':
-				case 'int64':
-				case 'int64_t':
-				case 'ulonglong':
-				case 'unsigned long long':
-				case 'unsigned long long int':
-				case 'uint64':
-				case 'uint64_t':
-					throw new Error( 'Error in Volume constructor : this type is not supported in JavaScript' );
-					break;
+			let upperThreshold = Infinity;
+			Object.defineProperty( this, 'upperThreshold', {
+				get: function () {
 
-				case 'Float32':
-				case 'float32':
-				case 'float':
-					this.data = new Float32Array( arrayBuffer );
-					break;
+					return upperThreshold;
 
-				case 'Float64':
-				case 'float64':
-				case 'double':
-					this.data = new Float64Array( arrayBuffer );
-					break;
+				},
+				set: function ( value ) {
 
-				default:
-					this.data = new Uint8Array( arrayBuffer );
+					upperThreshold = value;
+					this.sliceList.forEach( function ( slice ) {
 
-			}
+						slice.geometryNeedsUpdate = true;
 
-			if ( this.data.length !== this.xLength * this.yLength * this.zLength ) {
+					} );
 
-				throw new Error( 'Error in Volume constructor, lengths are not matching arrayBuffer size' );
+				}
+			} );
+			/**
+     * @member {Array} sliceList The list of all the slices associated to this volume
+     */
 
-			}
+			this.sliceList = [];
+			/**
+     * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
+     */
 
 		}
-		/**
-   * @member {Array}  spacing Spacing to apply to the volume from IJK to RAS coordinate system
-   */
-
-
-		this.spacing = [ 1, 1, 1 ];
-		/**
-   * @member {Array}  offset Offset of the volume in the RAS coordinate system
-   */
-
-		this.offset = [ 0, 0, 0 ];
-		/**
-   * @member {Martrix3} matrix The IJK to RAS matrix
-   */
-
-		this.matrix = new THREE.Matrix3();
-		this.matrix.identity();
-		/**
-   * @member {Martrix3} inverseMatrix The RAS to IJK matrix
-   */
-
-		/**
-   * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices.
-   *                      If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
-   */
-
-		let lowerThreshold = - Infinity;
-		Object.defineProperty( this, 'lowerThreshold', {
-			get: function () {
-
-				return lowerThreshold;
-
-			},
-			set: function ( value ) {
-
-				lowerThreshold = value;
-				this.sliceList.forEach( function ( slice ) {
-
-					slice.geometryNeedsUpdate = true;
-
-				} );
-
-			}
-		} );
-		/**
-   * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices.
-   *                      If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume
-   */
-
-		let upperThreshold = Infinity;
-		Object.defineProperty( this, 'upperThreshold', {
-			get: function () {
-
-				return upperThreshold;
-
-			},
-			set: function ( value ) {
-
-				upperThreshold = value;
-				this.sliceList.forEach( function ( slice ) {
-
-					slice.geometryNeedsUpdate = true;
-
-				} );
-
-			}
-		} );
-		/**
-   * @member {Array} sliceList The list of all the slices associated to this volume
-   */
-
-		this.sliceList = [];
-		/**
-   * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space
-   */
-
-	}
-
-	Volume.prototype = {
-		constructor: Volume,
-
 		/**
    * @member {Function} getData Shortcut for data[access(i,j,k)]
    * @memberof Volume
@@ -220,12 +218,13 @@
    * @param {number} k    Third coordinate
    * @returns {number}  value in the data array
    */
-		getData: function ( i, j, k ) {
 
-			return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
 
-		},
+		getData( i, j, k ) {
+
+			return this.data[ k * this.xLength * this.yLength + j * this.xLength + i ];
 
+		}
 		/**
    * @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system
    * @memberof Volume
@@ -234,27 +233,29 @@
    * @param {number} k    Third coordinate
    * @returns {number}  index
    */
-		access: function ( i, j, k ) {
 
-			return k * this.xLength * this.yLength + j * this.xLength + i;
 
-		},
+		access( i, j, k ) {
+
+			return k * this.xLength * this.yLength + j * this.xLength + i;
 
+		}
 		/**
    * @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data
    * @memberof Volume
    * @param {number} index index of the voxel
    * @returns {Array}  [x,y,z]
    */
-		reverseAccess: function ( index ) {
+
+
+		reverseAccess( index ) {
 
 			const z = Math.floor( index / ( this.yLength * this.xLength ) );
 			const y = Math.floor( ( index - z * this.yLength * this.xLength ) / this.xLength );
 			const x = index - z * this.yLength * this.xLength - y * this.xLength;
 			return [ x, y, z ];
 
-		},
-
+		}
 		/**
    * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
    * @memberof Volume
@@ -265,7 +266,9 @@
    * @param {Object}   context    You can specify a context in which call the function, default if this Volume
    * @returns {Volume}   this
    */
-		map: function ( functionToMap, context ) {
+
+
+		map( functionToMap, context ) {
 
 			const length = this.data.length;
 			context = context || this;
@@ -278,8 +281,7 @@
 
 			return this;
 
-		},
-
+		}
 		/**
    * @member {Function} extractPerpendicularPlane Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system.
    * @memberof Volume
@@ -287,7 +289,9 @@
    * @param {number}            index the index of the slice
    * @returns {Object} an object containing all the usefull information on the geometry of the slice
    */
-		extractPerpendicularPlane: function ( axis, RASIndex ) {
+
+
+		extractPerpendicularPlane( axis, RASIndex ) {
 
 			let firstSpacing, secondSpacing, positionOffset, IJKIndex;
 			const axisInIJK = new THREE.Vector3(),
@@ -386,8 +390,7 @@
 				planeHeight: planeHeight
 			};
 
-		},
-
+		}
 		/**
    * @member {Function} extractSlice Returns a slice corresponding to the given axis and index
    *                        The coordinate are given in the Right Anterior Superior coordinate format
@@ -396,21 +399,24 @@
    * @param {number}            index the index of the slice
    * @returns {VolumeSlice} the extracted slice
    */
-		extractSlice: function ( axis, index ) {
+
+
+		extractSlice( axis, index ) {
 
 			const slice = new THREE.VolumeSlice( this, index, axis );
 			this.sliceList.push( slice );
 			return slice;
 
-		},
-
+		}
 		/**
    * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
    * @see THREE.VolumeSlice.repaint
    * @memberof Volume
    * @returns {Volume} this
    */
-		repaintAllSlices: function () {
+
+
+		repaintAllSlices() {
 
 			this.sliceList.forEach( function ( slice ) {
 
@@ -419,14 +425,15 @@
 			} );
 			return this;
 
-		},
-
+		}
 		/**
    * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
    * @memberof Volume
    * @returns {Array} [min,max]
    */
-		computeMinMax: function () {
+
+
+		computeMinMax() {
 
 			let min = Infinity;
 			let max = - Infinity; // buffer the length
@@ -451,7 +458,8 @@
 			return [ min, max ];
 
 		}
-	};
+
+	}
 
 	THREE.Volume = Volume;
 

+ 90 - 88
examples/js/misc/VolumeSlice.js

@@ -9,103 +9,103 @@
  * @see Volume
  */
 
-	function VolumeSlice( volume, index, axis ) {
+	class VolumeSlice {
 
-		const slice = this;
-		/**
-   * @member {Volume} volume The associated volume
-   */
-
-		this.volume = volume;
-		/**
-   * @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint
-   */
-
-		index = index || 0;
-		Object.defineProperty( this, 'index', {
-			get: function () {
+		constructor( volume, index, axis ) {
 
-				return index;
-
-			},
-			set: function ( value ) {
-
-				index = value;
-				slice.geometryNeedsUpdate = true;
-				return index;
-
-			}
-		} );
-		/**
-   * @member {String} axis The normal axis
-   */
+			const slice = this;
+			/**
+     * @member {Volume} volume The associated volume
+     */
 
-		this.axis = axis || 'z';
-		/**
-   * @member {HTMLCanvasElement} canvas The final canvas used for the texture
-   */
+			this.volume = volume;
+			/**
+     * @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint
+     */
 
-		/**
-   * @member {CanvasRenderingContext2D} ctx Context of the canvas
-   */
+			index = index || 0;
+			Object.defineProperty( this, 'index', {
+				get: function () {
 
-		this.canvas = document.createElement( 'canvas' );
-		/**
-   * @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data
-   */
+					return index;
 
-		/**
-   * @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer
-   */
+				},
+				set: function ( value ) {
 
-		this.canvasBuffer = document.createElement( 'canvas' );
-		this.updateGeometry();
-		const canvasMap = new THREE.Texture( this.canvas );
-		canvasMap.minFilter = THREE.LinearFilter;
-		canvasMap.wrapS = canvasMap.wrapT = THREE.ClampToEdgeWrapping;
-		const material = new THREE.MeshBasicMaterial( {
-			map: canvasMap,
-			side: THREE.DoubleSide,
-			transparent: true
-		} );
-		/**
-   * @member {Mesh} mesh The mesh ready to get used in the scene
-   */
+					index = value;
+					slice.geometryNeedsUpdate = true;
+					return index;
 
-		this.mesh = new THREE.Mesh( this.geometry, material );
-		this.mesh.matrixAutoUpdate = false;
-		/**
-   * @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint
-   */
-
-		this.geometryNeedsUpdate = true;
-		this.repaint();
-		/**
-   * @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas
-   */
-
-		/**
-   * @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas
-   */
-
-		/**
-   * @member {Function} sliceAccess Function that allow the slice to access right data
-   * @see Volume.extractPerpendicularPlane
-   * @param {Number} i The first coordinate
-   * @param {Number} j The second coordinate
-   * @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice
-   */
-
-	}
-
-	VolumeSlice.prototype = {
-		constructor: VolumeSlice,
+				}
+			} );
+			/**
+     * @member {String} axis The normal axis
+     */
+
+			this.axis = axis || 'z';
+			/**
+     * @member {HTMLCanvasElement} canvas The final canvas used for the texture
+     */
+
+			/**
+     * @member {CanvasRenderingContext2D} ctx Context of the canvas
+     */
+
+			this.canvas = document.createElement( 'canvas' );
+			/**
+     * @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data
+     */
+
+			/**
+     * @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer
+     */
+
+			this.canvasBuffer = document.createElement( 'canvas' );
+			this.updateGeometry();
+			const canvasMap = new THREE.Texture( this.canvas );
+			canvasMap.minFilter = THREE.LinearFilter;
+			canvasMap.wrapS = canvasMap.wrapT = THREE.ClampToEdgeWrapping;
+			const material = new THREE.MeshBasicMaterial( {
+				map: canvasMap,
+				side: THREE.DoubleSide,
+				transparent: true
+			} );
+			/**
+     * @member {Mesh} mesh The mesh ready to get used in the scene
+     */
+
+			this.mesh = new THREE.Mesh( this.geometry, material );
+			this.mesh.matrixAutoUpdate = false;
+			/**
+     * @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint
+     */
+
+			this.geometryNeedsUpdate = true;
+			this.repaint();
+			/**
+     * @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas
+     */
+
+			/**
+     * @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas
+     */
+
+			/**
+     * @member {Function} sliceAccess Function that allow the slice to access right data
+     * @see Volume.extractPerpendicularPlane
+     * @param {Number} i The first coordinate
+     * @param {Number} j The second coordinate
+     * @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice
+     */
 
+		}
 		/**
    * @member {Function} repaint Refresh the texture and the geometry if geometryNeedsUpdate is set to true
    * @memberof VolumeSlice
    */
-		repaint: function () {
+
+
+		repaint() {
 
 			if ( this.geometryNeedsUpdate ) {
 
@@ -179,14 +179,15 @@
 			this.ctx.drawImage( canvas, 0, 0, iLength, jLength, 0, 0, this.canvas.width, this.canvas.height );
 			this.mesh.material.map.needsUpdate = true;
 
-		},
-
+		}
 		/**
    * @member {Function} Refresh the geometry according to axis and index
    * @see Volume.extractPerpendicularPlane
    * @memberof VolumeSlice
    */
-		updateGeometry: function () {
+
+
+		updateGeometry() {
 
 			const extracted = this.volume.extractPerpendicularPlane( this.axis, this.index );
 			this.sliceAccess = extracted.sliceAccess;
@@ -215,7 +216,8 @@
 			this.geometryNeedsUpdate = false;
 
 		}
-	};
+
+	}
 
 	THREE.VolumeSlice = VolumeSlice;
 

+ 1 - 1
examples/js/modifiers/SimplifyModifier.js

@@ -306,7 +306,7 @@
 
 		for ( let i = u.faces.length - 1; i >= 0; i -- ) {
 
-			if ( u.faces[ i ].hasVertex( v ) ) {
+			if ( u.faces[ i ] && u.faces[ i ].hasVertex( v ) ) {
 
 				removeFace( u.faces[ i ], faces );
 

+ 5 - 1
examples/js/objects/ShadowMesh.js

@@ -14,7 +14,11 @@
 				color: 0x000000,
 				transparent: true,
 				opacity: 0.6,
-				depthWrite: false
+				depthWrite: false,
+				stencilWrite: true,
+				stencilFunc: THREE.EqualStencilFunc,
+				stencilRef: 0,
+				stencilZPass: THREE.IncrementStencilOp
 			} );
 			super( mesh.geometry, shadowMaterial );
 			this.isShadowMesh = true;

+ 4 - 2
examples/js/postprocessing/OutlinePass.js

@@ -497,12 +497,14 @@
 
 				void main() {
 					vec2 invSize = 1.0 / texSize;
-					float weightSum = gaussianPdf(0.0, kernelRadius);
+					float sigma = kernelRadius/2.0;
+					float weightSum = gaussianPdf(0.0, sigma);
 					vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum;
 					vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);
 					vec2 uvOffset = delta;
 					for( int i = 1; i <= MAX_RADIUS; i ++ ) {
-						float w = gaussianPdf(uvOffset.x, kernelRadius);
+						float x = kernelRadius * float(i) / float(MAX_RADIUS);
+						float w = gaussianPdf(x, sigma);
 						vec4 sample1 = texture2D( colorTexture, vUv + uvOffset);
 						vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);
 						diffuseSum += ((sample1 + sample2) * w);

+ 6 - 2
examples/js/shaders/MMDToonShader.js

@@ -14,7 +14,9 @@
  *    (Replace lights_phong_pars_fragment with lights_mmd_toon_pars_fragment)
  *  * Add mmd_toon_matcap_fragment.
  */
-	const lights_mmd_toon_pars_fragment = `
+	const lights_mmd_toon_pars_fragment =
+/* glsl */
+`
 varying vec3 vViewPosition;
 
 struct BlinnPhongMaterial {
@@ -47,7 +49,9 @@ void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in Geometric
 
 #define Material_LightProbeLOD( material )	(0)
 `;
-	const mmd_toon_matcap_fragment = `
+	const mmd_toon_matcap_fragment =
+/* glsl */
+`
 #ifdef USE_MATCAP
 
 	vec3 viewDir = normalize( vViewPosition );

+ 1 - 1
examples/js/utils/BufferGeometryUtils.js

@@ -409,7 +409,7 @@
 
 		if ( attribute.isInstancedInterleavedBufferAttribute ) {
 
-			newAttribute = new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );
+			newAttribute = new THREE.InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );
 
 		} else {
 

+ 2 - 18
examples/js/utils/GeometryUtils.js

@@ -23,13 +23,7 @@
 
 		if ( 0 <= -- iterations ) {
 
-			const tmp = [];
-			Array.prototype.push.apply( tmp, hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ) );
-			Array.prototype.push.apply( tmp, hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ) );
-			Array.prototype.push.apply( tmp, hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ) );
-			Array.prototype.push.apply( tmp, hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) ); // Return recursive call
-
-			return tmp;
+			return [ ...hilbert2D( vec[ 0 ], half, iterations, v0, v3, v2, v1 ), ...hilbert2D( vec[ 1 ], half, iterations, v0, v1, v2, v3 ), ...hilbert2D( vec[ 2 ], half, iterations, v0, v1, v2, v3 ), ...hilbert2D( vec[ 3 ], half, iterations, v2, v1, v0, v3 ) ];
 
 		} // Return complete Hilbert Curve.
 
@@ -66,17 +60,7 @@
 
 		if ( -- iterations >= 0 ) {
 
-			const tmp = [];
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ) );
-			Array.prototype.push.apply( tmp, hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) ); // Return recursive call
-
-			return tmp;
+			return [ ...hilbert3D( vec[ 0 ], half, iterations, v0, v3, v4, v7, v6, v5, v2, v1 ), ...hilbert3D( vec[ 1 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ), ...hilbert3D( vec[ 2 ], half, iterations, v0, v7, v6, v1, v2, v5, v4, v3 ), ...hilbert3D( vec[ 3 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ), ...hilbert3D( vec[ 4 ], half, iterations, v2, v3, v0, v1, v6, v7, v4, v5 ), ...hilbert3D( vec[ 5 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ), ...hilbert3D( vec[ 6 ], half, iterations, v4, v3, v2, v5, v6, v1, v0, v7 ), ...hilbert3D( vec[ 7 ], half, iterations, v6, v5, v2, v1, v0, v3, v4, v7 ) ];
 
 		} // Return complete Hilbert Curve.
 

+ 1 - 1
examples/jsm/animation/MMDPhysics.js

@@ -209,7 +209,7 @@ class MMDPhysics {
 		// mesh's default world transform as position(0, 0, 0),
 		// quaternion(0, 0, 0, 1) and scale(0, 0, 0)
 
-		let parent = mesh.parent;
+		const parent = mesh.parent;
 
 		if ( parent !== null ) mesh.parent = null;
 

+ 17 - 2
examples/jsm/controls/ArcballControls.js

@@ -1040,7 +1040,7 @@ class ArcballControls extends EventDispatcher {
 
 							}
 
-							this._v3_1.setFromMatrixPosition(this._gizmoMatrixState);
+							this._v3_1.setFromMatrixPosition( this._gizmoMatrixState );
 
 							this.applyTransformMatrix( this.scale( size, this._v3_1 ) );
 
@@ -2279,7 +2279,7 @@ class ArcballControls extends EventDispatcher {
 		this._gizmoMatrixState0.identity().setPosition( tbCenter );
 		this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
 
-		if ( this.camera.zoom != 1 ) {
+		if ( this.camera.zoom !== 1 ) {
 
 			//adapt gizmos size to camera zoom
 			const size = 1 / this.camera.zoom;
@@ -2294,8 +2294,23 @@ class ArcballControls extends EventDispatcher {
 
 		this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
 
+		//
+
+		this._gizmos.traverse( function( object ) {
+
+			if ( object.isLine ) {
+		
+				object.geometry.dispose();
+				object.material.dispose();
+		
+			}
+		
+		} );
+
 		this._gizmos.clear();
 
+		//
+
 		this._gizmos.add( gizmoX );
 		this._gizmos.add( gizmoY );
 		this._gizmos.add( gizmoZ );

+ 9 - 1
examples/jsm/controls/TransformControls.js

@@ -205,7 +205,15 @@ class TransformControls extends Object3D {
 		this.camera.updateMatrixWorld();
 		this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
 
-		this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
+		if ( this.camera.isOrthographicCamera ) {
+
+			this.camera.getWorldDirection( this.eye );
+
+		} else {
+
+			this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize();
+
+		}
 
 		super.updateMatrixWorld( this );
 

+ 0 - 1248
examples/jsm/controls/experimental/CameraControls.js

@@ -1,1248 +0,0 @@
-import {
-	EventDispatcher,
-	MOUSE,
-	Quaternion,
-	Spherical,
-	TOUCH,
-	Vector2,
-	Vector3
-} from 'three';
-
-var CameraControls = function ( object, domElement ) {
-
-	if ( domElement === undefined ) console.warn( 'THREE.CameraControls: The second parameter "domElement" is now mandatory.' );
-	if ( domElement === document ) console.error( 'THREE.CameraControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
-
-	this.object = object;
-	this.domElement = domElement;
-
-	// Set to false to disable this control
-	this.enabled = true;
-
-	// "target" sets the location of focus, where the object orbits around
-	this.target = new Vector3();
-
-	// Set to true to enable trackball behavior
-	this.trackball = false;
-
-	// How far you can dolly in and out ( PerspectiveCamera only )
-	this.minDistance = 0;
-	this.maxDistance = Infinity;
-
-	// How far you can zoom in and out ( OrthographicCamera only )
-	this.minZoom = 0;
-	this.maxZoom = Infinity;
-
-	// How far you can orbit vertically, upper and lower limits.
-	// Range is 0 to Math.PI radians.
-	this.minPolarAngle = 0; // radians
-	this.maxPolarAngle = Math.PI; // radians
-
-	// How far you can orbit horizontally, upper and lower limits.
-	// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
-	this.minAzimuthAngle = - Infinity; // radians
-	this.maxAzimuthAngle = Infinity; // radians
-
-	// Set to true to enable damping (inertia)
-	// If damping is enabled, you must call controls.update() in your animation loop
-	this.enableDamping = false;
-	this.dampingFactor = 0.05;
-
-	// This option enables dollying in and out; property named as "zoom" for backwards compatibility
-	// Set to false to disable zooming
-	this.enableZoom = true;
-	this.zoomSpeed = 1.0;
-
-	// Set to false to disable rotating
-	this.enableRotate = true;
-	this.rotateSpeed = 1.0;
-
-	// Set to false to disable panning
-	this.enablePan = true;
-	this.panSpeed = 1.0;
-	this.screenSpacePanning = false; // if true, pan in screen-space
-	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
-
-	// Set to true to automatically rotate around the target
-	// If auto-rotate is enabled, you must call controls.update() in your animation loop
-	// auto-rotate is not supported for trackball behavior
-	this.autoRotate = false;
-	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
-
-	// Set to false to disable use of the keys
-	this.enableKeys = true;
-
-	// The four arrow keys
-	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
-
-	// Mouse buttons
-	this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
-
-	// Touch fingers
-	this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
-
-	// for reset
-	this.target0 = this.target.clone();
-	this.position0 = this.object.position.clone();
-	this.quaternion0 = this.object.quaternion.clone();
-	this.zoom0 = this.object.zoom;
-
-	//
-	// public methods
-	//
-
-	this.getPolarAngle = function () {
-
-		return spherical.phi;
-
-	};
-
-	this.getAzimuthalAngle = function () {
-
-		return spherical.theta;
-
-	};
-
-	this.saveState = function () {
-
-		scope.target0.copy( scope.target );
-		scope.position0.copy( scope.object.position );
-		scope.quaternion0.copy( scope.object.quaternion );
-		scope.zoom0 = scope.object.zoom;
-
-	};
-
-	this.reset = function () {
-
-		scope.target.copy( scope.target0 );
-		scope.object.position.copy( scope.position0 );
-		scope.object.quaternion.copy( scope.quaternion0 );
-		scope.object.zoom = scope.zoom0;
-
-		scope.object.updateProjectionMatrix();
-		scope.dispatchEvent( changeEvent );
-
-		scope.update();
-
-		state = STATE.NONE;
-
-	};
-
-	// this method is exposed, but perhaps it would be better if we can make it private...
-	this.update = function () {
-
-		var offset = new Vector3();
-
-		// so camera.up is the orbit axis
-		var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
-		var quatInverse = quat.clone().invert();
-
-		var lastPosition = new Vector3();
-		var lastQuaternion = new Quaternion();
-
-		var q = new Quaternion();
-		var vec = new Vector3();
-
-		return function update() {
-
-			var position = scope.object.position;
-
-			offset.copy( position ).sub( scope.target );
-
-			if ( scope.trackball ) {
-
-				// rotate around screen-space y-axis
-
-				if ( sphericalDelta.theta ) {
-
-					vec.set( 0, 1, 0 ).applyQuaternion( scope.object.quaternion );
-
-					const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1;
-
-					q.setFromAxisAngle( vec, sphericalDelta.theta * factor );
-
-					scope.object.quaternion.premultiply( q );
-					offset.applyQuaternion( q );
-
-				}
-
-				// rotate around screen-space x-axis
-
-				if ( sphericalDelta.phi ) {
-
-					vec.set( 1, 0, 0 ).applyQuaternion( scope.object.quaternion );
-
-					const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1;
-
-					q.setFromAxisAngle( vec, sphericalDelta.phi * factor );
-
-					scope.object.quaternion.premultiply( q );
-					offset.applyQuaternion( q );
-
-				}
-
-				offset.multiplyScalar( scale );
-				offset.clampLength( scope.minDistance, scope.maxDistance );
-
-			} else {
-
-				// rotate offset to "y-axis-is-up" space
-				offset.applyQuaternion( quat );
-
-				if ( scope.autoRotate && state === STATE.NONE ) {
-
-					rotateLeft( getAutoRotationAngle() );
-
-				}
-
-				spherical.setFromVector3( offset );
-
-				if ( scope.enableDamping ) {
-
-					spherical.theta += sphericalDelta.theta * scope.dampingFactor;
-					spherical.phi += sphericalDelta.phi * scope.dampingFactor;
-
-				} else {
-
-					spherical.theta += sphericalDelta.theta;
-					spherical.phi += sphericalDelta.phi;
-
-				}
-
-				// restrict theta to be between desired limits
-				spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) );
-
-				// restrict phi to be between desired limits
-				spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
-
-				spherical.makeSafe();
-
-				spherical.radius *= scale;
-
-				// restrict radius to be between desired limits
-				spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
-
-				offset.setFromSpherical( spherical );
-
-				// rotate offset back to "camera-up-vector-is-up" space
-				offset.applyQuaternion( quatInverse );
-
-			}
-
-			// move target to panned location
-
-			if ( scope.enableDamping === true ) {
-
-				scope.target.addScaledVector( panOffset, scope.dampingFactor );
-
-			} else {
-
-				scope.target.add( panOffset );
-
-			}
-
-			position.copy( scope.target ).add( offset );
-
-			if ( scope.trackball === false ) {
-
-				scope.object.lookAt( scope.target );
-
-			}
-
-			if ( scope.enableDamping === true ) {
-
-				sphericalDelta.theta *= ( 1 - scope.dampingFactor );
-				sphericalDelta.phi *= ( 1 - scope.dampingFactor );
-
-				panOffset.multiplyScalar( 1 - scope.dampingFactor );
-
-			} else {
-
-				sphericalDelta.set( 0, 0, 0 );
-
-				panOffset.set( 0, 0, 0 );
-
-			}
-
-			scale = 1;
-
-			// update condition is:
-			// min(camera displacement, camera rotation in radians)^2 > EPS
-			// using small-angle approximation cos(x/2) = 1 - x^2 / 8
-
-			if ( zoomChanged ||
-				lastPosition.distanceToSquared( scope.object.position ) > EPS ||
-				8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
-
-				scope.dispatchEvent( changeEvent );
-
-				lastPosition.copy( scope.object.position );
-				lastQuaternion.copy( scope.object.quaternion );
-				zoomChanged = false;
-
-				return true;
-
-			}
-
-			return false;
-
-		};
-
-	}();
-
-	this.dispose = function () {
-
-		scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
-		scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
-		scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
-
-		scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
-		scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
-		scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
-
-		document.removeEventListener( 'mousemove', onMouseMove, false );
-		document.removeEventListener( 'mouseup', onMouseUp, false );
-
-		scope.domElement.removeEventListener( 'keydown', onKeyDown, false );
-
-		//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
-
-	};
-
-	//
-	// internals
-	//
-
-	var scope = this;
-
-	var changeEvent = { type: 'change' };
-	var startEvent = { type: 'start' };
-	var endEvent = { type: 'end' };
-
-	var STATE = {
-		NONE: - 1,
-		ROTATE: 0,
-		DOLLY: 1,
-		PAN: 2,
-		TOUCH_ROTATE: 3,
-		TOUCH_PAN: 4,
-		TOUCH_DOLLY_PAN: 5,
-		TOUCH_DOLLY_ROTATE: 6
-	};
-
-	var state = STATE.NONE;
-
-	var EPS = 0.000001;
-
-	// current position in spherical coordinates
-	var spherical = new Spherical();
-	var sphericalDelta = new Spherical();
-
-	var scale = 1;
-	var panOffset = new Vector3();
-	var zoomChanged = false;
-
-	var rotateStart = new Vector2();
-	var rotateEnd = new Vector2();
-	var rotateDelta = new Vector2();
-
-	var panStart = new Vector2();
-	var panEnd = new Vector2();
-	var panDelta = new Vector2();
-
-	var dollyStart = new Vector2();
-	var dollyEnd = new Vector2();
-	var dollyDelta = new Vector2();
-
-	function getAutoRotationAngle() {
-
-		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
-
-	}
-
-	function getZoomScale() {
-
-		return Math.pow( 0.95, scope.zoomSpeed );
-
-	}
-
-	function rotateLeft( angle ) {
-
-		sphericalDelta.theta -= angle;
-
-	}
-
-	function rotateUp( angle ) {
-
-		sphericalDelta.phi -= angle;
-
-	}
-
-	var panLeft = function () {
-
-		var v = new Vector3();
-
-		return function panLeft( distance, objectMatrix ) {
-
-			v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
-			v.multiplyScalar( - distance );
-
-			panOffset.add( v );
-
-		};
-
-	}();
-
-	var panUp = function () {
-
-		var v = new Vector3();
-
-		return function panUp( distance, objectMatrix ) {
-
-			if ( scope.screenSpacePanning === true ) {
-
-				v.setFromMatrixColumn( objectMatrix, 1 );
-
-			} else {
-
-				v.setFromMatrixColumn( objectMatrix, 0 );
-				v.crossVectors( scope.object.up, v );
-
-			}
-
-			v.multiplyScalar( distance );
-
-			panOffset.add( v );
-
-		};
-
-	}();
-
-	// deltaX and deltaY are in pixels; right and down are positive
-	var pan = function () {
-
-		var offset = new Vector3();
-
-		return function pan( deltaX, deltaY ) {
-
-			var element = scope.domElement;
-
-			if ( scope.object.isPerspectiveCamera ) {
-
-				// perspective
-				var position = scope.object.position;
-				offset.copy( position ).sub( scope.target );
-				var targetDistance = offset.length();
-
-				// half of the fov is center to top of screen
-				targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
-
-				// we use only clientHeight here so aspect ratio does not distort speed
-				panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
-				panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
-
-			} else if ( scope.object.isOrthographicCamera ) {
-
-				// orthographic
-				panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
-				panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
-
-			} else {
-
-				// camera neither orthographic nor perspective
-				console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - pan disabled.' );
-				scope.enablePan = false;
-
-			}
-
-		};
-
-	}();
-
-	function dollyIn( dollyScale ) {
-
-		if ( scope.object.isPerspectiveCamera ) {
-
-			scale /= dollyScale;
-
-		} else if ( scope.object.isOrthographicCamera ) {
-
-			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
-			scope.object.updateProjectionMatrix();
-			zoomChanged = true;
-
-		} else {
-
-			console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-			scope.enableZoom = false;
-
-		}
-
-	}
-
-	function dollyOut( dollyScale ) {
-
-		if ( scope.object.isPerspectiveCamera ) {
-
-			scale *= dollyScale;
-
-		} else if ( scope.object.isOrthographicCamera ) {
-
-			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
-			scope.object.updateProjectionMatrix();
-			zoomChanged = true;
-
-		} else {
-
-			console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-			scope.enableZoom = false;
-
-		}
-
-	}
-
-	//
-	// event callbacks - update the object state
-	//
-
-	function handleMouseDownRotate( event ) {
-
-		rotateStart.set( event.clientX, event.clientY );
-
-	}
-
-	function handleMouseDownDolly( event ) {
-
-		dollyStart.set( event.clientX, event.clientY );
-
-	}
-
-	function handleMouseDownPan( event ) {
-
-		panStart.set( event.clientX, event.clientY );
-
-	}
-
-	function handleMouseMoveRotate( event ) {
-
-		rotateEnd.set( event.clientX, event.clientY );
-
-		rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-
-		var element = scope.domElement;
-
-		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-
-		rotateStart.copy( rotateEnd );
-
-		scope.update();
-
-	}
-
-	function handleMouseMoveDolly( event ) {
-
-		dollyEnd.set( event.clientX, event.clientY );
-
-		dollyDelta.subVectors( dollyEnd, dollyStart );
-
-		if ( dollyDelta.y > 0 ) {
-
-			dollyIn( getZoomScale() );
-
-		} else if ( dollyDelta.y < 0 ) {
-
-			dollyOut( getZoomScale() );
-
-		}
-
-		dollyStart.copy( dollyEnd );
-
-		scope.update();
-
-	}
-
-	function handleMouseMovePan( event ) {
-
-		panEnd.set( event.clientX, event.clientY );
-
-		panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-
-		pan( panDelta.x, panDelta.y );
-
-		panStart.copy( panEnd );
-
-		scope.update();
-
-	}
-
-	function handleMouseUp( /*event*/ ) {
-
-		// no-op
-
-	}
-
-	function handleMouseWheel( event ) {
-
-		if ( event.deltaY < 0 ) {
-
-			dollyOut( getZoomScale() );
-
-		} else if ( event.deltaY > 0 ) {
-
-			dollyIn( getZoomScale() );
-
-		}
-
-		scope.update();
-
-	}
-
-	function handleKeyDown( event ) {
-
-		var needsUpdate = false;
-
-		switch ( event.keyCode ) {
-
-			case scope.keys.UP:
-				pan( 0, scope.keyPanSpeed );
-				needsUpdate = true;
-				break;
-
-			case scope.keys.BOTTOM:
-				pan( 0, - scope.keyPanSpeed );
-				needsUpdate = true;
-				break;
-
-			case scope.keys.LEFT:
-				pan( scope.keyPanSpeed, 0 );
-				needsUpdate = true;
-				break;
-
-			case scope.keys.RIGHT:
-				pan( - scope.keyPanSpeed, 0 );
-				needsUpdate = true;
-				break;
-
-		}
-
-		if ( needsUpdate ) {
-
-			// prevent the browser from scrolling on cursor keys
-			event.preventDefault();
-
-			scope.update();
-
-		}
-
-
-	}
-
-	function handleTouchStartRotate( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
-
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-
-			rotateStart.set( x, y );
-
-		}
-
-	}
-
-	function handleTouchStartPan( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
-
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-
-			panStart.set( x, y );
-
-		}
-
-	}
-
-	function handleTouchStartDolly( event ) {
-
-		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-		var distance = Math.sqrt( dx * dx + dy * dy );
-
-		dollyStart.set( 0, distance );
-
-	}
-
-	function handleTouchStartDollyPan( event ) {
-
-		if ( scope.enableZoom ) handleTouchStartDolly( event );
-
-		if ( scope.enablePan ) handleTouchStartPan( event );
-
-	}
-
-	function handleTouchStartDollyRotate( event ) {
-
-		if ( scope.enableZoom ) handleTouchStartDolly( event );
-
-		if ( scope.enableRotate ) handleTouchStartRotate( event );
-
-	}
-
-	function handleTouchMoveRotate( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
-
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-
-			rotateEnd.set( x, y );
-
-		}
-
-		rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-
-		var element = scope.domElement;
-
-		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-
-		rotateStart.copy( rotateEnd );
-
-	}
-
-	function handleTouchMovePan( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
-
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-
-			panEnd.set( x, y );
-
-		}
-
-		panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-
-		pan( panDelta.x, panDelta.y );
-
-		panStart.copy( panEnd );
-
-	}
-
-	function handleTouchMoveDolly( event ) {
-
-		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-		var distance = Math.sqrt( dx * dx + dy * dy );
-
-		dollyEnd.set( 0, distance );
-
-		dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
-
-		dollyIn( dollyDelta.y );
-
-		dollyStart.copy( dollyEnd );
-
-	}
-
-	function handleTouchMoveDollyPan( event ) {
-
-		if ( scope.enableZoom ) handleTouchMoveDolly( event );
-
-		if ( scope.enablePan ) handleTouchMovePan( event );
-
-	}
-
-	function handleTouchMoveDollyRotate( event ) {
-
-		if ( scope.enableZoom ) handleTouchMoveDolly( event );
-
-		if ( scope.enableRotate ) handleTouchMoveRotate( event );
-
-	}
-
-	function handleTouchEnd( /*event*/ ) {
-
-		// no-op
-
-	}
-
-	//
-	// event handlers - FSM: listen for events and reset state
-	//
-
-	function onMouseDown( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		// Prevent the browser from scrolling.
-
-		event.preventDefault();
-
-		// Manually set the focus since calling preventDefault above
-		// prevents the browser from setting it automatically.
-
-		scope.domElement.focus ? scope.domElement.focus() : window.focus();
-
-		var mouseAction;
-
-		switch ( event.button ) {
-
-			case 0:
-
-				mouseAction = scope.mouseButtons.LEFT;
-				break;
-
-			case 1:
-
-				mouseAction = scope.mouseButtons.MIDDLE;
-				break;
-
-			case 2:
-
-				mouseAction = scope.mouseButtons.RIGHT;
-				break;
-
-			default:
-
-				mouseAction = - 1;
-
-		}
-
-		switch ( mouseAction ) {
-
-			case MOUSE.DOLLY:
-
-				if ( scope.enableZoom === false ) return;
-
-				handleMouseDownDolly( event );
-
-				state = STATE.DOLLY;
-
-				break;
-
-			case MOUSE.ROTATE:
-
-				if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
-					if ( scope.enablePan === false ) return;
-
-					handleMouseDownPan( event );
-
-					state = STATE.PAN;
-
-				} else {
-
-					if ( scope.enableRotate === false ) return;
-
-					handleMouseDownRotate( event );
-
-					state = STATE.ROTATE;
-
-				}
-
-				break;
-
-			case MOUSE.PAN:
-
-				if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
-					if ( scope.enableRotate === false ) return;
-
-					handleMouseDownRotate( event );
-
-					state = STATE.ROTATE;
-
-				} else {
-
-					if ( scope.enablePan === false ) return;
-
-					handleMouseDownPan( event );
-
-					state = STATE.PAN;
-
-				}
-
-				break;
-
-			default:
-
-				state = STATE.NONE;
-
-		}
-
-		if ( state !== STATE.NONE ) {
-
-			document.addEventListener( 'mousemove', onMouseMove, false );
-			document.addEventListener( 'mouseup', onMouseUp, false );
-
-			scope.dispatchEvent( startEvent );
-
-		}
-
-	}
-
-	function onMouseMove( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault();
-
-		switch ( state ) {
-
-			case STATE.ROTATE:
-
-				if ( scope.enableRotate === false ) return;
-
-				handleMouseMoveRotate( event );
-
-				break;
-
-			case STATE.DOLLY:
-
-				if ( scope.enableZoom === false ) return;
-
-				handleMouseMoveDolly( event );
-
-				break;
-
-			case STATE.PAN:
-
-				if ( scope.enablePan === false ) return;
-
-				handleMouseMovePan( event );
-
-				break;
-
-		}
-
-	}
-
-	function onMouseUp( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		handleMouseUp( event );
-
-		document.removeEventListener( 'mousemove', onMouseMove, false );
-		document.removeEventListener( 'mouseup', onMouseUp, false );
-
-		scope.dispatchEvent( endEvent );
-
-		state = STATE.NONE;
-
-	}
-
-	function onMouseWheel( event ) {
-
-		if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
-
-		event.preventDefault();
-		event.stopPropagation();
-
-		scope.dispatchEvent( startEvent );
-
-		handleMouseWheel( event );
-
-		scope.dispatchEvent( endEvent );
-
-	}
-
-	function onKeyDown( event ) {
-
-		if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
-
-		handleKeyDown( event );
-
-	}
-
-	function onTouchStart( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault();
-
-		switch ( event.touches.length ) {
-
-			case 1:
-
-				switch ( scope.touches.ONE ) {
-
-					case TOUCH.ROTATE:
-
-						if ( scope.enableRotate === false ) return;
-
-						handleTouchStartRotate( event );
-
-						state = STATE.TOUCH_ROTATE;
-
-						break;
-
-					case TOUCH.PAN:
-
-						if ( scope.enablePan === false ) return;
-
-						handleTouchStartPan( event );
-
-						state = STATE.TOUCH_PAN;
-
-						break;
-
-					default:
-
-						state = STATE.NONE;
-
-				}
-
-				break;
-
-			case 2:
-
-				switch ( scope.touches.TWO ) {
-
-					case TOUCH.DOLLY_PAN:
-
-						if ( scope.enableZoom === false && scope.enablePan === false ) return;
-
-						handleTouchStartDollyPan( event );
-
-						state = STATE.TOUCH_DOLLY_PAN;
-
-						break;
-
-					case TOUCH.DOLLY_ROTATE:
-
-						if ( scope.enableZoom === false && scope.enableRotate === false ) return;
-
-						handleTouchStartDollyRotate( event );
-
-						state = STATE.TOUCH_DOLLY_ROTATE;
-
-						break;
-
-					default:
-
-						state = STATE.NONE;
-
-				}
-
-				break;
-
-			default:
-
-				state = STATE.NONE;
-
-		}
-
-		if ( state !== STATE.NONE ) {
-
-			scope.dispatchEvent( startEvent );
-
-		}
-
-	}
-
-	function onTouchMove( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault();
-		event.stopPropagation();
-
-		switch ( state ) {
-
-			case STATE.TOUCH_ROTATE:
-
-				if ( scope.enableRotate === false ) return;
-
-				handleTouchMoveRotate( event );
-
-				scope.update();
-
-				break;
-
-			case STATE.TOUCH_PAN:
-
-				if ( scope.enablePan === false ) return;
-
-				handleTouchMovePan( event );
-
-				scope.update();
-
-				break;
-
-			case STATE.TOUCH_DOLLY_PAN:
-
-				if ( scope.enableZoom === false && scope.enablePan === false ) return;
-
-				handleTouchMoveDollyPan( event );
-
-				scope.update();
-
-				break;
-
-			case STATE.TOUCH_DOLLY_ROTATE:
-
-				if ( scope.enableZoom === false && scope.enableRotate === false ) return;
-
-				handleTouchMoveDollyRotate( event );
-
-				scope.update();
-
-				break;
-
-			default:
-
-				state = STATE.NONE;
-
-		}
-
-	}
-
-	function onTouchEnd( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		handleTouchEnd( event );
-
-		scope.dispatchEvent( endEvent );
-
-		state = STATE.NONE;
-
-	}
-
-	function onContextMenu( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault();
-
-	}
-
-	//
-
-	scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
-
-	scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
-	scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
-
-	scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
-	scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
-	scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
-
-	scope.domElement.addEventListener( 'keydown', onKeyDown, false );
-
-	// make sure element can receive keys.
-
-	if ( scope.domElement.tabIndex === - 1 ) {
-
-		scope.domElement.tabIndex = 0;
-
-	}
-
-	// force an update at start
-
-	this.object.lookAt( scope.target );
-	this.update();
-	this.saveState();
-
-};
-
-CameraControls.prototype = Object.create( EventDispatcher.prototype );
-CameraControls.prototype.constructor = CameraControls;
-
-
-// OrbitControls maintains the "up" direction, camera.up (+Y by default).
-//
-//    Orbit - left mouse / touch: one-finger move
-//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
-
-var OrbitControls = function ( object, domElement ) {
-
-	CameraControls.call( this, object, domElement );
-
-	this.mouseButtons.LEFT = MOUSE.ROTATE;
-	this.mouseButtons.RIGHT = MOUSE.PAN;
-
-	this.touches.ONE = TOUCH.ROTATE;
-	this.touches.TWO = TOUCH.DOLLY_PAN;
-
-};
-
-OrbitControls.prototype = Object.create( EventDispatcher.prototype );
-OrbitControls.prototype.constructor = OrbitControls;
-
-
-// MapControls maintains the "up" direction, camera.up (+Y by default)
-//
-//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
-//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-//    Pan - left mouse, or left right + ctrl/meta/shiftKey, or arrow keys / touch: one-finger move
-
-
-var MapControls = function ( object, domElement ) {
-
-	CameraControls.call( this, object, domElement );
-
-	this.mouseButtons.LEFT = MOUSE.PAN;
-	this.mouseButtons.RIGHT = MOUSE.ROTATE;
-
-	this.touches.ONE = TOUCH.PAN;
-	this.touches.TWO = TOUCH.DOLLY_ROTATE;
-
-};
-
-MapControls.prototype = Object.create( EventDispatcher.prototype );
-MapControls.prototype.constructor = MapControls;
-
-
-// TrackballControls allows the camera to rotate over the polls and does not maintain camera.up
-//
-//    Orbit - left mouse / touch: one-finger move
-//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
-
-var TrackballControls = function ( object, domElement ) {
-
-	CameraControls.call( this, object, domElement );
-
-	this.trackball = true;
-	this.screenSpacePanning = true;
-	this.autoRotate = false;
-
-	this.mouseButtons.LEFT = MOUSE.ROTATE;
-	this.mouseButtons.RIGHT = MOUSE.PAN;
-
-	this.touches.ONE = TOUCH.ROTATE;
-	this.touches.TWO = TOUCH.DOLLY_PAN;
-
-};
-
-TrackballControls.prototype = Object.create( EventDispatcher.prototype );
-TrackballControls.prototype.constructor = TrackballControls;
-
-
-export { CameraControls, OrbitControls, MapControls, TrackballControls };

+ 29 - 16
examples/jsm/exporters/GLTFExporter.js

@@ -4,6 +4,7 @@ import {
 	DoubleSide,
 	InterpolateDiscrete,
 	InterpolateLinear,
+	LinearEncoding,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	LinearMipmapNearestFilter,
@@ -18,6 +19,7 @@ import {
 	RepeatWrapping,
 	Scene,
 	Source,
+	sRGBEncoding,
 	Vector3
 } from 'three';
 
@@ -343,27 +345,15 @@ function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) {
 
 }
 
-let cachedCanvas = null;
-
 function getCanvas() {
 
-	if ( cachedCanvas ) {
-
-		return cachedCanvas;
-
-	}
-
 	if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
 
-		cachedCanvas = new OffscreenCanvas( 1, 1 );
-
-	} else {
-
-		cachedCanvas = document.createElement( 'canvas' );
+		return new OffscreenCanvas( 1, 1 );
 
 	}
 
-	return cachedCanvas;
+	return document.createElement( 'canvas' );
 
 }
 
@@ -744,6 +734,26 @@ class GLTFWriter {
 
 		if ( metalnessMap === roughnessMap ) return metalnessMap;
 
+		function getEncodingConversion( map ) {
+
+			if ( map.encoding === sRGBEncoding ) {
+
+				return function SRGBToLinear( c ) {
+
+					return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+				}
+
+			}
+
+			return function LinearToLinear( c ) {
+
+				return c;
+
+			}
+
+		}
+
 		console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 
 		const metalness = metalnessMap?.image;
@@ -766,11 +776,12 @@ class GLTFWriter {
 
 			context.drawImage( metalness, 0, 0, width, height );
 
+			const convert = getEncodingConversion( metalnessMap );
 			const data = context.getImageData( 0, 0, width, height ).data;
 
 			for ( let i = 2; i < data.length; i += 4 ) {
 
-				composite.data[ i ] = data[ i ];
+				composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
 
 			}
 
@@ -780,11 +791,12 @@ class GLTFWriter {
 
 			context.drawImage( roughness, 0, 0, width, height );
 
+			const convert = getEncodingConversion( roughnessMap );
 			const data = context.getImageData( 0, 0, width, height ).data;
 
 			for ( let i = 1; i < data.length; i += 4 ) {
 
-				composite.data[ i ] = data[ i ];
+				composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
 
 			}
 
@@ -799,6 +811,7 @@ class GLTFWriter {
 		const texture = reference.clone();
 
 		texture.source = new Source( canvas );
+		texture.encoding = LinearEncoding;
 
 		return texture;
 

+ 29 - 29
examples/jsm/exporters/KTX2Exporter.js

@@ -39,46 +39,46 @@ import {
 	VK_FORMAT_R8G8_UNORM,
 	VK_FORMAT_R8G8B8A8_SRGB,
 	VK_FORMAT_R8G8B8A8_UNORM,
- } from '../libs/ktx-parse.module.js';
+} from '../libs/ktx-parse.module.js';
 
 const VK_FORMAT_MAP = {
 
-	[RGBAFormat]: {
-		[FloatType]: {
-			[LinearEncoding]: VK_FORMAT_R32G32B32A32_SFLOAT,
+	[ RGBAFormat ]: {
+		[ FloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R32G32B32A32_SFLOAT,
 		},
-		[HalfFloatType]: {
-			[LinearEncoding]: VK_FORMAT_R16G16B16A16_SFLOAT,
+		[ HalfFloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R16G16B16A16_SFLOAT,
 		},
-		[UnsignedByteType]: {
-			[LinearEncoding]: VK_FORMAT_R8G8B8A8_UNORM,
-			[sRGBEncoding]: VK_FORMAT_R8G8B8A8_SRGB,
+		[ UnsignedByteType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R8G8B8A8_UNORM,
+			[ sRGBEncoding ]: VK_FORMAT_R8G8B8A8_SRGB,
 		},
 	},
 
-	[RGFormat]: {
-		[FloatType]: {
-			[LinearEncoding]: VK_FORMAT_R32G32_SFLOAT,
+	[ RGFormat ]: {
+		[ FloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R32G32_SFLOAT,
 		},
-		[HalfFloatType]: {
-			[LinearEncoding]: VK_FORMAT_R16G16_SFLOAT,
+		[ HalfFloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R16G16_SFLOAT,
 		},
-		[UnsignedByteType]: {
-			[LinearEncoding]: VK_FORMAT_R8G8_UNORM,
-			[sRGBEncoding]: VK_FORMAT_R8G8_SRGB,
+		[ UnsignedByteType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R8G8_UNORM,
+			[ sRGBEncoding ]: VK_FORMAT_R8G8_SRGB,
 		},
 	},
 
-	[RedFormat]: {
-		[FloatType]: {
-			[LinearEncoding]: VK_FORMAT_R32_SFLOAT,
+	[ RedFormat ]: {
+		[ FloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R32_SFLOAT,
 		},
-		[HalfFloatType]: {
-			[LinearEncoding]: VK_FORMAT_R16_SFLOAT,
+		[ HalfFloatType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R16_SFLOAT,
 		},
-		[UnsignedByteType]: {
-			[LinearEncoding]: VK_FORMAT_R8_SRGB,
-			[sRGBEncoding]: VK_FORMAT_R8_UNORM,
+		[ UnsignedByteType ]: {
+			[ LinearEncoding ]: VK_FORMAT_R8_SRGB,
+			[ sRGBEncoding ]: VK_FORMAT_R8_UNORM,
 		},
 	},
 
@@ -195,8 +195,8 @@ export class KTX2Exporter {
 				channelType: channelType,
 				bitOffset: i * array.BYTES_PER_ELEMENT,
 				bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
-				samplePosition: [0, 0, 0, 0],
-				sampleLower: texture.type === UnsignedByteType ? 0 : -1,
+				samplePosition: [ 0, 0, 0, 0 ],
+				sampleLower: texture.type === UnsignedByteType ? 0 : - 1,
 				sampleUpper: texture.type === UnsignedByteType ? 255 : 1,
 
 			} );
@@ -214,7 +214,7 @@ export class KTX2Exporter {
 
 		//
 
-		container.keyValue['KTXwriter'] = `three.js ${ REVISION }`;
+		container.keyValue[ 'KTXwriter' ] = `three.js ${ REVISION }`;
 
 		//
 
@@ -250,7 +250,7 @@ function toDataTexture( renderer, rtt ) {
 
 	renderer.readRenderTargetPixels( rtt, 0, 0, rtt.width, rtt.height, view );
 
-	return new DataTexture( view, rtt.width, rt.height, rtt.texture.format, rtt.texture.type );
+	return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );
 
 }
 

+ 7 - 0
examples/jsm/exporters/USDZExporter.js

@@ -407,6 +407,13 @@ function buildMaterial( material, textures ) {
 
 	}
 
+
+	if ( material.side === THREE.DoubleSide ) {
+
+		console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material );
+
+	}
+
 	if ( material.map !== null ) {
 
 		inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );

+ 9 - 9
examples/jsm/interactive/HTMLMesh.js

@@ -203,7 +203,7 @@ function html2canvas( element ) {
 
 	}
 
-	function buildRectPath(x, y, w, h, r) {
+	function buildRectPath( x, y, w, h, r ) {
 
 		if ( w < 2 * r ) r = w / 2;
 		if ( h < 2 * r ) r = h / 2;
@@ -356,7 +356,7 @@ function html2canvas( element ) {
 				const luminance = Math.sqrt( 0.299 * ( color.r ** 2 ) + 0.587 * ( color.g ** 2 ) + 0.114 * ( color.b ** 2 ) );
 				const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
 
-				if ( element.type  === 'radio' ) {
+				if ( element.type === 'radio' ) {
 
 					buildRectPath( x, y, width, height, height );
 
@@ -380,7 +380,7 @@ function html2canvas( element ) {
 
 				}
 
-				if ( element.type  === 'checkbox' ) {
+				if ( element.type === 'checkbox' ) {
 
 					buildRectPath( x, y, width, height, 2 );
 
@@ -395,7 +395,7 @@ function html2canvas( element ) {
 						const currentTextAlign = context.textAlign;
 
 						context.textAlign = 'center';
-						
+
 						const properties = {
 							color: accentTextColor,
 							fontFamily: style.fontFamily,
@@ -411,10 +411,10 @@ function html2canvas( element ) {
 
 				}
 
-				if ( element.type  === 'range' ) {
+				if ( element.type === 'range' ) {
 
-					const [min,max,value] = ['min','max','value'].map(property => parseFloat(element[property]));
-					const position = ((value-min)/(max-min)) * (width - height);
+					const [ min, max, value ] = [ 'min', 'max', 'value' ].map( property => parseFloat( element[ property ] ) );
+					const position = ( ( value - min ) / ( max - min ) ) * ( width - height );
 
 					buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
 					context.fillStyle = accentTextColor;
@@ -423,7 +423,7 @@ function html2canvas( element ) {
 					context.fill();
 					context.stroke();
 
-					buildRectPath( x, y + ( height / 4 ),position + ( height / 2 ), height / 2, height / 4 );
+					buildRectPath( x, y + ( height / 4 ), position + ( height / 2 ), height / 2, height / 4 );
 					context.fillStyle = accentColor;
 					context.fill();
 
@@ -522,7 +522,7 @@ function htmlevent( element, event, x, y ) {
 
 				element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
 
-				if ( element instanceof HTMLInputElement && element.type  === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
+				if ( element instanceof HTMLInputElement && element.type === 'range' && ( event === 'mousedown' || event === 'click' ) ) {
 
 					const [ min, max ] = [ 'min', 'max' ].map( property => parseFloat( element[ property ] ) );
 

+ 3 - 13
examples/jsm/loaders/EXRLoader.js

@@ -1761,21 +1761,11 @@ class EXRLoader extends DataTextureLoader {
 
 		const parseInt64 = function ( dataView, offset ) {
 
-			let int;
-
-			if ( 'getBigInt64' in DataView.prototype ) {
-
-				int = Number( dataView.getBigInt64( offset.value, true ) );
-
-			} else {
-
-				int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 );
-
-			}
+			const Int64 = Number( dataView.getBigInt64( offset.value, true ) );
 
 			offset.value += ULONG_SIZE;
 
-			return int;
+			return Int64;
 
 		};
 
@@ -2056,7 +2046,7 @@ class EXRLoader extends DataTextureLoader {
 
 			}
 
-			if ( ( spec & ~0x04 ) != 0 ) { // unsupported tiled, deep-image, multi-part
+			if ( ( spec & ~ 0x04 ) != 0 ) { // unsupported tiled, deep-image, multi-part
 
 				console.error( 'EXRHeader:', EXRHeader );
 				throw new Error( 'THREE.EXRLoader: provided file is currently unsupported.' );

+ 1 - 1
examples/jsm/loaders/FontLoader.js

@@ -72,7 +72,7 @@ class Font {
 
 		for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
 
-			Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+			shapes.push( ...paths[ p ].toShapes() );
 
 		}
 

+ 28 - 28
examples/jsm/loaders/GLTFLoader.js

@@ -1955,47 +1955,47 @@ class GLTFCubicSplineInterpolant extends Interpolant {
 
 	}
 
-}
+	interpolate_( i1, t0, t, t1 ) {
 
-GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) {
+		const result = this.resultBuffer;
+		const values = this.sampleValues;
+		const stride = this.valueSize;
 
-	const result = this.resultBuffer;
-	const values = this.sampleValues;
-	const stride = this.valueSize;
+		const stride2 = stride * 2;
+		const stride3 = stride * 3;
 
-	const stride2 = stride * 2;
-	const stride3 = stride * 3;
+		const td = t1 - t0;
 
-	const td = t1 - t0;
+		const p = ( t - t0 ) / td;
+		const pp = p * p;
+		const ppp = pp * p;
 
-	const p = ( t - t0 ) / td;
-	const pp = p * p;
-	const ppp = pp * p;
+		const offset1 = i1 * stride3;
+		const offset0 = offset1 - stride3;
 
-	const offset1 = i1 * stride3;
-	const offset0 = offset1 - stride3;
+		const s2 = - 2 * ppp + 3 * pp;
+		const s3 = ppp - pp;
+		const s0 = 1 - s2;
+		const s1 = s3 - pp + p;
 
-	const s2 = - 2 * ppp + 3 * pp;
-	const s3 = ppp - pp;
-	const s0 = 1 - s2;
-	const s1 = s3 - pp + p;
+		// Layout of keyframe output values for CUBICSPLINE animations:
+		//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
+		for ( let i = 0; i !== stride; i ++ ) {
 
-	// Layout of keyframe output values for CUBICSPLINE animations:
-	//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
-	for ( let i = 0; i !== stride; i ++ ) {
+			const p0 = values[ offset0 + i + stride ]; // splineVertex_k
+			const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
+			const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
+			const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
 
-		const p0 = values[ offset0 + i + stride ]; // splineVertex_k
-		const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
-		const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
-		const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)
+			result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
 
-		result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;
+		}
 
-	}
+		return result;
 
-	return result;
+	}
 
-};
+}
 
 const _q = new Quaternion();
 

+ 162 - 30
examples/jsm/loaders/KTX2Loader.js

@@ -3,8 +3,8 @@
  *
  * KTX 2.0 is a container format for various GPU texture formats. The loader
  * supports Basis Universal GPU textures, which can be quickly transcoded to
- * a wide variety of GPU texture compression formats. While KTX 2.0 also allows
- * other hardware-specific formats, this loader does not yet parse them.
+ * a wide variety of GPU texture compression formats, as well as some
+ * uncompressed DataTexture and Data3DTexture formats.
  *
  * References:
  * - KTX: http://github.khronos.org/KTX-Specification/
@@ -13,28 +13,52 @@
 
 import {
 	CompressedTexture,
+	Data3DTexture,
+	DataTexture,
 	FileLoader,
+	FloatType,
+	HalfFloatType,
 	LinearEncoding,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	Loader,
-	RGBAFormat,
+	RedFormat,
+	RGB_ETC1_Format,
+	RGB_ETC2_Format,
+	RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format,
 	RGBA_ASTC_4x4_Format,
 	RGBA_BPTC_Format,
 	RGBA_ETC2_EAC_Format,
 	RGBA_PVRTC_4BPPV1_Format,
 	RGBA_S3TC_DXT5_Format,
-	RGB_ETC1_Format,
-	RGB_ETC2_Format,
-	RGB_PVRTC_4BPPV1_Format,
-	RGB_S3TC_DXT1_Format,
+	RGBAFormat,
+	RGFormat,
 	sRGBEncoding,
 	UnsignedByteType
 } from 'three';
 import { WorkerPool } from '../utils/WorkerPool.js';
+import * as KTX from '../libs/ktx-parse.module.js';
+
+const {
+	read,
+	KHR_DF_FLAG_ALPHA_PREMULTIPLIED,
+	KHR_DF_TRANSFER_SRGB,
+	VK_FORMAT_UNDEFINED,
+	VK_FORMAT_R16_SFLOAT,
+	VK_FORMAT_R16G16_SFLOAT,
+	VK_FORMAT_R16G16B16A16_SFLOAT,
+	VK_FORMAT_R32_SFLOAT,
+	VK_FORMAT_R32G32_SFLOAT,
+	VK_FORMAT_R32G32B32A32_SFLOAT,
+	VK_FORMAT_R8_SRGB,
+	VK_FORMAT_R8_UNORM,
+	VK_FORMAT_R8G8_SRGB,
+	VK_FORMAT_R8G8_UNORM,
+	VK_FORMAT_R8G8B8A8_SRGB,
+	VK_FORMAT_R8G8B8A8_UNORM,
+} = KTX; // eslint-disable-line no-undef
 
-const KTX2TransferSRGB = 2;
-const KTX2_ALPHA_PREMULTIPLIED = 1;
 const _taskCache = new WeakMap();
 
 let _activeLoaders = 0;
@@ -189,8 +213,6 @@ class KTX2Loader extends Loader {
 		loader.setResponseType( 'arraybuffer' );
 		loader.setWithCredentials( this.withCredentials );
 
-		const texture = new CompressedTexture();
-
 		loader.load( url, ( buffer ) => {
 
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
@@ -203,21 +225,12 @@ class KTX2Loader extends Loader {
 
 			}
 
-			this._createTexture( [ buffer ] )
-				.then( function ( _texture ) {
-
-					texture.copy( _texture );
-					texture.needsUpdate = true;
-
-					if ( onLoad ) onLoad( texture );
-
-				} )
+			this._createTexture( buffer )
+				.then( ( texture ) => onLoad ? onLoad( texture ) : null )
 				.catch( onError );
 
 		}, onProgress, onError );
 
-		return texture;
-
 	}
 
 	_createTextureFrom( transcodeResult ) {
@@ -231,29 +244,39 @@ class KTX2Loader extends Loader {
 		texture.magFilter = LinearFilter;
 		texture.generateMipmaps = false;
 		texture.needsUpdate = true;
-		texture.encoding = dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding;
-		texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED );
+		texture.encoding = dfdTransferFn === KHR_DF_TRANSFER_SRGB ? sRGBEncoding : LinearEncoding;
+		texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED );
 
 		return texture;
 
 	}
 
 	/**
-	 * @param {ArrayBuffer[]} buffers
+	 * @param {ArrayBuffer} buffer
 	 * @param {object?} config
-	 * @return {Promise<CompressedTexture>}
+	 * @return {Promise<CompressedTexture|DataTexture|Data3DTexture>}
 	 */
-	_createTexture( buffers, config = {} ) {
+	_createTexture( buffer, config = {} ) {
+
+		const container = read( new Uint8Array( buffer ) );
+
+		if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) {
+
+			return createDataTexture( container );
+
+		}
+
+		//
 
 		const taskConfig = config;
 		const texturePending = this.init().then( () => {
 
-			return this.workerPool.postMessage( { type: 'transcode', buffers, taskConfig: taskConfig }, buffers );
+			return this.workerPool.postMessage( { type: 'transcode', buffer, taskConfig: taskConfig }, [ buffer ] );
 
 		} ).then( ( e ) => this._createTextureFrom( e.data ) );
 
 		// Cache the task result.
-		_taskCache.set( buffers[ 0 ], { promise: texturePending } );
+		_taskCache.set( buffer, { promise: texturePending } );
 
 		return texturePending;
 
@@ -342,7 +365,7 @@ KTX2Loader.BasisWorker = function () {
 
 					try {
 
-						const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode( message.buffers[ 0 ] );
+						const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode( message.buffer );
 
 						const buffers = [];
 
@@ -588,4 +611,113 @@ KTX2Loader.BasisWorker = function () {
 
 };
 
+//
+// DataTexture and Data3DTexture parsing.
+
+const FORMAT_MAP = {
+
+	[ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat,
+	[ VK_FORMAT_R16G16B16A16_SFLOAT ]: RGBAFormat,
+	[ VK_FORMAT_R8G8B8A8_UNORM ]: RGBAFormat,
+	[ VK_FORMAT_R8G8B8A8_SRGB ]: RGBAFormat,
+
+	[ VK_FORMAT_R32G32_SFLOAT ]: RGFormat,
+	[ VK_FORMAT_R16G16_SFLOAT ]: RGFormat,
+	[ VK_FORMAT_R8G8_UNORM ]: RGFormat,
+	[ VK_FORMAT_R8G8_SRGB ]: RGFormat,
+
+	[ VK_FORMAT_R32_SFLOAT ]: RedFormat,
+	[ VK_FORMAT_R16_SFLOAT ]: RedFormat,
+	[ VK_FORMAT_R8_SRGB ]: RedFormat,
+	[ VK_FORMAT_R8_UNORM ]: RedFormat,
+
+};
+
+const TYPE_MAP = {
+
+	[ VK_FORMAT_R32G32B32A32_SFLOAT ]: FloatType,
+	[ VK_FORMAT_R16G16B16A16_SFLOAT ]: HalfFloatType,
+	[ VK_FORMAT_R8G8B8A8_UNORM ]: UnsignedByteType,
+	[ VK_FORMAT_R8G8B8A8_SRGB ]: UnsignedByteType,
+
+	[ VK_FORMAT_R32G32_SFLOAT ]: FloatType,
+	[ VK_FORMAT_R16G16_SFLOAT ]: HalfFloatType,
+	[ VK_FORMAT_R8G8_UNORM ]: UnsignedByteType,
+	[ VK_FORMAT_R8G8_SRGB ]: UnsignedByteType,
+
+	[ VK_FORMAT_R32_SFLOAT ]: FloatType,
+	[ VK_FORMAT_R16_SFLOAT ]: HalfFloatType,
+	[ VK_FORMAT_R8_SRGB ]: UnsignedByteType,
+	[ VK_FORMAT_R8_UNORM ]: UnsignedByteType,
+
+};
+
+const ENCODING_MAP = {
+
+	[ VK_FORMAT_R8G8B8A8_SRGB ]: sRGBEncoding,
+	[ VK_FORMAT_R8G8_SRGB ]: sRGBEncoding,
+	[ VK_FORMAT_R8_SRGB ]: sRGBEncoding,
+
+};
+
+function createDataTexture( container ) {
+
+	const { vkFormat, pixelWidth, pixelHeight, pixelDepth } = container;
+
+	if ( FORMAT_MAP[ vkFormat ] === undefined ) {
+
+		throw new Error( 'THREE.KTX2Loader: Unsupported vkFormat.' );
+
+	}
+
+	//
+
+	let view;
+
+	const levelData = container.levels[ 0 ].levelData;
+
+	if ( TYPE_MAP[ vkFormat ] === FloatType ) {
+
+		view = new Float32Array(
+
+			levelData.buffer,
+			levelData.byteOffset,
+			levelData.byteLength / Float32Array.BYTES_PER_ELEMENT
+
+		);
+
+	} else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) {
+
+		view = new Uint16Array(
+
+			levelData.buffer,
+			levelData.byteOffset,
+			levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT
+
+		);
+
+	} else {
+
+		view = levelData;
+
+	}
+
+	//
+
+	const texture = pixelDepth === 0
+		? new DataTexture( view, pixelWidth, pixelHeight )
+		: new Data3DTexture( view, pixelWidth, pixelHeight, pixelDepth );
+
+	texture.type = TYPE_MAP[ vkFormat ];
+	texture.format = FORMAT_MAP[ vkFormat ];
+	texture.encoding = ENCODING_MAP[ vkFormat ] || LinearEncoding;
+
+	texture.needsUpdate = true;
+
+	//
+
+	return Promise.resolve( texture );
+
+}
+
 export { KTX2Loader };

+ 136 - 74
examples/jsm/loaders/LDrawLoader.js

@@ -28,10 +28,10 @@ const FINISH_TYPE_METAL = 5;
 
 // State machine to search a subobject path.
 // The LDraw standard establishes these various possible subfolders.
-const FILE_LOCATION_AS_IS = 0;
-const FILE_LOCATION_TRY_PARTS = 1;
-const FILE_LOCATION_TRY_P = 2;
-const FILE_LOCATION_TRY_MODELS = 3;
+const FILE_LOCATION_TRY_PARTS = 0;
+const FILE_LOCATION_TRY_P = 1;
+const FILE_LOCATION_TRY_MODELS = 2;
+const FILE_LOCATION_AS_IS = 3;
 const FILE_LOCATION_TRY_RELATIVE = 4;
 const FILE_LOCATION_TRY_ABSOLUTE = 5;
 const FILE_LOCATION_NOT_FOUND = 6;
@@ -688,7 +688,9 @@ class LDrawParsedCache {
 		result.type = original.type;
 		result.category = original.category;
 		result.keywords = original.keywords;
+		result.author = original.author;
 		result.subobjects = original.subobjects;
+		result.fileName = original.fileName;
 		result.totalFaces = original.totalFaces;
 		result.startingConstructionStep = original.startingConstructionStep;
 		result.materials = original.materials;
@@ -700,7 +702,7 @@ class LDrawParsedCache {
 	async fetchData( fileName ) {
 
 		let triedLowerCase = false;
-		let locationState = FILE_LOCATION_AS_IS;
+		let locationState = FILE_LOCATION_TRY_PARTS;
 		while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 
 			let subobjectURL = fileName;
@@ -743,7 +745,7 @@ class LDrawParsedCache {
 						fileName = fileName.toLowerCase();
 						subobjectURL = fileName;
 						triedLowerCase = true;
-						locationState = FILE_LOCATION_AS_IS;
+						locationState = FILE_LOCATION_TRY_PARTS;
 
 					}
 
@@ -794,6 +796,7 @@ class LDrawParsedCache {
 		let type = 'Model';
 		let category = null;
 		let keywords = null;
+		let author = null;
 		let totalFaces = 0;
 
 		// split into lines
@@ -995,6 +998,12 @@ class LDrawParsedCache {
 
 								break;
 
+							case 'Author:':
+
+								author = lp.getToken();
+
+								break;
+
 							default:
 								// Other meta directives are not implemented
 								break;
@@ -1221,6 +1230,7 @@ class LDrawParsedCache {
 			type,
 			category,
 			keywords,
+			author,
 			subobjects,
 			totalFaces,
 			startingConstructionStep,
@@ -1356,6 +1366,9 @@ class LDrawPartsGeometryCache {
 			const group = new Group();
 			group.userData.category = info.category;
 			group.userData.keywords = info.keywords;
+			group.userData.author = info.author;
+			group.userData.type = info.type;
+			group.userData.fileName = info.fileName;
 			info.group = group;
 
 			const subobjectInfos = await Promise.all( promises );
@@ -1380,6 +1393,7 @@ class LDrawPartsGeometryCache {
 					subobjectGroup.name = subobject.fileName;
 
 					loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
+					subobjectGroup.userData.colorCode = subobject.colorCode;
 
 					group.add( subobjectGroup );
 					continue;
@@ -1473,6 +1487,7 @@ class LDrawPartsGeometryCache {
 			if ( subobject ) {
 
 				loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
+				group.userData.colorCode = subobject.colorCode;
 
 			}
 
@@ -1883,6 +1898,16 @@ class LDrawLoader extends Loader {
 		// The path to load parts from the LDraw parts library from.
 		this.partsLibraryPath = '';
 
+		// Material assigned to not available colors for meshes and edges
+		this.missingColorMaterial = new MeshStandardMaterial( { color: 0xFF00FF, roughness: 0.3, metalness: 0 } );
+		this.missingColorMaterial.name = 'Missing material';
+		this.missingEdgeColorMaterial = new LineBasicMaterial( { color: 0xFF00FF } );
+		this.missingEdgeColorMaterial.name = 'Missing material - Edge';
+		this.missingConditionalEdgeColorMaterial = new LDrawConditionalLineMaterial( { fog: true, color: 0xFF00FF } );
+		this.missingConditionalEdgeColorMaterial.name = 'Missing material - Conditional Edge';
+		this.missingColorMaterial.userData.edgeMaterial = this.missingEdgeColorMaterial;
+		this.missingEdgeColorMaterial.userData.conditionalEdgeMaterial = this.missingConditionalEdgeColorMaterial;
+
 	}
 
 	setPartsLibraryPath( path ) {
@@ -1934,6 +1959,7 @@ class LDrawLoader extends Loader {
 
 					this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 					this.computeConstructionSteps( group );
+					group.userData.fileName = url;
 					onLoad( group );
 
 				} )
@@ -1949,7 +1975,9 @@ class LDrawLoader extends Loader {
 			.parseModel( text, this.materialLibrary )
 			.then( group => {
 
+				this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 				this.computeConstructionSteps( group );
+				group.userData.fileName = '';
 				onLoad( group );
 
 			} );
@@ -2080,8 +2108,11 @@ class LDrawLoader extends Loader {
 				material = loader.getMaterial( colorCode );
 				if ( material === null ) {
 
-					// otherwise throw an error if this is final opportunity to set the material
-					throw new Error( `LDrawLoader: Material properties for code ${ colorCode } not available.` );
+					// otherwise throw a warning if this is final opportunity to set the material
+					console.warn( `LDrawLoader: Material properties for code ${ colorCode } not available.` );
+
+					// And return the 'missing color' material
+					material = loader.missingColorMaterial;
 
 				}
 
@@ -2118,8 +2149,8 @@ class LDrawLoader extends Loader {
 
 	getMainEdgeMaterial() {
 
-		const mainMat = this.getMainMaterial();
-		return mainMat && mainMat.userData ? mainMat.userData.edgeMaterial : null;
+		const mat = this.getMaterial( MAIN_EDGE_COLOUR_CODE );
+		return mat ? mat.userData.edgeMaterial : null;
 
 	}
 
@@ -2162,113 +2193,113 @@ class LDrawLoader extends Loader {
 
 			}
 
-			switch ( token.toUpperCase() ) {
+			if ( ! parseLuminance( token ) ) {
 
-				case 'CODE':
+				switch ( token.toUpperCase() ) {
 
-					code = lineParser.getToken();
-					break;
+					case 'CODE':
 
-				case 'VALUE':
+						code = lineParser.getToken();
+						break;
 
-					color = lineParser.getToken();
-					if ( color.startsWith( '0x' ) ) {
+					case 'VALUE':
 
-						color = '#' + color.substring( 2 );
+						color = lineParser.getToken();
+						if ( color.startsWith( '0x' ) ) {
 
-					} else if ( ! color.startsWith( '#' ) ) {
+							color = '#' + color.substring( 2 );
 
-						throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
+						} else if ( ! color.startsWith( '#' ) ) {
 
-					}
+							throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' );
 
-					break;
+						}
 
-				case 'EDGE':
+						break;
 
-					edgeColor = lineParser.getToken();
-					if ( edgeColor.startsWith( '0x' ) ) {
+					case 'EDGE':
 
-						edgeColor = '#' + edgeColor.substring( 2 );
+						edgeColor = lineParser.getToken();
+						if ( edgeColor.startsWith( '0x' ) ) {
 
-					} else if ( ! edgeColor.startsWith( '#' ) ) {
+							edgeColor = '#' + edgeColor.substring( 2 );
 
-						// Try to see if edge color is a color code
-						edgeMaterial = this.getMaterial( edgeColor );
-						if ( ! edgeMaterial ) {
+						} else if ( ! edgeColor.startsWith( '#' ) ) {
 
-							throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
+							// Try to see if edge color is a color code
+							edgeMaterial = this.getMaterial( edgeColor );
+							if ( ! edgeMaterial ) {
 
-						}
+								throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' );
 
-						// Get the edge material for this triangle material
-						edgeMaterial = edgeMaterial.userData.edgeMaterial;
+							}
 
-					}
+							// Get the edge material for this triangle material
+							edgeMaterial = edgeMaterial.userData.edgeMaterial;
 
-					break;
+						}
 
-				case 'ALPHA':
+						break;
 
-					alpha = parseInt( lineParser.getToken() );
+					case 'ALPHA':
 
-					if ( isNaN( alpha ) ) {
+						alpha = parseInt( lineParser.getToken() );
 
-						throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
+						if ( isNaN( alpha ) ) {
 
-					}
+							throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' );
 
-					alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
+						}
 
-					if ( alpha < 1 ) {
+						alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
 
-						isTransparent = true;
+						if ( alpha < 1 ) {
 
-					}
+							isTransparent = true;
 
-					break;
+						}
 
-				case 'LUMINANCE':
+						break;
 
-					luminance = parseInt( lineParser.getToken() );
+					case 'LUMINANCE':
 
-					if ( isNaN( luminance ) ) {
+						if ( ! parseLuminance( lineParser.getToken() ) ) {
 
-						throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
+							throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' );
 
-					}
+						}
 
-					luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
+						break;
 
-					break;
+					case 'CHROME':
+						finishType = FINISH_TYPE_CHROME;
+						break;
 
-				case 'CHROME':
-					finishType = FINISH_TYPE_CHROME;
-					break;
+					case 'PEARLESCENT':
+						finishType = FINISH_TYPE_PEARLESCENT;
+						break;
 
-				case 'PEARLESCENT':
-					finishType = FINISH_TYPE_PEARLESCENT;
-					break;
+					case 'RUBBER':
+						finishType = FINISH_TYPE_RUBBER;
+						break;
 
-				case 'RUBBER':
-					finishType = FINISH_TYPE_RUBBER;
-					break;
+					case 'MATTE_METALLIC':
+						finishType = FINISH_TYPE_MATTE_METALLIC;
+						break;
 
-				case 'MATTE_METALLIC':
-					finishType = FINISH_TYPE_MATTE_METALLIC;
-					break;
+					case 'METAL':
+						finishType = FINISH_TYPE_METAL;
+						break;
 
-				case 'METAL':
-					finishType = FINISH_TYPE_METAL;
-					break;
+					case 'MATERIAL':
+						// Not implemented
+						lineParser.setToEnd();
+						break;
 
-				case 'MATERIAL':
-					// Not implemented
-					lineParser.setToEnd();
-					break;
+					default:
+						throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
 
-				default:
-					throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' );
+				}
 
 			}
 
@@ -2358,6 +2389,8 @@ class LDrawLoader extends Loader {
 
 			} );
 			edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear();
+			edgeMaterial.userData.conditionalEdgeMaterial.userData.code = code;
+			edgeMaterial.userData.conditionalEdgeMaterial.name = name + ' - Conditional Edge';
 
 		}
 
@@ -2370,6 +2403,35 @@ class LDrawLoader extends Loader {
 
 		return material;
 
+		function parseLuminance( token ) {
+
+			// Returns success
+
+			let lum;
+
+			if ( token.startsWith( 'LUMINANCE' ) ) {
+
+				lum = parseInt( token.substring( 9 ) );
+
+			}
+			else {
+
+				lum = parseInt( token );
+
+			}
+
+			if ( isNaN( lum ) ) {
+
+				return false;
+
+			}
+
+			luminance = Math.max( 0, Math.min( 1, lum / 255 ) );
+
+			return true;
+
+		}
+
 	}
 
 	computeConstructionSteps( model ) {

+ 6 - 14
examples/jsm/loaders/OBJLoader.js

@@ -23,6 +23,7 @@ const _material_library_pattern = /^mtllib /;
 const _material_use_pattern = /^usemtl /;
 // usemap map_name
 const _map_use_pattern = /^usemap /;
+const _face_vertex_data_separator_pattern = /\s+/;
 
 const _vA = new Vector3();
 const _vB = new Vector3();
@@ -503,31 +504,22 @@ class OBJLoader extends Loader {
 		}
 
 		const lines = text.split( '\n' );
-		let line = '', lineFirstChar = '';
-		let lineLength = 0;
 		let result = [];
 
-		// Faster to just trim left side of the line. Use if available.
-		const trimLeft = ( typeof ''.trimLeft === 'function' );
-
 		for ( let i = 0, l = lines.length; i < l; i ++ ) {
 
-			line = lines[ i ];
-
-			line = trimLeft ? line.trimLeft() : line.trim();
-
-			lineLength = line.length;
+			const line = lines[ i ].trimStart();
 
-			if ( lineLength === 0 ) continue;
+			if ( line.length === 0 ) continue;
 
-			lineFirstChar = line.charAt( 0 );
+			const lineFirstChar = line.charAt( 0 );
 
 			// @todo invoke passed in handler if any
 			if ( lineFirstChar === '#' ) continue;
 
 			if ( lineFirstChar === 'v' ) {
 
-				const data = line.split( /\s+/ );
+				const data = line.split( _face_vertex_data_separator_pattern );
 
 				switch ( data[ 0 ] ) {
 
@@ -575,7 +567,7 @@ class OBJLoader extends Loader {
 			} else if ( lineFirstChar === 'f' ) {
 
 				const lineData = line.slice( 1 ).trim();
-				const vertexData = lineData.split( /\s+/ );
+				const vertexData = lineData.split( _face_vertex_data_separator_pattern );
 				const faceVertices = [];
 
 				// Parse the face vertex data into an easy to work with format

+ 16 - 9
examples/jsm/loaders/PCDLoader.js

@@ -309,25 +309,32 @@ class PCDLoader extends Loader {
 
 				if ( offset.x !== undefined ) {
 
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ 0 ] * i, this.littleEndian ) );
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ 1 ] * i, this.littleEndian ) );
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ 2 ] * i, this.littleEndian ) );
+					const xIndex = PCDheader.fields.indexOf( 'x' );
+					const yIndex = PCDheader.fields.indexOf( 'y' );
+					const zIndex = PCDheader.fields.indexOf( 'z' );
+					position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
+					position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
+					position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
 
 				}
 
 				if ( offset.rgb !== undefined ) {
 
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 2 ) / 255.0 );
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 1 ) / 255.0 );
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 0 ) / 255.0 );
+					const rgbIndex = PCDheader.fields.indexOf( 'rgb' );
+					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0 );
+					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0 );
+					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0 );
 
 				}
 
 				if ( offset.normal_x !== undefined ) {
 
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) );
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) );
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) );
+					const xIndex = PCDheader.fields.indexOf( 'normal_x' );
+					const yIndex = PCDheader.fields.indexOf( 'normal_y' );
+					const zIndex = PCDheader.fields.indexOf( 'normal_z' );
+					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) );
+					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) );
+					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) );
 
 				}
 

+ 3 - 3
examples/jsm/loaders/PLYLoader.js

@@ -90,7 +90,7 @@ class PLYLoader extends Loader {
 
 		function parseHeader( data ) {
 
-			const patternHeader = /^ply([\s\S]*)end_header\r?\n/;
+			const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/;
 			let headerText = '';
 			let headerLength = 0;
 			const result = patternHeader.exec( data );
@@ -109,7 +109,7 @@ class PLYLoader extends Loader {
 				objInfo: ''
 			};
 
-			const lines = headerText.split( '\n' );
+			const lines = headerText.split( /\r\n|\r|\n/ );
 			let currentElement;
 
 			function make_ply_element_property( propertValues, propertyNameMapping ) {
@@ -283,7 +283,7 @@ class PLYLoader extends Loader {
 
 			}
 
-			const lines = body.split( '\n' );
+			const lines = body.split( /\r\n|\r|\n/ );
 			let currentElement = 0;
 			let currentElementCount = 0;
 

+ 1 - 1
examples/jsm/loaders/RGBELoader.js

@@ -450,7 +450,7 @@ class RGBELoader extends DataTextureLoader {
 					texture.magFilter = LinearFilter;
 					texture.generateMipmaps = false;
 					texture.flipY = true;
-			
+
 					break;
 
 			}

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