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>
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
 		<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>
 		</p>
 
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>
 		<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_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<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>
 		</p>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>

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

@@ -52,7 +52,7 @@
 		<h2>Properties</h2>
 		<h2>Properties</h2>
 		<p>See the base [page:CurvePath] class for common properties.</p>
 		<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>
 		<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.
 			Disposes of the internally-created [page:Line.material material] and [page:Line.geometry geometry] used by this helper.
 		</p>
 		</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>
 		<h3>[method:undefined update]()</h3>
 		<p>Updates the helper based on the projectionMatrix of the camera.</p>
 		<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 );
 		scene.add( light );
 		</code>
 		</code>
 
 
-		<h2>Examples</h2>
-		<p>
-			[example:webgl_animation_skinning_blending animation / skinning / blending ]
-		</p>
-
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
 
 
 		<h3>[name]( [param:Integer color], [param:Float intensity] )</h3>
 		<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>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float reflectivity]</h3>
 		<p>
 		<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>
 		<p>Whether the material is affected by fog. Default is `false`.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Boolean wireframe]</h3>
 		<p>Render geometry as wireframe. Default is false (i.e. render as smooth shaded).</p>
 		<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>
 		<p>Whether the material is affected by fog. Default is `false`.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float nearDistance]</h3>
 		<p>
 		<p>

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

@@ -120,7 +120,10 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float reflectivity]</h3>
 		<p>How much the environment map affects the surface; also see [page:.combine].</p>
 		<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>
 		<p>Whether the material is affected by fog. Default is `true`.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture matcap]</h3>
 		<p>The matcap map. Default is null.</p>
 		<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>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture normalMap]</h3>
 		<p>
 		<p>

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

@@ -192,7 +192,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float metalness]</h3>
 		<p>
 		<p>

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

@@ -134,7 +134,11 @@
 		<p>Intensity of the baked light. Default is 1.</p>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture normalMap]</h3>
 		<p>
 		<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>
 		<p>Whether the material is affected by fog. Default is `true`.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Number size]</h3>
 		<p>Defines the size of the points in pixels. Default is 1.0.<br/>
 		<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>
 		<h2>Examples</h2>
 		<p>
 		<p>
-			[example:webgl_raycast_sprite WebGL / raycast / sprite]<br />
+			[example:webgl_raycaster_sprite WebGL / raycast / sprite]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:svg_sandbox SVG / sandbox]
 			[example:svg_sandbox SVG / sandbox]
 		</p>
 		</p>
@@ -71,7 +71,10 @@
 		</p>
 		</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Radians rotation]</h3>
 		<p>The rotation of the sprite in radians. Default is 0.</p>
 		<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 />
 		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].
 		See the discussion [link:https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199 here].
 		</p>
 		</p>
 
 

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

@@ -25,7 +25,7 @@
 		<p>
 		<p>
 		[page:Float width] - The width of the renderTarget. <br />
 		[page:Float width] - The width of the renderTarget. <br />
 		[page:Float height] - The height 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.
 		texture and depthBuffer/stencilBuffer booleans.
 
 
 		For an explanation of the texture parameters see [page:Texture Texture]. The following are
 		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 />
 		Default is null.<br />
 
 
 		[page:String precision] - Shader precision. Can be `"highp"`, `"mediump"` or `"lowp"`.
 		[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 />
 		[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>
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
 		<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>
 		</p>
 
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>
 		<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_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<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>
 		</p>
 
 
 		<h2>생성자</h2>
 		<h2>생성자</h2>

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

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

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

@@ -130,7 +130,8 @@
 
 
 		<h3>[property:Object morphAttributes]</h3>
 		<h3>[property:Object morphAttributes]</h3>
 		<p>
 		<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>
 		</p>
 
 
 		<h3>[property:Boolean morphTargetsRelative]</h3>
 		<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_interactive_raycasting_points Raycasting to Points]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_geometry_terrain_raycast Terrain raycasting]<br />
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]<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>
 		</p>
 
 
 
 

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

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

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

@@ -63,12 +63,18 @@
 		</p>
 		</p>
 
 
 
 
-
-
-
 		<h2>方法</h2>
 		<h2>方法</h2>
 		<p>请到基类 [page:LineSegments] 页面查看公共方法.</p>
 		<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>
 		<h3>[method:undefined update]()</h3>
 		<p>基于相机的投影矩阵更新辅助对象.</p>
 		<p>基于相机的投影矩阵更新辅助对象.</p>

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

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

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

@@ -83,7 +83,10 @@
 		<p>烘焙光的强度。默认值为1。</p>
 		<p>烘焙光的强度。默认值为1。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float reflectivity]</h3>
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。

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

@@ -68,7 +68,10 @@
 		</p>
 		</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Boolean wireframe]</h3>
 		<p> 将几何体渲染为线框。默认值为*false*(即渲染为平滑着色)。</p>
 		<p> 将几何体渲染为线框。默认值为*false*(即渲染为平滑着色)。</p>

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

@@ -79,7 +79,10 @@
 		</p>
 		</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float nearDistance]</h3>
 		<p>
 		<p>

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

@@ -102,7 +102,10 @@
 		<p>烘焙光的强度。默认值为1。</p>
 		<p>烘焙光的强度。默认值为1。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float reflectivity]</h3>
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。</p>
 		<p> 环境贴图对表面的影响程度; 见[page:.combine]。默认值为1,有效范围介于0(无反射)和1(完全反射)之间。</p>

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

@@ -88,7 +88,11 @@
 		<p>材质是否受雾影响。默认为*true*。</p>
 		<p>材质是否受雾影响。默认为*true*。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture matcap]</h3>
 		<p>matcap贴图,默认为null。</p>
 		<p>matcap贴图,默认为null。</p>

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

@@ -126,7 +126,11 @@
 		<p>烘焙光的强度。默认值为1。</p>
 		<p>烘焙光的强度。默认值为1。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture normalMap]</h3>
 		<p> 用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。
 		<p> 用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。

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

@@ -159,7 +159,11 @@
 		<p>烘焙光的强度。默认值为1。</p>
 		<p>烘焙光的强度。默认值为1。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Float metalness]</h3>
 		<p> 材质与金属的相似度。非金属材质,如木材或石材,使用0.0,金属使用1.0,通常没有中间值。
 		<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>
 		<p>Intensity of the baked light. Default is 1.</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Texture normalMap]</h3>
 		<p>
 		<p>

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

@@ -82,7 +82,10 @@
 		<p>材质是否受雾影响。默认为*true*。</p>
 		<p>材质是否受雾影响。默认为*true*。</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Number size]</h3>
 		<p>设置点的大小。默认值为1.0。<br/>
 		<p>设置点的大小。默认值为1.0。<br/>

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

@@ -26,7 +26,7 @@
 
 
 		<h2>例子</h2>
 		<h2>例子</h2>
 		<p>
 		<p>
-			[example:webgl_raycast_sprite WebGL / raycast / sprite]<br />
+			[example:webgl_raycaster_sprite WebGL / raycast / sprite]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:webgl_sprites WebGL / sprites]<br />
 			[example:svg_sandbox SVG / sandbox]
 			[example:svg_sandbox SVG / sandbox]
 		</p>
 		</p>
@@ -69,7 +69,10 @@
 		</p>
 		</p>
 
 
 		<h3>[property:Texture map]</h3>
 		<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>
 		<h3>[property:Radians rotation]</h3>
 		<p> sprite的转动,以弧度为单位。默认值为0。</p>
 		<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: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>
 		</p>
 
 
 		<h3>[method:this makeBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )</h3>
 		<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:WebGLRenderingContext context] - 可用于将渲染器附加到已有的渲染环境([link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext])中。默认值是null<br />
 
 
 		[page:String precision] - 着色器精度. 可以是 *"highp"*, *"mediump"* 或者 *"lowp"*.
 		[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 />
 		[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>
 		<h2>属性</h2>
 
 
 		<h3>[property:String name]</h3>
 		<h3>[property:String name]</h3>
-		<p>The name of the respective shader program.</p>
+		<p>相应着色器程序的名称。</p>
 
 
 		<h3>[property:String id]</h3>
 		<h3>[property:String id]</h3>
-		<p>The identifier of this instance.</p>
+		<p>该实例的 id 标识。</p>
 
 
 		<h3>[property:String cacheKey]</h3>
 		<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>
 		<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>
 		<h3>[property:Object program]</h3>
-		<p>The actual shader program.</p>
+		<p>实际的着色器程序。</p>
 
 
 		<h3>[property:WebGLShader vertexShader]</h3>
 		<h3>[property:WebGLShader vertexShader]</h3>
-		<p>The vertex shader.</p>
+		<p>顶点着色器。</p>
 
 
 		<h3>[property:WebGLShader fragmentShader]</h3>
 		<h3>[property:WebGLShader fragmentShader]</h3>
-		<p>The fragment shader.</p>
+		<p>片元着色器。</p>
 
 
 		<h2>方法</h2>
 		<h2>方法</h2>
 
 
@@ -147,7 +147,7 @@
 
 
 		<h3>[method:undefined destroy]()</h3>
 		<h3>[method:undefined destroy]()</h3>
 		<p>
 		<p>
-		Destroys an instance of [name].
+		销毁 WebGLProgram 的实例。
 		</p>
 		</p>
 
 
 		<h2>源码</h2>
 		<h2>源码</h2>

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

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

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

@@ -121,7 +121,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 			</code>
 
 
 			<p>
 			<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>
 			</p>
 
 
 			<h3>Examples</h3>
 			<h3>Examples</h3>

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

@@ -115,7 +115,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 			</code>
 
 
         <p>
         <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>
         </p>
 
 
         <h3>Examples</h3>
         <h3>Examples</h3>

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

@@ -117,7 +117,7 @@ line.geometry.computeBoundingSphere();
 			</code>
 			</code>
 
 
 			<p>
 			<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>
 			</p>
 
 
 			<h3>Examples</h3>
 			<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>
 			<code>material.side = THREE.DoubleSide</code>
 		</p>
 		</p>
 
 
-		<h2>Why does three.js sometimes return strange results for invalid inputs?</h2>
+		<h2>为什么有时候无效的输入会让three.js返回奇怪的结果?</h2>
 		<p>
 		<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>
 		</p>
 	</body>
 	</body>
 </html>
 </html>

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

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

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

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

+ 202 - 199
editor/js/EditorControls.js

@@ -1,354 +1,357 @@
 import * as THREE from 'three';
 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 };
 export { EditorControls };

+ 36 - 37
editor/js/History.js

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

+ 1 - 1
editor/sw.js

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

+ 25 - 105
examples/css3d_molecules.html

@@ -10,44 +10,6 @@
 				background-color: #050505;
 				background-color: #050505;
 				background: radial-gradient(ellipse at center,  rgba(43,45,48,1) 0%,rgba(0,0,0,1) 100%);
 				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 {
 			.bond {
 				width: 5px;
 				width: 5px;
 				height: 10px;
 				height: 10px;
@@ -59,10 +21,6 @@
 	<body>
 	<body>
 		<div id="container"></div>
 		<div id="container"></div>
 		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> css3d - molecules</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 -->
 		<!-- Import maps polyfill -->
 		<!-- Remove this when import maps will be widely supported -->
 		<!-- Remove this when import maps will be widely supported -->
@@ -83,6 +41,7 @@
 			import { TrackballControls } from './jsm/controls/TrackballControls.js';
 			import { TrackballControls } from './jsm/controls/TrackballControls.js';
 			import { PDBLoader } from './jsm/loaders/PDBLoader.js';
 			import { PDBLoader } from './jsm/loaders/PDBLoader.js';
 			import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from './jsm/renderers/CSS3DRenderer.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 camera, scene, renderer;
 			let controls;
 			let controls;
@@ -95,7 +54,11 @@
 			const tmpVec4 = new THREE.Vector3();
 			const tmpVec4 = new THREE.Vector3();
 			const offset = new THREE.Vector3();
 			const offset = new THREE.Vector3();
 
 
-			let visualizationType = 2;
+			const VIZ_TYPE = {
+				'Atoms': 0,
+				'Bonds': 1,
+				'Atoms + Bonds': 2
+			};
 
 
 			const MOLECULES = {
 			const MOLECULES = {
 				'Ethanol': 'ethanol.pdb',
 				'Ethanol': 'ethanol.pdb',
@@ -118,12 +81,15 @@
 				'Graphite': 'graphite.pdb'
 				'Graphite': 'graphite.pdb'
 			};
 			};
 
 
+			const params = {
+				vizType: 2,
+				molecule: 'caffeine.pdb'
+			};
+
 			const loader = new PDBLoader();
 			const loader = new PDBLoader();
 			const colorSpriteMap = {};
 			const colorSpriteMap = {};
 			const baseSprite = document.createElement( 'img' );
 			const baseSprite = document.createElement( 'img' );
 
 
-			const menu = document.getElementById( 'menu' );
-
 			init();
 			init();
 			animate();
 			animate();
 
 
@@ -152,8 +118,7 @@
 
 
 				baseSprite.onload = function () {
 				baseSprite.onload = function () {
 
 
-					loadMolecule( 'models/pdb/caffeine.pdb' );
-					createMenu();
+					loadMolecule( params.molecule );
 
 
 				};
 				};
 
 
@@ -163,56 +128,21 @@
 
 
 				window.addEventListener( 'resize', onWindowResize );
 				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 ++ ) {
 				for ( let i = 0; i < objects.length; i ++ ) {
 
 
@@ -471,19 +403,7 @@
 
 
 					//console.log( "CSS3DObjects:", objects.length );
 					//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_sprites",
 		"webgl_points_waves",
 		"webgl_points_waves",
 		"webgl_portal",
 		"webgl_portal",
-		"webgl_raycast_sprite",
-		"webgl_raycast_texture",
+		"webgl_raycaster_bvh",
+		"webgl_raycaster_sprite",
+		"webgl_raycaster_texture",
 		"webgl_read_float_buffer",
 		"webgl_read_float_buffer",
 		"webgl_refraction",
 		"webgl_refraction",
 		"webgl_rtt",
 		"webgl_rtt",
@@ -309,6 +310,7 @@
 	],
 	],
 	"webgpu": [
 	"webgpu": [
 		"webgpu_compute",
 		"webgpu_compute",
+		"webgpu_cubemap_adjustments",
 		"webgpu_cubemap_mix",
 		"webgpu_cubemap_mix",
 		"webgpu_depth_texture",
 		"webgpu_depth_texture",
 		"webgpu_instance_mesh",
 		"webgpu_instance_mesh",
@@ -318,6 +320,7 @@
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf",
 		"webgpu_materials",
 		"webgpu_materials",
 		"webgpu_nodes_playground",
 		"webgpu_nodes_playground",
+		"webgpu_particles",
 		"webgpu_rtt",
 		"webgpu_rtt",
 		"webgpu_sandbox",
 		"webgpu_sandbox",
 		"webgpu_skinning",
 		"webgpu_skinning",
@@ -333,6 +336,7 @@
 	],
 	],
 	"webxr": [
 	"webxr": [
 		"webxr_ar_cones",
 		"webxr_ar_cones",
+		"webxr_ar_dragging",
 		"webxr_ar_hittest",
 		"webxr_ar_hittest",
 		"webxr_ar_lighting",
 		"webxr_ar_lighting",
 		"webxr_ar_paint",
 		"webxr_ar_paint",

+ 9 - 16
examples/index.html

@@ -90,18 +90,18 @@
 
 
 			for ( const key in files ) {
 			for ( const key in files ) {
 
 
-				const section = files[ key ];
+				const category = files[ key ];
 
 
 				const header = document.createElement( 'h2' );
 				const header = document.createElement( 'h2' );
 				header.textContent = key;
 				header.textContent = key;
 				header.setAttribute( 'data-category', key );
 				header.setAttribute( 'data-category', key );
 				container.appendChild( header );
 				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 );
 					container.appendChild( link );
 
 
 					links[ file ] = 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 = `
 			const template = `
 				<div class="card">
 				<div class="card">
-					<a href="${file}.html" target="viewer">
+					<a href="${ file }.html" target="viewer">
 						<div class="cover">
 						<div class="cover">
 							<img src="screenshots/${ file }.jpg" loading="lazy" width="400" />
 							<img src="screenshots/${ file }.jpg" loading="lazy" width="400" />
 						</div>
 						</div>
-						<div class="title">${getName( file )}</div>
+						<div class="title">${ getName( file ) }${ external }</div>
 					</a>
 					</a>
 				</div>
 				</div>
 			`;
 			`;
@@ -332,18 +334,9 @@
 
 
 				link.classList.remove( 'hidden' );
 				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 {
 			} else {
 
 
 				link.classList.add( 'hidden' );
 				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 );
 				this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
 
 
-				if ( this.camera.zoom != 1 ) {
+				if ( this.camera.zoom !== 1 ) {
 
 
 					//adapt gizmos size to camera zoom
 					//adapt gizmos size to camera zoom
 					const size = 1 / this.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 );
 				this._gizmos.add( gizmoX );
 
 

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

@@ -181,7 +181,17 @@
 
 
 			this.camera.updateMatrixWorld();
 			this.camera.updateMatrixWorld();
 			this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
 			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 );
 			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() {
 	function getCanvas() {
 
 
-		if ( cachedCanvas ) {
-
-			return cachedCanvas;
-
-		}
-
 		if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
 		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 ) {
 		buildMetalRoughTexture( metalnessMap, roughnessMap ) {
 
 
 			if ( metalnessMap === roughnessMap ) return metalnessMap;
 			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.' );
 			console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 			const metalness = metalnessMap?.image;
 			const metalness = metalnessMap?.image;
 			const roughness = roughnessMap?.image;
 			const roughness = roughnessMap?.image;
@@ -692,11 +701,12 @@
 			if ( metalness ) {
 			if ( metalness ) {
 
 
 				context.drawImage( metalness, 0, 0, width, height );
 				context.drawImage( metalness, 0, 0, width, height );
+				const convert = getEncodingConversion( metalnessMap );
 				const data = context.getImageData( 0, 0, width, height ).data;
 				const data = context.getImageData( 0, 0, width, height ).data;
 
 
 				for ( let i = 2; i < data.length; i += 4 ) {
 				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 ) {
 			if ( roughness ) {
 
 
 				context.drawImage( roughness, 0, 0, width, height );
 				context.drawImage( roughness, 0, 0, width, height );
+				const convert = getEncodingConversion( roughnessMap );
 				const data = context.getImageData( 0, 0, width, height ).data;
 				const data = context.getImageData( 0, 0, width, height ).data;
 
 
 				for ( let i = 1; i < data.length; i += 4 ) {
 				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 reference = metalnessMap || roughnessMap;
 			const texture = reference.clone();
 			const texture = reference.clone();
 			texture.source = new THREE.Source( canvas );
 			texture.source = new THREE.Source( canvas );
+			texture.encoding = THREE.LinearEncoding;
 			return texture;
 			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 ) {
 		if ( material.map !== null ) {
 
 
 			inputs.push( `${pad}color3f inputs:diffuseColor.connect = </Materials/Material_${material.id}/Texture_${material.map.id}_diffuse.outputs:rgb>` );
 			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 ) {
 			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;
 				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 ++ ) {
 			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();
 	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
  * 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
  * 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:
  * References:
  * - KTX: http://github.khronos.org/KTX-Specification/
  * - KTX: http://github.khronos.org/KTX-Specification/
  * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor
  * - 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();
 	const _taskCache = new WeakMap();
 
 
@@ -137,7 +153,6 @@
 			const loader = new THREE.FileLoader( this.manager );
 			const loader = new THREE.FileLoader( this.manager );
 			loader.setResponseType( 'arraybuffer' );
 			loader.setResponseType( 'arraybuffer' );
 			loader.setWithCredentials( this.withCredentials );
 			loader.setWithCredentials( this.withCredentials );
-			const texture = new THREE.CompressedTexture();
 			loader.load( url, buffer => {
 			loader.load( url, buffer => {
 
 
 				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
 				// 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 );
 			}, onProgress, onError );
-			return texture;
 
 
 		}
 		}
 
 
@@ -181,32 +189,41 @@
 			texture.magFilter = THREE.LinearFilter;
 			texture.magFilter = THREE.LinearFilter;
 			texture.generateMipmaps = false;
 			texture.generateMipmaps = false;
 			texture.needsUpdate = true;
 			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;
 			return texture;
 
 
 		}
 		}
 		/**
 		/**
-   * @param {ArrayBuffer[]} buffers
+   * @param {ArrayBuffer} buffer
    * @param {object?} config
    * @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 taskConfig = config;
 			const texturePending = this.init().then( () => {
 			const texturePending = this.init().then( () => {
 
 
 				return this.workerPool.postMessage( {
 				return this.workerPool.postMessage( {
 					type: 'transcode',
 					type: 'transcode',
-					buffers,
+					buffer,
 					taskConfig: taskConfig
 					taskConfig: taskConfig
-				}, buffers );
+				}, [ buffer ] );
 
 
 			} ).then( e => this._createTextureFrom( e.data ) ); // Cache the task result.
 			} ).then( e => this._createTextureFrom( e.data ) ); // Cache the task result.
 
 
-			_taskCache.set( buffers[ 0 ], {
+			_taskCache.set( buffer, {
 				promise: texturePending
 				promise: texturePending
 			} );
 			} );
 
 
@@ -299,7 +316,7 @@
 								format,
 								format,
 								dfdTransferFn,
 								dfdTransferFn,
 								dfdFlags
 								dfdFlags
-							} = transcode( message.buffers[ 0 ] );
+							} = transcode( message.buffer );
 							const buffers = [];
 							const buffers = [];
 
 
 							for ( let i = 0; i < mipmaps.length; ++ i ) {
 							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;
 	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.
 	const FINISH_TYPE_METAL = 5; // State machine to search a subobject path.
 	// The LDraw standard establishes these various possible subfolders.
 	// 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_RELATIVE = 4;
 	const FILE_LOCATION_TRY_ABSOLUTE = 5;
 	const FILE_LOCATION_TRY_ABSOLUTE = 5;
 	const FILE_LOCATION_NOT_FOUND = 6;
 	const FILE_LOCATION_NOT_FOUND = 6;
@@ -655,7 +655,9 @@
 			result.type = original.type;
 			result.type = original.type;
 			result.category = original.category;
 			result.category = original.category;
 			result.keywords = original.keywords;
 			result.keywords = original.keywords;
+			result.author = original.author;
 			result.subobjects = original.subobjects;
 			result.subobjects = original.subobjects;
+			result.fileName = original.fileName;
 			result.totalFaces = original.totalFaces;
 			result.totalFaces = original.totalFaces;
 			result.startingConstructionStep = original.startingConstructionStep;
 			result.startingConstructionStep = original.startingConstructionStep;
 			result.materials = original.materials;
 			result.materials = original.materials;
@@ -667,7 +669,7 @@
 		async fetchData( fileName ) {
 		async fetchData( fileName ) {
 
 
 			let triedLowerCase = false;
 			let triedLowerCase = false;
-			let locationState = FILE_LOCATION_AS_IS;
+			let locationState = FILE_LOCATION_TRY_PARTS;
 
 
 			while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 			while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 
 
@@ -711,7 +713,7 @@
 							fileName = fileName.toLowerCase();
 							fileName = fileName.toLowerCase();
 							subobjectURL = fileName;
 							subobjectURL = fileName;
 							triedLowerCase = true;
 							triedLowerCase = true;
-							locationState = FILE_LOCATION_AS_IS;
+							locationState = FILE_LOCATION_TRY_PARTS;
 
 
 						}
 						}
 
 
@@ -761,6 +763,7 @@
 			let type = 'Model';
 			let type = 'Model';
 			let category = null;
 			let category = null;
 			let keywords = null;
 			let keywords = null;
+			let author = null;
 			let totalFaces = 0; // split into lines
 			let totalFaces = 0; // split into lines
 
 
 			if ( text.indexOf( '\r\n' ) !== - 1 ) {
 			if ( text.indexOf( '\r\n' ) !== - 1 ) {
@@ -936,6 +939,10 @@
 									startingConstructionStep = true;
 									startingConstructionStep = true;
 									break;
 									break;
 
 
+								case 'Author:':
+									author = lp.getToken();
+									break;
+
 								default:
 								default:
 									// Other meta directives are not implemented
 									// Other meta directives are not implemented
 									break;
 									break;
@@ -1141,6 +1148,7 @@
 				type,
 				type,
 				category,
 				category,
 				keywords,
 				keywords,
+				author,
 				subobjects,
 				subobjects,
 				totalFaces,
 				totalFaces,
 				startingConstructionStep,
 				startingConstructionStep,
@@ -1277,6 +1285,9 @@
 				const group = new THREE.Group();
 				const group = new THREE.Group();
 				group.userData.category = info.category;
 				group.userData.category = info.category;
 				group.userData.keywords = info.keywords;
 				group.userData.keywords = info.keywords;
+				group.userData.author = info.author;
+				group.userData.type = info.type;
+				group.userData.fileName = info.fileName;
 				info.group = group;
 				info.group = group;
 				const subobjectInfos = await Promise.all( promises );
 				const subobjectInfos = await Promise.all( promises );
 
 
@@ -1300,6 +1311,7 @@
 						subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
 						subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep;
 						subobjectGroup.name = subobject.fileName;
 						subobjectGroup.name = subobject.fileName;
 						loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
 						loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
+						subobjectGroup.userData.colorCode = subobject.colorCode;
 						group.add( subobjectGroup );
 						group.add( subobjectGroup );
 						continue;
 						continue;
 
 
@@ -1388,6 +1400,7 @@
 				if ( subobject ) {
 				if ( subobject ) {
 
 
 					loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
 					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.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.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 					this.computeConstructionSteps( group );
 					this.computeConstructionSteps( group );
+					group.userData.fileName = url;
 					onLoad( group );
 					onLoad( group );
 
 
 				} ).catch( onError );
 				} ).catch( onError );
@@ -1854,7 +1886,9 @@
 
 
 			this.partsCache.parseModel( text, this.materialLibrary ).then( group => {
 			this.partsCache.parseModel( text, this.materialLibrary ).then( group => {
 
 
+				this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 				this.computeConstructionSteps( group );
 				this.computeConstructionSteps( group );
+				group.userData.fileName = '';
 				onLoad( group );
 				onLoad( group );
 
 
 			} );
 			} );
@@ -1984,8 +2018,10 @@
 
 
 					if ( material === null ) {
 					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() {
 		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
 					opacity: alpha
 				} );
 				} );
 				edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear();
 				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 );
 			this.addMaterial( material );
 			return 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 ) {
 		computeConstructionSteps( model ) {

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

@@ -7,6 +7,7 @@
 	const _material_use_pattern = /^usemtl /; // usemap map_name
 	const _material_use_pattern = /^usemtl /; // usemap map_name
 
 
 	const _map_use_pattern = /^usemap /;
 	const _map_use_pattern = /^usemap /;
+	const _face_vertex_data_separator_pattern = /\s+/;
 
 
 	const _vA = new THREE.Vector3();
 	const _vA = new THREE.Vector3();
 
 
@@ -438,26 +439,19 @@
 			}
 			}
 
 
 			const lines = text.split( '\n' );
 			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 ++ ) {
 			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 === '#' ) continue;
 
 
 				if ( lineFirstChar === 'v' ) {
 				if ( lineFirstChar === 'v' ) {
 
 
-					const data = line.split( /\s+/ );
+					const data = line.split( _face_vertex_data_separator_pattern );
 
 
 					switch ( data[ 0 ] ) {
 					switch ( data[ 0 ] ) {
 
 
@@ -492,7 +486,7 @@
 				} else if ( lineFirstChar === 'f' ) {
 				} else if ( lineFirstChar === 'f' ) {
 
 
 					const lineData = line.slice( 1 ).trim();
 					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
 					const faceVertices = []; // Parse the face vertex data into an easy to work with format
 
 
 					for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) {
 					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 ) {
 					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 ) {
 					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 ) {
 					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 ) {
 			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 headerText = '';
 				let headerLength = 0;
 				let headerLength = 0;
 				const result = patternHeader.exec( data );
 				const result = patternHeader.exec( data );
@@ -99,7 +99,7 @@
 					headerLength: headerLength,
 					headerLength: headerLength,
 					objInfo: ''
 					objInfo: ''
 				};
 				};
-				const lines = headerText.split( '\n' );
+				const lines = headerText.split( /\r\n|\r|\n/ );
 				let currentElement;
 				let currentElement;
 
 
 				function make_ply_element_property( propertValues, propertyNameMapping ) {
 				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 currentElement = 0;
 				let currentElementCount = 0;
 				let currentElementCount = 0;
 
 

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

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

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

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

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

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

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

@@ -1,14 +1,14 @@
 ( function () {
 ( 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.offset = this.IFF.reader.offset;
 			this.IFF.debugger.closeForms();
 			this.IFF.debugger.closeForms();
@@ -440,7 +440,8 @@
 			}
 			}
 
 
 		}
 		}
-	};
+
+	}
 
 
 	THREE.LWO2Parser = LWO2Parser;
 	THREE.LWO2Parser = LWO2Parser;
 
 

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

@@ -1,14 +1,14 @@
 ( function () {
 ( 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.offset = this.IFF.reader.offset;
 			this.IFF.debugger.closeForms();
 			this.IFF.debugger.closeForms();
@@ -394,7 +394,8 @@
 			}
 			}
 
 
 		}
 		}
-	};
+
+	}
 
 
 	THREE.LWO3Parser = LWO3Parser;
 	THREE.LWO3Parser = LWO3Parser;
 
 

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

@@ -13,205 +13,203 @@
  * @param   {ArrayBuffer}   arrayBuffer     The buffer with volume data
  * @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)]
    * @member {Function} getData Shortcut for data[access(i,j,k)]
    * @memberof Volume
    * @memberof Volume
@@ -220,12 +218,13 @@
    * @param {number} k    Third coordinate
    * @param {number} k    Third coordinate
    * @returns {number}  value in the data array
    * @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
    * @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system
    * @memberof Volume
    * @memberof Volume
@@ -234,27 +233,29 @@
    * @param {number} k    Third coordinate
    * @param {number} k    Third coordinate
    * @returns {number}  index
    * @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
    * @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data
    * @memberof Volume
    * @memberof Volume
    * @param {number} index index of the voxel
    * @param {number} index index of the voxel
    * @returns {Array}  [x,y,z]
    * @returns {Array}  [x,y,z]
    */
    */
-		reverseAccess: function ( index ) {
+
+
+		reverseAccess( index ) {
 
 
 			const z = Math.floor( index / ( this.yLength * this.xLength ) );
 			const z = Math.floor( index / ( this.yLength * this.xLength ) );
 			const y = Math.floor( ( index - z * this.yLength * this.xLength ) / 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;
 			const x = index - z * this.yLength * this.xLength - y * this.xLength;
 			return [ x, y, z ];
 			return [ x, y, z ];
 
 
-		},
-
+		}
 		/**
 		/**
    * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
    * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced
    * @memberof Volume
    * @memberof Volume
@@ -265,7 +266,9 @@
    * @param {Object}   context    You can specify a context in which call the function, default if this Volume
    * @param {Object}   context    You can specify a context in which call the function, default if this Volume
    * @returns {Volume}   this
    * @returns {Volume}   this
    */
    */
-		map: function ( functionToMap, context ) {
+
+
+		map( functionToMap, context ) {
 
 
 			const length = this.data.length;
 			const length = this.data.length;
 			context = context || this;
 			context = context || this;
@@ -278,8 +281,7 @@
 
 
 			return this;
 			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.
    * @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
    * @memberof Volume
@@ -287,7 +289,9 @@
    * @param {number}            index the index of the slice
    * @param {number}            index the index of the slice
    * @returns {Object} an object containing all the usefull information on the geometry 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;
 			let firstSpacing, secondSpacing, positionOffset, IJKIndex;
 			const axisInIJK = new THREE.Vector3(),
 			const axisInIJK = new THREE.Vector3(),
@@ -386,8 +390,7 @@
 				planeHeight: planeHeight
 				planeHeight: planeHeight
 			};
 			};
 
 
-		},
-
+		}
 		/**
 		/**
    * @member {Function} extractSlice Returns a slice corresponding to the given axis and index
    * @member {Function} extractSlice Returns a slice corresponding to the given axis and index
    *                        The coordinate are given in the Right Anterior Superior coordinate format
    *                        The coordinate are given in the Right Anterior Superior coordinate format
@@ -396,21 +399,24 @@
    * @param {number}            index the index of the slice
    * @param {number}            index the index of the slice
    * @returns {VolumeSlice} the extracted slice
    * @returns {VolumeSlice} the extracted slice
    */
    */
-		extractSlice: function ( axis, index ) {
+
+
+		extractSlice( axis, index ) {
 
 
 			const slice = new THREE.VolumeSlice( this, index, axis );
 			const slice = new THREE.VolumeSlice( this, index, axis );
 			this.sliceList.push( slice );
 			this.sliceList.push( slice );
 			return slice;
 			return slice;
 
 
-		},
-
+		}
 		/**
 		/**
    * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
    * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume
    * @see THREE.VolumeSlice.repaint
    * @see THREE.VolumeSlice.repaint
    * @memberof Volume
    * @memberof Volume
    * @returns {Volume} this
    * @returns {Volume} this
    */
    */
-		repaintAllSlices: function () {
+
+
+		repaintAllSlices() {
 
 
 			this.sliceList.forEach( function ( slice ) {
 			this.sliceList.forEach( function ( slice ) {
 
 
@@ -419,14 +425,15 @@
 			} );
 			} );
 			return this;
 			return this;
 
 
-		},
-
+		}
 		/**
 		/**
    * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
    * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume
    * @memberof Volume
    * @memberof Volume
    * @returns {Array} [min,max]
    * @returns {Array} [min,max]
    */
    */
-		computeMinMax: function () {
+
+
+		computeMinMax() {
 
 
 			let min = Infinity;
 			let min = Infinity;
 			let max = - Infinity; // buffer the length
 			let max = - Infinity; // buffer the length
@@ -451,7 +458,8 @@
 			return [ min, max ];
 			return [ min, max ];
 
 
 		}
 		}
-	};
+
+	}
 
 
 	THREE.Volume = Volume;
 	THREE.Volume = Volume;
 
 

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

@@ -9,103 +9,103 @@
  * @see Volume
  * @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
    * @member {Function} repaint Refresh the texture and the geometry if geometryNeedsUpdate is set to true
    * @memberof VolumeSlice
    * @memberof VolumeSlice
    */
    */
-		repaint: function () {
+
+
+		repaint() {
 
 
 			if ( this.geometryNeedsUpdate ) {
 			if ( this.geometryNeedsUpdate ) {
 
 
@@ -179,14 +179,15 @@
 			this.ctx.drawImage( canvas, 0, 0, iLength, jLength, 0, 0, this.canvas.width, this.canvas.height );
 			this.ctx.drawImage( canvas, 0, 0, iLength, jLength, 0, 0, this.canvas.width, this.canvas.height );
 			this.mesh.material.map.needsUpdate = true;
 			this.mesh.material.map.needsUpdate = true;
 
 
-		},
-
+		}
 		/**
 		/**
    * @member {Function} Refresh the geometry according to axis and index
    * @member {Function} Refresh the geometry according to axis and index
    * @see Volume.extractPerpendicularPlane
    * @see Volume.extractPerpendicularPlane
    * @memberof VolumeSlice
    * @memberof VolumeSlice
    */
    */
-		updateGeometry: function () {
+
+
+		updateGeometry() {
 
 
 			const extracted = this.volume.extractPerpendicularPlane( this.axis, this.index );
 			const extracted = this.volume.extractPerpendicularPlane( this.axis, this.index );
 			this.sliceAccess = extracted.sliceAccess;
 			this.sliceAccess = extracted.sliceAccess;
@@ -215,7 +216,8 @@
 			this.geometryNeedsUpdate = false;
 			this.geometryNeedsUpdate = false;
 
 
 		}
 		}
-	};
+
+	}
 
 
 	THREE.VolumeSlice = VolumeSlice;
 	THREE.VolumeSlice = VolumeSlice;
 
 

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

@@ -306,7 +306,7 @@
 
 
 		for ( let i = u.faces.length - 1; i >= 0; i -- ) {
 		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 );
 				removeFace( u.faces[ i ], faces );
 
 

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

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

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

@@ -497,12 +497,14 @@
 
 
 				void main() {
 				void main() {
 					vec2 invSize = 1.0 / texSize;
 					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;
 					vec4 diffuseSum = texture2D( colorTexture, vUv) * weightSum;
 					vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);
 					vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);
 					vec2 uvOffset = delta;
 					vec2 uvOffset = delta;
 					for( int i = 1; i <= MAX_RADIUS; i ++ ) {
 					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 sample1 = texture2D( colorTexture, vUv + uvOffset);
 						vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);
 						vec4 sample2 = texture2D( colorTexture, vUv - uvOffset);
 						diffuseSum += ((sample1 + sample2) * w);
 						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)
  *    (Replace lights_phong_pars_fragment with lights_mmd_toon_pars_fragment)
  *  * Add mmd_toon_matcap_fragment.
  *  * Add mmd_toon_matcap_fragment.
  */
  */
-	const lights_mmd_toon_pars_fragment = `
+	const lights_mmd_toon_pars_fragment =
+/* glsl */
+`
 varying vec3 vViewPosition;
 varying vec3 vViewPosition;
 
 
 struct BlinnPhongMaterial {
 struct BlinnPhongMaterial {
@@ -47,7 +49,9 @@ void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in Geometric
 
 
 #define Material_LightProbeLOD( material )	(0)
 #define Material_LightProbeLOD( material )	(0)
 `;
 `;
-	const mmd_toon_matcap_fragment = `
+	const mmd_toon_matcap_fragment =
+/* glsl */
+`
 #ifdef USE_MATCAP
 #ifdef USE_MATCAP
 
 
 	vec3 viewDir = normalize( vViewPosition );
 	vec3 viewDir = normalize( vViewPosition );

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

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

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

@@ -23,13 +23,7 @@
 
 
 		if ( 0 <= -- iterations ) {
 		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.
 		} // Return complete Hilbert Curve.
 
 
@@ -66,17 +60,7 @@
 
 
 		if ( -- iterations >= 0 ) {
 		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.
 		} // 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),
 		// mesh's default world transform as position(0, 0, 0),
 		// quaternion(0, 0, 0, 1) and scale(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;
 		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 ) );
 							this.applyTransformMatrix( this.scale( size, this._v3_1 ) );
 
 
@@ -2279,7 +2279,7 @@ class ArcballControls extends EventDispatcher {
 		this._gizmoMatrixState0.identity().setPosition( tbCenter );
 		this._gizmoMatrixState0.identity().setPosition( tbCenter );
 		this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
 		this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
 
 
-		if ( this.camera.zoom != 1 ) {
+		if ( this.camera.zoom !== 1 ) {
 
 
 			//adapt gizmos size to camera zoom
 			//adapt gizmos size to camera zoom
 			const size = 1 / this.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._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 );
 		this._gizmos.add( gizmoX );
 		this._gizmos.add( gizmoY );
 		this._gizmos.add( gizmoY );
 		this._gizmos.add( gizmoZ );
 		this._gizmos.add( gizmoZ );

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

@@ -205,7 +205,15 @@ class TransformControls extends Object3D {
 		this.camera.updateMatrixWorld();
 		this.camera.updateMatrixWorld();
 		this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale );
 		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 );
 		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,
 	DoubleSide,
 	InterpolateDiscrete,
 	InterpolateDiscrete,
 	InterpolateLinear,
 	InterpolateLinear,
+	LinearEncoding,
 	LinearFilter,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	LinearMipmapLinearFilter,
 	LinearMipmapNearestFilter,
 	LinearMipmapNearestFilter,
@@ -18,6 +19,7 @@ import {
 	RepeatWrapping,
 	RepeatWrapping,
 	Scene,
 	Scene,
 	Source,
 	Source,
+	sRGBEncoding,
 	Vector3
 	Vector3
 } from 'three';
 } from 'three';
 
 
@@ -343,27 +345,15 @@ function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) {
 
 
 }
 }
 
 
-let cachedCanvas = null;
-
 function getCanvas() {
 function getCanvas() {
 
 
-	if ( cachedCanvas ) {
-
-		return cachedCanvas;
-
-	}
-
 	if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
 	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;
 		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.' );
 		console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 
 
 		const metalness = metalnessMap?.image;
 		const metalness = metalnessMap?.image;
@@ -766,11 +776,12 @@ class GLTFWriter {
 
 
 			context.drawImage( metalness, 0, 0, width, height );
 			context.drawImage( metalness, 0, 0, width, height );
 
 
+			const convert = getEncodingConversion( metalnessMap );
 			const data = context.getImageData( 0, 0, width, height ).data;
 			const data = context.getImageData( 0, 0, width, height ).data;
 
 
 			for ( let i = 2; i < data.length; i += 4 ) {
 			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 );
 			context.drawImage( roughness, 0, 0, width, height );
 
 
+			const convert = getEncodingConversion( roughnessMap );
 			const data = context.getImageData( 0, 0, width, height ).data;
 			const data = context.getImageData( 0, 0, width, height ).data;
 
 
 			for ( let i = 1; i < data.length; i += 4 ) {
 			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();
 		const texture = reference.clone();
 
 
 		texture.source = new Source( canvas );
 		texture.source = new Source( canvas );
+		texture.encoding = LinearEncoding;
 
 
 		return texture;
 		return texture;
 
 

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

@@ -39,46 +39,46 @@ import {
 	VK_FORMAT_R8G8_UNORM,
 	VK_FORMAT_R8G8_UNORM,
 	VK_FORMAT_R8G8B8A8_SRGB,
 	VK_FORMAT_R8G8B8A8_SRGB,
 	VK_FORMAT_R8G8B8A8_UNORM,
 	VK_FORMAT_R8G8B8A8_UNORM,
- } from '../libs/ktx-parse.module.js';
+} from '../libs/ktx-parse.module.js';
 
 
 const VK_FORMAT_MAP = {
 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,
 				channelType: channelType,
 				bitOffset: i * array.BYTES_PER_ELEMENT,
 				bitOffset: i * array.BYTES_PER_ELEMENT,
 				bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
 				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,
 				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 );
 	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 ) {
 	if ( material.map !== null ) {
 
 
 		inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );
 		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 ( w < 2 * r ) r = w / 2;
 		if ( h < 2 * r ) r = h / 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 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';
 				const accentTextColor = luminance < 0.5 ? 'white' : '#111111';
 
 
-				if ( element.type  === 'radio' ) {
+				if ( element.type === 'radio' ) {
 
 
 					buildRectPath( x, y, width, height, height );
 					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 );
 					buildRectPath( x, y, width, height, 2 );
 
 
@@ -395,7 +395,7 @@ function html2canvas( element ) {
 						const currentTextAlign = context.textAlign;
 						const currentTextAlign = context.textAlign;
 
 
 						context.textAlign = 'center';
 						context.textAlign = 'center';
-						
+
 						const properties = {
 						const properties = {
 							color: accentTextColor,
 							color: accentTextColor,
 							fontFamily: style.fontFamily,
 							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 );
 					buildRectPath( x, y + ( height / 4 ), width, height / 2, height / 4 );
 					context.fillStyle = accentTextColor;
 					context.fillStyle = accentTextColor;
@@ -423,7 +423,7 @@ function html2canvas( element ) {
 					context.fill();
 					context.fill();
 					context.stroke();
 					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.fillStyle = accentColor;
 					context.fill();
 					context.fill();
 
 
@@ -522,7 +522,7 @@ function htmlevent( element, event, x, y ) {
 
 
 				element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
 				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 ] ) );
 					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 ) {
 		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;
 			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 );
 				console.error( 'EXRHeader:', EXRHeader );
 				throw new Error( 'THREE.EXRLoader: provided file is currently unsupported.' );
 				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 ++ ) {
 		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();
 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
  * 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
  * 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:
  * References:
  * - KTX: http://github.khronos.org/KTX-Specification/
  * - KTX: http://github.khronos.org/KTX-Specification/
@@ -13,28 +13,52 @@
 
 
 import {
 import {
 	CompressedTexture,
 	CompressedTexture,
+	Data3DTexture,
+	DataTexture,
 	FileLoader,
 	FileLoader,
+	FloatType,
+	HalfFloatType,
 	LinearEncoding,
 	LinearEncoding,
 	LinearFilter,
 	LinearFilter,
 	LinearMipmapLinearFilter,
 	LinearMipmapLinearFilter,
 	Loader,
 	Loader,
-	RGBAFormat,
+	RedFormat,
+	RGB_ETC1_Format,
+	RGB_ETC2_Format,
+	RGB_PVRTC_4BPPV1_Format,
+	RGB_S3TC_DXT1_Format,
 	RGBA_ASTC_4x4_Format,
 	RGBA_ASTC_4x4_Format,
 	RGBA_BPTC_Format,
 	RGBA_BPTC_Format,
 	RGBA_ETC2_EAC_Format,
 	RGBA_ETC2_EAC_Format,
 	RGBA_PVRTC_4BPPV1_Format,
 	RGBA_PVRTC_4BPPV1_Format,
 	RGBA_S3TC_DXT5_Format,
 	RGBA_S3TC_DXT5_Format,
-	RGB_ETC1_Format,
-	RGB_ETC2_Format,
-	RGB_PVRTC_4BPPV1_Format,
-	RGB_S3TC_DXT1_Format,
+	RGBAFormat,
+	RGFormat,
 	sRGBEncoding,
 	sRGBEncoding,
 	UnsignedByteType
 	UnsignedByteType
 } from 'three';
 } from 'three';
 import { WorkerPool } from '../utils/WorkerPool.js';
 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();
 const _taskCache = new WeakMap();
 
 
 let _activeLoaders = 0;
 let _activeLoaders = 0;
@@ -189,8 +213,6 @@ class KTX2Loader extends Loader {
 		loader.setResponseType( 'arraybuffer' );
 		loader.setResponseType( 'arraybuffer' );
 		loader.setWithCredentials( this.withCredentials );
 		loader.setWithCredentials( this.withCredentials );
 
 
-		const texture = new CompressedTexture();
-
 		loader.load( url, ( buffer ) => {
 		loader.load( url, ( buffer ) => {
 
 
 			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
 			// 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 );
 				.catch( onError );
 
 
 		}, onProgress, onError );
 		}, onProgress, onError );
 
 
-		return texture;
-
 	}
 	}
 
 
 	_createTextureFrom( transcodeResult ) {
 	_createTextureFrom( transcodeResult ) {
@@ -231,29 +244,39 @@ class KTX2Loader extends Loader {
 		texture.magFilter = LinearFilter;
 		texture.magFilter = LinearFilter;
 		texture.generateMipmaps = false;
 		texture.generateMipmaps = false;
 		texture.needsUpdate = true;
 		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;
 		return texture;
 
 
 	}
 	}
 
 
 	/**
 	/**
-	 * @param {ArrayBuffer[]} buffers
+	 * @param {ArrayBuffer} buffer
 	 * @param {object?} config
 	 * @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 taskConfig = config;
 		const texturePending = this.init().then( () => {
 		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 ) );
 		} ).then( ( e ) => this._createTextureFrom( e.data ) );
 
 
 		// Cache the task result.
 		// Cache the task result.
-		_taskCache.set( buffers[ 0 ], { promise: texturePending } );
+		_taskCache.set( buffer, { promise: texturePending } );
 
 
 		return texturePending;
 		return texturePending;
 
 
@@ -342,7 +365,7 @@ KTX2Loader.BasisWorker = function () {
 
 
 					try {
 					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 = [];
 						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 };
 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.
 // State machine to search a subobject path.
 // The LDraw standard establishes these various possible subfolders.
 // 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_RELATIVE = 4;
 const FILE_LOCATION_TRY_ABSOLUTE = 5;
 const FILE_LOCATION_TRY_ABSOLUTE = 5;
 const FILE_LOCATION_NOT_FOUND = 6;
 const FILE_LOCATION_NOT_FOUND = 6;
@@ -688,7 +688,9 @@ class LDrawParsedCache {
 		result.type = original.type;
 		result.type = original.type;
 		result.category = original.category;
 		result.category = original.category;
 		result.keywords = original.keywords;
 		result.keywords = original.keywords;
+		result.author = original.author;
 		result.subobjects = original.subobjects;
 		result.subobjects = original.subobjects;
+		result.fileName = original.fileName;
 		result.totalFaces = original.totalFaces;
 		result.totalFaces = original.totalFaces;
 		result.startingConstructionStep = original.startingConstructionStep;
 		result.startingConstructionStep = original.startingConstructionStep;
 		result.materials = original.materials;
 		result.materials = original.materials;
@@ -700,7 +702,7 @@ class LDrawParsedCache {
 	async fetchData( fileName ) {
 	async fetchData( fileName ) {
 
 
 		let triedLowerCase = false;
 		let triedLowerCase = false;
-		let locationState = FILE_LOCATION_AS_IS;
+		let locationState = FILE_LOCATION_TRY_PARTS;
 		while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 		while ( locationState !== FILE_LOCATION_NOT_FOUND ) {
 
 
 			let subobjectURL = fileName;
 			let subobjectURL = fileName;
@@ -743,7 +745,7 @@ class LDrawParsedCache {
 						fileName = fileName.toLowerCase();
 						fileName = fileName.toLowerCase();
 						subobjectURL = fileName;
 						subobjectURL = fileName;
 						triedLowerCase = true;
 						triedLowerCase = true;
-						locationState = FILE_LOCATION_AS_IS;
+						locationState = FILE_LOCATION_TRY_PARTS;
 
 
 					}
 					}
 
 
@@ -794,6 +796,7 @@ class LDrawParsedCache {
 		let type = 'Model';
 		let type = 'Model';
 		let category = null;
 		let category = null;
 		let keywords = null;
 		let keywords = null;
+		let author = null;
 		let totalFaces = 0;
 		let totalFaces = 0;
 
 
 		// split into lines
 		// split into lines
@@ -995,6 +998,12 @@ class LDrawParsedCache {
 
 
 								break;
 								break;
 
 
+							case 'Author:':
+
+								author = lp.getToken();
+
+								break;
+
 							default:
 							default:
 								// Other meta directives are not implemented
 								// Other meta directives are not implemented
 								break;
 								break;
@@ -1221,6 +1230,7 @@ class LDrawParsedCache {
 			type,
 			type,
 			category,
 			category,
 			keywords,
 			keywords,
+			author,
 			subobjects,
 			subobjects,
 			totalFaces,
 			totalFaces,
 			startingConstructionStep,
 			startingConstructionStep,
@@ -1356,6 +1366,9 @@ class LDrawPartsGeometryCache {
 			const group = new Group();
 			const group = new Group();
 			group.userData.category = info.category;
 			group.userData.category = info.category;
 			group.userData.keywords = info.keywords;
 			group.userData.keywords = info.keywords;
+			group.userData.author = info.author;
+			group.userData.type = info.type;
+			group.userData.fileName = info.fileName;
 			info.group = group;
 			info.group = group;
 
 
 			const subobjectInfos = await Promise.all( promises );
 			const subobjectInfos = await Promise.all( promises );
@@ -1380,6 +1393,7 @@ class LDrawPartsGeometryCache {
 					subobjectGroup.name = subobject.fileName;
 					subobjectGroup.name = subobject.fileName;
 
 
 					loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
 					loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );
+					subobjectGroup.userData.colorCode = subobject.colorCode;
 
 
 					group.add( subobjectGroup );
 					group.add( subobjectGroup );
 					continue;
 					continue;
@@ -1473,6 +1487,7 @@ class LDrawPartsGeometryCache {
 			if ( subobject ) {
 			if ( subobject ) {
 
 
 				loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials );
 				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.
 		// 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 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 ) {
 	setPartsLibraryPath( path ) {
@@ -1934,6 +1959,7 @@ class LDrawLoader extends Loader {
 
 
 					this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 					this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 					this.computeConstructionSteps( group );
 					this.computeConstructionSteps( group );
+					group.userData.fileName = url;
 					onLoad( group );
 					onLoad( group );
 
 
 				} )
 				} )
@@ -1949,7 +1975,9 @@ class LDrawLoader extends Loader {
 			.parseModel( text, this.materialLibrary )
 			.parseModel( text, this.materialLibrary )
 			.then( group => {
 			.then( group => {
 
 
+				this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );
 				this.computeConstructionSteps( group );
 				this.computeConstructionSteps( group );
+				group.userData.fileName = '';
 				onLoad( group );
 				onLoad( group );
 
 
 			} );
 			} );
@@ -2080,8 +2108,11 @@ class LDrawLoader extends Loader {
 				material = loader.getMaterial( colorCode );
 				material = loader.getMaterial( colorCode );
 				if ( material === null ) {
 				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() {
 	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.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;
 		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 ) {
 	computeConstructionSteps( model ) {

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

@@ -23,6 +23,7 @@ const _material_library_pattern = /^mtllib /;
 const _material_use_pattern = /^usemtl /;
 const _material_use_pattern = /^usemtl /;
 // usemap map_name
 // usemap map_name
 const _map_use_pattern = /^usemap /;
 const _map_use_pattern = /^usemap /;
+const _face_vertex_data_separator_pattern = /\s+/;
 
 
 const _vA = new Vector3();
 const _vA = new Vector3();
 const _vB = new Vector3();
 const _vB = new Vector3();
@@ -503,31 +504,22 @@ class OBJLoader extends Loader {
 		}
 		}
 
 
 		const lines = text.split( '\n' );
 		const lines = text.split( '\n' );
-		let line = '', lineFirstChar = '';
-		let lineLength = 0;
 		let result = [];
 		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 ++ ) {
 		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
 			// @todo invoke passed in handler if any
 			if ( lineFirstChar === '#' ) continue;
 			if ( lineFirstChar === '#' ) continue;
 
 
 			if ( lineFirstChar === 'v' ) {
 			if ( lineFirstChar === 'v' ) {
 
 
-				const data = line.split( /\s+/ );
+				const data = line.split( _face_vertex_data_separator_pattern );
 
 
 				switch ( data[ 0 ] ) {
 				switch ( data[ 0 ] ) {
 
 
@@ -575,7 +567,7 @@ class OBJLoader extends Loader {
 			} else if ( lineFirstChar === 'f' ) {
 			} else if ( lineFirstChar === 'f' ) {
 
 
 				const lineData = line.slice( 1 ).trim();
 				const lineData = line.slice( 1 ).trim();
-				const vertexData = lineData.split( /\s+/ );
+				const vertexData = lineData.split( _face_vertex_data_separator_pattern );
 				const faceVertices = [];
 				const faceVertices = [];
 
 
 				// Parse the face vertex data into an easy to work with format
 				// 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 ) {
 				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 ) {
 				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 ) {
 				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 ) {
 		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 headerText = '';
 			let headerLength = 0;
 			let headerLength = 0;
 			const result = patternHeader.exec( data );
 			const result = patternHeader.exec( data );
@@ -109,7 +109,7 @@ class PLYLoader extends Loader {
 				objInfo: ''
 				objInfo: ''
 			};
 			};
 
 
-			const lines = headerText.split( '\n' );
+			const lines = headerText.split( /\r\n|\r|\n/ );
 			let currentElement;
 			let currentElement;
 
 
 			function make_ply_element_property( propertValues, propertyNameMapping ) {
 			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 currentElement = 0;
 			let currentElementCount = 0;
 			let currentElementCount = 0;
 
 

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

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

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