Browse Source

Merge branch 'dev' of https://gitee.com/gogoend/three.js into zh_doc_20200520

gogoend 5 years ago
parent
commit
818cbfee10
100 changed files with 5438 additions and 5967 deletions
  1. 10 38
      .github/workflows/ci.yml
  2. 2 2
      .gitignore
  3. 76 0
      CODE_OF_CONDUCT.md
  4. 9 1
      README.md
  5. 19 16
      build/three.js
  6. 483 505
      build/three.min.js
  7. 32 29
      build/three.module.js
  8. 1 1
      docs/api/en/audio/AudioContext.html
  9. 13 16
      docs/api/en/cameras/CubeCamera.html
  10. 2 2
      docs/api/en/core/InstancedBufferGeometry.html
  11. 5 0
      docs/api/en/core/InterleavedBufferAttribute.html
  12. 2 0
      docs/api/en/extras/curves/CatmullRomCurve3.html
  13. 81 5
      docs/api/en/extras/objects/ImmediateRenderObject.html
  14. 70 0
      docs/api/en/lights/LightProbe.html
  15. 0 10
      docs/api/en/loaders/FileLoader.html
  16. 12 0
      docs/api/en/loaders/Loader.html
  17. 2 0
      docs/api/en/materials/LineBasicMaterial.html
  18. 1 4
      docs/api/en/math/Matrix4.html
  19. 138 0
      docs/api/en/math/SphericalHarmonics3.html
  20. 16 0
      docs/api/en/objects/Line.html
  21. 1 1
      docs/api/zh/audio/AudioContext.html
  22. 10 12
      docs/api/zh/cameras/CubeCamera.html
  23. 2 2
      docs/api/zh/core/InstancedBufferGeometry.html
  24. 5 0
      docs/api/zh/core/InterleavedBufferAttribute.html
  25. 2 0
      docs/api/zh/extras/curves/CatmullRomCurve3.html
  26. 80 5
      docs/api/zh/extras/objects/ImmediateRenderObject.html
  27. 70 0
      docs/api/zh/lights/LightProbe.html
  28. 2 0
      docs/api/zh/materials/LineBasicMaterial.html
  29. 0 2
      docs/api/zh/math/Matrix4.html
  30. 138 0
      docs/api/zh/math/SphericalHarmonics3.html
  31. 16 0
      docs/api/zh/objects/Line.html
  32. 46 0
      docs/examples/en/lights/LightProbeGenerator.html
  33. 46 0
      docs/examples/zh/lights/LightProbeGenerator.html
  34. 12 0
      docs/list.js
  35. 1 1
      docs/manual/en/introduction/Animation-system.html
  36. 2 2
      docs/manual/en/introduction/How-to-use-post-processing.html
  37. 1 1
      docs/manual/en/introduction/Useful-links.html
  38. 1 1
      examples/css3d_molecules.html
  39. 99 5
      examples/files.js
  40. 29 16
      examples/index.html
  41. 2 0
      examples/js/controls/DragControls.js
  42. 1 1
      examples/js/controls/TransformControls.js
  43. 16 3
      examples/js/exporters/GLTFExporter.js
  44. 15 3
      examples/js/interactive/SelectionBox.js
  45. 3 1
      examples/js/loaders/BasisTextureLoader.js
  46. 16 7
      examples/js/loaders/ColladaLoader.js
  47. 3 1
      examples/js/loaders/DRACOLoader.js
  48. 69 17
      examples/js/loaders/EXRLoader.js
  49. 7 0
      examples/js/loaders/FBXLoader.js
  50. 13 2
      examples/js/loaders/GCodeLoader.js
  51. 18 0
      examples/js/loaders/GLTFLoader.js
  52. 0 3034
      examples/js/loaders/LWOLoader.js
  53. 71 16
      examples/js/loaders/OBJLoader.js
  54. 644 18
      examples/js/loaders/VRMLLoader.js
  55. 1 0
      examples/js/postprocessing/EffectComposer.js
  56. 1 1
      examples/js/shaders/BokehShader.js
  57. 15 18
      examples/js/shaders/GodRaysShader.js
  58. 2 0
      examples/jsm/controls/DragControls.js
  59. 1 1
      examples/jsm/controls/TransformControls.js
  60. 16 3
      examples/jsm/exporters/GLTFExporter.js
  61. 1 1
      examples/jsm/geometries/ParametricGeometries.d.ts
  62. 15 3
      examples/jsm/interactive/SelectionBox.js
  63. 15 15
      examples/jsm/libs/stats.module.d.ts
  64. 3 3
      examples/jsm/libs/stats.module.js
  65. 3 1
      examples/jsm/loaders/BasisTextureLoader.js
  66. 16 7
      examples/jsm/loaders/ColladaLoader.js
  67. 3 1
      examples/jsm/loaders/DRACOLoader.js
  68. 69 17
      examples/jsm/loaders/EXRLoader.js
  69. 7 0
      examples/jsm/loaders/FBXLoader.js
  70. 13 2
      examples/jsm/loaders/GCodeLoader.js
  71. 11 1
      examples/jsm/loaders/GLTFLoader.d.ts
  72. 18 0
      examples/jsm/loaders/GLTFLoader.js
  73. 3 2006
      examples/jsm/loaders/LWOLoader.js
  74. 73 17
      examples/jsm/loaders/OBJLoader.js
  75. 646 18
      examples/jsm/loaders/VRMLLoader.js
  76. 1221 0
      examples/jsm/loaders/lwo/IFFParser.js
  77. 420 0
      examples/jsm/loaders/lwo/LWO2Parser.js
  78. 380 0
      examples/jsm/loaders/lwo/LWO3Parser.js
  79. 21 13
      examples/jsm/loaders/obj2/OBJLoader2Parser.js
  80. 1 1
      examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.d.ts
  81. 1 1
      examples/jsm/misc/GPUComputationRenderer.d.ts
  82. 1 1
      examples/jsm/nodes/core/NodeBuilder.d.ts
  83. 7 7
      examples/jsm/nodes/misc/TextureCubeUVNode.js
  84. 2 2
      examples/jsm/nodes/utils/ColorSpaceNode.d.ts
  85. 1 0
      examples/jsm/postprocessing/EffectComposer.js
  86. 2 4
      examples/jsm/postprocessing/SAOPass.d.ts
  87. 2 4
      examples/jsm/postprocessing/SSAOPass.d.ts
  88. 1 1
      examples/jsm/postprocessing/ShaderPass.d.ts
  89. 1 1
      examples/jsm/renderers/Projector.d.ts
  90. 1 1
      examples/jsm/shaders/BokehShader.js
  91. 0 1
      examples/jsm/shaders/GodRaysShader.d.ts
  92. 16 19
      examples/jsm/shaders/GodRaysShader.js
  93. 3 2
      examples/jsm/utils/TypedArrayUtils.d.ts
  94. 1 6
      examples/misc_animation_authoring.html
  95. 1 1
      examples/misc_animation_keys.html
  96. BIN
      examples/models/gltf/Xbot.blend
  97. BIN
      examples/models/gltf/Xbot.glb
  98. 1 1
      examples/models/obj/verify/verify.obj
  99. 7 6
      examples/models/vrml/creaseAngle.wrl
  100. 0 0
      examples/models/vrml/crystal.wrl

+ 10 - 38
.github/workflows/ci.yml

@@ -23,15 +23,7 @@ jobs:
       - name: Install node
       - name: Install node
         uses: actions/setup-node@v1
         uses: actions/setup-node@v1
         with:
         with:
-          node-version: '10.x'
-      - name: Restore cache
-        uses: actions/cache@v1
-        env:
-          cache-name: cache-node-modules
-        with:
-          path: ~/.npm
-          key: ${{ runner.os }}-ci-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
-          restore-keys: ${{ runner.os }}-ci-${{ env.cache-name }}-
+          node-version: 10
       - name: Install packages
       - name: Install packages
         run: npm ci
         run: npm ci
 
 
@@ -47,17 +39,11 @@ jobs:
       - name: Install node
       - name: Install node
         uses: actions/setup-node@v1
         uses: actions/setup-node@v1
         with:
         with:
-          node-version: '10.x'
-      - name: Restore cache
-        uses: actions/cache@v1
-        env:
-          cache-name: cache-node-modules
-        with:
-          path: ~/.npm
-          key: ${{ runner.os }}-ci-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
-          restore-keys: ${{ runner.os }}-ci-${{ env.cache-name }}-
+          node-version: 10
       - name: Install packages
       - name: Install packages
-        run: npm ci && npm run build-test
+        run: npm ci && npm ci --prefix test
+      - name: Build
+        run: npm run build
 
 
       - name: === Unit testing ===
       - name: === Unit testing ===
         run: npm run test-unit
         run: npm run test-unit
@@ -77,17 +63,11 @@ jobs:
       - name: Install node
       - name: Install node
         uses: actions/setup-node@v1
         uses: actions/setup-node@v1
         with:
         with:
-          node-version: '10.x'
-      - name: Restore cache
-        uses: actions/cache@v1
-        env:
-          cache-name: cache-node-modules
-        with:
-          path: ~/.npm
-          key: ${{ runner.os }}-ci-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
-          restore-keys: ${{ runner.os }}-ci-${{ env.cache-name }}-
+          node-version: 10
       - name: Install packages
       - name: Install packages
-        run: npm ci && sudo apt-get install xvfb && npm run build
+        run: npm ci && npm ci --prefix test && sudo apt-get install xvfb
+      - name: Build
+        run: npm run build
 
 
       - name: === E2E testing ===
       - name: === E2E testing ===
         run: xvfb-run --auto-servernum npm run test-e2e
         run: xvfb-run --auto-servernum npm run test-e2e
@@ -101,15 +81,7 @@ jobs:
       - name: Install node
       - name: Install node
         uses: actions/setup-node@v1
         uses: actions/setup-node@v1
         with:
         with:
-          node-version: '10.x'
-      - name: Restore cache
-        uses: actions/cache@v1
-        env:
-          cache-name: cache-node-modules
-        with:
-          path: ~/.npm
-          key: ${{ runner.os }}-ci-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
-          restore-keys: ${{ runner.os }}-ci-${{ env.cache-name }}-
+          node-version: 10
       - name: Install packages
       - name: Install packages
         run: npm ci
         run: npm ci
 
 

+ 2 - 2
.gitignore

@@ -1,10 +1,10 @@
 .DS_Store
 .DS_Store
 *.swp
 *.swp
 .project
 .project
-node_modules
 .idea/
 .idea/
 .vscode/
 .vscode/
 npm-debug.log
 npm-debug.log
 .jshintrc
 .jshintrc
 .vs/
 .vs/
-test/unit/three.*.unit.js
+**/node_modules
+test/unit/build

+ 76 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at [email protected]. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq

+ 9 - 1
README.md

@@ -64,7 +64,15 @@ function animate() {
 }
 }
 ```
 ```
 
 
-If everything went well you should see [this](https://jsfiddle.net/8kubjpL5/).
+If everything went well, you should see [this](https://jsfiddle.net/8kubjpL5/).
+
+### Cloning this repository ###
+
+Cloning the repo with all its history results in a ~2GB download. If you don't need the whole history you can use the `depth` parameter to significantly reduce download size.
+
+```sh
+git clone --depth=1 https://github.com/mrdoob/three.js.git
+```
 
 
 ### Change log ###
 ### Change log ###
 
 

File diff suppressed because it is too large
+ 19 - 16
build/three.js


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


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


+ 1 - 1
docs/api/en/audio/AudioContext.html

@@ -29,7 +29,7 @@
 		otherwise set it to a new [link:https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext].
 		otherwise set it to a new [link:https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext].
 		</p>
 		</p>
 
 
-		<h3>[method:AudioContext setContext]( [param:AudioConetxt value] )</h3>
+		<h3>[method:AudioContext setContext]( [param:AudioContext value] )</h3>
 		<p>
 		<p>
 		 Set the variable *context* in the outer scope to *value*.
 		 Set the variable *context* in the outer scope to *value*.
 		</p>
 		</p>

+ 13 - 16
docs/api/en/cameras/CubeCamera.html

@@ -16,12 +16,16 @@
 
 
 		<h2>Code Example</h2>
 		<h2>Code Example</h2>
 
 
-		<code>// Create cube camera
-		var cubeCamera = new THREE.CubeCamera( 1, 100000, 128 );
+		<code>
+		// Create cube render target
+		var cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 128, { format: THREE.RGBFormat, generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } );
+
+		// Create cube camera
+		var cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget );
 		scene.add( cubeCamera );
 		scene.add( cubeCamera );
 
 
 		// Create car
 		// Create car
-		var chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeCamera.renderTarget.texture } );
+		var chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } );
 		var car = new Mesh( carGeometry, chromeMaterial );
 		var car = new Mesh( carGeometry, chromeMaterial );
 		scene.add( car );
 		scene.add( car );
 
 
@@ -45,36 +49,29 @@
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
 
 
 
 
-		<h3>[name]( [param:Number near], [param:Number far], [param:Number cubeResolution], [param:Object options] )</h3>
+		<h3>[name]( [param:Number near], [param:Number far], [param:WebGLCubeRenderTarget renderTarget] )</h3>
 		<p>
 		<p>
 		near -- The near clipping distance. <br />
 		near -- The near clipping distance. <br />
-		far -- The far clipping distance <br />
-		cubeResolution -- Sets the length of the cube's edges. <br />
-		options - (optional) object that holds texture parameters passed to the auto-generated WebGLCubeRenderTarget.
-		If not specified, the options default to:
-		<code>
-		{ format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter }
-		</code>
-
+		far -- The far clipping distance. <br />
+		renderTarget -- The destination cube render target.
 		</p>
 		</p>
+
 		<p>
 		<p>
 		Constructs a CubeCamera that contains 6 [page:PerspectiveCamera PerspectiveCameras] that
 		Constructs a CubeCamera that contains 6 [page:PerspectiveCamera PerspectiveCameras] that
 		render to a [page:WebGLCubeRenderTarget].
 		render to a [page:WebGLCubeRenderTarget].
 		</p>
 		</p>
 
 
-
 		<h2>Properties</h2>
 		<h2>Properties</h2>
 		<p>See the base [page:Object3D] class for common properties.</p>
 		<p>See the base [page:Object3D] class for common properties.</p>
 
 
 		<h3>[property:WebGLCubeRenderTarget renderTarget]</h3>
 		<h3>[property:WebGLCubeRenderTarget renderTarget]</h3>
 		<p>
 		<p>
-		The cube texture that gets generated.
+		The destination cube render target.
 		</p>
 		</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 		<p>See the base [page:Object3D] class for common methods.</p>
 		<p>See the base [page:Object3D] class for common methods.</p>
 
 
-
 		<h3>[method:null update]( [param:WebGLRenderer renderer], [param:Scene scene] )</h3>
 		<h3>[method:null update]( [param:WebGLRenderer renderer], [param:Scene scene] )</h3>
 		<p>
 		<p>
 		renderer -- The current WebGL renderer <br />
 		renderer -- The current WebGL renderer <br />
@@ -86,7 +83,7 @@
 
 
 		<h3>[method:null clear]( [param:WebGLRenderer renderer], [param:Boolean color], [param:Boolean depth], [param:Boolean stencil] )</h3>
 		<h3>[method:null clear]( [param:WebGLRenderer renderer], [param:Boolean color], [param:Boolean depth], [param:Boolean stencil] )</h3>
 		<p>
 		<p>
-		Call this to clear the [page:CubeCamera.renderTarget renderTarget] color, depth, and/or stencil buffers.
+		Call this to clear the renderTarget's color, depth, and/or stencil buffers.
 		The color buffer is set to the renderer's current clear color. Arguments default to *true*.
 		The color buffer is set to the renderer's current clear color. Arguments default to *true*.
 		</p>
 		</p>
 
 

+ 2 - 2
docs/api/en/core/InstancedBufferGeometry.html

@@ -24,9 +24,9 @@
 		<h2>Properties</h2>
 		<h2>Properties</h2>
 		<p>See [page:BufferGeometry] for inherited properties.</p>
 		<p>See [page:BufferGeometry] for inherited properties.</p>
 
 
-		<h3>[property:Number maxInstancedCount]</h3>
+		<h3>[property:Number instanceCount]</h3>
 		<p>
 		<p>
-			Default is *undefined*.
+			Default is *Infinity*.
 		</p>
 		</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>

+ 5 - 0
docs/api/en/core/InterleavedBufferAttribute.html

@@ -45,6 +45,11 @@
 			How many values make up each item.
 			How many values make up each item.
 		</p>
 		</p>
 
 
+		<h3>[property:String name]</h3>
+		<p>
+		Optional name for this attribute instance. Default is an empty string.
+		</p>
+
 		<h3>[property:Integer offset]</h3>
 		<h3>[property:Integer offset]</h3>
 		<p>
 		<p>
 			The offset in the underlying array buffer where an item starts.
 			The offset in the underlying array buffer where an item starts.

+ 2 - 0
docs/api/en/extras/curves/CatmullRomCurve3.html

@@ -72,6 +72,8 @@
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 		<p>See the base [page:Curve] class for common methods.</p>
 		<p>See the base [page:Curve] class for common methods.</p>
 
 
+		<h2>Source</h2>
+
 		<p>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 		</p>
 		</p>

+ 81 - 5
docs/api/en/extras/objects/ImmediateRenderObject.html

@@ -12,27 +12,103 @@
 
 
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
-		<p class="desc">base class for immediate rendering objects.</p>
+		<p class="desc">
+			This experimental class provides a fast code path for rendering meshes with frequently updated
+			geometry data. When the renderer encounters an instance of [name], it only takes care about
+			the most primitive rendering operations (e.g. binding vertex attributes, determining correct shader
+			program or perfoming the actual draw call). Features like view frustum culling, wireframe rendering
+			or using multiple materials are not supported. Besides [name] can only be used to render triangles.
+		</p>
+
+		<p class="desc">
+			[name] does not work with instances of [page:BufferGeometry] or [page:Geometry]. The
+			raw geometry data have to be maintained as properties of the [name].
+		</p>
+
+		<p class="desc">
+			Using [name] makes only sense if you are updating your geometry data per frame. You can then
+			benefit of a faster code path compared to the default mesh redering logic.
+		</p>
 
 
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_marchingcubes Marching Cubes]
+		</p>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
 
 
 
 
-		<h3>[name]()</h3>
+		<h3>[name]( [param:Material material] )</h3>
 		<p>
 		<p>
-		This creates a new [name].
+		[page:Material material] — The material of the [name].
 		</p>
 		</p>
 
 
+		<h2>Properties</h2>
+		<p>See the base [page:Object3D] class for common properties.</p>
+
+		<h3>[property:Boolean material]</h3>
+		<p>
+			The material of the [name]. Assigning multiple materials is not supported.
+		</p>
+
+		<h3>[property:Boolean hasPositions]</h3>
+		<p>
+			Whether position data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasNormals]</h3>
+		<p>
+			Whether normal data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasColors]</h3>
+		<p>
+			Whether color data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasUvs]</h3>
+		<p>
+			Whether texture coordinates are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Float32Array positionArray]</h3>
+		<p>
+			The buffer holding position data. Default is *null*.
+		</p>
+
+		<h3>[property:Float32Array normalArray]</h3>
+		<p>
+			The buffer holding normal data. Default is *null*.
+		</p>
+
+		<h3>[property:Float32Array colorArray]</h3>
+		<p>
+			The buffer holding color data. Default is *null*.
+		</p>
+
+		<h3>[property:Float32Array uvArray]</h3>
+		<p>
+			The buffer holding texture coordinates. Default is *null*.
+		</p>
+
+		<h3>[property:Integer count]</h3>
+		<p>
+			The number of primitives to be rendered. Default is *0*.
+			This property will be set to *0* after each rendering so you usually
+			set it in the implementatio of [page:.render]().
+		</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
+		<p>See the base [page:Object3D] class for common methods.</p>
 
 
 		<h3>[method:null render]([param:Function renderCallback])</h3>
 		<h3>[method:null render]([param:Function renderCallback])</h3>
 		<p>
 		<p>
-		renderCallback -- A function to render the generated object.
+		renderCallback -- A function to render the generated geometry data.
 		</p>
 		</p>
 		<p>
 		<p>
-		This function needs to be overridden to start the creation of the object and should call renderCallback when finished.
+		This method needs to be implemented by the deriving class of [name]. You normally want to implement the
+		vertex buffer update logic and execute *renderCallback* at the end of your implementation.
 		</p>
 		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>

+ 70 - 0
docs/api/en/lights/LightProbe.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+			Light probes are an alternative way of adding light to a 3D scene. Unlike classical light sources (e.g. directional,
+			point or spot lights), light probes do not emit light. Instead they store information about light passing through
+			3D space. During rendering, the light that hits a 3D object is approximated by using the data from the light probe.
+		</p>
+
+		<p class="desc">
+			Light probes are usually created from (radiance) environment maps. The class [page:LightProbeGenerator] can
+			be used to create light probes from instances of [page:CubeTexture] or [page:WebGLCubeRenderTarget].
+			However, light estimation data could also be provided in other forms e.g. by WebXR. This enables the rendering
+			of augmented reality content that reacts to real world lighting.
+		</p>
+
+		<p class="desc">
+			The current probe implementation in three.js supports so-called diffuse light probes. This type of light probe
+			is functionally equivalent to an irradiance environment map.
+		</p>
+
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_lightprobe WebGL / light probe ]<br />
+			[example:webgl_lightprobe_cubecamera WebGL / light probe / cube camera ]
+		</p>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [param:SphericalHarmonics3 sh], [param:Float intensity] )</h3>
+		<p>
+		[page:SphericalHarmonics3 sh] - (optional) An instance of [page:SphericalHarmonics3].<br />
+		[page:Float intensity] - (optional) Numeric value of the light probe's intensity. Default is 1.<br /><br />
+
+		Creates a new [name].
+		</p>
+
+		<h2>Properties</h2>
+		<p>
+				See the base [page:Light Light] class for common properties. The [page:Light.color color] property is currently
+				not evaluated and thus has no effect.
+		</p>
+
+		<h3>[property:SphericalHarmonics3 sh]</h3>
+		<p>
+			A light probe uses spherical harmonics to encode lighting information.
+		</p>
+
+		<h2>Methods</h2>
+		<p>
+				See the base [page:Light Light] class for common methods.
+		</p>
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 0 - 10
docs/api/en/loaders/FileLoader.html

@@ -69,9 +69,6 @@
 			See [page:.setMimeType]. Default is *undefined*.
 			See [page:.setMimeType]. Default is *undefined*.
 		</p>
 		</p>
 
 
-		<h3>[property:Object requestHeader]</h3>
-		<p>The [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] used in HTTP request. See [page:.setRequestHeader]. Default is *undefined*.</p>
-
 		<h3>[property:String responseType]</h3>
 		<h3>[property:String responseType]</h3>
 		<p>The expected response type. See [page:.setResponseType]. Default is *undefined*.</p>
 		<p>The expected response type. See [page:.setResponseType]. Default is *undefined*.</p>
 
 
@@ -102,13 +99,6 @@
 			of the file being loaded. Note that in many cases this will be determined automatically, so by default it is *undefined*.
 			of the file being loaded. Note that in many cases this will be determined automatically, so by default it is *undefined*.
 		</p>
 		</p>
 
 
-		<h3>[method:FileLoader setRequestHeader]( [param:Object requestHeader] )</h3>
-		<p>
-			[page:object requestHeader] - key: The name of the header whose value is to be set. value: The value to set as the body of the header.<br /><br />
-
-			Set the [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] used in HTTP request.
-		</p>
-
 		<h3>[method:FileLoader setResponseType]( [param:String responseType] )</h3>
 		<h3>[method:FileLoader setResponseType]( [param:String responseType] )</h3>
 		<p>
 		<p>
 			Change the response type. Valid values are:<br />
 			Change the response type. Valid values are:<br />

+ 12 - 0
docs/api/en/loaders/Loader.html

@@ -50,6 +50,11 @@
 			Default is the empty string.
 			Default is the empty string.
 		</p>
 		</p>
 
 
+		<h3>[property:Object requestHeader]</h3>
+		<p>
+			The [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] used in HTTP request. See [page:.setRequestHeader]. Default is empty object.
+		</p>
+
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
 		<h3>[method:void load]()</h3>
 		<h3>[method:void load]()</h3>
@@ -89,6 +94,13 @@
 			[page:String resourcePath] — Set the base path for dependent resources like textures.
 			[page:String resourcePath] — Set the base path for dependent resources like textures.
 		</p>
 		</p>
 
 
+		<h3>[method:Loader setRequestHeader]( [param:Object requestHeader] )</h3>
+		<p>
+			[page:object requestHeader] - key: The name of the header whose value is to be set. value: The value to set as the body of the header.<br /><br />
+
+			Set the [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] used in HTTP request.
+		</p>
+
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>

+ 2 - 0
docs/api/en/materials/LineBasicMaterial.html

@@ -88,6 +88,8 @@
 			property and it is ignored by the [page:WebGLRenderer WebGL] renderer.
 			property and it is ignored by the [page:WebGLRenderer WebGL] renderer.
 		</p>
 		</p>
 
 
+		<h3>[property:Boolean morphTargets]</h3>
+		<p>Define whether the material uses morphTargets. Default is false.</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 		<p>See the base [page:Material] class for common methods.</p>
 		<p>See the base [page:Material] class for common methods.</p>

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

@@ -125,10 +125,7 @@ m.elements = [ 11, 21, 31, 41,
 		<h3>[method:this compose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] )</h3>
 		<h3>[method:this compose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] )</h3>
 		<p>
 		<p>
 		Sets this matrix to the transformation composed of [page:Vector3 position],
 		Sets this matrix to the transformation composed of [page:Vector3 position],
-		[page:Quaternion quaternion] and [page:Vector3 scale]. Internally this calls
-		[page:.makeRotationFromQuaternion makeRotationFromQuaternion]( [page:Quaternion quaternion] )
-		followed by [page:.scale scale]( [page:Vector3 scale] ), then finally
-		[page:.setPosition setPosition]( [page:Vector3 position] ).
+		[page:Quaternion quaternion] and [page:Vector3 scale].
 		</p>
 		</p>
 
 
 		<h3>[method:this copy]( [param:Matrix4 m] )</h3>
 		<h3>[method:this copy]( [param:Matrix4 m] )</h3>

+ 138 - 0
docs/api/en/math/SphericalHarmonics3.html

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<p class="desc">Represents a third-order spherical harmonics (SH). Light probes use this class to encode lighting information.</p>
+
+		<h2>Constructor</h2>
+		<h3>[name]()</h3>
+		<p>
+		Creates a new instance of [name].
+		</p>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Array coefficients]</h3>
+		<p>An array holding the (9) SH coefficients. A single coefficient is represented as an instance of [page:Vector3].</p>
+
+		<h2>Methods</h2>
+
+		<h3>[method:SphericalHarmonics3 add]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to add.<br /><br />
+
+			Adds the given SH to this instance.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 addScaledSH]( [param:SphericalHarmonics3 sh], [param:Number scale] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to add.<br />
+			[page:Number scale] - The scale factor.<br /><br />
+
+			A convenience method for performing [page:.add]() and [page:.scale]() at once.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 clone]()</h3>
+		<p>
+			Returns a new instance of [name] with equal coefficients.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 copy]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to copy.<br /><br />
+
+			Copies the given SH to this instance.
+		</p>
+
+		<h3>[method:Boolean equals]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to compare with.<br /><br />
+
+			Returns true if the given SH and this instance have equal coefficients.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 fromArray]( [param:Array array], [param:Number offset] )</h3>
+		<p>
+			[page:Array array] - The array holding the numbers of the SH coefficients.<br />
+			[page:Number offset] - (optional) The array offset.<br /><br />
+
+			Sets the coefficients of this instance from the given array.
+		</p>
+
+		<h3>[method:Vector3 getAt]( [param:Vector3 normal], [param:Vector3 target] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Vector3 target] - The result vector.<br /><br />
+
+			Returns the radiance in the direction of the given normal.
+		</p>
+
+		<h3>[method:Vector3 getIrradianceAt]( [param:Vector3 normal], [param:Vector3 target] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Vector3 target] - The result vector.<br /><br />
+
+			Returns the irradiance (radiance convolved with cosine lobe) in the direction of the given normal.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 lerp]( [param:SphericalHarmonics3 sh], [param:Number alpha] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to interpolate with.<br />
+			[page:Number alpha] - The alpha factor.<br /><br />
+
+			Linear interpolates between the given SH and this instance by the given alpha factor.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 scale]( [param:Number scale] )</h3>
+		<p>
+			[page:Number sh] - The scale factor.<br /><br />
+
+			Scales this SH by the given scale factor.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 set]( [param:Array coefficients] )</h3>
+		<p>
+			[page:Array coefficients] - An array of SH coefficients.<br /><br />
+
+			Sets the given SH coefficients to this instance.
+		</p>
+
+		<h3>[method:Array toArray]( [param:Array array], [param:Number offset] )</h3>
+		<p>
+			[page:Array array] - (optional) The target array.<br />
+			[page:Number offset] - (optional) The array offset.<br /><br />
+
+			Returns an array with the coefficients, or copies them into the provided array. The coefficients
+			are represented as numbers.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 zero]()</h3>
+		<p>
+			Sets all SH coefficients to 0.
+		</p>
+
+		<h2>Static Methods</h2>
+
+		<h3>[method:void getBasisAt]( [param:Vector3 normal], [param:Array shBasis] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Array shBasis] - The resulting SH basis.<br /><br />
+
+			Computes the SH basis for the given normal vector.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 16 - 0
docs/api/en/objects/Line.html

@@ -60,6 +60,17 @@
 		<h3>[property:Material material]</h3>
 		<h3>[property:Material material]</h3>
 		<p>Material for the line.</p>
 		<p>Material for the line.</p>
 
 
+		<h3>[property:Array morphTargetInfluences]</h3>
+		<p>
+		An array of weights typically from 0-1 that specify how much of the morph is applied.
+		Undefined by default, but reset to a blank array by [page:.updateMorphTargets]().
+		</p>
+
+		<h3>[property:Object morphTargetDictionary]</h3>
+		<p>
+		A dictionary of morphTargets based on the morphTarget.name property.
+		Undefined by default, but rebuilt [page:.updateMorphTargets]().
+		</p>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 		<p>See the base [page:Object3D] class for common methods.</p>
 		<p>See the base [page:Object3D] class for common methods.</p>
@@ -80,6 +91,11 @@
 		Returns a clone of this Line object and its descendants.
 		Returns a clone of this Line object and its descendants.
 		</p>
 		</p>
 
 
+		<h3>[method:null updateMorphTargets]()</h3>
+		<p>
+		Updates the morphTargets to have no influence on the object. Resets the
+		[page:.morphTargetInfluences] and [page:.morphTargetDictionary] properties.
+		</p>
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 

+ 1 - 1
docs/api/zh/audio/AudioContext.html

@@ -29,7 +29,7 @@
 		否则创建一个新的[link:https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext].
 		否则创建一个新的[link:https://developer.mozilla.org/en-US/docs/Web/API/AudioContext AudioContext].
 		</p>
 		</p>
 
 
-		<h3>[method:AudioContext setContext]( [param:AudioConetxt value] )</h3>
+		<h3>[method:AudioContext setContext]( [param:AudioContext value] )</h3>
 		<p>
 		<p>
 		 外部用来设置 *context* 的值.
 		 外部用来设置 *context* 的值.
 		</p>
 		</p>

+ 10 - 12
docs/api/zh/cameras/CubeCamera.html

@@ -16,12 +16,16 @@
 
 
 		<h2>代码示例</h2>
 		<h2>代码示例</h2>
 
 
-		<code>// Create cube camera
-		var cubeCamera = new THREE.CubeCamera( 1, 100000, 128 );
+		<code>
+		// Create cube render target
+		var cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 128, { format: THREE.RGBFormat, generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } );
+
+		// Create cube camera
+		var cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget );
 		scene.add( cubeCamera );
 		scene.add( cubeCamera );
 
 
 		// Create car
 		// Create car
-		var chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeCamera.renderTarget.texture } );
+		var chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } );
 		var car = new Mesh( carGeometry, chromeMaterial );
 		var car = new Mesh( carGeometry, chromeMaterial );
 		scene.add( car );
 		scene.add( car );
 
 
@@ -46,16 +50,11 @@
 
 
 
 
 
 
-		<h3>[name]( [param:Number near], [param:Number far], [param:Number cubeResolution], [param:Object options] )</h3>
+		<h3>[name]( [param:Number near], [param:Number far], [param:WebGLCubeRenderTarget renderTarget] )</h3>
 		<p>
 		<p>
 		near -- 近剪切面的距离<br />
 		near -- 近剪切面的距离<br />
 		far -- 远剪切面的距离<br />
 		far -- 远剪切面的距离<br />
-		cubeResolution -- 设置立方体边缘的长度<br />
-		options - (optional) object that holds texture parameters passed to the auto-generated WebGLCubeRenderTarget.
-		If not specified, the options default to:
-		<code>
-		{ format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter }
-		</code>
+		renderTarget -- The destination cube render target.
 		</p>
 		</p>
 		<p>
 		<p>
 		构造一个包含6个[page:PerspectiveCamera PerspectiveCameras](透视摄像机)的立方摄像机,
 		构造一个包含6个[page:PerspectiveCamera PerspectiveCameras](透视摄像机)的立方摄像机,
@@ -68,8 +67,7 @@
 
 
 		<h3>[property:WebGLCubeRenderTarget renderTarget]</h3>
 		<h3>[property:WebGLCubeRenderTarget renderTarget]</h3>
 		<p>
 		<p>
-		生成的立方体纹理<br>
-		(译注:生成的立方体纹理保存在其中的.texture对象中,可作为贴图赋值给其他材质)
+			The destination cube render target.
 		</p>
 		</p>
 
 
 		<h2>方法</h2>
 		<h2>方法</h2>

+ 2 - 2
docs/api/zh/core/InstancedBufferGeometry.html

@@ -24,9 +24,9 @@
 		<h2>属性</h2>
 		<h2>属性</h2>
 		<p>继承属性详见 [page:BufferGeometry]。</p>
 		<p>继承属性详见 [page:BufferGeometry]。</p>
 
 
-		<h3>[property:Number maxInstancedCount]</h3>
+		<h3>[property:Number instanceCount]</h3>
 		<p>
 		<p>
-			默认值是 *undefined*。
+			默认值是 *Infinity*。
 		</p>
 		</p>
 
 
 		<h2>方法</h2>
 		<h2>方法</h2>

+ 5 - 0
docs/api/zh/core/InterleavedBufferAttribute.html

@@ -44,6 +44,11 @@
 			队列中每个矢量有多少个元素构成。
 			队列中每个矢量有多少个元素构成。
 		</p>
 		</p>
 
 
+		<h3>[property:String name]</h3>
+		<p>
+		Optional name for this attribute instance. Default is an empty string.
+		</p>
+
 		<h3>[property:Integer offset]</h3>
 		<h3>[property:Integer offset]</h3>
 		<p>
 		<p>
 			缓存队列中每个元素的起始位置的偏移量。
 			缓存队列中每个元素的起始位置的偏移量。

+ 2 - 0
docs/api/zh/extras/curves/CatmullRomCurve3.html

@@ -73,6 +73,8 @@
 		<h2>方法</h2>
 		<h2>方法</h2>
 		<p>共有方法请参见其基类[page:Curve]。</p>
 		<p>共有方法请参见其基类[page:Curve]。</p>
 
 
+		<h2>源代码</h2>
+
 		<p>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 		</p>
 		</p>

+ 80 - 5
docs/api/zh/extras/objects/ImmediateRenderObject.html

@@ -12,27 +12,102 @@
 
 
 		<h1>即时渲染对象([name])</h1>
 		<h1>即时渲染对象([name])</h1>
 
 
-		<p class="desc">即时渲染对象的基类。</p>
+		<p class="desc">
+			This experimental class provides a fast code path for rendering meshes with frequently updated
+			geometry data. When the renderer encounters an instance of [name], it only takes care about
+			the most primitive rendering operations (e.g. binding vertex attributes, determining correct shader
+			program or perfoming the actual draw call). Features like view frustum culling, wireframe rendering
+			or using multiple materials are not supported. Besides [name] can only be used to render triangles.
+		</p>
 
 
+		<p class="desc">
+			[name] does not work with instances of [page:BufferGeometry] or [page:Geometry]. The
+			raw geometry data have to be maintained as properties of the [name].
+		</p>
+
+		<p class="desc">
+			Using [name] makes only sense if you are updating your geometry data per frame. You can then
+			benefit of a faster code path compared to the default mesh redering logic.
+		</p>
+
+		<h2>例子</h2>
+		<p>
+			[example:webgl_marchingcubes Marching Cubes]
+		</p>
 
 
 		<h2>构造函数(Constructor)</h2>
 		<h2>构造函数(Constructor)</h2>
 
 
+		<h3>[name]( [param:Material material] )</h3>
+		<p>
+		[page:Material material] — The material of the [name].
+		</p>
+
+		<h2>Properties</h2>
+		<p>See the base [page:Object3D] class for common properties.</p>
+
+		<h3>[property:Boolean material]</h3>
+		<p>
+			The material of the [name]. Assigning multiple materials is not supported.
+		</p>
+
+		<h3>[property:Boolean hasPositions]</h3>
+		<p>
+			Whether position data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasNormals]</h3>
+		<p>
+			Whether normal data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasColors]</h3>
+		<p>
+			Whether color data are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Boolean hasUvs]</h3>
+		<p>
+			Whether texture coordinates are defined or not. Default is *false*.
+		</p>
+
+		<h3>[property:Float32Array positionArray]</h3>
+		<p>
+			The buffer holding position data. Default is *null*.
+		</p>
+
+		<h3>[property:Float32Array normalArray]</h3>
+		<p>
+			The buffer holding normal data. Default is *null*.
+		</p>
 
 
-		<h3>[name]()</h3>
+		<h3>[property:Float32Array colorArray]</h3>
 		<p>
 		<p>
-			这会创建一个新的 [name]。
+			The buffer holding color data. Default is *null*.
 		</p>
 		</p>
 
 
+		<h3>[property:Float32Array uvArray]</h3>
+		<p>
+			The buffer holding texture coordinates. Default is *null*.
+		</p>
+
+		<h3>[property:Integer count]</h3>
+		<p>
+			The number of primitives to be rendered. Default is *0*.
+			This property will be set to *0* after each rendering so you usually
+			set it in the implementatio of [page:.render]().
+		</p>
 
 
 		<h2>方法(Methods)</h2>
 		<h2>方法(Methods)</h2>
 
 
+		<p>See the base [page:Object3D] class for common methods.</p>
 
 
 		<h3>[method:null render]([param:Function renderCallback])</h3>
 		<h3>[method:null render]([param:Function renderCallback])</h3>
 		<p>
 		<p>
-		renderCallback -- 生成对象的渲染函数。
+		renderCallback -- A function to render the generated geometry data.
 		</p>
 		</p>
 		<p>
 		<p>
-		需要重写此函数以开始创建对象,并在完成时调用renderCallback。
+		This method needs to be implemented by the deriving class of [name]. You normally want to implement the
+		vertex buffer update logic and execute *renderCallback* at the end of your implementation.
 		</p>
 		</p>
 
 
 		<h2>源码(Source)</h2>
 		<h2>源码(Source)</h2>

+ 70 - 0
docs/api/zh/lights/LightProbe.html

@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+			Light probes are an alternative way of adding light to a 3D scene. Unlike classical light sources (e.g. directional,
+			point or spot lights), light probes do not emit light. Instead they store information about light passing through
+			3D space. During rendering, the light that hits a 3D object is approximated by using the data from the light probe.
+		</p>
+
+		<p class="desc">
+			Light probes are usually created from (radiance) environment maps. The class [page:LightProbeGenerator] can
+			be used to create light probes from instances of [page:CubeTexture] or [page:WebGLCubeRenderTarget].
+			However, light estimation data could also be provided in other forms e.g. by WebXR. This enables the rendering
+			of augmented reality content that reacts to real world lighting.
+		</p>
+
+		<p class="desc">
+			The current probe implementation in three.js supports so-called diffuse light probes. This type of light probe
+			is functionally equivalent to an irradiance environment map.
+		</p>
+
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_lightprobe WebGL / light probe ]<br />
+			[example:webgl_lightprobe_cubecamera WebGL / light probe / cube camera ]
+		</p>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [param:SphericalHarmonics3 sh], [param:Float intensity] )</h3>
+		<p>
+		[page:SphericalHarmonics3 sh] - (optional) An instance of [page:SphericalHarmonics3].<br />
+		[page:Float intensity] - (optional) Numeric value of the light probe's intensity. Default is 1.<br /><br />
+
+		Creates a new [name].
+		</p>
+
+		<h2>Properties</h2>
+		<p>
+				See the base [page:Light Light] class for common properties. The [page:Light.color color] property is currently
+				not evaluated and thus has no effect.
+		</p>
+
+		<h3>[property:SphericalHarmonics3 sh]</h3>
+		<p>
+			A light probe uses spherical harmonics to encode lighting information.
+		</p>
+
+		<h2>Methods</h2>
+		<p>
+				See the base [page:Light Light] class for common methods.
+		</p>
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 2 - 0
docs/api/zh/materials/LineBasicMaterial.html

@@ -77,6 +77,8 @@
 			并且会被[page:WebGLRenderer WebGL]渲染器忽略。
 			并且会被[page:WebGLRenderer WebGL]渲染器忽略。
 		</p>
 		</p>
 
 
+		<h3>[property:Boolean morphTargets]</h3>
+		<p>Define whether the material uses morphTargets. Default is false.</p>
 
 
 		<h2>方法(Methods)</h2>
 		<h2>方法(Methods)</h2>
 		<p>共有方法请参见其基类[page:Material]。</p>
 		<p>共有方法请参见其基类[page:Material]。</p>

+ 0 - 2
docs/api/zh/math/Matrix4.html

@@ -117,8 +117,6 @@ m.elements = [ 11, 21, 31, 41,
 		<h3>[method:this compose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] )</h3>
 		<h3>[method:this compose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] )</h3>
 		<p>
 		<p>
 		设置将该对象由位置[page:Vector3 position],四元数[page:Quaternion quaternion] 和 缩放[page:Vector3 scale]
 		设置将该对象由位置[page:Vector3 position],四元数[page:Quaternion quaternion] 和 缩放[page:Vector3 scale]
-		组合变换的矩阵。内部先调用[page:.makeRotationFromQuaternion makeRotationFromQuaternion]( [page:Quaternion quaternion] )
-		再调用缩放[page:.scale scale]( [page:Vector3 scale] )最后是平移[page:.setPosition setPosition]( [page:Vector3 position] )。
 		</p>
 		</p>
 
 
 		<h3>[method:this copy]( [param:Matrix4 m] )</h3>
 		<h3>[method:this copy]( [param:Matrix4 m] )</h3>

+ 138 - 0
docs/api/zh/math/SphericalHarmonics3.html

@@ -0,0 +1,138 @@
+<!DOCTYPE html>
+<html lang="zh">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<p class="desc">Represents a third-order spherical harmonics (SH). Light probes use this class to encode lighting information.</p>
+
+		<h2>Constructor</h2>
+		<h3>[name]()</h3>
+		<p>
+		Creates a new instance of [name].
+		</p>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Array coefficients]</h3>
+		<p>An array holding the (9) SH coefficients. A single coefficient is represented as an instance of [page:Vector3].</p>
+
+		<h2>Methods</h2>
+
+		<h3>[method:SphericalHarmonics3 add]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to add.<br /><br />
+
+			Adds the given SH to this instance.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 addScaledSH]( [param:SphericalHarmonics3 sh], [param:Number scale] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to add.<br />
+			[page:Number scale] - The scale factor.<br /><br />
+
+			A convenience method for performing [page:.add]() and [page:.scale]() at once.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 clone]()</h3>
+		<p>
+			Returns a new instance of [name] with equal coefficients.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 copy]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to copy.<br /><br />
+
+			Copies the given SH to this instance.
+		</p>
+
+		<h3>[method:Boolean equals]( [param:SphericalHarmonics3 sh] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to compare with.<br /><br />
+
+			Returns true if the given SH and this instance have equal coefficients.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 fromArray]( [param:Array array], [param:Number offset] )</h3>
+		<p>
+			[page:Array array] - The array holding the numbers of the SH coefficients.<br />
+			[page:Number offset] - (optional) The array offset.<br /><br />
+
+			Sets the coefficients of this instance from the given array.
+		</p>
+
+		<h3>[method:Vector3 getAt]( [param:Vector3 normal], [param:Vector3 target] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Vector3 target] - The result vector.<br /><br />
+
+			Returns the radiance in the direction of the given normal.
+		</p>
+
+		<h3>[method:Vector3 getIrradianceAt]( [param:Vector3 normal], [param:Vector3 target] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Vector3 target] - The result vector.<br /><br />
+
+			Returns the irradiance (radiance convolved with cosine lobe) in the direction of the given normal.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 lerp]( [param:SphericalHarmonics3 sh], [param:Number alpha] )</h3>
+		<p>
+			[page:SphericalHarmonics3 sh] - The SH to interpolate with.<br />
+			[page:Number alpha] - The alpha factor.<br /><br />
+
+			Linear interpolates between the given SH and this instance by the given alpha factor.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 scale]( [param:Number scale] )</h3>
+		<p>
+			[page:Number sh] - The scale factor.<br /><br />
+
+			Scales this SH by the given scale factor.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 set]( [param:Array coefficients] )</h3>
+		<p>
+			[page:Array coefficients] - An array of SH coefficients.<br /><br />
+
+			Sets the given SH coefficients to this instance.
+		</p>
+
+		<h3>[method:Array toArray]( [param:Array array], [param:Number offset] )</h3>
+		<p>
+			[page:Array array] - (optional) The target array.<br />
+			[page:Number offset] - (optional) The array offset.<br /><br />
+
+			Returns an array with the coefficients, or copies them into the provided array. The coefficients
+			are represented as numbers.
+		</p>
+
+		<h3>[method:SphericalHarmonics3 zero]()</h3>
+		<p>
+			Sets all SH coefficients to 0.
+		</p>
+
+		<h2>Static Methods</h2>
+
+		<h3>[method:void getBasisAt]( [param:Vector3 normal], [param:Array shBasis] )</h3>
+		<p>
+			[page:Vector3 normal] - The normal vector (assumed to be unit length).<br />
+			[page:Array shBasis] - The resulting SH basis.<br /><br />
+
+			Computes the SH basis for the given normal vector.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		</p>
+	</body>
+</html>

+ 16 - 0
docs/api/zh/objects/Line.html

@@ -57,6 +57,17 @@
 		<h3>[property:Material material]</h3>
 		<h3>[property:Material material]</h3>
 		<p>线的材质。</p>
 		<p>线的材质。</p>
 
 
+		<h3>[property:Array morphTargetInfluences]</h3>
+		<p>
+		An array of weights typically from 0-1 that specify how much of the morph is applied.
+		Undefined by default, but reset to a blank array by [page:.updateMorphTargets]().
+		</p>
+
+		<h3>[property:Object morphTargetDictionary]</h3>
+		<p>
+		A dictionary of morphTargets based on the morphTarget.name property.
+		Undefined by default, but rebuilt [page:.updateMorphTargets]().
+		</p>
 
 
 		<h2>方法</h2>
 		<h2>方法</h2>
 		<p>共有方法请参见其基类 [page:Object3D]。</p>
 		<p>共有方法请参见其基类 [page:Object3D]。</p>
@@ -78,6 +89,11 @@
 			返回这条线及其子集的一个克隆对象。
 			返回这条线及其子集的一个克隆对象。
 		</p>
 		</p>
 
 
+		<h3>[method:null updateMorphTargets]()</h3>
+		<p>
+		Updates the morphTargets to have no influence on the object. Resets the
+		[page:.morphTargetInfluences] and [page:.morphTargetDictionary] properties.
+		</p>
 
 
 		<h2>源代码</h2>
 		<h2>源代码</h2>
 
 

+ 46 - 0
docs/examples/en/lights/LightProbeGenerator.html

@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+			Utility class for creating instances of [page:LightProbe].
+		</p>
+
+		<h2>Examples</h2>
+
+		<p>
+			[example:webgl_lightprobe WebGL / light probe ]<br />
+			[example:webgl_lightprobe_cubecamera WebGL / light probe / cube camera ]
+		</p>
+
+		<h2>Static Methods</h2>
+
+		<h3>[method:LightProbe fromCubeTexture] ( [param:CubeTexture cubeTexture] )</h3>
+		<p>
+			Creates a light probe from the given (radiance) environment map. The method expects that the environment map is represented as a cube texture.
+		</p>
+
+		<h3>[method:LightProbe fromCubeRenderTarget] ( [param:WebGLRenderer renderer], [param:WebGLCubeRenderTarget cubeRenderTarget] )</h3>
+		<p>
+			Creates a light probe from the given (radiance) environment map. The method expects that the environment map is represented as a cube render target.
+		</p>
+		<p>
+			The [page:Texture.format format] of the cube render target must be set to *RGBA*.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lights/LightProbeGenerator.js examples/jsm/lights/LightProbeGenerator.js]
+		</p>
+	</body>
+</html>

+ 46 - 0
docs/examples/zh/lights/LightProbeGenerator.html

@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+
+		<h1>[name]</h1>
+
+		<p class="desc">
+			Utility class for creating instances of [page:LightProbe].
+		</p>
+
+		<h2>Examples</h2>
+
+		<p>
+			[example:webgl_lightprobe WebGL / light probe ]<br />
+			[example:webgl_lightprobe_cubecamera WebGL / light probe / cube camera ]
+		</p>
+
+		<h2>Static Methods</h2>
+
+		<h3>[method:LightProbe fromCubeTexture] ( [param:CubeTexture cubeTexture] )</h3>
+		<p>
+			Creates a light probe from the given (radiance) environment map. The method expects that the environment map is represented as a cube texture.
+		</p>
+
+		<h3>[method:LightProbe fromCubeRenderTarget] ( [param:WebGLRenderer renderer], [param:WebGLCubeRenderTarget cubeRenderTarget] )</h3>
+		<p>
+			Creates a light probe from the given (radiance) environment map. The method expects that the environment map is represented as a cube render target.
+		</p>
+		<p>
+			The [page:Texture.format format] of the cube render target must be set to *RGBA*.
+		</p>
+
+		<h2>Source</h2>
+
+		<p>
+			[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lights/LightProbeGenerator.js examples/jsm/lights/LightProbeGenerator.js]
+		</p>
+	</body>
+</html>

+ 12 - 0
docs/list.js

@@ -205,6 +205,7 @@ var list = {
 				"DirectionalLight": "api/en/lights/DirectionalLight",
 				"DirectionalLight": "api/en/lights/DirectionalLight",
 				"HemisphereLight": "api/en/lights/HemisphereLight",
 				"HemisphereLight": "api/en/lights/HemisphereLight",
 				"Light": "api/en/lights/Light",
 				"Light": "api/en/lights/Light",
+				"LightProbe": "api/en/lights/LightProbe",
 				"PointLight": "api/en/lights/PointLight",
 				"PointLight": "api/en/lights/PointLight",
 				"RectAreaLight": "api/en/lights/RectAreaLight",
 				"RectAreaLight": "api/en/lights/RectAreaLight",
 				"SpotLight": "api/en/lights/SpotLight"
 				"SpotLight": "api/en/lights/SpotLight"
@@ -279,6 +280,7 @@ var list = {
 				"Ray": "api/en/math/Ray",
 				"Ray": "api/en/math/Ray",
 				"Sphere": "api/en/math/Sphere",
 				"Sphere": "api/en/math/Sphere",
 				"Spherical": "api/en/math/Spherical",
 				"Spherical": "api/en/math/Spherical",
+				"SphericalHarmonics3": "api/en/math/SphericalHarmonics3",
 				"Triangle": "api/en/math/Triangle",
 				"Triangle": "api/en/math/Triangle",
 				"Vector2": "api/en/math/Vector2",
 				"Vector2": "api/en/math/Vector2",
 				"Vector3": "api/en/math/Vector3",
 				"Vector3": "api/en/math/Vector3",
@@ -378,6 +380,10 @@ var list = {
 				"VertexTangentsHelper": "examples/en/helpers/VertexTangentsHelper"
 				"VertexTangentsHelper": "examples/en/helpers/VertexTangentsHelper"
 			},
 			},
 
 
+			"Lights": {
+				"LightProbeGenerator": "examples/en/lights/LightProbeGenerator"
+			},
+
 			"Loaders": {
 			"Loaders": {
 				"BasisTextureLoader": "examples/en/loaders/BasisTextureLoader",
 				"BasisTextureLoader": "examples/en/loaders/BasisTextureLoader",
 				"DRACOLoader": "examples/en/loaders/DRACOLoader",
 				"DRACOLoader": "examples/en/loaders/DRACOLoader",
@@ -657,6 +663,7 @@ var list = {
 				"DirectionalLight": "api/zh/lights/DirectionalLight",
 				"DirectionalLight": "api/zh/lights/DirectionalLight",
 				"HemisphereLight": "api/zh/lights/HemisphereLight",
 				"HemisphereLight": "api/zh/lights/HemisphereLight",
 				"Light": "api/zh/lights/Light",
 				"Light": "api/zh/lights/Light",
+				"LightProbe": "api/zh/lights/LightProbe",
 				"PointLight": "api/zh/lights/PointLight",
 				"PointLight": "api/zh/lights/PointLight",
 				"RectAreaLight": "api/zh/lights/RectAreaLight",
 				"RectAreaLight": "api/zh/lights/RectAreaLight",
 				"SpotLight": "api/zh/lights/SpotLight"
 				"SpotLight": "api/zh/lights/SpotLight"
@@ -731,6 +738,7 @@ var list = {
 				"Ray": "api/zh/math/Ray",
 				"Ray": "api/zh/math/Ray",
 				"Sphere": "api/zh/math/Sphere",
 				"Sphere": "api/zh/math/Sphere",
 				"Spherical": "api/zh/math/Spherical",
 				"Spherical": "api/zh/math/Spherical",
+				"SphericalHarmonics3": "api/zh/math/SphericalHarmonics3",
 				"Triangle": "api/zh/math/Triangle",
 				"Triangle": "api/zh/math/Triangle",
 				"Vector2": "api/zh/math/Vector2",
 				"Vector2": "api/zh/math/Vector2",
 				"Vector3": "api/zh/math/Vector3",
 				"Vector3": "api/zh/math/Vector3",
@@ -821,6 +829,10 @@ var list = {
 				"DecalGeometry": "examples/zh/geometries/DecalGeometry"
 				"DecalGeometry": "examples/zh/geometries/DecalGeometry"
 			},
 			},
 
 
+			"灯光": {
+				"LightProbeGenerator": "examples/zh/lights/LightProbeGenerator"
+			},
+
 			"辅助对象": {
 			"辅助对象": {
 				"FaceNormalsHelper": "examples/zh/helpers/FaceNormalsHelper",
 				"FaceNormalsHelper": "examples/zh/helpers/FaceNormalsHelper",
 				"LightProbeHelper": "examples/zh/helpers/LightProbeHelper",
 				"LightProbeHelper": "examples/zh/helpers/LightProbeHelper",

+ 1 - 1
docs/manual/en/introduction/Animation-system.html

@@ -24,7 +24,7 @@
 
 
 			To achieve all this in one homogeneous system, the three.js animation system
 			To achieve all this in one homogeneous system, the three.js animation system
 			[link:https://github.com/mrdoob/three.js/issues/6881 has completely changed in 2015]
 			[link:https://github.com/mrdoob/three.js/issues/6881 has completely changed in 2015]
-			(be aware of outdated information!), and it has now an architecture similar to
+			(beware of outdated information!), and it has now an architecture similar to
 			Unity/Unreal Engine 4. This page gives a short overview of the main components of the
 			Unity/Unreal Engine 4. This page gives a short overview of the main components of the
 			system and how they work together.
 			system and how they work together.
 
 

+ 2 - 2
docs/manual/en/introduction/How-to-use-post-processing.html

@@ -14,7 +14,7 @@
 			Many three.js applications render their 3D objects directly to the screen. Sometimes, however, you want to apply one or more graphical
 			Many three.js applications render their 3D objects directly to the screen. Sometimes, however, you want to apply one or more graphical
 			effects like Depth-Of-Field, Bloom, Film Grain or various types of Anti-aliasing. Post-processing is a widely used approach
 			effects like Depth-Of-Field, Bloom, Film Grain or various types of Anti-aliasing. Post-processing is a widely used approach
 			to implement such effects. First, the scene is rendered to a render target which represents a buffer in the video card's memory.
 			to implement such effects. First, the scene is rendered to a render target which represents a buffer in the video card's memory.
-			In the next step one ore more post-processing passes apply filters and effects to the image buffer before it is eventually rendered to
+			In the next step one or more post-processing passes apply filters and effects to the image buffer before it is eventually rendered to
 			the screen.
 			the screen.
 		</p>
 		</p>
 		<p>
 		<p>
@@ -24,7 +24,7 @@
 		<h2>Workflow</h2>
 		<h2>Workflow</h2>
 
 
 		<p>
 		<p>
-			The first step in the process is to import all necessary files from the examples directory. The guide assumes your are using the official
+			The first step in the process is to import all necessary files from the examples directory. The guide assumes you are using the official
 			[link:https://www.npmjs.com/package/three npm package] of three.js. For our basic demo in this guide we need the following files.
 			[link:https://www.npmjs.com/package/three npm package] of three.js. For our basic demo in this guide we need the following files.
 		</p>
 		</p>
 
 

+ 1 - 1
docs/manual/en/introduction/Useful-links.html

@@ -13,7 +13,7 @@
 		<p class="desc">
 		<p class="desc">
 			The following is a collection of links that you might find useful when learning three.js.<br />
 			The following is a collection of links that you might find useful when learning three.js.<br />
 			If you find something that you'd like to add here, or think that one of the links below is no longer
 			If you find something that you'd like to add here, or think that one of the links below is no longer
-			relevant or working, feel free to click the 'edit' button in the top right and make some changes!<br /><br />
+			relevant or working, feel free to click the 'edit' button in the bottom right and make some changes!<br /><br />
 
 
 			Note also that as three.js is under rapid development, a lot of these links will contain information that is
 			Note also that as three.js is under rapid development, a lot of these links will contain information that is
 			out of date - if something isn't working as you'd expect or as one of these links says it should,
 			out of date - if something isn't working as you'd expect or as one of these links says it should,

+ 1 - 1
examples/css3d_molecules.html

@@ -118,7 +118,7 @@
 			function init() {
 			function init() {
 
 
 				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 5000 );
 				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 5000 );
-				camera.position.z = 1500;
+				camera.position.z = 1000;
 
 
 				scene = new THREE.Scene();
 				scene = new THREE.Scene();
 
 

+ 99 - 5
examples/files.js

@@ -194,7 +194,6 @@ var files = {
 		"webgl_multiple_views",
 		"webgl_multiple_views",
 		"webgl_nearestneighbour",
 		"webgl_nearestneighbour",
 		"webgl_panorama_cube",
 		"webgl_panorama_cube",
-		"webgl_panorama_dualfisheye",
 		"webgl_panorama_equirectangular",
 		"webgl_panorama_equirectangular",
 		"webgl_performance",
 		"webgl_performance",
 		"webgl_performance_doublesided",
 		"webgl_performance_doublesided",
@@ -298,6 +297,7 @@ var files = {
 		"webgl_custom_attributes_points3",
 		"webgl_custom_attributes_points3",
 		"webgl_fire",
 		"webgl_fire",
 		"webgl_gpgpu_birds",
 		"webgl_gpgpu_birds",
+		"webgl_gpgpu_birds_gltf",
 		"webgl_gpgpu_water",
 		"webgl_gpgpu_water",
 		"webgl_gpgpu_protoplanet",
 		"webgl_gpgpu_protoplanet",
 		"webgl_instancing_modified",
 		"webgl_instancing_modified",
@@ -393,12 +393,106 @@ var files = {
 };
 };
 
 
 var tags = {
 var tags = {
-	"webgl_clipping_intersection": [ "csg", "solid" ],
-	"webgl_fire": [ "smoke" ],
-	"webgl_materials_translucency": [ "subsurface", "scattering" ],
+	"webgl_animation_cloth": [ "physics", "integration" ],
+	"webgl_clipping": [ "solid" ],
+	"webgl_clipping_advanced": [ "solid" ],
+	"webgl_clipping_intersection": [ "solid" ],
+	"webgl_clipping_stencil": [ "solid" ],
+	"webgl_decals": [ "normals" ],
+	"webgl_depth_texture": [ "renderTarget" ],
+	"webgl_framebuffer_texture": [ "renderTarget" ],
+	"webgl_geometry_colors_lookuptable": [ "vertex" ],
+	"webgl_geometry_hierarchy": [ "group" ],
+	"webgl_geometry_hierarchy2": [ "scene graph" ],
+	"webgl_geometry_minecraft_ao": [ "ambient occlusion" ],
+	"webgl_geometry_nurbs": [ "curve", "surface" ],
+	"webgl_geometry_spline_editor": [ "curve" ],
+	"webgl_geometry_text": [ "font" ],
+	"webgl_geometry_text_shapes": [ "font" ],
+	"webgl_geometry_text_stroke": [ "font" ],
+	"webgl_helpers": [ "normals", "tangents", "bounding box" ],
+	"webgl_instancing_performance": [ "batching", "merging" ],
+	"webgl_interactive_buffergeometry": [ "raycast", "outline" ],
+	"webgl_interactive_cubes": [ "raycast", "highlinght" ],
+	"webgl_interactive_cubes_gpu": [ "raycast", "highlight" ],
+	"webgl_interactive_cubes_ortho": [ "raycast", "highlight" ],
+	"webgl_interactive_lines": [ "raycast" ],
+	"webgl_interactive_points": [ "raycast" ],
+	"webgl_interactive_raycasting_points": [ "raycast" ],
+	"webgl_interactive_voxelpainter": [ "raycast" ],
+	"webgl_layers": [ "groups" ],
+	"webgl_lights_hemisphere": [ "directional" ],
+	"webgl_lights_pointlights": [ "multiple" ],
+	"webgl_loader_ttf": [ "text", "font" ],
+	"webgl_loader_pdb": [ "molecules" ],
+	"webgl_lod": [ "level", "details" ],
+	"webgl_materials_blending": [ "alpha" ],
+	"webgl_materials_blending_custom": [ "alpha" ],
+	"webgl_materials_channels": [ "normal", "depth", "rgba packing" ],
+	"webgl_materials_cubemap_mipmaps": [ "envmap" ],
+	"webgl_materials_envmaps_parallax": [ "onBeforeCompile" ],
+	"webgl_materials_lightmap": [ "shadows" ],
+	"webgl_materials_physical_clearcoat": [ "anisotropy" ],
+	"webgl_materials_physical_transparency": [ "alpha" ],
+	"webgl_materials_shaders_fresnel": [ "refraction" ],
+	"webgl_materials_standard": [ "pbr" ],
+	"webgl_materials_texture_canvas": [ "paint" ],
+	"webgl_materials_texture_filters": [ "mipmap", "min", "mag" ],
+	"webgl_materials_texture_manualmipmap": [ "mipmap", "min", "mag" ],
+	"webgl_materials_subsurface_scattering": [ "derivatives", "translucency" ],
+	"webgl_materials_wireframe": [ "derivatives" ],
+	"webgl_math_obb": [ "intersection", "bounding" ],
+	"webgl_math_orientation_transform": [ "rotation" ],
+	"webgl_mirror": [ "reflection" ],
+	"webgl_morphtargets_horse": [ "animation" ],
+	"webgl_multiple_elements": [ "differential equations", "physics" ],
+	"webgl_multiple_elements_text": [ "font" ],
+	"webgl_nearestneighbour": [ "kdtree" ],
+	"webgl_panorama_cube": [ "envmap" ],
+	"webgl_panorama_equirectangular": [ "envmap" ],
+	"webgl_points_billboards": [ "particles" ],
+	"webgl_points_dynamic": [ "particles" ],
+	"webgl_points_sprites": [ "particles", "snow" ],
+	"webgl_points_waves": [ "particles" ],
+	"webgl_read_float_buffer": [ "texture" ],
+	"webgl_refraction": [ "water" ],
+	"webgl_rtt": [ "renderTarget", "texture" ],
+	"webgl_shaders_ocean": [ "water" ],
+	"webgl_shaders_ocean2": [ "water" ],
+	"webgl_shaders_sky": [ "sun" ],
+	"webgl_shaders_tonemapping": [ "hrd" ],
+	"webgl_shaders_vector": [ "font" ],
+	"webgl_shading_physical": [ "pbr" ],
+	"webgl_shadow_contact": [ "onBeforeCompile", "soft" ],
+	"webgl_shadowmap_viewer": [ "directional", "spot" ],
+	"webgl_skinning_simple": [ "animation" ],
+	"webgl_tonemapping": [ "gltf" ],
+	"webgl_loader_nodes": [ "caustics", "displace", "xray" ],
+	"webgl_postprocessing_afterimage": [ "trails" ],
+	"webgl_postprocessing_dof": [ "bokeh" ],
+	"webgl_postprocessing_dof2": [ "bokeh" ],
+	"webgl_postprocessing_fxaa": [ "msaa", "multisampled" ],
+	"webgl_postprocessing_godrays": [ "light scattering" ],
+	"webgl_postprocessing_ssaa": [ "msaa", "multisampled" ],
+	"webgl_postprocessing_ssaa_unbiased": [ "msaa", "multisampled" ],
+	"webgl_postprocessing_sao": [ "ambient occlusion" ],
+	"webgl_postprocessing_smaa": [ "msaa", "multisampled" ],
+	"webgl_postprocessing_sobel": [ "filter", "edge detection" ],
+	"webgl_postprocessing_ssao": [ "ambient occlusion" ],
 	"webgl_postprocessing_unreal_bloom": [ "glow" ],
 	"webgl_postprocessing_unreal_bloom": [ "glow" ],
 	"webgl_postprocessing_unreal_bloom_selective": [ "glow" ],
 	"webgl_postprocessing_unreal_bloom_selective": [ "glow" ],
+	"webgl_fire": [ "smoke" ],
+	"webgl_materials_modified": [ "onBeforeCompile" ],
 	"webgl_shadowmap_csm": [ "cascade" ],
 	"webgl_shadowmap_csm": [ "cascade" ],
 	"webgl_shadowmap_pcss": [ "soft" ],
 	"webgl_shadowmap_pcss": [ "soft" ],
-	"webgl_simple_gi": [ "global", "illumination" ]
+	"webgl_simple_gi": [ "global illumination" ],
+	"webgl_tiled_forward": [ "derivatives" ],
+	"webgl2_multisampled_renderbuffers": [ "msaa" ],
+	"physics_ammo_cloth": [ "integration" ],
+	"misc_controls_deviceorientation": [ "accelerometer", "sensors" ],
+	"misc_controls_drag": [ "translate" ],
+	"misc_controls_map": [ "drag" ],
+	"misc_controls_orbit": [ "rotation" ],
+	"misc_controls_trackball": [ "rotation" ],
+	"misc_controls_transform": [ "scale", "rotate", "translate" ]
 };
 };

+ 29 - 16
examples/index.html

@@ -6,11 +6,6 @@
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<link rel="shortcut icon" href="../files/favicon.ico" />
 		<link rel="shortcut icon" href="../files/favicon.ico" />
 		<link rel="stylesheet" type="text/css" href="../files/main.css">
 		<link rel="stylesheet" type="text/css" href="../files/main.css">
-		<style>
-			#panel #content .link {
-				display: block;
-			}
-		</style>
 	</head>
 	</head>
 	<body>
 	<body>
 
 
@@ -110,13 +105,13 @@
 
 
 			// Events
 			// Events
 
 
-			filterInput.onfocus = function ( event ) {
+			filterInput.onfocus = function ( ) {
 
 
 				panel.classList.add( 'searchFocused' );
 				panel.classList.add( 'searchFocused' );
 
 
 			};
 			};
 
 
-			filterInput.onblur = function ( event ) {
+			filterInput.onblur = function ( ) {
 
 
 				if ( filterInput.value === '' ) {
 				if ( filterInput.value === '' ) {
 
 
@@ -126,7 +121,7 @@
 
 
 			};
 			};
 
 
-			exitSearchButton.onclick = function ( event ) {
+			exitSearchButton.onclick = function ( ) {
 
 
 				filterInput.value = '';
 				filterInput.value = '';
 				updateFilter();
 				updateFilter();
@@ -169,12 +164,21 @@
 
 
 		function createLink( file ) {
 		function createLink( file ) {
 
 
-			var link = document.createElement( 'a' );
-			link.className = 'link';
-			link.href = file + '.html';
-			link.textContent = getName( file );
-			link.setAttribute( 'target', 'viewer' );
-			link.addEventListener( 'click', function ( event ) {
+
+			var template = [
+				'<div class="card">',
+				'	<a href="' + file + '.html" target="viewer">',
+				'		<div class="cover">',
+				'			<img src="screenshots/' + file + '.jpg" loading="lazy" width="400" />',
+				'		</div>',
+				'		<div class="title">' + getName( file ) + '</div>',
+				'	</a>',
+				'</div>'
+			].join( "\n" );
+
+			var link = createElementFromHTML( template );
+
+			link.querySelector( 'a[target="viewer"]' ).addEventListener( 'click', function ( event ) {
 
 
 				if ( event.button !== 0 || event.ctrlKey || event.altKey || event.metaKey ) return;
 				if ( event.button !== 0 || event.ctrlKey || event.altKey || event.metaKey ) return;
 
 
@@ -263,12 +267,12 @@
 
 
 				}
 				}
 
 
-				link.innerHTML = text;
+				link.querySelector( '.title' ).innerHTML = text;
 
 
 			} else {
 			} else {
 
 
 				link.classList.add( 'hidden' );
 				link.classList.add( 'hidden' );
-				link.innerHTML = name;
+				link.querySelector( '.title' ).innerHTML = name;
 
 
 			}
 			}
 
 
@@ -333,6 +337,15 @@
 
 
 		}
 		}
 
 
+
+		function createElementFromHTML( htmlString ) {
+
+			var div = document.createElement( 'div' );
+			div.innerHTML = htmlString.trim();
+			return div.firstChild;
+
+		}
+
 		</script>
 		</script>
 
 
 	</body>
 	</body>

+ 2 - 0
examples/js/controls/DragControls.js

@@ -44,6 +44,8 @@ THREE.DragControls = function ( _objects, _camera, _domElement ) {
 		_domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false );
 		_domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false );
 		_domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false );
 		_domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false );
 
 
+		_domElement.style.cursor = '';
+
 	}
 	}
 
 
 	function dispose() {
 	function dispose() {

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

@@ -56,7 +56,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 
 		var allIntersections = raycaster.intersectObject( object, true );
 		var allIntersections = raycaster.intersectObject( object, true );
 
 
-		for ( var i = allIntersections.length; i --; ) {
+		for ( var i = 0; i < allIntersections.length; i ++ ) {
 
 
 			if ( allIntersections[ i ].object.visible || includeInvisible ) {
 			if ( allIntersections[ i ].object.visible || includeInvisible ) {
 
 

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

@@ -506,9 +506,22 @@ THREE.GLTFExporter.prototype = {
 
 
 				for ( var a = 0; a < attribute.itemSize; a ++ ) {
 				for ( var a = 0; a < attribute.itemSize; a ++ ) {
 
 
-					// @TODO Fails on InterleavedBufferAttribute, and could probably be
-					// optimized for normal BufferAttribute.
-					var value = attribute.array[ i * attribute.itemSize + a ];
+					var value;
+
+					if ( attribute.itemSize > 3 ) {
+
+						 // no support for interleaved data for itemSize > 3
+
+						value = attribute.array[ i * attribute.itemSize + a ];
+
+					} else {
+
+						if ( a === 0 ) value = attribute.getX( i );
+						if ( a === 1 ) value = attribute.getY( i );
+						if ( a === 2 ) value = attribute.getZ( i );
+						if ( a === 3 ) value = attribute.getW( i );
+
+					}
 
 
 					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
 					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
 
 

+ 15 - 3
examples/js/interactive/SelectionBox.js

@@ -54,6 +54,20 @@ THREE.SelectionBox = ( function () {
 		startPoint = startPoint || this.startPoint;
 		startPoint = startPoint || this.startPoint;
 		endPoint = endPoint || this.endPoint;
 		endPoint = endPoint || this.endPoint;
 
 
+		// Avoid invalid frustum
+
+		if ( startPoint.x === endPoint.x ) {
+
+			endPoint.x += Number.EPSILON;
+
+		}
+
+		if ( startPoint.y === endPoint.y ) {
+
+			endPoint.y += Number.EPSILON;
+
+		}
+
 		this.camera.updateProjectionMatrix();
 		this.camera.updateProjectionMatrix();
 		this.camera.updateMatrixWorld();
 		this.camera.updateMatrixWorld();
 
 
@@ -65,7 +79,7 @@ THREE.SelectionBox = ( function () {
 			endPoint.x = Math.max( startPoint.x, endPoint.x );
 			endPoint.x = Math.max( startPoint.x, endPoint.x );
 			endPoint.y = Math.min( startPoint.y, endPoint.y );
 			endPoint.y = Math.min( startPoint.y, endPoint.y );
 
 
-			vecNear.copy( this.camera.position );
+			vecNear.setFromMatrixPosition( this.camera.matrixWorld );
 			vecTopLeft.copy( tmpPoint );
 			vecTopLeft.copy( tmpPoint );
 			vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
 			vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
 			vecDownRight.copy( endPoint );
 			vecDownRight.copy( endPoint );
@@ -102,8 +116,6 @@ THREE.SelectionBox = ( function () {
 
 
 		} else if ( this.camera.isOrthographicCamera ) {
 		} else if ( this.camera.isOrthographicCamera ) {
 
 
-			if ( startPoint.equals( endPoint ) ) endPoint.addScalar( Number.EPSILON ); // avoid invalid frustum
-
 			var left = Math.min( startPoint.x, endPoint.x );
 			var left = Math.min( startPoint.x, endPoint.x );
 			var top = Math.max( startPoint.y, endPoint.y );
 			var top = Math.max( startPoint.y, endPoint.y );
 			var right = Math.max( startPoint.x, endPoint.x );
 			var right = Math.max( startPoint.x, endPoint.x );

+ 3 - 1
examples/js/loaders/BasisTextureLoader.js

@@ -185,8 +185,10 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 
 			} );
 			} );
 
 
+		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 		texturePending
 		texturePending
-			.finally( () => {
+			.catch( () => true )
+			.then( () => {
 
 
 				if ( worker && taskID ) {
 				if ( worker && taskID ) {
 
 

+ 16 - 7
examples/js/loaders/ColladaLoader.js

@@ -217,6 +217,8 @@ THREE.ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 				channels: {}
 				channels: {}
 			};
 			};
 
 
+			var hasChildren = false;
+
 			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
 
 				var child = xml.childNodes[ i ];
 				var child = xml.childNodes[ i ];
@@ -242,6 +244,12 @@ THREE.ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 						data.channels[ id ] = parseAnimationChannel( child );
 						data.channels[ id ] = parseAnimationChannel( child );
 						break;
 						break;
 
 
+					case 'animation':
+						// hierarchy of related animations
+						parseAnimation( child );
+						hasChildren = true;
+						break;
+
 					default:
 					default:
 						console.log( child );
 						console.log( child );
 
 
@@ -249,7 +257,13 @@ THREE.ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
-			library.animations[ xml.getAttribute( 'id' ) ] = data;
+			if ( hasChildren === false ) {
+
+				// since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment
+
+				library.animations[ xml.getAttribute( 'id' ) || THREE.MathUtils.generateUUID() ] = data;
+
+			}
 
 
 		}
 		}
 
 
@@ -3558,12 +3572,7 @@ THREE.ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.proto
 
 
 			}
 			}
 
 
-			if ( object.name === '' ) {
-
-				object.name = ( type === 'JOINT' ) ? data.sid : data.name;
-
-			}
-
+			object.name = ( type === 'JOINT' ) ? data.sid : data.name;
 			object.matrix.copy( matrix );
 			object.matrix.copy( matrix );
 			object.matrix.decompose( object.position, object.quaternion, object.scale );
 			object.matrix.decompose( object.position, object.quaternion, object.scale );
 
 

+ 3 - 1
examples/js/loaders/DRACOLoader.js

@@ -197,8 +197,10 @@ THREE.DRACOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototy
 			.then( ( message ) => this._createGeometry( message.geometry ) );
 			.then( ( message ) => this._createGeometry( message.geometry ) );
 
 
 		// Remove task from the task list.
 		// Remove task from the task list.
+		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 		geometryPending
 		geometryPending
-			.finally( () => {
+			.catch( () => true )
+			.then( () => {
 
 
 				if ( worker && taskID ) {
 				if ( worker && taskID ) {
 
 

+ 69 - 17
examples/js/loaders/EXRLoader.js

@@ -98,6 +98,10 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 		const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
 		const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
 		const HUF_DECMASK = HUF_DECSIZE - 1;
 		const HUF_DECMASK = HUF_DECSIZE - 1;
 
 
+		const NBITS = 16;
+		const A_OFFSET = 1 << ( NBITS - 1 );
+		const MOD_MASK = ( 1 << NBITS ) - 1;
+
 		const SHORT_ZEROCODE_RUN = 59;
 		const SHORT_ZEROCODE_RUN = 59;
 		const LONG_ZEROCODE_RUN = 63;
 		const LONG_ZEROCODE_RUN = 63;
 		const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
 		const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
@@ -472,8 +476,22 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 
 
 		}
 		}
 
 
-		function wav2Decode( buffer, j, nx, ox, ny, oy ) {
+		function wdec16( l, h ) {
+
+			var m = UInt16( l );
+			var d = UInt16( h );
+
+			var bb = ( m - ( d >> 1 ) ) & MOD_MASK;
+			var aa = ( d + bb - A_OFFSET ) & MOD_MASK;
+
+			wdec14Return.a = aa;
+			wdec14Return.b = bb;
+
+		}
 
 
+		function wav2Decode( buffer, j, nx, ox, ny, oy, mx ) {
+
+			var w14 = mx < ( 1 << 14 );
 			var n = ( nx > ny ) ? ny : nx;
 			var n = ( nx > ny ) ? ny : nx;
 			var p = 1;
 			var p = 1;
 			var p2;
 			var p2;
@@ -505,25 +523,52 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 						var p10 = px + oy1;
 						var p10 = px + oy1;
 						var p11 = p10 + ox1;
 						var p11 = p10 + ox1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						if ( w14 ) {
 
 
-						i00 = wdec14Return.a;
-						i10 = wdec14Return.b;
+							wdec14( buffer[ px + j ], buffer[ p10 + j ] );
 
 
-						wdec14( buffer[ p01 + j ], buffer[ p11 + j ] );
+							i00 = wdec14Return.a;
+							i10 = wdec14Return.b;
 
 
-						i01 = wdec14Return.a;
-						i11 = wdec14Return.b;
+							wdec14( buffer[ p01 + j ], buffer[ p11 + j ] );
 
 
-						wdec14( i00, i01 );
+							i01 = wdec14Return.a;
+							i11 = wdec14Return.b;
 
 
-						buffer[ px + j ] = wdec14Return.a;
-						buffer[ p01 + j ] = wdec14Return.b;
+							wdec14( i00, i01 );
+
+							buffer[ px + j ] = wdec14Return.a;
+							buffer[ p01 + j ] = wdec14Return.b;
+
+							wdec14( i10, i11 );
 
 
-						wdec14( i10, i11 );
+							buffer[ p10 + j ] = wdec14Return.a;
+							buffer[ p11 + j ] = wdec14Return.b;
 
 
-						buffer[ p10 + j ] = wdec14Return.a;
-						buffer[ p11 + j ] = wdec14Return.b;
+						} else {
+
+							wdec16( buffer[ px + j ], buffer[ p10 + j ] );
+
+							i00 = wdec14Return.a;
+							i10 = wdec14Return.b;
+
+							wdec16( buffer[ p01 + j ], buffer[ p11 + j ] );
+
+							i01 = wdec14Return.a;
+							i11 = wdec14Return.b;
+
+							wdec16( i00, i01 );
+
+							buffer[ px + j ] = wdec14Return.a;
+							buffer[ p01 + j ] = wdec14Return.b;
+
+							wdec16( i10, i11 );
+
+							buffer[ p10 + j ] = wdec14Return.a;
+							buffer[ p11 + j ] = wdec14Return.b;
+
+
+						}
 
 
 					}
 					}
 
 
@@ -531,7 +576,10 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 
 
 						var p10 = px + oy1;
 						var p10 = px + oy1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						if ( w14 )
+							wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						else
+							wdec16( buffer[ px + j ], buffer[ p10 + j ] );
 
 
 						i00 = wdec14Return.a;
 						i00 = wdec14Return.a;
 						buffer[ p10 + j ] = wdec14Return.b;
 						buffer[ p10 + j ] = wdec14Return.b;
@@ -551,7 +599,10 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 
 
 						var p01 = px + ox1;
 						var p01 = px + ox1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p01 + j ] );
+						if ( w14 )
+							wdec14( buffer[ px + j ], buffer[ p01 + j ] );
+						else
+							wdec16( buffer[ px + j ], buffer[ p01 + j ] );
 
 
 						i00 = wdec14Return.a;
 						i00 = wdec14Return.a;
 						buffer[ p01 + j ] = wdec14Return.b;
 						buffer[ p01 + j ] = wdec14Return.b;
@@ -1294,7 +1345,7 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 
 
 			// Reverse LUT
 			// Reverse LUT
 			var lut = new Uint16Array( USHORT_RANGE );
 			var lut = new Uint16Array( USHORT_RANGE );
-			reverseLutFromBitmap( bitmap, lut );
+			var maxValue = reverseLutFromBitmap( bitmap, lut );
 
 
 			var length = parseUint32( inDataView, inOffset );
 			var length = parseUint32( inDataView, inOffset );
 
 
@@ -1314,7 +1365,8 @@ THREE.EXRLoader.prototype = Object.assign( Object.create( THREE.DataTextureLoade
 						cd.nx,
 						cd.nx,
 						cd.size,
 						cd.size,
 						cd.ny,
 						cd.ny,
-						cd.nx * cd.size
+						cd.nx * cd.size,
+						maxValue
 					);
 					);
 
 
 				}
 				}

+ 7 - 0
examples/js/loaders/FBXLoader.js

@@ -2492,6 +2492,13 @@ THREE.FBXLoader = ( function () {
 
 
 										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
 										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
 
 
+										if ( rawModel === undefined ) {
+
+											console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child );
+											return;
+
+										}
+
 										var node = {
 										var node = {
 
 
 											modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
 											modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',

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

@@ -203,9 +203,20 @@ THREE.GCodeLoader.prototype = Object.assign( Object.create( THREE.Loader.prototy
 			for ( var i = 0; i < layers.length; i ++ ) {
 			for ( var i = 0; i < layers.length; i ++ ) {
 
 
 				var layer = layers[ i ];
 				var layer = layers[ i ];
+				var layerVertex = layer.vertex;
+				var layerPathVertex = layer.pathVertex;
 
 
-				vertex = vertex.concat( layer.vertex );
-				pathVertex = pathVertex.concat( layer.pathVertex );
+				for ( var j = 0; j < layerVertex.length; j ++ ) {
+
+					vertex.push( layerVertex[ j ] );
+
+				}
+
+				for ( var j = 0; j < layerPathVertex.length; j ++ ) {
+
+					pathVertex.push( layerPathVertex[ j ] );
+
+				}
 
 
 			}
 			}
 
 

+ 18 - 0
examples/js/loaders/GLTFLoader.js

@@ -67,6 +67,7 @@ THREE.GLTFLoader = ( function () {
 
 
 			loader.setPath( this.path );
 			loader.setPath( this.path );
 			loader.setResponseType( 'arraybuffer' );
 			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( this.requestHeader );
 
 
 			if ( scope.crossOrigin === 'use-credentials' ) {
 			if ( scope.crossOrigin === 'use-credentials' ) {
 
 
@@ -218,6 +219,7 @@ THREE.GLTFLoader = ( function () {
 
 
 			} );
 			} );
 
 
+			parser.fileLoader.setRequestHeader( this.requestHeader );
 			parser.parse( onLoad, onError );
 			parser.parse( onLoad, onError );
 
 
 		}
 		}
@@ -1404,6 +1406,9 @@ THREE.GLTFLoader = ( function () {
 		// loader object cache
 		// loader object cache
 		this.cache = new GLTFRegistry();
 		this.cache = new GLTFRegistry();
 
 
+		// associations between Three.js objects and glTF elements
+		this.associations = new Map();
+
 		// BufferGeometry caching
 		// BufferGeometry caching
 		this.primitiveCache = {};
 		this.primitiveCache = {};
 
 
@@ -1913,6 +1918,11 @@ THREE.GLTFLoader = ( function () {
 			texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
 			texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping;
 			texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
 			texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping;
 
 
+			parser.associations.set( texture, {
+				type: 'textures',
+				index: textureIndex
+			} );
+
 			return texture;
 			return texture;
 
 
 		} );
 		} );
@@ -1962,7 +1972,9 @@ THREE.GLTFLoader = ( function () {
 
 
 				if ( transform ) {
 				if ( transform ) {
 
 
+					var gltfReference = this.associations.get( texture );
 					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
 					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
+					this.associations.set( texture, gltfReference );
 
 
 				}
 				}
 
 
@@ -2062,6 +2074,8 @@ THREE.GLTFLoader = ( function () {
 
 
 				this.cache.add( cacheKey, cachedMaterial );
 				this.cache.add( cacheKey, cachedMaterial );
 
 
+				this.associations.set( cachedMaterial, this.associations.get( material ) );
+
 			}
 			}
 
 
 			material = cachedMaterial;
 			material = cachedMaterial;
@@ -2257,6 +2271,8 @@ THREE.GLTFLoader = ( function () {
 
 
 			assignExtrasToUserData( material, materialDef );
 			assignExtrasToUserData( material, materialDef );
 
 
+			parser.associations.set( material, { type: 'materials', index: materialIndex } );
+
 			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 
 
 			return material;
 			return material;
@@ -3127,6 +3143,8 @@ THREE.GLTFLoader = ( function () {
 
 
 			}
 			}
 
 
+			parser.associations.set( node, { type: 'nodes', index: nodeIndex } );
+
 			return node;
 			return node;
 
 
 		} );
 		} );

+ 0 - 3034
examples/js/loaders/LWOLoader.js

@@ -1,3034 +0,0 @@
-/**
- * @version 1.1.1
- *
- * @author Lewy Blue https://github.com/looeee
- * @author Guilherme Avila https://github/sciecode
- *
- * @desc Load files in LWO3 and LWO2 format on Three.js
- *
- * LWO3 format specification:
- * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo3.html
- *
- * LWO2 format specification:
- * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html
- *
- * Development and test repository:
- *	https://github.com/threejs/lwoloader
- *
- **/
-
-function LWO2Parser( IFFParser ) {
-
-	this.IFF = IFFParser;
-
-}
-
-LWO2Parser.prototype = {
-
-	constructor: LWO2Parser,
-
-	parseBlock: function () {
-
-		this.IFF.debugger.offset = this.IFF.reader.offset;
-		this.IFF.debugger.closeForms();
-
-		var blockID = this.IFF.reader.getIDTag();
-		var length = this.IFF.reader.getUint32(); // size of data in bytes
-		if ( length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset ) {
-
-			this.IFF.reader.offset -= 4;
-			length = this.IFF.reader.getUint16();
-
-		}
-
-		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
-		this.IFF.debugger.length = length;
-
-		// Data types may be found in either LWO2 OR LWO3 spec
-		switch ( blockID ) {
-
-			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
-				this.IFF.parseForm( length );
-				break;
-
-			// SKIPPED CHUNKS
-			// if break; is called directly, the position in the lwoTree is not created
-			// any sub chunks and forms are added to the parent form instead
-			// MISC skipped
-			case 'ICON': // Thumbnail Icon Image
-			case 'VMPA': // Vertex Map Parameter
-			case 'BBOX': // bounding box
-			// case 'VMMD':
-			// case 'VTYP':
-
-			// normal maps can be specified, normally on models imported from other applications. Currently ignored
-			case 'NORM':
-
-			// ENVL FORM skipped
-			case 'PRE ':
-			case 'POST':
-			case 'KEY ':
-			case 'SPAN':
-
-			// CLIP FORM skipped
-			case 'TIME':
-			case 'CLRS':
-			case 'CLRA':
-			case 'FILT':
-			case 'DITH':
-			case 'CONT':
-			case 'BRIT':
-			case 'SATR':
-			case 'HUE ':
-			case 'GAMM':
-			case 'NEGA':
-			case 'IFLT':
-			case 'PFLT':
-
-			// Image Map Layer skipped
-			case 'PROJ':
-			case 'AXIS':
-			case 'AAST':
-			case 'PIXB':
-			case 'AUVO':
-			case 'STCK':
-
-			// Procedural Textures skipped
-			case 'PROC':
-			case 'VALU':
-			case 'FUNC':
-
-			// Gradient Textures skipped
-			case 'PNAM':
-			case 'INAM':
-			case 'GRST':
-			case 'GREN':
-			case 'GRPT':
-			case 'FKEY':
-			case 'IKEY':
-
-			// Texture Mapping Form skipped
-			case 'CSYS':
-
-			// Surface CHUNKs skipped
-			case 'OPAQ': // top level 'opacity' checkbox
-			case 'CMAP': // clip map
-
-			// Surface node CHUNKS skipped
-			// These mainly specify the node editor setup in LW
-			case 'NLOC':
-			case 'NZOM':
-			case 'NVER':
-			case 'NSRV':
-			case 'NVSK': // unknown
-			case 'NCRD':
-			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
-			case 'WRPH': // image wrap h
-			case 'NMOD':
-			case 'NPRW':
-			case 'NPLA':
-			case 'NODS':
-			case 'VERS':
-			case 'ENUM':
-			case 'TAG ':
-			case 'OPAC':
-
-			// Car Material CHUNKS
-			case 'CGMD':
-			case 'CGTY':
-			case 'CGST':
-			case 'CGEN':
-			case 'CGTS':
-			case 'CGTE':
-			case 'OSMP':
-			case 'OMDE':
-			case 'OUTR':
-			case 'FLAG':
-
-			case 'TRNL':
-			case 'GLOW':
-			case 'GVAL': // glow intensity
-			case 'SHRP':
-			case 'RFOP':
-			case 'RSAN':
-			case 'TROP':
-			case 'RBLR':
-			case 'TBLR':
-			case 'CLRH':
-			case 'CLRF':
-			case 'ADTR':
-			case 'LINE':
-			case 'ALPH':
-			case 'VCOL':
-			case 'ENAB':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length );
-				break;
-
-			case 'SURF':
-				this.IFF.parseSurfaceLwo2( length );
-				break;
-
-			case 'CLIP':
-				this.IFF.parseClipLwo2( length );
-				break;
-
-			// Texture node chunks (not in spec)
-			case 'IPIX': // usePixelBlending
-			case 'IMIP': // useMipMaps
-			case 'IMOD': // imageBlendingMode
-			case 'AMOD': // unknown
-			case 'IINV': // imageInvertAlpha
-			case 'INCR': // imageInvertColor
-			case 'IAXS': // imageAxis ( for non-UV maps)
-			case 'IFOT': // imageFallofType
-			case 'ITIM': // timing for animated textures
-			case 'IWRL':
-			case 'IUTI':
-			case 'IINX':
-			case 'IINY':
-			case 'IINZ':
-			case 'IREF': // possibly a VX for reused texture nodes
-				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
-				else this.IFF.reader.skip( length );
-				break;
-
-			case 'OTAG':
-				this.IFF.parseObjectTag();
-				break;
-
-			case 'LAYR':
-				this.IFF.parseLayer( length );
-				break;
-
-			case 'PNTS':
-				this.IFF.parsePoints( length );
-				break;
-
-			case 'VMAP':
-				this.IFF.parseVertexMapping( length );
-				break;
-
-			case 'AUVU':
-			case 'AUVN':
-				this.IFF.reader.skip( length - 1 );
-				this.IFF.reader.getVariableLengthIndex(); // VX
-				break;
-
-			case 'POLS':
-				this.IFF.parsePolygonList( length );
-				break;
-
-			case 'TAGS':
-				this.IFF.parseTagStrings( length );
-				break;
-
-			case 'PTAG':
-				this.IFF.parsePolygonTagMapping( length );
-				break;
-
-			case 'VMAD':
-				this.IFF.parseVertexMapping( length, true );
-				break;
-
-			// Misc CHUNKS
-			case 'DESC': // Description Line
-				this.IFF.currentForm.description = this.IFF.reader.getString();
-				break;
-
-			case 'TEXT':
-			case 'CMNT':
-			case 'NCOM':
-				this.IFF.currentForm.comment = this.IFF.reader.getString();
-				break;
-
-			// Envelope Form
-			case 'NAME':
-				this.IFF.currentForm.channelName = this.IFF.reader.getString();
-				break;
-
-			// Image Map Layer
-			case 'WRAP':
-				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
-				break;
-
-			case 'IMAG':
-				var index = this.IFF.reader.getVariableLengthIndex();
-				this.IFF.currentForm.imageIndex = index;
-				break;
-
-			// Texture Mapping Form
-			case 'OREF':
-				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
-				break;
-
-			case 'ROID':
-				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
-				break;
-
-			// Surface Blocks
-			case 'SSHN':
-				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
-				break;
-
-			case 'AOVN':
-				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
-				break;
-
-			// Nodal Blocks
-			case 'NSTA':
-				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
-				break;
-
-			case 'NRNM':
-				this.IFF.currentForm.realName = this.IFF.reader.getString();
-				break;
-
-			case 'NNME':
-				this.IFF.currentForm.refName = this.IFF.reader.getString();
-				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
-				break;
-
-			// Nodal Blocks : connections
-			case 'INME':
-				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
-				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINN':
-				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
-				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINM':
-				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
-				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IONM':
-				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
-				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'FNAM':
-				this.IFF.currentForm.fileName = this.IFF.reader.getString();
-				break;
-
-			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
-				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
-				else this.IFF.reader.skip( length );
-				break;
-
-			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
-			case 'SMAN':
-				var maxSmoothingAngle = this.IFF.reader.getFloat32();
-				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
-				break;
-
-			// LWO2: Basic Surface Parameters
-			case 'COLR':
-				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
-				this.IFF.reader.skip( 2 ); // VX: envelope
-				break;
-
-			case 'LUMI':
-				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SPEC':
-				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'DIFF':
-				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'REFL':
-				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'GLOS':
-				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TRAN':
-				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'BUMP':
-				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SIDE':
-				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
-				break;
-
-			case 'RIMG':
-				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'RIND':
-				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TIMG':
-				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'IMAP':
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TMAP':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length ); // needs implementing
-				break;
-
-			case 'IUVI': // uv channel name
-				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
-				break;
-
-			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
-				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
-				break;
-			case 'IVTL': // heightWrappingMode
-				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
-				break;
-
-			// LWO2 USE
-			case 'BLOK':
-				// skip
-				break;
-
-			default:
-				this.IFF.parseUnknownCHUNK( blockID, length );
-
-		}
-
-		if ( blockID != 'FORM' ) {
-
-			this.IFF.debugger.node = 1;
-			this.IFF.debugger.nodeID = blockID;
-			this.IFF.debugger.log();
-
-		}
-
-		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
-
-			this.IFF.currentForm = this.IFF.parentForm;
-
-		}
-
-	}
-
-};
-
-function LWO3Parser( IFFParser ) {
-
-	this.IFF = IFFParser;
-
-}
-
-LWO3Parser.prototype = {
-
-	constructor: LWO3Parser,
-
-	parseBlock: function () {
-
-		this.IFF.debugger.offset = this.IFF.reader.offset;
-		this.IFF.debugger.closeForms();
-
-		var blockID = this.IFF.reader.getIDTag();
-		var length = this.IFF.reader.getUint32(); // size of data in bytes
-
-		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
-		this.IFF.debugger.length = length;
-
-		// Data types may be found in either LWO2 OR LWO3 spec
-		switch ( blockID ) {
-
-			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
-				this.IFF.parseForm( length );
-				break;
-
-			// SKIPPED CHUNKS
-			// MISC skipped
-			case 'ICON': // Thumbnail Icon Image
-			case 'VMPA': // Vertex Map Parameter
-			case 'BBOX': // bounding box
-			// case 'VMMD':
-			// case 'VTYP':
-
-			// normal maps can be specified, normally on models imported from other applications. Currently ignored
-			case 'NORM':
-
-			// ENVL FORM skipped
-			case 'PRE ':
-			case 'POST':
-			case 'KEY ':
-			case 'SPAN':
-
-			// CLIP FORM skipped
-			case 'TIME':
-			case 'CLRS':
-			case 'CLRA':
-			case 'FILT':
-			case 'DITH':
-			case 'CONT':
-			case 'BRIT':
-			case 'SATR':
-			case 'HUE ':
-			case 'GAMM':
-			case 'NEGA':
-			case 'IFLT':
-			case 'PFLT':
-
-			// Image Map Layer skipped
-			case 'PROJ':
-			case 'AXIS':
-			case 'AAST':
-			case 'PIXB':
-			case 'STCK':
-
-			// Procedural Textures skipped
-			case 'VALU':
-
-			// Gradient Textures skipped
-			case 'PNAM':
-			case 'INAM':
-			case 'GRST':
-			case 'GREN':
-			case 'GRPT':
-			case 'FKEY':
-			case 'IKEY':
-
-			// Texture Mapping Form skipped
-			case 'CSYS':
-
-				// Surface CHUNKs skipped
-			case 'OPAQ': // top level 'opacity' checkbox
-			case 'CMAP': // clip map
-
-			// Surface node CHUNKS skipped
-			// These mainly specify the node editor setup in LW
-			case 'NLOC':
-			case 'NZOM':
-			case 'NVER':
-			case 'NSRV':
-			case 'NCRD':
-			case 'NMOD':
-			case 'NSEL':
-			case 'NPRW':
-			case 'NPLA':
-			case 'VERS':
-			case 'ENUM':
-			case 'TAG ':
-
-			// Car Material CHUNKS
-			case 'CGMD':
-			case 'CGTY':
-			case 'CGST':
-			case 'CGEN':
-			case 'CGTS':
-			case 'CGTE':
-			case 'OSMP':
-			case 'OMDE':
-			case 'OUTR':
-			case 'FLAG':
-
-			case 'TRNL':
-			case 'SHRP':
-			case 'RFOP':
-			case 'RSAN':
-			case 'TROP':
-			case 'RBLR':
-			case 'TBLR':
-			case 'CLRH':
-			case 'CLRF':
-			case 'ADTR':
-			case 'GLOW':
-			case 'LINE':
-			case 'ALPH':
-			case 'VCOL':
-			case 'ENAB':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length );
-				break;
-
-			// Texture node chunks (not in spec)
-			case 'IPIX': // usePixelBlending
-			case 'IMIP': // useMipMaps
-			case 'IMOD': // imageBlendingMode
-			case 'AMOD': // unknown
-			case 'IINV': // imageInvertAlpha
-			case 'INCR': // imageInvertColor
-			case 'IAXS': // imageAxis ( for non-UV maps)
-			case 'IFOT': // imageFallofType
-			case 'ITIM': // timing for animated textures
-			case 'IWRL':
-			case 'IUTI':
-			case 'IINX':
-			case 'IINY':
-			case 'IINZ':
-			case 'IREF': // possibly a VX for reused texture nodes
-				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
-				else this.IFF.reader.skip( length );
-				break;
-
-			case 'OTAG':
-				this.IFF.parseObjectTag();
-				break;
-
-			case 'LAYR':
-				this.IFF.parseLayer( length );
-				break;
-
-			case 'PNTS':
-				this.IFF.parsePoints( length );
-				break;
-
-			case 'VMAP':
-				this.IFF.parseVertexMapping( length );
-				break;
-
-			case 'POLS':
-				this.IFF.parsePolygonList( length );
-				break;
-
-			case 'TAGS':
-				this.IFF.parseTagStrings( length );
-				break;
-
-			case 'PTAG':
-				this.IFF.parsePolygonTagMapping( length );
-				break;
-
-			case 'VMAD':
-				this.IFF.parseVertexMapping( length, true );
-				break;
-
-			// Misc CHUNKS
-			case 'DESC': // Description Line
-				this.IFF.currentForm.description = this.IFF.reader.getString();
-				break;
-
-			case 'TEXT':
-			case 'CMNT':
-			case 'NCOM':
-				this.IFF.currentForm.comment = this.IFF.reader.getString();
-				break;
-
-			// Envelope Form
-			case 'NAME':
-				this.IFF.currentForm.channelName = this.IFF.reader.getString();
-				break;
-
-			// Image Map Layer
-			case 'WRAP':
-				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
-				break;
-
-			case 'IMAG':
-				var index = this.IFF.reader.getVariableLengthIndex();
-				this.IFF.currentForm.imageIndex = index;
-				break;
-
-			// Texture Mapping Form
-			case 'OREF':
-				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
-				break;
-
-			case 'ROID':
-				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
-				break;
-
-			// Surface Blocks
-			case 'SSHN':
-				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
-				break;
-
-			case 'AOVN':
-				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
-				break;
-
-			// Nodal Blocks
-			case 'NSTA':
-				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
-				break;
-
-			case 'NRNM':
-				this.IFF.currentForm.realName = this.IFF.reader.getString();
-				break;
-
-			case 'NNME':
-				this.IFF.currentForm.refName = this.IFF.reader.getString();
-				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
-				break;
-
-			// Nodal Blocks : connections
-			case 'INME':
-				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
-				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINN':
-				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
-				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINM':
-				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
-				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IONM':
-				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
-				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'FNAM':
-				this.IFF.currentForm.fileName = this.IFF.reader.getString();
-				break;
-
-			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
-				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
-				else this.IFF.reader.skip( length );
-				break;
-
-			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
-			case 'SMAN':
-				var maxSmoothingAngle = this.IFF.reader.getFloat32();
-				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
-				break;
-
-			// LWO2: Basic Surface Parameters
-			case 'COLR':
-				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
-				this.IFF.reader.skip( 2 ); // VX: envelope
-				break;
-
-			case 'LUMI':
-				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SPEC':
-				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'DIFF':
-				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'REFL':
-				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'GLOS':
-				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TRAN':
-				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'BUMP':
-				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SIDE':
-				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
-				break;
-
-			case 'RIMG':
-				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'RIND':
-				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TIMG':
-				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'IMAP':
-				this.IFF.currentSurface.attributes.imageMapIndex = this.IFF.reader.getUint32();
-				break;
-
-			case 'IUVI': // uv channel name
-				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
-				break;
-
-			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
-				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
-				break;
-			case 'IVTL': // heightWrappingMode
-				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
-				break;
-
-			default:
-				this.IFF.parseUnknownCHUNK( blockID, length );
-
-		}
-
-		if ( blockID != 'FORM' ) {
-
-			this.IFF.debugger.node = 1;
-			this.IFF.debugger.nodeID = blockID;
-			this.IFF.debugger.log();
-
-		}
-
-		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
-
-			this.IFF.currentForm = this.IFF.parentForm;
-
-		}
-
-	}
-
-};
-
-/**
- * === IFFParser ===
- * - Parses data from the IFF buffer.
- * - LWO3 files are in IFF format and can contain the following data types, referred to by shorthand codes
- *
- * ATOMIC DATA TYPES
- *  ID Tag - 4x 7 bit uppercase ASCII chars: ID4
- *  signed integer, 1, 2, or 4 byte length: I1, I2, I4
- *  unsigned integer, 1, 2, or 4 byte length: U1, U2, U4
- *  float, 4 byte length: F4
- *  string, series of ASCII chars followed by null byte (If the length of the string including the null terminating byte is odd, an extra null is added so that the data that follows will begin on an even byte boundary): S0
- *
- * COMPOUND DATA TYPES
- *  Variable-length Index (index into an array or collection): U2 or U4 : VX
- *  Color (RGB): F4 + F4 + F4: COL12
- *  Coordinate (x, y, z): F4 + F4 + F4: VEC12
- *  Percentage F4 data type from 0->1 with 1 = 100%: FP4
- *  Angle in radian F4: ANG4
- *  Filename (string) S0: FNAM0
- *  XValue F4 + index (VX) + optional envelope( ENVL ): XVAL
- *  XValue vector VEC12 + index (VX) + optional envelope( ENVL ): XVAL3
- *
- *  The IFF file is arranged in chunks:
- *  CHUNK = ID4 + length (U4) + length X bytes of data + optional 0 pad byte
- *  optional 0 pad byte is there to ensure chunk ends on even boundary, not counted in size
- *
- * COMPOUND DATA TYPES
- * - Chunks are combined in Forms (collections of chunks)
- * - FORM = string 'FORM' (ID4) + length (U4) + type (ID4) + optional ( CHUNK | FORM )
- * - CHUNKS and FORMS are collectively referred to as blocks
- * - The entire file is contained in one top level FORM
- *
- **/
-
-function IFFParser( ) {
-
-	this.debugger = new Debugger();
-	// this.debugger.enable(); // un-comment to log IFF hierarchy.
-
-}
-
-IFFParser.prototype = {
-
-	constructor: IFFParser,
-
-	parse: function ( buffer ) {
-
-		this.reader = new DataViewReader( buffer );
-
-		this.tree = {
-			materials: {},
-			layers: [],
-			tags: [],
-			textures: [],
-		};
-
-		// start out at the top level to add any data before first layer is encountered
-		this.currentLayer = this.tree;
-		this.currentForm = this.tree;
-
-		this.parseTopForm();
-
-		if ( this.tree.format === undefined ) return;
-
-		if ( this.tree.format === 'LWO2' ) {
-
-			this.parser = new LWO2Parser( this );
-			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
-
-		} else if ( this.tree.format === 'LWO3' ) {
-
-			this.parser = new LWO3Parser( this );
-			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
-
-		}
-
-		this.debugger.offset = this.reader.offset;
-		this.debugger.closeForms();
-
-		return this.tree;
-
-	},
-
-	parseTopForm() {
-
-		this.debugger.offset = this.reader.offset;
-
-		var topForm = this.reader.getIDTag();
-
-		if ( topForm !== 'FORM' ) {
-
-			console.warn( "LWOLoader: Top-level FORM missing." );
-			return;
-
-		}
-
-		var length = this.reader.getUint32();
-
-		this.debugger.dataOffset = this.reader.offset;
-		this.debugger.length = length;
-
-		var type = this.reader.getIDTag();
-
-		if ( type === 'LWO2' ) {
-
-			this.tree.format = type;
-
-		} else if ( type === 'LWO3' ) {
-
-			this.tree.format = type;
-
-		}
-
-		this.debugger.node = 0;
-		this.debugger.nodeID = type;
-		this.debugger.log();
-
-		return;
-
-	},
-
-
-	///
-	// FORM PARSING METHODS
-	///
-
-	// Forms are organisational and can contain any number of sub chunks and sub forms
-	// FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * }
-	parseForm( length ) {
-
-		var type = this.reader.getIDTag();
-
-		switch ( type ) {
-
-			// SKIPPED FORMS
-			// if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped
-
-			case 'ISEQ': // Image sequence
-			case 'ANIM': // plug in animation
-			case 'STCC': // Color-cycling Still
-			case 'VPVL':
-			case 'VPRM':
-			case 'NROT':
-			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
-			case 'WRPH': // image wrap h
-			case 'FUNC':
-			case 'FALL':
-			case 'OPAC':
-			case 'GRAD': // gradient texture
-			case 'ENVS':
-			case 'VMOP':
-			case 'VMBG':
-
-			// Car Material FORMS
-			case 'OMAX':
-			case 'STEX':
-			case 'CKBG':
-			case 'CKEY':
-			case 'VMLA':
-			case 'VMLB':
-				this.debugger.skipped = true;
-				this.skipForm( length ); // not currently supported
-				break;
-
-			// if break; is called directly, the position in the lwoTree is not created
-			// any sub chunks and forms are added to the parent form instead
-			case 'META':
-			case 'NNDS':
-			case 'NODS':
-			case 'NDTA':
-			case 'ADAT':
-			case 'AOVS':
-			case 'BLOK':
-
-			// used by texture nodes
-			case 'IBGC': // imageBackgroundColor
-			case 'IOPC': // imageOpacity
-			case 'IIMG': // hold reference to image path
-			case 'TXTR':
-				// this.setupForm( type, length );
-				this.debugger.length = 4;
-				this.debugger.skipped = true;
-				break;
-
-			case 'IFAL': // imageFallof
-			case 'ISCL': // imageScale
-			case 'IPOS': // imagePosition
-			case 'IROT': // imageRotation
-			case 'IBMP':
-			case 'IUTD':
-			case 'IVTD':
-				this.parseTextureNodeAttribute( type );
-				break;
-
-			case 'ENVL':
-				this.parseEnvelope( length );
-				break;
-
-				// CLIP FORM AND SUB FORMS
-
-			case 'CLIP':
-				if ( this.tree.format === 'LWO2' ) {
-
-					this.parseForm( length );
-
-				} else {
-
-					this.parseClip( length );
-
-				}
-
-				break;
-
-			case 'STIL':
-				this.parseImage();
-				break;
-
-			case 'XREF': // clone of another STIL
-				this.reader.skip( 8 ); // unknown
-				this.currentForm.referenceTexture = {
-					index: this.reader.getUint32(),
-					refName: this.reader.getString() // internal unique ref
-				};
-				break;
-
-				// Not in spec, used by texture nodes
-
-			case 'IMST':
-				this.parseImageStateForm( length );
-				break;
-
-				// SURF FORM AND SUB FORMS
-
-			case 'SURF':
-				this.parseSurfaceForm( length );
-				break;
-
-			case 'VALU': // Not in spec
-				this.parseValueForm( length );
-				break;
-
-			case 'NTAG':
-				this.parseSubNode( length );
-				break;
-
-			case 'ATTR': // BSDF Node Attributes
-			case 'SATR': // Standard Node Attributes
-				this.setupForm( 'attributes', length );
-				break;
-
-			case 'NCON':
-				this.parseConnections( length );
-				break;
-
-			case 'SSHA':
-				this.parentForm = this.currentForm;
-				this.currentForm = this.currentSurface;
-				this.setupForm( 'surfaceShader', length );
-				break;
-
-			case 'SSHD':
-				this.setupForm( 'surfaceShaderData', length );
-				break;
-
-			case 'ENTR': // Not in spec
-				this.parseEntryForm( length );
-				break;
-
-				// Image Map Layer
-
-			case 'IMAP':
-				this.parseImageMap( length );
-				break;
-
-			case 'TAMP':
-				this.parseXVAL( 'amplitude', length );
-				break;
-
-				//Texture Mapping Form
-
-			case 'TMAP':
-				this.setupForm( 'textureMap', length );
-				break;
-
-			case 'CNTR':
-				this.parseXVAL3( 'center', length );
-				break;
-
-			case 'SIZE':
-				this.parseXVAL3( 'scale', length );
-				break;
-
-			case 'ROTA':
-				this.parseXVAL3( 'rotation', length );
-				break;
-
-			default:
-				this.parseUnknownForm( type, length );
-
-		}
-
-		this.debugger.node = 0;
-		this.debugger.nodeID = type;
-		this.debugger.log();
-
-	},
-
-	setupForm( type, length ) {
-
-		if ( ! this.currentForm ) this.currentForm = this.currentNode;
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		if ( ! this.currentForm[ type ] ) {
-
-			this.currentForm[ type ] = {};
-			this.currentForm = this.currentForm[ type ];
-
-
-		} else {
-
-			// should never see this unless there's a bug in the reader
-			console.warn( 'LWOLoader: form already exists on parent: ', type, this.currentForm );
-
-			this.currentForm = this.currentForm[ type ];
-
-		}
-
-
-	},
-
-	skipForm( length ) {
-
-		this.reader.skip( length - 4 );
-
-	},
-
-	parseUnknownForm( type, length ) {
-
-		console.warn( 'LWOLoader: unknown FORM encountered: ' + type, length );
-
-		printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 );
-		this.reader.skip( length - 4 );
-
-	},
-
-	parseSurfaceForm( length ) {
-
-		this.reader.skip( 8 ); // unknown Uint32 x2
-
-		var name = this.reader.getString();
-
-		var surface = {
-			attributes: {}, // LWO2 style non-node attributes will go here
-			connections: {},
-			name: name,
-			inputName: name,
-			nodes: {},
-			source: this.reader.getString(),
-		};
-
-		this.tree.materials[ name ] = surface;
-		this.currentSurface = surface;
-
-		this.parentForm = this.tree.materials;
-		this.currentForm = surface;
-		this.currentFormEnd = this.reader.offset + length;
-
-	},
-
-	parseSurfaceLwo2( length ) {
-
-		var name = this.reader.getString();
-
-		var surface = {
-			attributes: {}, // LWO2 style non-node attributes will go here
-			connections: {},
-			name: name,
-			nodes: {},
-			source: this.reader.getString(),
-		};
-
-		this.tree.materials[ name ] = surface;
-		this.currentSurface = surface;
-
-		this.parentForm = this.tree.materials;
-		this.currentForm = surface;
-		this.currentFormEnd = this.reader.offset + length;
-
-	},
-
-	parseSubNode( length ) {
-
-		// parse the NRNM CHUNK of the subnode FORM to get
-		// a meaningful name for the subNode
-		// some subnodes can be renamed, but Input and Surface cannot
-
-		this.reader.skip( 8 ); // NRNM + length
-		var name = this.reader.getString();
-
-		var node = {
-			name: name
-		};
-		this.currentForm = node;
-		this.currentNode = node;
-
-		this.currentFormEnd = this.reader.offset + length;
-
-
-	},
-
-	// collect attributes from all nodes at the top level of a surface
-	parseConnections( length ) {
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		this.currentForm = this.currentSurface.connections;
-
-	},
-
-	// surface node attribute data, e.g. specular, roughness etc
-	parseEntryForm( length ) {
-
-		this.reader.skip( 8 ); // NAME + length
-		var name = this.reader.getString();
-		this.currentForm = this.currentNode.attributes;
-
-		this.setupForm( name, length );
-
-	},
-
-	// parse values from material - doesn't match up to other LWO3 data types
-	// sub form of entry form
-	parseValueForm() {
-
-		this.reader.skip( 8 ); // unknown + length
-
-		var valueType = this.reader.getString();
-
-		if ( valueType === 'double' ) {
-
-			this.currentForm.value = this.reader.getUint64();
-
-		} else if ( valueType === 'int' ) {
-
-			this.currentForm.value = this.reader.getUint32();
-
-		} else if ( valueType === 'vparam' ) {
-
-			this.reader.skip( 24 );
-			this.currentForm.value = this.reader.getFloat64();
-
-		} else if ( valueType === 'vparam3' ) {
-
-			this.reader.skip( 24 );
-			this.currentForm.value = this.reader.getFloat64Array( 3 );
-
-		}
-
-	},
-
-	// holds various data about texture node image state
-	// Data other thanmipMapLevel unknown
-	parseImageStateForm() {
-
-		this.reader.skip( 8 ); // unknown
-
-		this.currentForm.mipMapLevel = this.reader.getFloat32();
-
-	},
-
-	// LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node)
-	parseImageMap( length ) {
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		if ( ! this.currentForm.maps ) this.currentForm.maps = [];
-
-		var map = {};
-		this.currentForm.maps.push( map );
-		this.currentForm = map;
-
-		this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX
-
-	},
-
-	parseTextureNodeAttribute( type ) {
-
-		this.reader.skip( 28 ); // FORM + length + VPRM + unknown + Uint32 x2 + float32
-
-		this.reader.skip( 20 ); // FORM + length + VPVL + float32 + Uint32
-
-		switch ( type ) {
-
-			case 'ISCL':
-				this.currentNode.scale = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IPOS':
-				this.currentNode.position = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IROT':
-				this.currentNode.rotation = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IFAL':
-				this.currentNode.falloff = this.reader.getFloat32Array( 3 );
-				break;
-
-			case 'IBMP':
-				this.currentNode.amplitude = this.reader.getFloat32();
-				break;
-			case 'IUTD':
-				this.currentNode.uTiles = this.reader.getFloat32();
-				break;
-			case 'IVTD':
-				this.currentNode.vTiles = this.reader.getFloat32();
-				break;
-
-		}
-
-		this.reader.skip( 2 ); // unknown
-
-
-	},
-
-	// ENVL forms are currently ignored
-	parseEnvelope( length ) {
-
-		this.reader.skip( length - 4 ); // skipping  entirely for now
-
-	},
-
-	///
-	// CHUNK PARSING METHODS
-	///
-
-	// clips can either be defined inside a surface node, or at the top
-	// level and they have a different format in each case
-	parseClip( length ) {
-
-		var tag = this.reader.getIDTag();
-
-		// inside surface node
-		if ( tag === 'FORM' ) {
-
-			this.reader.skip( 16 );
-
-			this.currentNode.fileName = this.reader.getString();
-
-			return;
-
-		}
-
-		// otherwise top level
-		this.reader.setOffset( this.reader.offset - 4 );
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		this.reader.skip( 8 ); // unknown
-
-		var texture = {
-			index: this.reader.getUint32()
-		};
-		this.tree.textures.push( texture );
-		this.currentForm = texture;
-
-	},
-
-	parseClipLwo2( length ) {
-
-		var texture = {
-			index: this.reader.getUint32(),
-			fileName: ""
-		};
-
-		// seach STIL block
-		while ( true ) {
-
-			var tag = this.reader.getIDTag();
-			var n_length = this.reader.getUint16();
-			if ( tag === 'STIL' ) {
-
-				texture.fileName = this.reader.getString();
-				break;
-
-			}
-
-			if ( n_length >= length ) {
-
-				break;
-
-			}
-
-		}
-
-		this.tree.textures.push( texture );
-		this.currentForm = texture;
-
-	},
-
-	parseImage() {
-
-		this.reader.skip( 8 ); // unknown
-		this.currentForm.fileName = this.reader.getString();
-
-	},
-
-	parseXVAL( type, length ) {
-
-		var endOffset = this.reader.offset + length - 4;
-		this.reader.skip( 8 );
-
-		this.currentForm[ type ] = this.reader.getFloat32();
-
-		this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope
-
-	},
-
-	parseXVAL3( type, length ) {
-
-		var endOffset = this.reader.offset + length - 4;
-		this.reader.skip( 8 );
-
-		this.currentForm[ type ] = {
-			x: this.reader.getFloat32(),
-			y: this.reader.getFloat32(),
-			z: this.reader.getFloat32(),
-		};
-
-		this.reader.setOffset( endOffset );
-
-	},
-
-	// Tags associated with an object
-	// OTAG { type[ID4], tag-string[S0] }
-	parseObjectTag() {
-
-		if ( ! this.tree.objectTags ) this.tree.objectTags = {};
-
-		this.tree.objectTags[ this.reader.getIDTag() ] = {
-			tagString: this.reader.getString()
-		};
-
-	},
-
-	// Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered.
-	// LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2]
-	parseLayer( length ) {
-
-		var layer = {
-			number: this.reader.getUint16(),
-			flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden.
-			pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present
-			name: this.reader.getString(),
-		};
-
-		this.tree.layers.push( layer );
-		this.currentLayer = layer;
-
-		var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength
-
-		// if we have not reached then end of the layer block, there must be a parent defined
-		this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent
-
-	},
-
-	// VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors
-	// Converting from left to right handed coordinate system:
-	// x -> -x and switch material FrontSide -> BackSide
-	parsePoints( length ) {
-
-		this.currentPoints = [];
-		for ( var i = 0; i < length / 4; i += 3 ) {
-
-			// z -> -z to match three.js right handed coords
-			this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
-
-		}
-
-	},
-
-	// parse VMAP or VMAD
-	// Associates a set of floating-point vectors with a set of points.
-	// VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * }
-
-	// VMAD Associates a set of floating-point vectors with the vertices of specific polygons.
-	// Similar to VMAP UVs, but associates with polygon vertices rather than points
-	// to solve to problem of UV seams:  VMAD chunks are paired with VMAPs of the same name,
-	// if they exist. The vector values in the VMAD will then replace those in the
-	// corresponding VMAP, but only for calculations involving the specified polygons.
-	// VMAD { type[ID4], dimension[U2], name[S0], ( vert[VX], poly[VX], value[F4] # dimension ) * }
-	parseVertexMapping( length, discontinuous ) {
-
-		var finalOffset = this.reader.offset + length;
-
-		var channelName = this.reader.getString();
-
-		if ( this.reader.offset === finalOffset ) {
-
-			// then we are in a texture node and the VMAP chunk is just a reference to a UV channel name
-			this.currentForm.UVChannel = channelName;
-			return;
-
-		}
-
-		// otherwise reset to initial length and parse normal VMAP CHUNK
-		this.reader.setOffset( this.reader.offset - stringOffset( channelName ) );
-
-		var type = this.reader.getIDTag();
-
-		this.reader.getUint16(); // dimension
-		var name = this.reader.getString();
-
-		var remainingLength = length - 6 - stringOffset( name );
-
-		switch ( type ) {
-
-			case 'TXUV':
-				this.parseUVMapping( name, finalOffset, discontinuous );
-				break;
-			case 'MORF':
-			case 'SPOT':
-				this.parseMorphTargets( name, finalOffset, type ); // can't be discontinuous
-				break;
-			// unsupported VMAPs
-			case 'APSL':
-			case 'NORM':
-			case 'WGHT':
-			case 'MNVW':
-			case 'PICK':
-			case 'RGB ':
-			case 'RGBA':
-				this.reader.skip( remainingLength );
-				break;
-			default:
-				console.warn( 'LWOLoader: unknown vertex map type: ' + type );
-				this.reader.skip( remainingLength );
-
-		}
-
-	},
-
-	parseUVMapping( name, finalOffset, discontinuous ) {
-
-		var uvIndices = [];
-		var polyIndices = [];
-		var uvs = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			uvIndices.push( this.reader.getVariableLengthIndex() );
-
-			if ( discontinuous ) polyIndices.push( this.reader.getVariableLengthIndex() );
-
-			uvs.push( this.reader.getFloat32(), this.reader.getFloat32() );
-
-		}
-
-		if ( discontinuous ) {
-
-			if ( ! this.currentLayer.discontinuousUVs ) this.currentLayer.discontinuousUVs = {};
-
-			this.currentLayer.discontinuousUVs[ name ] = {
-				uvIndices: uvIndices,
-				polyIndices: polyIndices,
-				uvs: uvs,
-			};
-
-		} else {
-
-			if ( ! this.currentLayer.uvs ) this.currentLayer.uvs = {};
-
-			this.currentLayer.uvs[ name ] = {
-				uvIndices: uvIndices,
-				uvs: uvs,
-			};
-
-		}
-
-	},
-
-	parseMorphTargets( name, finalOffset, type ) {
-
-		var indices = [];
-		var points = [];
-
-		type = ( type === 'MORF' ) ? 'relative' : 'absolute';
-
-		while ( this.reader.offset < finalOffset ) {
-
-			indices.push( this.reader.getVariableLengthIndex() );
-			// z -> -z to match three.js right handed coords
-			points.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
-
-		}
-
-		if ( ! this.currentLayer.morphTargets ) this.currentLayer.morphTargets = {};
-
-		this.currentLayer.morphTargets[ name ] = {
-			indices: indices,
-			points: points,
-			type: type,
-		};
-
-	},
-
-	// A list of polygons for the current layer.
-	// POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * }
-	parsePolygonList( length ) {
-
-		var finalOffset = this.reader.offset + length;
-		var type = this.reader.getIDTag();
-
-		var indices = [];
-
-		// hold a list of polygon sizes, to be split up later
-		var polygonDimensions = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			var numverts = this.reader.getUint16();
-
-			//var flags = numverts & 64512; // 6 high order bits are flags - ignoring for now
-			numverts = numverts & 1023; // remaining ten low order bits are vertex num
-			polygonDimensions.push( numverts );
-
-			for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() );
-
-		}
-
-		var geometryData = {
-			type: type,
-			vertexIndices: indices,
-			polygonDimensions: polygonDimensions,
-			points: this.currentPoints
-		};
-
-		// Note: assuming that all polys will be lines or points if the first is
-		if ( polygonDimensions[ 0 ] === 1 ) geometryData.type = 'points';
-		else if ( polygonDimensions[ 0 ] === 2 ) geometryData.type = 'lines';
-
-		this.currentLayer.geometry = geometryData;
-
-	},
-
-	// Lists the tag strings that can be associated with polygons by the PTAG chunk.
-	// TAGS { tag-string[S0] * }
-	parseTagStrings( length ) {
-
-		this.tree.tags = this.reader.getStringArray( length );
-
-	},
-
-	// Associates tags of a given type with polygons in the most recent POLS chunk.
-	// PTAG { type[ID4], ( poly[VX], tag[U2] ) * }
-	parsePolygonTagMapping( length ) {
-
-		var finalOffset = this.reader.offset + length;
-		var type = this.reader.getIDTag();
-		if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset );
-		else { //PART, SMGP, COLR not supported
-
-			this.reader.skip( length - 4 );
-
-		}
-
-	},
-
-	parseMaterialIndices( finalOffset ) {
-
-		// array holds polygon index followed by material index
-		this.currentLayer.geometry.materialIndices = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			var polygonIndex = this.reader.getVariableLengthIndex();
-			var materialIndex = this.reader.getUint16();
-
-			this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex );
-
-		}
-
-	},
-
-	parseUnknownCHUNK( blockID, length ) {
-
-		console.warn( 'LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length );
-
-		// print the chunk plus some bytes padding either side
-		// printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 );
-
-		var data = this.reader.getString( length );
-
-		this.currentForm[ blockID ] = data;
-
-	}
-
-};
-
-function DataViewReader( buffer ) {
-
-	this.dv = new DataView( buffer );
-	this.offset = 0;
-
-}
-
-DataViewReader.prototype = {
-
-	constructor: DataViewReader,
-
-	size: function () {
-
-		return this.dv.buffer.byteLength;
-
-	},
-
-	setOffset( offset ) {
-
-		if ( offset > 0 && offset < this.dv.buffer.byteLength ) {
-
-			this.offset = offset;
-
-		} else {
-
-			console.error( 'LWOLoader: invalid buffer offset' );
-
-		}
-
-	},
-
-	endOfFile: function () {
-
-		if ( this.offset >= this.size() ) return true;
-		return false;
-
-	},
-
-	skip: function ( length ) {
-
-		this.offset += length;
-
-	},
-
-	getUint8: function () {
-
-		var value = this.dv.getUint8( this.offset );
-		this.offset += 1;
-		return value;
-
-	},
-
-	getUint16: function () {
-
-		var value = this.dv.getUint16( this.offset );
-		this.offset += 2;
-		return value;
-
-	},
-
-	getInt32: function () {
-
-		var value = this.dv.getInt32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getUint32: function () {
-
-		var value = this.dv.getUint32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getUint64: function () {
-
-		var low, high;
-
-		high = this.getUint32();
-		low = this.getUint32();
-		return high * 0x100000000 + low;
-
-	},
-
-	getFloat32: function () {
-
-		var value = this.dv.getFloat32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getFloat32Array: function ( size ) {
-
-		var a = [];
-
-		for ( var i = 0; i < size; i ++ ) {
-
-			a.push( this.getFloat32() );
-
-		}
-
-		return a;
-
-	},
-
-	getFloat64: function () {
-
-		var value = this.dv.getFloat64( this.offset, this.littleEndian );
-		this.offset += 8;
-		return value;
-
-	},
-
-	getFloat64Array: function ( size ) {
-
-		var a = [];
-
-		for ( var i = 0; i < size; i ++ ) {
-
-			a.push( this.getFloat64() );
-
-		}
-
-		return a;
-
-	},
-
-	// get variable-length index data type
-	// VX ::= index[U2] | (index + 0xFF000000)[U4]
-	// If the index value is less than 65,280 (0xFF00),then VX === U2
-	// otherwise VX === U4 with bits 24-31 set
-	// When reading an index, if the first byte encountered is 255 (0xFF), then
-	// the four-byte form is being used and the first byte should be discarded or masked out.
-	getVariableLengthIndex() {
-
-		var firstByte = this.getUint8();
-
-		if ( firstByte === 255 ) {
-
-			return this.getUint8() * 65536 + this.getUint8() * 256 + this.getUint8();
-
-		}
-
-		return firstByte * 256 + this.getUint8();
-
-	},
-
-	// An ID tag is a sequence of 4 bytes containing 7-bit ASCII values
-	getIDTag() {
-
-		return this.getString( 4 );
-
-	},
-
-	getString: function ( size ) {
-
-		if ( size === 0 ) return;
-
-		// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
-		var a = [];
-
-		if ( size ) {
-
-			for ( var i = 0; i < size; i ++ ) {
-
-				a[ i ] = this.getUint8();
-
-			}
-
-		} else {
-
-			var currentChar;
-			var len = 0;
-
-			while ( currentChar !== 0 ) {
-
-				currentChar = this.getUint8();
-				if ( currentChar !== 0 ) a.push( currentChar );
-				len ++;
-
-			}
-
-			if ( ! isEven( len + 1 ) ) this.getUint8(); // if string with terminating nullbyte is uneven, extra nullbyte is added
-
-		}
-
-		return THREE.LoaderUtils.decodeText( new Uint8Array( a ) );
-
-	},
-
-	getStringArray: function ( size ) {
-
-		var a = this.getString( size );
-		a = a.split( '\0' );
-
-		return a.filter( Boolean ); // return array with any empty strings removed
-
-	}
-
-};
-
-// ************** DEBUGGER  **************
-
-function Debugger( ) {
-
-	this.active = false;
-	this.depth = 0;
-	this.formList = [];
-
-}
-
-Debugger.prototype = {
-
-	constructor: Debugger,
-
-	enable: function () {
-
-		this.active = true;
-
-	},
-
-	log: function () {
-
-		if ( ! this.active ) return;
-
-		var nodeType;
-
-		switch ( this.node ) {
-
-			case 0:
-				nodeType = "FORM";
-				break;
-
-			case 1:
-				nodeType = "CHK";
-				break;
-
-			case 2:
-				nodeType = "S-CHK";
-				break;
-
-		}
-
-		console.log(
-			"| ".repeat( this.depth ) +
-			nodeType,
-			this.nodeID,
-			`( ${this.offset} ) -> ( ${this.dataOffset + this.length} )`,
-			( ( this.node == 0 ) ? " {" : "" ),
-			( ( this.skipped ) ? "SKIPPED" : "" ),
-			( ( this.node == 0 && this.skipped ) ? "}" : "" )
-		);
-
-		if ( this.node == 0 && ! this.skipped ) {
-
-			this.depth += 1;
-			this.formList.push( this.dataOffset + this.length );
-
-		}
-
-		this.skipped = false;
-
-	},
-
-	closeForms: function () {
-
-		if ( ! this.active ) return;
-
-		for ( var i = this.formList.length - 1; i >= 0; i -- ) {
-
-			if ( this.offset >= this.formList[ i ] ) {
-
-				this.depth -= 1;
-				console.log( "| ".repeat( this.depth ) + "}" );
-				this.formList.splice( - 1, 1 );
-
-			}
-
-		}
-
-	}
-
-};
-
-// ************** UTILITY FUNCTIONS **************
-
-function isEven( num ) {
-
-	return num % 2;
-
-}
-
-// calculate the length of the string in the buffer
-// this will be string.length + nullbyte + optional padbyte to make the length even
-function stringOffset( string ) {
-
-	return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 );
-
-}
-
-// for testing purposes, dump buffer to console
-// printBuffer( this.reader.dv.buffer, this.reader.offset, length );
-function printBuffer( buffer, from, to ) {
-
-	console.log( THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ) );
-
-}
-
-var lwoTree;
-
-THREE.LWOLoader = function ( manager, parameters ) {
-
-	THREE.Loader.call( this, manager );
-
-	parameters = parameters || {};
-
-	this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : '';
-
-};
-
-THREE.LWOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
-
-	constructor: THREE.LWOLoader,
-
-	load: function ( url, onLoad, onProgress, onError ) {
-
-		var scope = this;
-
-		var path = ( scope.path === '' ) ? extractParentUrl( url, 'Objects' ) : scope.path;
-
-		// give the mesh a default name based on the filename
-		var modelName = url.split( path ).pop().split( '.' )[ 0 ];
-
-		var loader = new THREE.FileLoader( this.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-
-		loader.load( url, function ( buffer ) {
-
-			// console.time( 'Total parsing: ' );
-			onLoad( scope.parse( buffer, path, modelName ) );
-			// console.timeEnd( 'Total parsing: ' );
-
-		}, onProgress, onError );
-
-	},
-
-	parse: function ( iffBuffer, path, modelName ) {
-
-		lwoTree = new IFFParser().parse( iffBuffer );
-
-		// console.log( 'lwoTree', lwoTree );
-
-		var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
-
-		return new LWOTreeParser( textureLoader ).parse( modelName );
-
-	}
-
-} );
-
-// Parse the lwoTree object
-function LWOTreeParser( textureLoader ) {
-
-	this.textureLoader = textureLoader;
-
-}
-
-LWOTreeParser.prototype = {
-
-	constructor: LWOTreeParser,
-
-	parse: function ( modelName ) {
-
-		this.materials = new MaterialParser( this.textureLoader ).parse();
-		this.defaultLayerName = modelName;
-
-		this.meshes = this.parseLayers();
-
-		return {
-			materials: this.materials,
-			meshes: this.meshes,
-		};
-
-	},
-
-	parseLayers() {
-
-		// array of all meshes for building hierarchy
-		var meshes = [];
-
-		// final array containing meshes with scene graph hierarchy set up
-		var finalMeshes = [];
-
-		var geometryParser = new GeometryParser();
-
-		var scope = this;
-		lwoTree.layers.forEach( function ( layer ) {
-
-			var geometry = geometryParser.parse( layer.geometry, layer );
-
-			var mesh = scope.parseMesh( geometry, layer );
-
-			meshes[ layer.number ] = mesh;
-
-			if ( layer.parent === - 1 ) finalMeshes.push( mesh );
-			else meshes[ layer.parent ].add( mesh );
-
-
-		} );
-
-		this.applyPivots( finalMeshes );
-
-		return finalMeshes;
-
-	},
-
-	parseMesh( geometry, layer ) {
-
-		var mesh;
-
-		var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type );
-
-		this.duplicateUVs( geometry, materials );
-
-		if ( layer.geometry.type === 'points' ) mesh = new THREE.Points( geometry, materials );
-		else if ( layer.geometry.type === 'lines' ) mesh = new THREE.LineSegments( geometry, materials );
-		else mesh = new THREE.Mesh( geometry, materials );
-
-		if ( layer.name ) mesh.name = layer.name;
-		else mesh.name = this.defaultLayerName + '_layer_' + layer.number;
-
-		mesh.userData.pivot = layer.pivot;
-
-		return mesh;
-
-	},
-
-	// TODO: may need to be reversed in z to convert LWO to three.js coordinates
-	applyPivots( meshes ) {
-
-		meshes.forEach( function ( mesh ) {
-
-			mesh.traverse( function ( child ) {
-
-				var pivot = child.userData.pivot;
-
-				child.position.x += pivot[ 0 ];
-				child.position.y += pivot[ 1 ];
-				child.position.z += pivot[ 2 ];
-
-				if ( child.parent ) {
-
-					var parentPivot = child.parent.userData.pivot;
-
-					child.position.x -= parentPivot[ 0 ];
-					child.position.y -= parentPivot[ 1 ];
-					child.position.z -= parentPivot[ 2 ];
-
-				}
-
-			} );
-
-		} );
-
-	},
-
-	getMaterials( namesArray, type ) {
-
-		var materials = [];
-
-		var scope = this;
-
-		namesArray.forEach( function ( name, i ) {
-
-			materials[ i ] = scope.getMaterialByName( name );
-
-		} );
-
-		// convert materials to line or point mats if required
-		if ( type === 'points' || type === 'lines' ) {
-
-			materials.forEach( function ( mat, i ) {
-
-				var spec = {
-					color: mat.color,
-				};
-
-				if ( type === 'points' ) {
-
-					spec.size = 0.1;
-					spec.map = mat.map;
-					spec.morphTargets = mat.morphTargets;
-					materials[ i ] = new THREE.PointsMaterial( spec );
-
-				} else if ( type === 'lines' ) {
-
-					materials[ i ] = new THREE.LineBasicMaterial( spec );
-
-				}
-
-			} );
-
-		}
-
-		// if there is only one material, return that directly instead of array
-		var filtered = materials.filter( Boolean );
-		if ( filtered.length === 1 ) return filtered[ 0 ];
-
-		return materials;
-
-	},
-
-	getMaterialByName( name ) {
-
-		return this.materials.filter( function ( m ) {
-
-			return m.name === name;
-
-		} )[ 0 ];
-
-	},
-
-	// If the material has an aoMap, duplicate UVs
-	duplicateUVs( geometry, materials ) {
-
-		var duplicateUVs = false;
-
-		if ( ! Array.isArray( materials ) ) {
-
-			if ( materials.aoMap ) duplicateUVs = true;
-
-		} else {
-
-			materials.forEach( function ( material ) {
-
-				if ( material.aoMap ) duplicateUVs = true;
-
-			} );
-
-		}
-
-		if ( ! duplicateUVs ) return;
-
-		geometry.setAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
-
-	},
-
-};
-
-function MaterialParser( textureLoader ) {
-
-	this.textureLoader = textureLoader;
-
-}
-
-MaterialParser.prototype = {
-
-	constructor: MaterialParser,
-
-	parse: function () {
-
-		var materials = [];
-		this.textures = {};
-
-		for ( var name in lwoTree.materials ) {
-
-			if ( lwoTree.format === 'LWO3' ) {
-
-				materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) );
-
-			} else if ( lwoTree.format === 'LWO2' ) {
-
-				materials.push( this.parseMaterialLwo2( lwoTree.materials[ name ], name, lwoTree.textures ) );
-
-			}
-
-		}
-
-		return materials;
-
-	},
-
-	parseMaterial( materialData, name, textures ) {
-
-		var params = {
-			name: name,
-			side: this.getSide( materialData.attributes ),
-			flatShading: this.getSmooth( materialData.attributes ),
-		};
-
-		var connections = this.parseConnections( materialData.connections, materialData.nodes );
-
-		var maps = this.parseTextureNodes( connections.maps );
-
-		this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps );
-
-		var attributes = this.parseAttributes( connections.attributes, maps );
-
-		this.parseEnvMap( connections, maps, attributes );
-
-		params = Object.assign( maps, params );
-		params = Object.assign( params, attributes );
-
-		var materialType = this.getMaterialType( connections.attributes );
-
-		return new materialType( params );
-
-	},
-
-	parseMaterialLwo2( materialData, name/*, textures*/ ) {
-
-		var params = {
-			name: name,
-			side: this.getSide( materialData.attributes ),
-			flatShading: this.getSmooth( materialData.attributes ),
-		};
-
-		var attributes = this.parseAttributes( materialData.attributes, {} );
-		params = Object.assign( params, attributes );
-		return new THREE.MeshPhongMaterial( params );
-
-	},
-
-	// Note: converting from left to right handed coords by switching x -> -x in vertices, and
-	// then switching mat FrontSide -> BackSide
-	// NB: this means that THREE.FrontSide and THREE.BackSide have been switched!
-	getSide( attributes ) {
-
-		if ( ! attributes.side ) return THREE.BackSide;
-
-		switch ( attributes.side ) {
-
-			case 0:
-			case 1:
-				return THREE.BackSide;
-			case 2: return THREE.FrontSide;
-			case 3: return THREE.DoubleSide;
-
-		}
-
-	},
-
-	getSmooth( attributes ) {
-
-		if ( ! attributes.smooth ) return true;
-		return ! attributes.smooth;
-
-	},
-
-	parseConnections( connections, nodes ) {
-
-		var materialConnections = {
-			maps: {}
-		};
-
-		var inputName = connections.inputName;
-		var inputNodeName = connections.inputNodeName;
-		var nodeName = connections.nodeName;
-
-		var scope = this;
-		inputName.forEach( function ( name, index ) {
-
-			if ( name === 'Material' ) {
-
-				var matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes );
-				materialConnections.attributes = matNode.attributes;
-				materialConnections.envMap = matNode.fileName;
-				materialConnections.name = inputNodeName[ index ];
-
-			}
-
-		} );
-
-		nodeName.forEach( function ( name, index ) {
-
-			if ( name === materialConnections.name ) {
-
-				materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes );
-
-			}
-
-		} );
-
-		return materialConnections;
-
-	},
-
-	getNodeByRefName( refName, nodes ) {
-
-		for ( var name in nodes ) {
-
-			if ( nodes[ name ].refName === refName ) return nodes[ name ];
-
-		}
-
-	},
-
-	parseTextureNodes( textureNodes ) {
-
-		var maps = {};
-
-		for ( var name in textureNodes ) {
-
-			var node = textureNodes[ name ];
-			var path = node.fileName;
-
-			if ( ! path ) return;
-
-			var texture = this.loadTexture( path );
-
-			if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode );
-			if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode );
-
-			switch ( name ) {
-
-				case 'Color':
-					maps.map = texture;
-					break;
-				case 'Roughness':
-					maps.roughnessMap = texture;
-					maps.roughness = 0.5;
-					break;
-				case 'Specular':
-					maps.specularMap = texture;
-					maps.specular = 0xffffff;
-					break;
-				case 'Luminous':
-					maps.emissiveMap = texture;
-					maps.emissive = 0x808080;
-					break;
-				case 'Luminous Color':
-					maps.emissive = 0x808080;
-					break;
-				case 'Metallic':
-					maps.metalnessMap = texture;
-					maps.metalness = 0.5;
-					break;
-				case 'Transparency':
-				case 'Alpha':
-					maps.alphaMap = texture;
-					maps.transparent = true;
-					break;
-				case 'Normal':
-					maps.normalMap = texture;
-					if ( node.amplitude !== undefined ) maps.normalScale = new THREE.Vector2( node.amplitude, node.amplitude );
-					break;
-				case 'Bump':
-					maps.bumpMap = texture;
-					break;
-
-			}
-
-		}
-
-		// LWO BSDF materials can have both spec and rough, but this is not valid in three
-		if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap;
-
-		return maps;
-
-	},
-
-	// maps can also be defined on individual material attributes, parse those here
-	// This occurs on Standard (Phong) surfaces
-	parseAttributeImageMaps( attributes, textures, maps ) {
-
-		for ( var name in attributes ) {
-
-			var attribute = attributes[ name ];
-
-			if ( attribute.maps ) {
-
-				var mapData = attribute.maps[ 0 ];
-
-				var path = this.getTexturePathByIndex( mapData.imageIndex, textures );
-				if ( ! path ) return;
-
-				var texture = this.loadTexture( path );
-
-				if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w );
-				if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h );
-
-				switch ( name ) {
-
-					case 'Color':
-						maps.map = texture;
-						break;
-					case 'Diffuse':
-						maps.aoMap = texture;
-						break;
-					case 'Roughness':
-						maps.roughnessMap = texture;
-						maps.roughness = 1;
-						break;
-					case 'Specular':
-						maps.specularMap = texture;
-						maps.specular = 0xffffff;
-						break;
-					case 'Luminosity':
-						maps.emissiveMap = texture;
-						maps.emissive = 0x808080;
-						break;
-					case 'Metallic':
-						maps.metalnessMap = texture;
-						maps.metalness = 1;
-						break;
-					case 'Transparency':
-					case 'Alpha':
-						maps.alphaMap = texture;
-						maps.transparent = true;
-						break;
-					case 'Normal':
-						maps.normalMap = texture;
-						break;
-					case 'Bump':
-						maps.bumpMap = texture;
-						break;
-
-				}
-
-			}
-
-		}
-
-	},
-
-	parseAttributes( attributes, maps ) {
-
-		var params = {};
-
-		// don't use color data if color map is present
-		if ( attributes.Color && ! maps.map ) {
-
-			params.color = new THREE.Color().fromArray( attributes.Color.value );
-
-		} else params.color = new THREE.Color();
-
-
-		if ( attributes.Transparency && attributes.Transparency.value !== 0 ) {
-
-			params.opacity = 1 - attributes.Transparency.value;
-			params.transparent = true;
-
-		}
-
-		if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1;
-
-		if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 1 / attributes[ 'Refraction Index' ].value;
-
-		this.parsePhysicalAttributes( params, attributes, maps );
-		this.parseStandardAttributes( params, attributes, maps );
-		this.parsePhongAttributes( params, attributes, maps );
-
-		return params;
-
-	},
-
-	parsePhysicalAttributes( params, attributes/*, maps*/ ) {
-
-		if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) {
-
-			params.clearcoat = attributes.Clearcoat.value;
-
-			if ( attributes[ 'Clearcoat Gloss' ] ) {
-
-				params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value );
-
-			}
-
-		}
-
-	},
-
-	parseStandardAttributes( params, attributes, maps ) {
-
-
-		if ( attributes.Luminous ) {
-
-			params.emissiveIntensity = attributes.Luminous.value;
-
-			if ( attributes[ 'Luminous Color' ] && ! maps.emissive ) {
-
-				params.emissive = new THREE.Color().fromArray( attributes[ 'Luminous Color' ].value );
-
-			} else {
-
-				params.emissive = new THREE.Color( 0x808080 );
-
-			}
-
-		}
-
-		if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value;
-		if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value;
-
-	},
-
-	parsePhongAttributes( params, attributes, maps ) {
-
-		if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value );
-
-		if ( attributes.Reflection ) {
-
-			params.reflectivity = attributes.Reflection.value;
-			params.combine = THREE.AddOperation;
-
-		}
-
-		if ( attributes.Luminosity ) {
-
-			params.emissiveIntensity = attributes.Luminosity.value;
-
-			if ( ! maps.emissiveMap && ! maps.map ) {
-
-				params.emissive = params.color;
-
-			} else {
-
-				params.emissive = new THREE.Color( 0x808080 );
-
-			}
-
-		}
-
-		// parse specular if there is no roughness - we will interpret the material as 'Phong' in this case
-		if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) {
-
-			if ( attributes[ 'Color Highlight' ] ) {
-
-				params.specular = new THREE.Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value );
-
-			} else {
-
-				params.specular = new THREE.Color().setScalar( attributes.Specular.value );
-
-			}
-
-		}
-
-		if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 );
-
-	},
-
-	parseEnvMap( connections, maps, attributes ) {
-
-		if ( connections.envMap ) {
-
-			var envMap = this.loadTexture( connections.envMap );
-
-			if ( attributes.transparent && attributes.opacity < 0.999 ) {
-
-				envMap.mapping = THREE.EquirectangularRefractionMapping;
-
-				// Reflectivity and refraction mapping don't work well together in Phong materials
-				if ( attributes.reflectivity !== undefined ) {
-
-					delete attributes.reflectivity;
-					delete attributes.combine;
-
-				}
-
-				if ( attributes.metalness !== undefined ) {
-
-					delete attributes.metalness;
-
-				}
-
-			} else envMap.mapping = THREE.EquirectangularReflectionMapping;
-
-			maps.envMap = envMap;
-
-		}
-
-	},
-
-	// get texture defined at top level by its index
-	getTexturePathByIndex( index ) {
-
-		var fileName = '';
-
-		if ( ! lwoTree.textures ) return fileName;
-
-		lwoTree.textures.forEach( function ( texture ) {
-
-			if ( texture.index === index ) fileName = texture.fileName;
-
-		} );
-
-		return fileName;
-
-	},
-
-	loadTexture( path ) {
-
-		if ( ! path ) return null;
-
-		var texture;
-
-		texture = this.textureLoader.load(
-			path,
-			undefined,
-			undefined,
-			function () {
-
-				console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' );
-
-			}
-		);
-
-		return texture;
-
-	},
-
-	// 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
-	getWrappingType( num ) {
-
-		switch ( num ) {
-
-			case 0:
-				console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' );
-				return THREE.ClampToEdgeWrapping;
-			case 1: return THREE.RepeatWrapping;
-			case 2: return THREE.MirroredRepeatWrapping;
-			case 3: return THREE.ClampToEdgeWrapping;
-
-		}
-
-	},
-
-	getMaterialType( nodeData ) {
-
-		if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return THREE.MeshPhysicalMaterial;
-		if ( nodeData.Roughness ) return THREE.MeshStandardMaterial;
-		return THREE.MeshPhongMaterial;
-
-	}
-
-};
-
-function GeometryParser() {}
-
-GeometryParser.prototype = {
-
-	constructor: GeometryParser,
-
-	parse( geoData, layer ) {
-
-		var geometry = new THREE.BufferGeometry();
-
-		geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) );
-
-		var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions );
-		geometry.setIndex( indices );
-
-		this.parseGroups( geometry, geoData );
-
-		geometry.computeVertexNormals();
-
-		this.parseUVs( geometry, layer, indices );
-		this.parseMorphTargets( geometry, layer, indices );
-
-		// TODO: z may need to be reversed to account for coordinate system change
-		geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] );
-
-		// var userData = geometry.userData;
-		// geometry = geometry.toNonIndexed()
-		// geometry.userData = userData;
-
-		return geometry;
-
-	},
-
-	// split quads into tris
-	splitIndices( indices, polygonDimensions ) {
-
-		var remappedIndices = [];
-
-		var i = 0;
-		polygonDimensions.forEach( function ( dim ) {
-
-			if ( dim < 4 ) {
-
-				for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] );
-
-			} else if ( dim === 4 ) {
-
-				remappedIndices.push(
-					indices[ i ],
-					indices[ i + 1 ],
-					indices[ i + 2 ],
-
-					indices[ i ],
-					indices[ i + 2 ],
-					indices[ i + 3 ]
-
-				);
-
-			} else if ( dim > 4 ) {
-
-				for ( var k = 1; k < dim - 1; k ++ ) {
-
-					remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] );
-
-				}
-
-				console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' );
-
-			}
-
-			i += dim;
-
-		} );
-
-		return remappedIndices;
-
-	},
-
-	// NOTE: currently ignoring poly indices and assuming that they are intelligently ordered
-	parseGroups( geometry, geoData ) {
-
-		var tags = lwoTree.tags;
-		var matNames = [];
-
-		var elemSize = 3;
-		if ( geoData.type === 'lines' ) elemSize = 2;
-		if ( geoData.type === 'points' ) elemSize = 1;
-
-		var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices );
-
-		var indexNum = 0; // create new indices in numerical order
-		var indexPairs = {}; // original indices mapped to numerical indices
-
-		var prevMaterialIndex;
-
-		var prevStart = 0;
-		var currentCount = 0;
-
-		for ( var i = 0; i < remappedIndices.length; i += 2 ) {
-
-			var materialIndex = remappedIndices[ i + 1 ];
-
-			if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ];
-
-			if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex;
-
-			if ( materialIndex !== prevMaterialIndex ) {
-
-				var currentIndex;
-				if ( indexPairs[ tags[ prevMaterialIndex ] ] ) {
-
-					currentIndex = indexPairs[ tags[ prevMaterialIndex ] ];
-
-				} else {
-
-					currentIndex = indexNum;
-					indexPairs[ tags[ prevMaterialIndex ] ] = indexNum;
-					matNames[ indexNum ] = tags[ prevMaterialIndex ];
-					indexNum ++;
-
-				}
-
-				geometry.addGroup( prevStart, currentCount, currentIndex );
-
-				prevStart += currentCount;
-
-				prevMaterialIndex = materialIndex;
-				currentCount = 0;
-
-			}
-
-			currentCount += elemSize;
-
-		}
-
-		// the loop above doesn't add the last group, do that here.
-		if ( geometry.groups.length > 0 ) {
-
-			var currentIndex;
-			if ( indexPairs[ tags[ materialIndex ] ] ) {
-
-				currentIndex = indexPairs[ tags[ materialIndex ] ];
-
-			} else {
-
-				currentIndex = indexNum;
-				indexPairs[ tags[ materialIndex ] ] = indexNum;
-				matNames[ indexNum ] = tags[ materialIndex ];
-
-			}
-
-			geometry.addGroup( prevStart, currentCount, currentIndex );
-
-		}
-
-		// Mat names from TAGS chunk, used to build up an array of materials for this geometry
-		geometry.userData.matNames = matNames;
-
-	},
-
-	splitMaterialIndices( polygonDimensions, indices ) {
-
-		var remappedIndices = [];
-
-		polygonDimensions.forEach( function ( dim, i ) {
-
-			if ( dim <= 3 ) {
-
-				remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
-
-			} else if ( dim === 4 ) {
-
-				remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] );
-
-			} else {
-
-				 // ignore > 4 for now
-				for ( var k = 0; k < dim - 2; k ++ ) {
-
-					remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
-
-				}
-
-			}
-
-		} );
-
-		return remappedIndices;
-
-	},
-
-	// UV maps:
-	// 1: are defined via index into an array of points, not into a geometry
-	// - the geometry is also defined by an index into this array, but the indexes may not match
-	// 2: there can be any number of UV maps for a single geometry. Here these are combined,
-	// 	with preference given to the first map encountered
-	// 3: UV maps can be partial - that is, defined for only a part of the geometry
-	// 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most
-	// UV maps are defined as partially VMAP and partially VMAD
-	// VMADs are currently not supported
-	parseUVs( geometry, layer ) {
-
-		// start by creating a UV map set to zero for the whole geometry
-		var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () {
-
-			return 0;
-
-		} );
-
-		for ( var name in layer.uvs ) {
-
-			var uvs = layer.uvs[ name ].uvs;
-			var uvIndices = layer.uvs[ name ].uvIndices;
-
-			uvIndices.forEach( function ( i, j ) {
-
-				remappedUVs[ i * 2 ] = uvs[ j * 2 ];
-				remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ];
-
-			} );
-
-		}
-
-		geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) );
-
-	},
-
-	parseMorphTargets( geometry, layer ) {
-
-		var num = 0;
-		for ( var name in layer.morphTargets ) {
-
-			var remappedPoints = geometry.attributes.position.array.slice();
-
-			if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = [];
-
-			var morphPoints = layer.morphTargets[ name ].points;
-			var morphIndices = layer.morphTargets[ name ].indices;
-			var type = layer.morphTargets[ name ].type;
-
-			morphIndices.forEach( function ( i, j ) {
-
-				if ( type === 'relative' ) {
-
-					remappedPoints[ i * 3 ] += morphPoints[ j * 3 ];
-					remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ];
-					remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ];
-
-				} else {
-
-					remappedPoints[ i * 3 ] = morphPoints[ j * 3 ];
-					remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ];
-					remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ];
-
-				}
-
-			} );
-
-			geometry.morphAttributes.position[ num ] = new THREE.Float32BufferAttribute( remappedPoints, 3 );
-			geometry.morphAttributes.position[ num ].name = name;
-
-			num ++;
-
-		}
-
-		geometry.morphTargetsRelative = false;
-
-	},
-
-};
-
-
-// ************** UTILITY FUNCTIONS **************
-
-function extractParentUrl( url, dir ) {
-
-	var index = url.indexOf( dir );
-
-	if ( index === - 1 ) return './';
-
-	return url.substr( 0, index );
-
-}

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

@@ -13,6 +13,13 @@ THREE.OBJLoader = ( function () {
 	// usemap map_name
 	// usemap map_name
 	var map_use_pattern = /^usemap /;
 	var map_use_pattern = /^usemap /;
 
 
+	var vA = new THREE.Vector3();
+	var vB = new THREE.Vector3();
+	var vC = new THREE.Vector3();
+
+	var ab = new THREE.Vector3();
+	var cb = new THREE.Vector3();
+
 	function ParserState() {
 	function ParserState() {
 
 
 		var state = {
 		var state = {
@@ -55,7 +62,9 @@ THREE.OBJLoader = ( function () {
 						vertices: [],
 						vertices: [],
 						normals: [],
 						normals: [],
 						colors: [],
 						colors: [],
-						uvs: []
+						uvs: [],
+						hasNormalIndices: false,
+						hasUVIndices: false
 					},
 					},
 					materials: [],
 					materials: [],
 					smooth: true,
 					smooth: true,
@@ -248,6 +257,27 @@ THREE.OBJLoader = ( function () {
 
 
 			},
 			},
 
 
+			addFaceNormal: function ( a, b, c ) {
+
+				var src = this.vertices;
+				var dst = this.object.geometry.normals;
+
+				vA.fromArray( src, a );
+				vB.fromArray( src, b );
+				vC.fromArray( src, c );
+
+				cb.subVectors( vC, vB );
+				ab.subVectors( vA, vB );
+				cb.cross( ab );
+
+				cb.normalize();
+
+				dst.push( cb.x, cb.y, cb.z );
+				dst.push( cb.x, cb.y, cb.z );
+				dst.push( cb.x, cb.y, cb.z );
+
+			},
+
 			addColor: function ( a, b, c ) {
 			addColor: function ( a, b, c ) {
 
 
 				var src = this.colors;
 				var src = this.colors;
@@ -270,6 +300,16 @@ THREE.OBJLoader = ( function () {
 
 
 			},
 			},
 
 
+			addDefaultUV: function () {
+
+				var dst = this.object.geometry.uvs;
+
+				dst.push( 0, 0 );
+				dst.push( 0, 0 );
+				dst.push( 0, 0 );
+
+			},
+
 			addUVLine: function ( a ) {
 			addUVLine: function ( a ) {
 
 
 				var src = this.uvs;
 				var src = this.uvs;
@@ -290,26 +330,45 @@ THREE.OBJLoader = ( function () {
 				this.addVertex( ia, ib, ic );
 				this.addVertex( ia, ib, ic );
 				this.addColor( ia, ib, ic );
 				this.addColor( ia, ib, ic );
 
 
+				// normals
+
+				if ( na !== undefined && na !== '' ) {
+
+					var nLen = this.normals.length;
+
+					ia = this.parseNormalIndex( na, nLen );
+					ib = this.parseNormalIndex( nb, nLen );
+					ic = this.parseNormalIndex( nc, nLen );
+
+					this.addNormal( ia, ib, ic );
+
+					this.object.geometry.hasNormalIndices = true;
+
+				} else {
+
+					this.addFaceNormal( ia, ib, ic );
+
+				}
+
+				// uvs
+
 				if ( ua !== undefined && ua !== '' ) {
 				if ( ua !== undefined && ua !== '' ) {
 
 
 					var uvLen = this.uvs.length;
 					var uvLen = this.uvs.length;
+
 					ia = this.parseUVIndex( ua, uvLen );
 					ia = this.parseUVIndex( ua, uvLen );
 					ib = this.parseUVIndex( ub, uvLen );
 					ib = this.parseUVIndex( ub, uvLen );
 					ic = this.parseUVIndex( uc, uvLen );
 					ic = this.parseUVIndex( uc, uvLen );
-					this.addUV( ia, ib, ic );
 
 
-				}
+					this.addUV( ia, ib, ic );
 
 
-				if ( na !== undefined && na !== '' ) {
+					this.object.geometry.hasUVIndices = true;
 
 
-					// Normals are many times the same. If so, skip function call and parseInt.
-					var nLen = this.normals.length;
-					ia = this.parseNormalIndex( na, nLen );
+				} else {
 
 
-					ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
-					ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
+					// add placeholder values (for inconsistent face definitions)
 
 
-					this.addNormal( ia, ib, ic );
+					this.addDefaultUV();
 
 
 				}
 				}
 
 
@@ -652,14 +711,10 @@ THREE.OBJLoader = ( function () {
 
 
 				buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
 				buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
 
 
-				if ( geometry.normals.length > 0 ) {
+				if ( geometry.hasNormalIndices === true ) {
 
 
 					buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
 					buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
 
 
-				} else {
-
-					buffergeometry.computeVertexNormals();
-
 				}
 				}
 
 
 				if ( geometry.colors.length > 0 ) {
 				if ( geometry.colors.length > 0 ) {
@@ -669,7 +724,7 @@ THREE.OBJLoader = ( function () {
 
 
 				}
 				}
 
 
-				if ( geometry.uvs.length > 0 ) {
+				if ( geometry.hasUVIndices === true ) {
 
 
 					buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
 					buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
 
 

+ 644 - 18
examples/js/loaders/VRMLLoader.js

@@ -329,13 +329,13 @@ THREE.VRMLLoader = ( function () {
 
 
 					def: function ( ctx ) {
 					def: function ( ctx ) {
 
 
-						return ctx.Identifier[ 0 ].image;
+						return ( ctx.Identifier || ctx.NodeName )[ 0 ].image;
 
 
 					},
 					},
 
 
 					use: function ( ctx ) {
 					use: function ( ctx ) {
 
 
-						return { USE: ctx.Identifier[ 0 ].image };
+						return { USE: ( ctx.Identifier || ctx.NodeName )[ 0 ].image };
 
 
 					},
 					},
 
 
@@ -515,6 +515,8 @@ THREE.VRMLLoader = ( function () {
 
 
 					if ( object instanceof THREE.Object3D ) scene.add( object );
 					if ( object instanceof THREE.Object3D ) scene.add( object );
 
 
+					if ( node.name === 'WorldInfo' ) scene.userData.worldInfo = object;
+
 				}
 				}
 
 
 				return scene;
 				return scene;
@@ -582,6 +584,7 @@ THREE.VRMLLoader = ( function () {
 
 
 					case 'Group':
 					case 'Group':
 					case 'Transform':
 					case 'Transform':
+					case 'Collision':
 						build = buildGroupingNode( node );
 						build = buildGroupingNode( node );
 						break;
 						break;
 
 
@@ -641,6 +644,14 @@ THREE.VRMLLoader = ( function () {
 						build = buildSphereNode( node );
 						build = buildSphereNode( node );
 						break;
 						break;
 
 
+					case 'ElevationGrid':
+						build = buildElevationGridNode( node );
+						break;
+
+					case 'Extrusion':
+						build = buildExtrusionNode( node );
+						break;
+
 					case 'Color':
 					case 'Color':
 					case 'Coordinate':
 					case 'Coordinate':
 					case 'Normal':
 					case 'Normal':
@@ -648,9 +659,12 @@ THREE.VRMLLoader = ( function () {
 						build = buildGeometricNode( node );
 						build = buildGeometricNode( node );
 						break;
 						break;
 
 
+					case 'WorldInfo':
+						build = buildWorldInfoNode( node );
+						break;
+
 					case 'Anchor':
 					case 'Anchor':
 					case 'Billboard':
 					case 'Billboard':
-					case 'Collision':
 
 
 					case 'Inline':
 					case 'Inline':
 					case 'LOD':
 					case 'LOD':
@@ -662,7 +676,6 @@ THREE.VRMLLoader = ( function () {
 					case 'Script':
 					case 'Script':
 					case 'Sound':
 					case 'Sound':
 					case 'SpotLight':
 					case 'SpotLight':
-					case 'WorldInfo':
 
 
 					case 'CylinderSensor':
 					case 'CylinderSensor':
 					case 'PlaneSensor':
 					case 'PlaneSensor':
@@ -672,8 +685,6 @@ THREE.VRMLLoader = ( function () {
 					case 'TouchSensor':
 					case 'TouchSensor':
 					case 'VisibilitySensor':
 					case 'VisibilitySensor':
 
 
-					case 'ElevationGrid':
-					case 'Extrusion':
 					case 'Text':
 					case 'Text':
 
 
 					case 'FontStyle':
 					case 'FontStyle':
@@ -718,6 +729,14 @@ THREE.VRMLLoader = ( function () {
 
 
 					switch ( fieldName ) {
 					switch ( fieldName ) {
 
 
+						case 'bboxCenter':
+							// field not supported
+							break;
+
+						case 'bboxSize':
+							// field not supported
+							break;
+
 						case 'center':
 						case 'center':
 							// field not supported
 							// field not supported
 							break;
 							break;
@@ -726,6 +745,10 @@ THREE.VRMLLoader = ( function () {
 							parseFieldChildren( fieldValues, object );
 							parseFieldChildren( fieldValues, object );
 							break;
 							break;
 
 
+						case 'collide':
+							// field not supported
+							break;
+
 						case 'rotation':
 						case 'rotation':
 							var axis = new THREE.Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							var axis = new THREE.Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							var angle = fieldValues[ 3 ];
 							var angle = fieldValues[ 3 ];
@@ -744,11 +767,7 @@ THREE.VRMLLoader = ( function () {
 							object.position.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							object.position.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							break;
 							break;
 
 
-						case 'bboxCenter':
-							// field not supported
-							break;
-
-						case 'bboxSize':
+						case 'proxy':
 							// field not supported
 							// field not supported
 							break;
 							break;
 
 
@@ -1439,6 +1458,40 @@ THREE.VRMLLoader = ( function () {
 
 
 			}
 			}
 
 
+			function buildWorldInfoNode( node ) {
+
+				var worldInfo = {};
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'title':
+							worldInfo.title = fieldValues[ 0 ];
+							break;
+
+						case 'info':
+							worldInfo.info = fieldValues;
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				return worldInfo;
+
+			}
+
 			function buildIndexedFaceSetNode( node ) {
 			function buildIndexedFaceSetNode( node ) {
 
 
 				var color, coord, normal, texCoord;
 				var color, coord, normal, texCoord;
@@ -2037,6 +2090,557 @@ THREE.VRMLLoader = ( function () {
 
 
 			}
 			}
 
 
+			function buildElevationGridNode( node ) {
+
+				var color;
+				var normal;
+				var texCoord;
+				var height;
+
+				var colorPerVertex = true;
+				var normalPerVertex = true;
+				var solid = true;
+				var ccw = true;
+				var creaseAngle = 0;
+				var xDimension = 2;
+				var zDimension = 2;
+				var xSpacing = 1;
+				var zSpacing = 1;
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'color':
+							var colorNode = fieldValues[ 0 ];
+
+							if ( colorNode !== null ) {
+
+								color = getNode( colorNode );
+
+							}
+
+							break;
+
+						case 'normal':
+							var normalNode = fieldValues[ 0 ];
+
+							if ( normalNode !== null ) {
+
+								normal = getNode( normalNode );
+
+							}
+
+							break;
+
+						case 'texCoord':
+							var texCoordNode = fieldValues[ 0 ];
+
+							if ( texCoordNode !== null ) {
+
+								texCoord = getNode( texCoordNode );
+
+							}
+
+							break;
+
+						case 'height':
+							height = fieldValues;
+							break;
+
+						case 'ccw':
+							ccw = fieldValues[ 0 ];
+							break;
+
+						case 'colorPerVertex':
+							colorPerVertex = fieldValues[ 0 ];
+							break;
+
+						case 'creaseAngle':
+							creaseAngle = fieldValues[ 0 ];
+							break;
+
+						case 'normalPerVertex':
+							normalPerVertex = fieldValues[ 0 ];
+							break;
+
+						case 'solid':
+							solid = fieldValues[ 0 ];
+							break;
+
+						case 'xDimension':
+							xDimension = fieldValues[ 0 ];
+							break;
+
+						case 'xSpacing':
+							xSpacing = fieldValues[ 0 ];
+							break;
+
+						case 'zDimension':
+							zDimension = fieldValues[ 0 ];
+							break;
+
+						case 'zSpacing':
+							zSpacing = fieldValues[ 0 ];
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				// vertex data
+
+				var vertices = [];
+				var normals = [];
+				var colors = [];
+				var uvs = [];
+
+				for ( var i = 0; i < zDimension; i ++ ) {
+
+					for ( var j = 0; j < xDimension; j ++ ) {
+
+						// compute a row major index
+
+						var index = ( i * xDimension ) + j;
+
+						// vertices
+
+						var x = xSpacing * i;
+						var y = height[ index ];
+						var z = zSpacing * j;
+
+						vertices.push( x, y, z );
+
+						// colors
+
+						if ( color && colorPerVertex === true ) {
+
+							var r = color[ index * 3 + 0 ];
+							var g = color[ index * 3 + 1 ];
+							var b = color[ index * 3 + 2 ];
+
+							colors.push( r, g, b );
+
+						}
+
+						// normals
+
+						if ( normal && normalPerVertex === true ) {
+
+							var xn = normal[ index * 3 + 0 ];
+							var yn = normal[ index * 3 + 1 ];
+							var zn = normal[ index * 3 + 2 ];
+
+							normals.push( xn, yn, zn );
+
+						}
+
+						// uvs
+
+						if ( texCoord ) {
+
+							var s = texCoord[ index * 2 + 0 ];
+							var t = texCoord[ index * 2 + 1 ];
+
+							uvs.push( s, t );
+
+
+						} else {
+
+							uvs.push( i / ( xDimension - 1 ), j / ( zDimension - 1 ) );
+
+						}
+
+					}
+
+				}
+
+				// indices
+
+				var indices = [];
+
+				for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+					for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+						// from https://tecfa.unige.ch/guides/vrml/vrml97/spec/part1/nodesRef.html#ElevationGrid
+
+						var a = i + j * xDimension;
+						var b = i + ( j + 1 ) * xDimension;
+						var c = ( i + 1 ) + ( j + 1 ) * xDimension;
+						var d = ( i + 1 ) + j * xDimension;
+
+						// faces
+
+						if ( ccw === true ) {
+
+							indices.push( a, c, b );
+							indices.push( c, a, d );
+
+						} else {
+
+							indices.push( a, b, c );
+							indices.push( c, d, a );
+
+						}
+
+					}
+
+				}
+
+				//
+
+				var positionAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( vertices, 3 ) );
+				var uvAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( uvs, 2 ) );
+				var colorAttribute;
+				var normalAttribute;
+
+				// color attribute
+
+				if ( color ) {
+
+					if ( colorPerVertex === false ) {
+
+						for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+							for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+								var index = i + j * ( xDimension - 1 );
+
+								var r = color[ index * 3 + 0 ];
+								var g = color[ index * 3 + 1 ];
+								var b = color[ index * 3 + 2 ];
+
+								// one color per quad
+
+								colors.push( r, g, b ); colors.push( r, g, b ); colors.push( r, g, b );
+								colors.push( r, g, b ); colors.push( r, g, b ); colors.push( r, g, b );
+
+							}
+
+						}
+
+						colorAttribute = new THREE.Float32BufferAttribute( colors, 3 );
+
+					} else {
+
+						colorAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( colors, 3 ) );
+
+					}
+
+				}
+
+				// normal attribute
+
+				if ( normal ) {
+
+					if ( normalPerVertex === false ) {
+
+						for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+							for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+								var index = i + j * ( xDimension - 1 );
+
+								var xn = normal[ index * 3 + 0 ];
+								var yn = normal[ index * 3 + 1 ];
+								var zn = normal[ index * 3 + 2 ];
+
+								// one normal per quad
+
+								normals.push( xn, yn, zn ); normals.push( xn, yn, zn ); normals.push( xn, yn, zn );
+								normals.push( xn, yn, zn ); normals.push( xn, yn, zn ); normals.push( xn, yn, zn );
+
+							}
+
+						}
+
+						normalAttribute = new THREE.Float32BufferAttribute( normals, 3 );
+
+					} else {
+
+						normalAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( normals, 3 ) );
+
+					}
+
+				} else {
+
+					normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle );
+
+				}
+
+				// build geometry
+
+				var geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', positionAttribute );
+				geometry.setAttribute( 'normal', normalAttribute );
+				geometry.setAttribute( 'uv', uvAttribute );
+
+				if ( colorAttribute ) geometry.setAttribute( 'color', colorAttribute );
+
+				// "solid" influences the material so let's store it for later use
+
+				geometry._solid = solid;
+				geometry._type = 'mesh';
+
+				return geometry;
+
+			}
+
+			function buildExtrusionNode( node ) {
+
+				var crossSection = [ 1, 1, 1, - 1, - 1, - 1, - 1, 1, 1, 1 ];
+				var spine = [ 0, 0, 0, 0, 1, 0 ];
+				var scale;
+				var orientation;
+
+				var beginCap = true;
+				var ccw = true;
+				var creaseAngle = 0;
+				var endCap = true;
+				var solid = true;
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'beginCap':
+							beginCap = fieldValues[ 0 ];
+							break;
+
+						case 'ccw':
+							ccw = fieldValues[ 0 ];
+							break;
+
+						case 'convex':
+							// field not supported
+							break;
+
+						case 'creaseAngle':
+							creaseAngle = fieldValues[ 0 ];
+							break;
+
+						case 'crossSection':
+							crossSection = fieldValues;
+							break;
+
+						case 'endCap':
+							endCap = fieldValues[ 0 ];
+							break;
+
+						case 'orientation':
+							orientation = fieldValues;
+							break;
+
+						case 'scale':
+							scale = fieldValues;
+							break;
+
+						case 'solid':
+							solid = fieldValues[ 0 ];
+							break;
+
+						case 'spine':
+							spine = fieldValues; // only extrusion along the Y-axis are supported so far
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				var crossSectionClosed = ( crossSection[ 0 ] === crossSection[ crossSection.length - 2 ] && crossSection[ 1 ] === crossSection[ crossSection.length - 1 ] );
+
+				// vertices
+
+				var vertices = [];
+				var spineVector = new THREE.Vector3();
+				var scaling = new THREE.Vector3();
+
+				var axis = new THREE.Vector3();
+				var vertex = new THREE.Vector3();
+				var quaternion = new THREE.Quaternion();
+
+				for ( var i = 0, j = 0, o = 0, il = spine.length; i < il; i += 3, j += 2, o += 4 ) {
+
+					spineVector.fromArray( spine, i );
+
+					scaling.x = scale ? scale[ j + 0 ] : 1;
+					scaling.y = 1;
+					scaling.z = scale ? scale[ j + 1 ] : 1;
+
+					axis.x = orientation ? orientation[ o + 0 ] : 0;
+					axis.y = orientation ? orientation[ o + 1 ] : 0;
+					axis.z = orientation ? orientation[ o + 2 ] : 1;
+					var angle = orientation ? orientation[ o + 3 ] : 0;
+
+					for ( var k = 0, kl = crossSection.length; k < kl; k += 2 ) {
+
+						vertex.x = crossSection[ k + 0 ];
+						vertex.y = 0;
+						vertex.z = crossSection[ k + 1 ];
+
+						// scale
+
+						vertex.multiply( scaling );
+
+						// rotate
+
+						quaternion.setFromAxisAngle( axis, angle );
+						vertex.applyQuaternion( quaternion );
+
+						// translate
+
+						vertex.add( spineVector );
+
+						vertices.push( vertex.x, vertex.y, vertex.z );
+
+					}
+
+				}
+
+				// indices
+
+				var indices = [];
+
+				var spineCount = spine.length / 3;
+				var crossSectionCount = crossSection.length / 2;
+
+				for ( var i = 0; i < spineCount - 1; i ++ ) {
+
+					for ( var j = 0; j < crossSectionCount - 1; j ++ ) {
+
+						var a = j + i * crossSectionCount;
+						var b = ( j + 1 ) + i * crossSectionCount;
+						var c = j + ( i + 1 ) * crossSectionCount;
+						var d = ( j + 1 ) + ( i + 1 ) * crossSectionCount;
+
+						if ( ( j === crossSectionCount - 2 ) && ( crossSectionClosed === true ) ) {
+
+							b = i * crossSectionCount;
+							d = ( i + 1 ) * crossSectionCount;
+
+						}
+
+						if ( ccw === true ) {
+
+							indices.push( a, b, c );
+							indices.push( c, b, d );
+
+						} else {
+
+							indices.push( a, c, b );
+							indices.push( c, d, b );
+
+						}
+
+					}
+
+				}
+
+				// triangulate cap
+
+				if ( beginCap === true || endCap === true ) {
+
+					var contour = [];
+
+					for ( var i = 0, l = crossSection.length; i < l; i += 2 ) {
+
+						contour.push( new THREE.Vector2( crossSection[ i ], crossSection[ i + 1 ] ) );
+
+					}
+
+					var faces = THREE.ShapeUtils.triangulateShape( contour, [] );
+					var capIndices = [];
+
+					for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+						var face = faces[ i ];
+
+						capIndices.push( face[ 0 ], face[ 1 ], face[ 2 ] );
+
+					}
+
+					// begin cap
+
+					if ( beginCap === true ) {
+
+						for ( var i = 0, l = capIndices.length; i < l; i += 3 ) {
+
+							if ( ccw === true ) {
+
+								indices.push( capIndices[ i + 0 ], capIndices[ i + 1 ], capIndices[ i + 2 ] );
+
+							} else {
+
+								indices.push( capIndices[ i + 0 ], capIndices[ i + 2 ], capIndices[ i + 1 ] );
+
+							}
+
+						}
+
+					}
+
+					// end cap
+
+					if ( endCap === true ) {
+
+						var indexOffset = crossSectionCount * ( spineCount - 1 ); // references to the first vertex of the last cross section
+
+						for ( var i = 0, l = capIndices.length; i < l; i += 3 ) {
+
+							if ( ccw === true ) {
+
+								indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 2 ], indexOffset + capIndices[ i + 1 ] );
+
+							} else {
+
+								indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 1 ], indexOffset + capIndices[ i + 2 ] );
+
+							}
+
+						}
+
+					}
+
+				}
+
+				var positionAttribute = toNonIndexedAttribute( indices, new THREE.Float32BufferAttribute( vertices, 3 ) );
+				var normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle );
+
+				var geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', positionAttribute );
+				geometry.setAttribute( 'normal', normalAttribute );
+				// no uvs yet
+
+				// "solid" influences the material so let's store it for later use
+
+				geometry._solid = solid;
+				geometry._type = 'mesh';
+
+				return geometry;
+
+			}
+
 			// helper functions
 			// helper functions
 
 
 			function resolveUSE( identifier ) {
 			function resolveUSE( identifier ) {
@@ -2533,18 +3137,18 @@ THREE.VRMLLoader = ( function () {
 			var textureLoader = new THREE.TextureLoader( this.manager );
 			var textureLoader = new THREE.TextureLoader( this.manager );
 			textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
 			textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
 
 
-			// create JSON representing the tree structure of the VRML asset
-
-			var tree = generateVRMLTree( data );
-
 			// check version (only 2.0 is supported)
 			// check version (only 2.0 is supported)
 
 
-			if ( tree.version.indexOf( 'V2.0' ) === - 1 ) {
+			if ( data.indexOf( '#VRML V2.0' ) === - 1 ) {
 
 
 				throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' );
 				throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' );
 
 
 			}
 			}
 
 
+			// create JSON representing the tree structure of the VRML asset
+
+			var tree = generateVRMLTree( data );
+
 			// parse the tree structure to a three.js scene
 			// parse the tree structure to a three.js scene
 
 
 			var scene = parseTree( tree );
 			var scene = parseTree( tree );
@@ -2671,14 +3275,36 @@ THREE.VRMLLoader = ( function () {
 		$.RULE( 'def', function () {
 		$.RULE( 'def', function () {
 
 
 			$.CONSUME( DEF );
 			$.CONSUME( DEF );
-			$.CONSUME( Identifier );
+			$.OR( [
+				{ ALT: function () {
+
+					$.CONSUME( Identifier );
+
+				} },
+				{ ALT: function () {
+
+					$.CONSUME( NodeName );
+
+				} }
+			] );
 
 
 		} );
 		} );
 
 
 		$.RULE( 'use', function () {
 		$.RULE( 'use', function () {
 
 
 			$.CONSUME( USE );
 			$.CONSUME( USE );
-			$.CONSUME( Identifier );
+			$.OR( [
+				{ ALT: function () {
+
+					$.CONSUME( Identifier );
+
+				} },
+				{ ALT: function () {
+
+					$.CONSUME( NodeName );
+
+				} }
+			] );
 
 
 		} );
 		} );
 
 

+ 1 - 0
examples/js/postprocessing/EffectComposer.js

@@ -82,6 +82,7 @@ Object.assign( THREE.EffectComposer.prototype, {
 	insertPass: function ( pass, index ) {
 	insertPass: function ( pass, index ) {
 
 
 		this.passes.splice( index, 0, pass );
 		this.passes.splice( index, 0, pass );
+		pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
 
 
 	},
 	},
 
 

+ 1 - 1
examples/js/shaders/BokehShader.js

@@ -20,7 +20,7 @@ THREE.BokehShader = {
 		"focus": { value: 1.0 },
 		"focus": { value: 1.0 },
 		"aspect": { value: 1.0 },
 		"aspect": { value: 1.0 },
 		"aperture": { value: 0.025 },
 		"aperture": { value: 0.025 },
-		"maxblur": { value: 1.0 },
+		"maxblur": { value: 0.01 },
 		"nearClip": { value: 1.0 },
 		"nearClip": { value: 1.0 },
 		"farClip": { value: 1000.0 },
 		"farClip": { value: 1000.0 },
 
 

+ 15 - 18
examples/js/shaders/GodRaysShader.js

@@ -85,7 +85,7 @@ THREE.GodRaysGenerateShader = {
 			value: 1.0
 			value: 1.0
 		},
 		},
 		vSunPositionScreenSpace: {
 		vSunPositionScreenSpace: {
-			value: new THREE.Vector2( 0.5, 0.5 )
+			value: new THREE.Vector3()
 		}
 		}
 
 
 	},
 	},
@@ -111,14 +111,14 @@ THREE.GodRaysGenerateShader = {
 
 
 		"uniform sampler2D tInput;",
 		"uniform sampler2D tInput;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
+		"uniform vec3 vSunPositionScreenSpace;",
 		"uniform float fStepSize;", // filter step size
 		"uniform float fStepSize;", // filter step size
 
 
 		"void main() {",
 		"void main() {",
 
 
 		// delta from current pixel to "sun" position
 		// delta from current pixel to "sun" position
 
 
-		"	vec2 delta = vSunPositionScreenSpace - vUv;",
+		"	vec2 delta = vSunPositionScreenSpace.xy - vUv;",
 		"	float dist = length( delta );",
 		"	float dist = length( delta );",
 
 
 		// Step vector (uv space)
 		// Step vector (uv space)
@@ -157,22 +157,24 @@ THREE.GodRaysGenerateShader = {
 
 
 		// Unrolling loop manually makes it work in ANGLE
 		// Unrolling loop manually makes it work in ANGLE
 
 
-		"	if ( 0.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	float f = min( 1.0, max( vSunPositionScreenSpace.z / 1000.0, 0.0 ) );", // used to fade out godrays
+
+		"	if ( 0.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 1.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 1.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 2.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 2.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 3.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 3.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 4.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 4.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 5.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 5.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
 		// Should technically be dividing by 'iters', but 'TAPS_PER_PASS' smooths out
 		// Should technically be dividing by 'iters', but 'TAPS_PER_PASS' smooths out
@@ -210,10 +212,6 @@ THREE.GodRaysCombineShader = {
 
 
 		fGodRayIntensity: {
 		fGodRayIntensity: {
 			value: 0.69
 			value: 0.69
-		},
-
-		vSunPositionScreenSpace: {
-			value: new THREE.Vector2( 0.5, 0.5 )
 		}
 		}
 
 
 	},
 	},
@@ -238,7 +236,6 @@ THREE.GodRaysCombineShader = {
 		"uniform sampler2D tColors;",
 		"uniform sampler2D tColors;",
 		"uniform sampler2D tGodRays;",
 		"uniform sampler2D tGodRays;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
 		"uniform float fGodRayIntensity;",
 		"uniform float fGodRayIntensity;",
 
 
 		"void main() {",
 		"void main() {",
@@ -267,7 +264,7 @@ THREE.GodRaysFakeSunShader = {
 	uniforms: {
 	uniforms: {
 
 
 		vSunPositionScreenSpace: {
 		vSunPositionScreenSpace: {
-			value: new THREE.Vector2( 0.5, 0.5 )
+			value: new THREE.Vector3()
 		},
 		},
 
 
 		fAspect: {
 		fAspect: {
@@ -301,7 +298,7 @@ THREE.GodRaysFakeSunShader = {
 
 
 		"varying vec2 vUv;",
 		"varying vec2 vUv;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
+		"uniform vec3 vSunPositionScreenSpace;",
 		"uniform float fAspect;",
 		"uniform float fAspect;",
 
 
 		"uniform vec3 sunColor;",
 		"uniform vec3 sunColor;",
@@ -309,7 +306,7 @@ THREE.GodRaysFakeSunShader = {
 
 
 		"void main() {",
 		"void main() {",
 
 
-		"	vec2 diff = vUv - vSunPositionScreenSpace;",
+		"	vec2 diff = vUv - vSunPositionScreenSpace.xy;",
 
 
 		// Correct for aspect ratio
 		// Correct for aspect ratio
 
 
@@ -318,7 +315,7 @@ THREE.GodRaysFakeSunShader = {
 		"	float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );",
 		"	float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );",
 		"	prop = 0.35 * pow( 1.0 - prop, 3.0 );",
 		"	prop = 0.35 * pow( 1.0 - prop, 3.0 );",
 
 
-		"	gl_FragColor.xyz = mix( sunColor, bgColor, 1.0 - prop );",
+		"	gl_FragColor.xyz = ( vSunPositionScreenSpace.z > 0.0 ) ? mix( sunColor, bgColor, 1.0 - prop ) : bgColor;",
 		"	gl_FragColor.w = 1.0;",
 		"	gl_FragColor.w = 1.0;",
 
 
 		"}"
 		"}"

+ 2 - 0
examples/jsm/controls/DragControls.js

@@ -53,6 +53,8 @@ var DragControls = function ( _objects, _camera, _domElement ) {
 		_domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false );
 		_domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false );
 		_domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false );
 		_domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false );
 
 
+		_domElement.style.cursor = '';
+
 	}
 	}
 
 
 	function dispose() {
 	function dispose() {

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

@@ -79,7 +79,7 @@ var TransformControls = function ( camera, domElement ) {
 
 
 		var allIntersections = raycaster.intersectObject( object, true );
 		var allIntersections = raycaster.intersectObject( object, true );
 
 
-		for ( var i = allIntersections.length; i --; ) {
+		for ( var i = 0; i < allIntersections.length; i ++ ) {
 
 
 			if ( allIntersections[ i ].object.visible || includeInvisible ) {
 			if ( allIntersections[ i ].object.visible || includeInvisible ) {
 
 

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

@@ -528,9 +528,22 @@ GLTFExporter.prototype = {
 
 
 				for ( var a = 0; a < attribute.itemSize; a ++ ) {
 				for ( var a = 0; a < attribute.itemSize; a ++ ) {
 
 
-					// @TODO Fails on InterleavedBufferAttribute, and could probably be
-					// optimized for normal BufferAttribute.
-					var value = attribute.array[ i * attribute.itemSize + a ];
+					var value;
+
+					if ( attribute.itemSize > 3 ) {
+
+						 // no support for interleaved data for itemSize > 3
+
+						value = attribute.array[ i * attribute.itemSize + a ];
+
+					} else {
+
+						if ( a === 0 ) value = attribute.getX( i );
+						if ( a === 1 ) value = attribute.getY( i );
+						if ( a === 2 ) value = attribute.getZ( i );
+						if ( a === 3 ) value = attribute.getW( i );
+
+					}
 
 
 					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
 					if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
 
 

+ 1 - 1
examples/jsm/geometries/ParametricGeometries.d.ts

@@ -23,7 +23,7 @@ export namespace ParametricGeometries {
 
 
 	export class SphereGeometry {
 	export class SphereGeometry {
 
 
-  	constructor( size: number, u: number, v );
+  	constructor( size: number, u: number, v: number );
 
 
 	}
 	}
 
 

+ 15 - 3
examples/jsm/interactive/SelectionBox.js

@@ -59,6 +59,20 @@ var SelectionBox = ( function () {
 		startPoint = startPoint || this.startPoint;
 		startPoint = startPoint || this.startPoint;
 		endPoint = endPoint || this.endPoint;
 		endPoint = endPoint || this.endPoint;
 
 
+		// Avoid invalid frustum
+
+		if ( startPoint.x === endPoint.x ) {
+
+			endPoint.x += Number.EPSILON;
+
+		}
+
+		if ( startPoint.y === endPoint.y ) {
+
+			endPoint.y += Number.EPSILON;
+
+		}
+
 		this.camera.updateProjectionMatrix();
 		this.camera.updateProjectionMatrix();
 		this.camera.updateMatrixWorld();
 		this.camera.updateMatrixWorld();
 
 
@@ -70,7 +84,7 @@ var SelectionBox = ( function () {
 			endPoint.x = Math.max( startPoint.x, endPoint.x );
 			endPoint.x = Math.max( startPoint.x, endPoint.x );
 			endPoint.y = Math.min( startPoint.y, endPoint.y );
 			endPoint.y = Math.min( startPoint.y, endPoint.y );
 
 
-			vecNear.copy( this.camera.position );
+			vecNear.setFromMatrixPosition( this.camera.matrixWorld );
 			vecTopLeft.copy( tmpPoint );
 			vecTopLeft.copy( tmpPoint );
 			vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
 			vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
 			vecDownRight.copy( endPoint );
 			vecDownRight.copy( endPoint );
@@ -107,8 +121,6 @@ var SelectionBox = ( function () {
 
 
 		} else if ( this.camera.isOrthographicCamera ) {
 		} else if ( this.camera.isOrthographicCamera ) {
 
 
-			if ( startPoint.equals( endPoint ) ) endPoint.addScalar( Number.EPSILON ); // avoid invalid frustum
-
 			var left = Math.min( startPoint.x, endPoint.x );
 			var left = Math.min( startPoint.x, endPoint.x );
 			var top = Math.max( startPoint.y, endPoint.y );
 			var top = Math.max( startPoint.y, endPoint.y );
 			var right = Math.max( startPoint.x, endPoint.x );
 			var right = Math.max( startPoint.x, endPoint.x );

+ 15 - 15
examples/jsm/libs/stats.module.d.ts

@@ -1,24 +1,24 @@
 declare interface Stats {
 declare interface Stats {
-  REVISION: number;
-  dom: HTMLDivElement;
-  addPanel(panel: Stats.Panel): Stats.Panel;
-  showPanel(id: number): void;
-  begin(): void;
-  end(): void;
-  update(): void;
-  domElement: HTMLDivElement;
-  setMode(id: number): void;
+	REVISION: number;
+	dom: HTMLDivElement;
+	addPanel( panel: Stats.Panel ): Stats.Panel;
+	showPanel( id: number ): void;
+	begin(): void;
+	end(): void;
+	update(): void;
+	domElement: HTMLDivElement;
+	setMode( id: number ): void;
 }
 }
 
 
 declare function Stats(): Stats;
 declare function Stats(): Stats;
 
 
 declare namespace Stats {
 declare namespace Stats {
-  interface Panel {
-    dom: HTMLCanvasElement;
-    update(value: number, maxValue: number): void;
-  }
+	interface Panel {
+		dom: HTMLCanvasElement;
+		update( value: number, maxValue: number ): void;
+	}
 
 
-  function Panel(): Panel;
+	function Panel(): Panel;
 }
 }
 
 
-export default Stats
+export default Stats;

+ 3 - 3
examples/jsm/libs/stats.module.js

@@ -115,9 +115,9 @@ Stats.Panel = function ( name, fg, bg ) {
 	var PR = round( window.devicePixelRatio || 1 );
 	var PR = round( window.devicePixelRatio || 1 );
 
 
 	var WIDTH = 80 * PR, HEIGHT = 48 * PR,
 	var WIDTH = 80 * PR, HEIGHT = 48 * PR,
-			TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
-			GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
-			GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
+		TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
+		GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
+		GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
 
 
 	var canvas = document.createElement( 'canvas' );
 	var canvas = document.createElement( 'canvas' );
 	canvas.width = WIDTH;
 	canvas.width = WIDTH;

+ 3 - 1
examples/jsm/loaders/BasisTextureLoader.js

@@ -199,8 +199,10 @@ BasisTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ),
 
 
 			} );
 			} );
 
 
+		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 		texturePending
 		texturePending
-			.finally( () => {
+			.catch( () => true )
+			.then( () => {
 
 
 				if ( worker && taskID ) {
 				if ( worker && taskID ) {
 
 

+ 16 - 7
examples/jsm/loaders/ColladaLoader.js

@@ -257,6 +257,8 @@ ColladaLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 				channels: {}
 				channels: {}
 			};
 			};
 
 
+			var hasChildren = false;
+
 			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
 
 				var child = xml.childNodes[ i ];
 				var child = xml.childNodes[ i ];
@@ -282,6 +284,12 @@ ColladaLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 						data.channels[ id ] = parseAnimationChannel( child );
 						data.channels[ id ] = parseAnimationChannel( child );
 						break;
 						break;
 
 
+					case 'animation':
+						// hierarchy of related animations
+						parseAnimation( child );
+						hasChildren = true;
+						break;
+
 					default:
 					default:
 						console.log( child );
 						console.log( child );
 
 
@@ -289,7 +297,13 @@ ColladaLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
-			library.animations[ xml.getAttribute( 'id' ) ] = data;
+			if ( hasChildren === false ) {
+
+				// since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment
+
+				library.animations[ xml.getAttribute( 'id' ) || MathUtils.generateUUID() ] = data;
+
+			}
 
 
 		}
 		}
 
 
@@ -3598,12 +3612,7 @@ ColladaLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
 
 			}
 			}
 
 
-			if ( object.name === '' ) {
-
-				object.name = ( type === 'JOINT' ) ? data.sid : data.name;
-
-			}
-
+			object.name = ( type === 'JOINT' ) ? data.sid : data.name;
 			object.matrix.copy( matrix );
 			object.matrix.copy( matrix );
 			object.matrix.decompose( object.position, object.quaternion, object.scale );
 			object.matrix.decompose( object.position, object.quaternion, object.scale );
 
 

+ 3 - 1
examples/jsm/loaders/DRACOLoader.js

@@ -204,8 +204,10 @@ DRACOLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 			.then( ( message ) => this._createGeometry( message.geometry ) );
 			.then( ( message ) => this._createGeometry( message.geometry ) );
 
 
 		// Remove task from the task list.
 		// Remove task from the task list.
+		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 		geometryPending
 		geometryPending
-			.finally( () => {
+			.catch( () => true )
+			.then( () => {
 
 
 				if ( worker && taskID ) {
 				if ( worker && taskID ) {
 
 

+ 69 - 17
examples/jsm/loaders/EXRLoader.js

@@ -113,6 +113,10 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 		const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
 		const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size
 		const HUF_DECMASK = HUF_DECSIZE - 1;
 		const HUF_DECMASK = HUF_DECSIZE - 1;
 
 
+		const NBITS = 16;
+		const A_OFFSET = 1 << ( NBITS - 1 );
+		const MOD_MASK = ( 1 << NBITS ) - 1;
+
 		const SHORT_ZEROCODE_RUN = 59;
 		const SHORT_ZEROCODE_RUN = 59;
 		const LONG_ZEROCODE_RUN = 63;
 		const LONG_ZEROCODE_RUN = 63;
 		const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
 		const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
@@ -487,8 +491,22 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 
 
 		}
 		}
 
 
-		function wav2Decode( buffer, j, nx, ox, ny, oy ) {
+		function wdec16( l, h ) {
+
+			var m = UInt16( l );
+			var d = UInt16( h );
+
+			var bb = ( m - ( d >> 1 ) ) & MOD_MASK;
+			var aa = ( d + bb - A_OFFSET ) & MOD_MASK;
+
+			wdec14Return.a = aa;
+			wdec14Return.b = bb;
+
+		}
 
 
+		function wav2Decode( buffer, j, nx, ox, ny, oy, mx ) {
+
+			var w14 = mx < ( 1 << 14 );
 			var n = ( nx > ny ) ? ny : nx;
 			var n = ( nx > ny ) ? ny : nx;
 			var p = 1;
 			var p = 1;
 			var p2;
 			var p2;
@@ -520,25 +538,52 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 						var p10 = px + oy1;
 						var p10 = px + oy1;
 						var p11 = p10 + ox1;
 						var p11 = p10 + ox1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						if ( w14 ) {
 
 
-						i00 = wdec14Return.a;
-						i10 = wdec14Return.b;
+							wdec14( buffer[ px + j ], buffer[ p10 + j ] );
 
 
-						wdec14( buffer[ p01 + j ], buffer[ p11 + j ] );
+							i00 = wdec14Return.a;
+							i10 = wdec14Return.b;
 
 
-						i01 = wdec14Return.a;
-						i11 = wdec14Return.b;
+							wdec14( buffer[ p01 + j ], buffer[ p11 + j ] );
 
 
-						wdec14( i00, i01 );
+							i01 = wdec14Return.a;
+							i11 = wdec14Return.b;
 
 
-						buffer[ px + j ] = wdec14Return.a;
-						buffer[ p01 + j ] = wdec14Return.b;
+							wdec14( i00, i01 );
+
+							buffer[ px + j ] = wdec14Return.a;
+							buffer[ p01 + j ] = wdec14Return.b;
+
+							wdec14( i10, i11 );
 
 
-						wdec14( i10, i11 );
+							buffer[ p10 + j ] = wdec14Return.a;
+							buffer[ p11 + j ] = wdec14Return.b;
 
 
-						buffer[ p10 + j ] = wdec14Return.a;
-						buffer[ p11 + j ] = wdec14Return.b;
+						} else {
+
+							wdec16( buffer[ px + j ], buffer[ p10 + j ] );
+
+							i00 = wdec14Return.a;
+							i10 = wdec14Return.b;
+
+							wdec16( buffer[ p01 + j ], buffer[ p11 + j ] );
+
+							i01 = wdec14Return.a;
+							i11 = wdec14Return.b;
+
+							wdec16( i00, i01 );
+
+							buffer[ px + j ] = wdec14Return.a;
+							buffer[ p01 + j ] = wdec14Return.b;
+
+							wdec16( i10, i11 );
+
+							buffer[ p10 + j ] = wdec14Return.a;
+							buffer[ p11 + j ] = wdec14Return.b;
+
+
+						}
 
 
 					}
 					}
 
 
@@ -546,7 +591,10 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 
 
 						var p10 = px + oy1;
 						var p10 = px + oy1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						if ( w14 )
+							wdec14( buffer[ px + j ], buffer[ p10 + j ] );
+						else
+							wdec16( buffer[ px + j ], buffer[ p10 + j ] );
 
 
 						i00 = wdec14Return.a;
 						i00 = wdec14Return.a;
 						buffer[ p10 + j ] = wdec14Return.b;
 						buffer[ p10 + j ] = wdec14Return.b;
@@ -566,7 +614,10 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 
 
 						var p01 = px + ox1;
 						var p01 = px + ox1;
 
 
-						wdec14( buffer[ px + j ], buffer[ p01 + j ] );
+						if ( w14 )
+							wdec14( buffer[ px + j ], buffer[ p01 + j ] );
+						else
+							wdec16( buffer[ px + j ], buffer[ p01 + j ] );
 
 
 						i00 = wdec14Return.a;
 						i00 = wdec14Return.a;
 						buffer[ p01 + j ] = wdec14Return.b;
 						buffer[ p01 + j ] = wdec14Return.b;
@@ -1309,7 +1360,7 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 
 
 			// Reverse LUT
 			// Reverse LUT
 			var lut = new Uint16Array( USHORT_RANGE );
 			var lut = new Uint16Array( USHORT_RANGE );
-			reverseLutFromBitmap( bitmap, lut );
+			var maxValue = reverseLutFromBitmap( bitmap, lut );
 
 
 			var length = parseUint32( inDataView, inOffset );
 			var length = parseUint32( inDataView, inOffset );
 
 
@@ -1329,7 +1380,8 @@ EXRLoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype
 						cd.nx,
 						cd.nx,
 						cd.size,
 						cd.size,
 						cd.ny,
 						cd.ny,
-						cd.nx * cd.size
+						cd.nx * cd.size,
+						maxValue
 					);
 					);
 
 
 				}
 				}

+ 7 - 0
examples/jsm/loaders/FBXLoader.js

@@ -2539,6 +2539,13 @@ var FBXLoader = ( function () {
 
 
 										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
 										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
 
 
+										if ( rawModel === undefined ) {
+
+											console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child );
+											return;
+
+										}
+
 										var node = {
 										var node = {
 
 
 											modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
 											modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',

+ 13 - 2
examples/jsm/loaders/GCodeLoader.js

@@ -214,9 +214,20 @@ GCodeLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 			for ( var i = 0; i < layers.length; i ++ ) {
 			for ( var i = 0; i < layers.length; i ++ ) {
 
 
 				var layer = layers[ i ];
 				var layer = layers[ i ];
+				var layerVertex = layer.vertex;
+				var layerPathVertex = layer.pathVertex;
 
 
-				vertex = vertex.concat( layer.vertex );
-				pathVertex = pathVertex.concat( layer.pathVertex );
+				for ( var j = 0; j < layerVertex.length; j ++ ) {
+
+					vertex.push( layerVertex[ j ] );
+
+				}
+
+				for ( var j = 0; j < layerPathVertex.length; j ++ ) {
+
+					pathVertex.push( layerPathVertex[ j ] );
+
+				}
 
 
 			}
 			}
 
 

+ 11 - 1
examples/jsm/loaders/GLTFLoader.d.ts

@@ -3,7 +3,10 @@ import {
 	Camera,
 	Camera,
 	Group,
 	Group,
 	Loader,
 	Loader,
-	LoadingManager
+	LoadingManager,
+	Object3D,
+	Material,
+	Texture
 } from '../../../src/Three';
 } from '../../../src/Three';
 
 
 import { DRACOLoader } from './DRACOLoader';
 import { DRACOLoader } from './DRACOLoader';
@@ -39,10 +42,17 @@ export class GLTFLoader extends Loader {
 
 
 }
 }
 
 
+export interface GLTFReference {
+	type: 'materials'|'nodes'|'textures';
+	index: number;
+}
+
 export class GLTFParser {
 export class GLTFParser {
 
 
 	json: any;
 	json: any;
 
 
+	associations: Map<Object3D|Material|Texture, GLTFReference>;
+
 	getDependency: ( type: string, index: number ) => Promise<any>;
 	getDependency: ( type: string, index: number ) => Promise<any>;
 	getDependencies: ( type: string ) => Promise<any[]>;
 	getDependencies: ( type: string ) => Promise<any[]>;
 
 

+ 18 - 0
examples/jsm/loaders/GLTFLoader.js

@@ -131,6 +131,7 @@ var GLTFLoader = ( function () {
 
 
 			loader.setPath( this.path );
 			loader.setPath( this.path );
 			loader.setResponseType( 'arraybuffer' );
 			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( this.requestHeader );
 
 
 			if ( scope.crossOrigin === 'use-credentials' ) {
 			if ( scope.crossOrigin === 'use-credentials' ) {
 
 
@@ -282,6 +283,7 @@ var GLTFLoader = ( function () {
 
 
 			} );
 			} );
 
 
+			parser.fileLoader.setRequestHeader( this.requestHeader );
 			parser.parse( onLoad, onError );
 			parser.parse( onLoad, onError );
 
 
 		}
 		}
@@ -1468,6 +1470,9 @@ var GLTFLoader = ( function () {
 		// loader object cache
 		// loader object cache
 		this.cache = new GLTFRegistry();
 		this.cache = new GLTFRegistry();
 
 
+		// associations between Three.js objects and glTF elements
+		this.associations = new Map();
+
 		// BufferGeometry caching
 		// BufferGeometry caching
 		this.primitiveCache = {};
 		this.primitiveCache = {};
 
 
@@ -1977,6 +1982,11 @@ var GLTFLoader = ( function () {
 			texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping;
 			texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping;
 			texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping;
 			texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping;
 
 
+			parser.associations.set( texture, {
+				type: 'textures',
+				index: textureIndex
+			} );
+
 			return texture;
 			return texture;
 
 
 		} );
 		} );
@@ -2026,7 +2036,9 @@ var GLTFLoader = ( function () {
 
 
 				if ( transform ) {
 				if ( transform ) {
 
 
+					var gltfReference = this.associations.get( texture );
 					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
 					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
+					this.associations.set( texture, gltfReference );
 
 
 				}
 				}
 
 
@@ -2126,6 +2138,8 @@ var GLTFLoader = ( function () {
 
 
 				this.cache.add( cacheKey, cachedMaterial );
 				this.cache.add( cacheKey, cachedMaterial );
 
 
+				this.associations.set( cachedMaterial, this.associations.get( material ) );
+
 			}
 			}
 
 
 			material = cachedMaterial;
 			material = cachedMaterial;
@@ -2321,6 +2335,8 @@ var GLTFLoader = ( function () {
 
 
 			assignExtrasToUserData( material, materialDef );
 			assignExtrasToUserData( material, materialDef );
 
 
+			parser.associations.set( material, { type: 'materials', index: materialIndex } );
+
 			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );
 
 
 			return material;
 			return material;
@@ -3191,6 +3207,8 @@ var GLTFLoader = ( function () {
 
 
 			}
 			}
 
 
+			parser.associations.set( node, { type: 'nodes', index: nodeIndex } );
+
 			return node;
 			return node;
 
 
 		} );
 		} );

+ 3 - 2006
examples/jsm/loaders/LWOLoader.js

@@ -1,8 +1,8 @@
 /**
 /**
  * @version 1.1.1
  * @version 1.1.1
  *
  *
- * @author Lewy Blue https://github.com/looeee
- * @author Guilherme Avila https://github/sciecode
+ * @author Lewy Blue / https://github.com/looeee
+ * @author Guilherme Avila / https://github/sciecode
  *
  *
  * @desc Load files in LWO3 and LWO2 format on Three.js
  * @desc Load files in LWO3 and LWO2 format on Three.js
  *
  *
@@ -12,9 +12,6 @@
  * LWO2 format specification:
  * LWO2 format specification:
  * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html
  * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html
  *
  *
- * Development and test repository:
- *	https://github.com/threejs/lwoloader
- *
  **/
  **/
 
 
 import {
 import {
@@ -33,7 +30,6 @@ import {
 	LineBasicMaterial,
 	LineBasicMaterial,
 	LineSegments,
 	LineSegments,
 	Loader,
 	Loader,
-	LoaderUtils,
 	Mesh,
 	Mesh,
 	MeshPhongMaterial,
 	MeshPhongMaterial,
 	MeshPhysicalMaterial,
 	MeshPhysicalMaterial,
@@ -46,2006 +42,7 @@ import {
 	Vector2
 	Vector2
 } from "../../../build/three.module.js";
 } from "../../../build/three.module.js";
 
 
-function LWO2Parser( IFFParser ) {
-
-	this.IFF = IFFParser;
-
-}
-
-LWO2Parser.prototype = {
-
-	constructor: LWO2Parser,
-
-	parseBlock: function () {
-
-		this.IFF.debugger.offset = this.IFF.reader.offset;
-		this.IFF.debugger.closeForms();
-
-		var blockID = this.IFF.reader.getIDTag();
-		var length = this.IFF.reader.getUint32(); // size of data in bytes
-		if ( length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset ) {
-
-			this.IFF.reader.offset -= 4;
-			length = this.IFF.reader.getUint16();
-
-		}
-
-		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
-		this.IFF.debugger.length = length;
-
-		// Data types may be found in either LWO2 OR LWO3 spec
-		switch ( blockID ) {
-
-			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
-				this.IFF.parseForm( length );
-				break;
-
-			// SKIPPED CHUNKS
-			// if break; is called directly, the position in the lwoTree is not created
-			// any sub chunks and forms are added to the parent form instead
-			// MISC skipped
-			case 'ICON': // Thumbnail Icon Image
-			case 'VMPA': // Vertex Map Parameter
-			case 'BBOX': // bounding box
-			// case 'VMMD':
-			// case 'VTYP':
-
-			// normal maps can be specified, normally on models imported from other applications. Currently ignored
-			case 'NORM':
-
-			// ENVL FORM skipped
-			case 'PRE ':
-			case 'POST':
-			case 'KEY ':
-			case 'SPAN':
-
-			// CLIP FORM skipped
-			case 'TIME':
-			case 'CLRS':
-			case 'CLRA':
-			case 'FILT':
-			case 'DITH':
-			case 'CONT':
-			case 'BRIT':
-			case 'SATR':
-			case 'HUE ':
-			case 'GAMM':
-			case 'NEGA':
-			case 'IFLT':
-			case 'PFLT':
-
-			// Image Map Layer skipped
-			case 'PROJ':
-			case 'AXIS':
-			case 'AAST':
-			case 'PIXB':
-			case 'AUVO':
-			case 'STCK':
-
-			// Procedural Textures skipped
-			case 'PROC':
-			case 'VALU':
-			case 'FUNC':
-
-			// Gradient Textures skipped
-			case 'PNAM':
-			case 'INAM':
-			case 'GRST':
-			case 'GREN':
-			case 'GRPT':
-			case 'FKEY':
-			case 'IKEY':
-
-			// Texture Mapping Form skipped
-			case 'CSYS':
-
-			// Surface CHUNKs skipped
-			case 'OPAQ': // top level 'opacity' checkbox
-			case 'CMAP': // clip map
-
-			// Surface node CHUNKS skipped
-			// These mainly specify the node editor setup in LW
-			case 'NLOC':
-			case 'NZOM':
-			case 'NVER':
-			case 'NSRV':
-			case 'NVSK': // unknown
-			case 'NCRD':
-			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
-			case 'WRPH': // image wrap h
-			case 'NMOD':
-			case 'NPRW':
-			case 'NPLA':
-			case 'NODS':
-			case 'VERS':
-			case 'ENUM':
-			case 'TAG ':
-			case 'OPAC':
-
-			// Car Material CHUNKS
-			case 'CGMD':
-			case 'CGTY':
-			case 'CGST':
-			case 'CGEN':
-			case 'CGTS':
-			case 'CGTE':
-			case 'OSMP':
-			case 'OMDE':
-			case 'OUTR':
-			case 'FLAG':
-
-			case 'TRNL':
-			case 'GLOW':
-			case 'GVAL': // glow intensity
-			case 'SHRP':
-			case 'RFOP':
-			case 'RSAN':
-			case 'TROP':
-			case 'RBLR':
-			case 'TBLR':
-			case 'CLRH':
-			case 'CLRF':
-			case 'ADTR':
-			case 'LINE':
-			case 'ALPH':
-			case 'VCOL':
-			case 'ENAB':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length );
-				break;
-
-			case 'SURF':
-				this.IFF.parseSurfaceLwo2( length );
-				break;
-
-			case 'CLIP':
-				this.IFF.parseClipLwo2( length );
-				break;
-
-			// Texture node chunks (not in spec)
-			case 'IPIX': // usePixelBlending
-			case 'IMIP': // useMipMaps
-			case 'IMOD': // imageBlendingMode
-			case 'AMOD': // unknown
-			case 'IINV': // imageInvertAlpha
-			case 'INCR': // imageInvertColor
-			case 'IAXS': // imageAxis ( for non-UV maps)
-			case 'IFOT': // imageFallofType
-			case 'ITIM': // timing for animated textures
-			case 'IWRL':
-			case 'IUTI':
-			case 'IINX':
-			case 'IINY':
-			case 'IINZ':
-			case 'IREF': // possibly a VX for reused texture nodes
-				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
-				else this.IFF.reader.skip( length );
-				break;
-
-			case 'OTAG':
-				this.IFF.parseObjectTag();
-				break;
-
-			case 'LAYR':
-				this.IFF.parseLayer( length );
-				break;
-
-			case 'PNTS':
-				this.IFF.parsePoints( length );
-				break;
-
-			case 'VMAP':
-				this.IFF.parseVertexMapping( length );
-				break;
-
-			case 'AUVU':
-			case 'AUVN':
-				this.IFF.reader.skip( length - 1 );
-				this.IFF.reader.getVariableLengthIndex(); // VX
-				break;
-
-			case 'POLS':
-				this.IFF.parsePolygonList( length );
-				break;
-
-			case 'TAGS':
-				this.IFF.parseTagStrings( length );
-				break;
-
-			case 'PTAG':
-				this.IFF.parsePolygonTagMapping( length );
-				break;
-
-			case 'VMAD':
-				this.IFF.parseVertexMapping( length, true );
-				break;
-
-			// Misc CHUNKS
-			case 'DESC': // Description Line
-				this.IFF.currentForm.description = this.IFF.reader.getString();
-				break;
-
-			case 'TEXT':
-			case 'CMNT':
-			case 'NCOM':
-				this.IFF.currentForm.comment = this.IFF.reader.getString();
-				break;
-
-			// Envelope Form
-			case 'NAME':
-				this.IFF.currentForm.channelName = this.IFF.reader.getString();
-				break;
-
-			// Image Map Layer
-			case 'WRAP':
-				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
-				break;
-
-			case 'IMAG':
-				var index = this.IFF.reader.getVariableLengthIndex();
-				this.IFF.currentForm.imageIndex = index;
-				break;
-
-			// Texture Mapping Form
-			case 'OREF':
-				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
-				break;
-
-			case 'ROID':
-				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
-				break;
-
-			// Surface Blocks
-			case 'SSHN':
-				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
-				break;
-
-			case 'AOVN':
-				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
-				break;
-
-			// Nodal Blocks
-			case 'NSTA':
-				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
-				break;
-
-			case 'NRNM':
-				this.IFF.currentForm.realName = this.IFF.reader.getString();
-				break;
-
-			case 'NNME':
-				this.IFF.currentForm.refName = this.IFF.reader.getString();
-				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
-				break;
-
-			// Nodal Blocks : connections
-			case 'INME':
-				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
-				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINN':
-				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
-				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINM':
-				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
-				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IONM':
-				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
-				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'FNAM':
-				this.IFF.currentForm.fileName = this.IFF.reader.getString();
-				break;
-
-			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
-				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
-				else this.IFF.reader.skip( length );
-				break;
-
-			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
-			case 'SMAN':
-				var maxSmoothingAngle = this.IFF.reader.getFloat32();
-				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
-				break;
-
-			// LWO2: Basic Surface Parameters
-			case 'COLR':
-				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
-				this.IFF.reader.skip( 2 ); // VX: envelope
-				break;
-
-			case 'LUMI':
-				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SPEC':
-				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'DIFF':
-				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'REFL':
-				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'GLOS':
-				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TRAN':
-				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'BUMP':
-				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SIDE':
-				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
-				break;
-
-			case 'RIMG':
-				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'RIND':
-				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TIMG':
-				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'IMAP':
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TMAP':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length ); // needs implementing
-				break;
-
-			case 'IUVI': // uv channel name
-				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
-				break;
-
-			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
-				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
-				break;
-			case 'IVTL': // heightWrappingMode
-				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
-				break;
-
-			// LWO2 USE
-			case 'BLOK':
-				// skip
-				break;
-
-			default:
-				this.IFF.parseUnknownCHUNK( blockID, length );
-
-		}
-
-		if ( blockID != 'FORM' ) {
-
-			this.IFF.debugger.node = 1;
-			this.IFF.debugger.nodeID = blockID;
-			this.IFF.debugger.log();
-
-		}
-
-		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
-
-			this.IFF.currentForm = this.IFF.parentForm;
-
-		}
-
-	}
-
-};
-
-function LWO3Parser( IFFParser ) {
-
-	this.IFF = IFFParser;
-
-}
-
-LWO3Parser.prototype = {
-
-	constructor: LWO3Parser,
-
-	parseBlock: function () {
-
-		this.IFF.debugger.offset = this.IFF.reader.offset;
-		this.IFF.debugger.closeForms();
-
-		var blockID = this.IFF.reader.getIDTag();
-		var length = this.IFF.reader.getUint32(); // size of data in bytes
-
-		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
-		this.IFF.debugger.length = length;
-
-		// Data types may be found in either LWO2 OR LWO3 spec
-		switch ( blockID ) {
-
-			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
-				this.IFF.parseForm( length );
-				break;
-
-			// SKIPPED CHUNKS
-			// MISC skipped
-			case 'ICON': // Thumbnail Icon Image
-			case 'VMPA': // Vertex Map Parameter
-			case 'BBOX': // bounding box
-			// case 'VMMD':
-			// case 'VTYP':
-
-			// normal maps can be specified, normally on models imported from other applications. Currently ignored
-			case 'NORM':
-
-			// ENVL FORM skipped
-			case 'PRE ':
-			case 'POST':
-			case 'KEY ':
-			case 'SPAN':
-
-			// CLIP FORM skipped
-			case 'TIME':
-			case 'CLRS':
-			case 'CLRA':
-			case 'FILT':
-			case 'DITH':
-			case 'CONT':
-			case 'BRIT':
-			case 'SATR':
-			case 'HUE ':
-			case 'GAMM':
-			case 'NEGA':
-			case 'IFLT':
-			case 'PFLT':
-
-			// Image Map Layer skipped
-			case 'PROJ':
-			case 'AXIS':
-			case 'AAST':
-			case 'PIXB':
-			case 'STCK':
-
-			// Procedural Textures skipped
-			case 'VALU':
-
-			// Gradient Textures skipped
-			case 'PNAM':
-			case 'INAM':
-			case 'GRST':
-			case 'GREN':
-			case 'GRPT':
-			case 'FKEY':
-			case 'IKEY':
-
-			// Texture Mapping Form skipped
-			case 'CSYS':
-
-				// Surface CHUNKs skipped
-			case 'OPAQ': // top level 'opacity' checkbox
-			case 'CMAP': // clip map
-
-			// Surface node CHUNKS skipped
-			// These mainly specify the node editor setup in LW
-			case 'NLOC':
-			case 'NZOM':
-			case 'NVER':
-			case 'NSRV':
-			case 'NCRD':
-			case 'NMOD':
-			case 'NSEL':
-			case 'NPRW':
-			case 'NPLA':
-			case 'VERS':
-			case 'ENUM':
-			case 'TAG ':
-
-			// Car Material CHUNKS
-			case 'CGMD':
-			case 'CGTY':
-			case 'CGST':
-			case 'CGEN':
-			case 'CGTS':
-			case 'CGTE':
-			case 'OSMP':
-			case 'OMDE':
-			case 'OUTR':
-			case 'FLAG':
-
-			case 'TRNL':
-			case 'SHRP':
-			case 'RFOP':
-			case 'RSAN':
-			case 'TROP':
-			case 'RBLR':
-			case 'TBLR':
-			case 'CLRH':
-			case 'CLRF':
-			case 'ADTR':
-			case 'GLOW':
-			case 'LINE':
-			case 'ALPH':
-			case 'VCOL':
-			case 'ENAB':
-				this.IFF.debugger.skipped = true;
-				this.IFF.reader.skip( length );
-				break;
-
-			// Texture node chunks (not in spec)
-			case 'IPIX': // usePixelBlending
-			case 'IMIP': // useMipMaps
-			case 'IMOD': // imageBlendingMode
-			case 'AMOD': // unknown
-			case 'IINV': // imageInvertAlpha
-			case 'INCR': // imageInvertColor
-			case 'IAXS': // imageAxis ( for non-UV maps)
-			case 'IFOT': // imageFallofType
-			case 'ITIM': // timing for animated textures
-			case 'IWRL':
-			case 'IUTI':
-			case 'IINX':
-			case 'IINY':
-			case 'IINZ':
-			case 'IREF': // possibly a VX for reused texture nodes
-				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
-				else this.IFF.reader.skip( length );
-				break;
-
-			case 'OTAG':
-				this.IFF.parseObjectTag();
-				break;
-
-			case 'LAYR':
-				this.IFF.parseLayer( length );
-				break;
-
-			case 'PNTS':
-				this.IFF.parsePoints( length );
-				break;
-
-			case 'VMAP':
-				this.IFF.parseVertexMapping( length );
-				break;
-
-			case 'POLS':
-				this.IFF.parsePolygonList( length );
-				break;
-
-			case 'TAGS':
-				this.IFF.parseTagStrings( length );
-				break;
-
-			case 'PTAG':
-				this.IFF.parsePolygonTagMapping( length );
-				break;
-
-			case 'VMAD':
-				this.IFF.parseVertexMapping( length, true );
-				break;
-
-			// Misc CHUNKS
-			case 'DESC': // Description Line
-				this.IFF.currentForm.description = this.IFF.reader.getString();
-				break;
-
-			case 'TEXT':
-			case 'CMNT':
-			case 'NCOM':
-				this.IFF.currentForm.comment = this.IFF.reader.getString();
-				break;
-
-			// Envelope Form
-			case 'NAME':
-				this.IFF.currentForm.channelName = this.IFF.reader.getString();
-				break;
-
-			// Image Map Layer
-			case 'WRAP':
-				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
-				break;
-
-			case 'IMAG':
-				var index = this.IFF.reader.getVariableLengthIndex();
-				this.IFF.currentForm.imageIndex = index;
-				break;
-
-			// Texture Mapping Form
-			case 'OREF':
-				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
-				break;
-
-			case 'ROID':
-				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
-				break;
-
-			// Surface Blocks
-			case 'SSHN':
-				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
-				break;
-
-			case 'AOVN':
-				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
-				break;
-
-			// Nodal Blocks
-			case 'NSTA':
-				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
-				break;
-
-			case 'NRNM':
-				this.IFF.currentForm.realName = this.IFF.reader.getString();
-				break;
-
-			case 'NNME':
-				this.IFF.currentForm.refName = this.IFF.reader.getString();
-				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
-				break;
-
-			// Nodal Blocks : connections
-			case 'INME':
-				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
-				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINN':
-				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
-				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IINM':
-				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
-				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'IONM':
-				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
-				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
-				break;
-
-			case 'FNAM':
-				this.IFF.currentForm.fileName = this.IFF.reader.getString();
-				break;
-
-			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
-				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
-				else this.IFF.reader.skip( length );
-				break;
-
-			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
-			case 'SMAN':
-				var maxSmoothingAngle = this.IFF.reader.getFloat32();
-				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
-				break;
-
-			// LWO2: Basic Surface Parameters
-			case 'COLR':
-				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
-				this.IFF.reader.skip( 2 ); // VX: envelope
-				break;
-
-			case 'LUMI':
-				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SPEC':
-				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'DIFF':
-				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'REFL':
-				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'GLOS':
-				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TRAN':
-				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'BUMP':
-				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'SIDE':
-				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
-				break;
-
-			case 'RIMG':
-				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'RIND':
-				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
-				this.IFF.reader.skip( 2 );
-				break;
-
-			case 'TIMG':
-				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
-				break;
-
-			case 'IMAP':
-				this.IFF.currentSurface.attributes.imageMapIndex = this.IFF.reader.getUint32();
-				break;
-
-			case 'IUVI': // uv channel name
-				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
-				break;
-
-			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
-				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
-				break;
-			case 'IVTL': // heightWrappingMode
-				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
-				break;
-
-			default:
-				this.IFF.parseUnknownCHUNK( blockID, length );
-
-		}
-
-		if ( blockID != 'FORM' ) {
-
-			this.IFF.debugger.node = 1;
-			this.IFF.debugger.nodeID = blockID;
-			this.IFF.debugger.log();
-
-		}
-
-		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
-
-			this.IFF.currentForm = this.IFF.parentForm;
-
-		}
-
-	}
-
-};
-
-/**
- * === IFFParser ===
- * - Parses data from the IFF buffer.
- * - LWO3 files are in IFF format and can contain the following data types, referred to by shorthand codes
- *
- * ATOMIC DATA TYPES
- *  ID Tag - 4x 7 bit uppercase ASCII chars: ID4
- *  signed integer, 1, 2, or 4 byte length: I1, I2, I4
- *  unsigned integer, 1, 2, or 4 byte length: U1, U2, U4
- *  float, 4 byte length: F4
- *  string, series of ASCII chars followed by null byte (If the length of the string including the null terminating byte is odd, an extra null is added so that the data that follows will begin on an even byte boundary): S0
- *
- * COMPOUND DATA TYPES
- *  Variable-length Index (index into an array or collection): U2 or U4 : VX
- *  Color (RGB): F4 + F4 + F4: COL12
- *  Coordinate (x, y, z): F4 + F4 + F4: VEC12
- *  Percentage F4 data type from 0->1 with 1 = 100%: FP4
- *  Angle in radian F4: ANG4
- *  Filename (string) S0: FNAM0
- *  XValue F4 + index (VX) + optional envelope( ENVL ): XVAL
- *  XValue vector VEC12 + index (VX) + optional envelope( ENVL ): XVAL3
- *
- *  The IFF file is arranged in chunks:
- *  CHUNK = ID4 + length (U4) + length X bytes of data + optional 0 pad byte
- *  optional 0 pad byte is there to ensure chunk ends on even boundary, not counted in size
- *
- * COMPOUND DATA TYPES
- * - Chunks are combined in Forms (collections of chunks)
- * - FORM = string 'FORM' (ID4) + length (U4) + type (ID4) + optional ( CHUNK | FORM )
- * - CHUNKS and FORMS are collectively referred to as blocks
- * - The entire file is contained in one top level FORM
- *
- **/
-
-function IFFParser( ) {
-
-	this.debugger = new Debugger();
-	// this.debugger.enable(); // un-comment to log IFF hierarchy.
-
-}
-
-IFFParser.prototype = {
-
-	constructor: IFFParser,
-
-	parse: function ( buffer ) {
-
-		this.reader = new DataViewReader( buffer );
-
-		this.tree = {
-			materials: {},
-			layers: [],
-			tags: [],
-			textures: [],
-		};
-
-		// start out at the top level to add any data before first layer is encountered
-		this.currentLayer = this.tree;
-		this.currentForm = this.tree;
-
-		this.parseTopForm();
-
-		if ( this.tree.format === undefined ) return;
-
-		if ( this.tree.format === 'LWO2' ) {
-
-			this.parser = new LWO2Parser( this );
-			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
-
-		} else if ( this.tree.format === 'LWO3' ) {
-
-			this.parser = new LWO3Parser( this );
-			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
-
-		}
-
-		this.debugger.offset = this.reader.offset;
-		this.debugger.closeForms();
-
-		return this.tree;
-
-	},
-
-	parseTopForm() {
-
-		this.debugger.offset = this.reader.offset;
-
-		var topForm = this.reader.getIDTag();
-
-		if ( topForm !== 'FORM' ) {
-
-			console.warn( "LWOLoader: Top-level FORM missing." );
-			return;
-
-		}
-
-		var length = this.reader.getUint32();
-
-		this.debugger.dataOffset = this.reader.offset;
-		this.debugger.length = length;
-
-		var type = this.reader.getIDTag();
-
-		if ( type === 'LWO2' ) {
-
-			this.tree.format = type;
-
-		} else if ( type === 'LWO3' ) {
-
-			this.tree.format = type;
-
-		}
-
-		this.debugger.node = 0;
-		this.debugger.nodeID = type;
-		this.debugger.log();
-
-		return;
-
-	},
-
-
-	///
-	// FORM PARSING METHODS
-	///
-
-	// Forms are organisational and can contain any number of sub chunks and sub forms
-	// FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * }
-	parseForm( length ) {
-
-		var type = this.reader.getIDTag();
-
-		switch ( type ) {
-
-			// SKIPPED FORMS
-			// if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped
-
-			case 'ISEQ': // Image sequence
-			case 'ANIM': // plug in animation
-			case 'STCC': // Color-cycling Still
-			case 'VPVL':
-			case 'VPRM':
-			case 'NROT':
-			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
-			case 'WRPH': // image wrap h
-			case 'FUNC':
-			case 'FALL':
-			case 'OPAC':
-			case 'GRAD': // gradient texture
-			case 'ENVS':
-			case 'VMOP':
-			case 'VMBG':
-
-			// Car Material FORMS
-			case 'OMAX':
-			case 'STEX':
-			case 'CKBG':
-			case 'CKEY':
-			case 'VMLA':
-			case 'VMLB':
-				this.debugger.skipped = true;
-				this.skipForm( length ); // not currently supported
-				break;
-
-			// if break; is called directly, the position in the lwoTree is not created
-			// any sub chunks and forms are added to the parent form instead
-			case 'META':
-			case 'NNDS':
-			case 'NODS':
-			case 'NDTA':
-			case 'ADAT':
-			case 'AOVS':
-			case 'BLOK':
-
-			// used by texture nodes
-			case 'IBGC': // imageBackgroundColor
-			case 'IOPC': // imageOpacity
-			case 'IIMG': // hold reference to image path
-			case 'TXTR':
-				// this.setupForm( type, length );
-				this.debugger.length = 4;
-				this.debugger.skipped = true;
-				break;
-
-			case 'IFAL': // imageFallof
-			case 'ISCL': // imageScale
-			case 'IPOS': // imagePosition
-			case 'IROT': // imageRotation
-			case 'IBMP':
-			case 'IUTD':
-			case 'IVTD':
-				this.parseTextureNodeAttribute( type );
-				break;
-
-			case 'ENVL':
-				this.parseEnvelope( length );
-				break;
-
-				// CLIP FORM AND SUB FORMS
-
-			case 'CLIP':
-				if ( this.tree.format === 'LWO2' ) {
-
-					this.parseForm( length );
-
-				} else {
-
-					this.parseClip( length );
-
-				}
-
-				break;
-
-			case 'STIL':
-				this.parseImage();
-				break;
-
-			case 'XREF': // clone of another STIL
-				this.reader.skip( 8 ); // unknown
-				this.currentForm.referenceTexture = {
-					index: this.reader.getUint32(),
-					refName: this.reader.getString() // internal unique ref
-				};
-				break;
-
-				// Not in spec, used by texture nodes
-
-			case 'IMST':
-				this.parseImageStateForm( length );
-				break;
-
-				// SURF FORM AND SUB FORMS
-
-			case 'SURF':
-				this.parseSurfaceForm( length );
-				break;
-
-			case 'VALU': // Not in spec
-				this.parseValueForm( length );
-				break;
-
-			case 'NTAG':
-				this.parseSubNode( length );
-				break;
-
-			case 'ATTR': // BSDF Node Attributes
-			case 'SATR': // Standard Node Attributes
-				this.setupForm( 'attributes', length );
-				break;
-
-			case 'NCON':
-				this.parseConnections( length );
-				break;
-
-			case 'SSHA':
-				this.parentForm = this.currentForm;
-				this.currentForm = this.currentSurface;
-				this.setupForm( 'surfaceShader', length );
-				break;
-
-			case 'SSHD':
-				this.setupForm( 'surfaceShaderData', length );
-				break;
-
-			case 'ENTR': // Not in spec
-				this.parseEntryForm( length );
-				break;
-
-				// Image Map Layer
-
-			case 'IMAP':
-				this.parseImageMap( length );
-				break;
-
-			case 'TAMP':
-				this.parseXVAL( 'amplitude', length );
-				break;
-
-				//Texture Mapping Form
-
-			case 'TMAP':
-				this.setupForm( 'textureMap', length );
-				break;
-
-			case 'CNTR':
-				this.parseXVAL3( 'center', length );
-				break;
-
-			case 'SIZE':
-				this.parseXVAL3( 'scale', length );
-				break;
-
-			case 'ROTA':
-				this.parseXVAL3( 'rotation', length );
-				break;
-
-			default:
-				this.parseUnknownForm( type, length );
-
-		}
-
-		this.debugger.node = 0;
-		this.debugger.nodeID = type;
-		this.debugger.log();
-
-	},
-
-	setupForm( type, length ) {
-
-		if ( ! this.currentForm ) this.currentForm = this.currentNode;
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		if ( ! this.currentForm[ type ] ) {
-
-			this.currentForm[ type ] = {};
-			this.currentForm = this.currentForm[ type ];
-
-
-		} else {
-
-			// should never see this unless there's a bug in the reader
-			console.warn( 'LWOLoader: form already exists on parent: ', type, this.currentForm );
-
-			this.currentForm = this.currentForm[ type ];
-
-		}
-
-
-	},
-
-	skipForm( length ) {
-
-		this.reader.skip( length - 4 );
-
-	},
-
-	parseUnknownForm( type, length ) {
-
-		console.warn( 'LWOLoader: unknown FORM encountered: ' + type, length );
-
-		printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 );
-		this.reader.skip( length - 4 );
-
-	},
-
-	parseSurfaceForm( length ) {
-
-		this.reader.skip( 8 ); // unknown Uint32 x2
-
-		var name = this.reader.getString();
-
-		var surface = {
-			attributes: {}, // LWO2 style non-node attributes will go here
-			connections: {},
-			name: name,
-			inputName: name,
-			nodes: {},
-			source: this.reader.getString(),
-		};
-
-		this.tree.materials[ name ] = surface;
-		this.currentSurface = surface;
-
-		this.parentForm = this.tree.materials;
-		this.currentForm = surface;
-		this.currentFormEnd = this.reader.offset + length;
-
-	},
-
-	parseSurfaceLwo2( length ) {
-
-		var name = this.reader.getString();
-
-		var surface = {
-			attributes: {}, // LWO2 style non-node attributes will go here
-			connections: {},
-			name: name,
-			nodes: {},
-			source: this.reader.getString(),
-		};
-
-		this.tree.materials[ name ] = surface;
-		this.currentSurface = surface;
-
-		this.parentForm = this.tree.materials;
-		this.currentForm = surface;
-		this.currentFormEnd = this.reader.offset + length;
-
-	},
-
-	parseSubNode( length ) {
-
-		// parse the NRNM CHUNK of the subnode FORM to get
-		// a meaningful name for the subNode
-		// some subnodes can be renamed, but Input and Surface cannot
-
-		this.reader.skip( 8 ); // NRNM + length
-		var name = this.reader.getString();
-
-		var node = {
-			name: name
-		};
-		this.currentForm = node;
-		this.currentNode = node;
-
-		this.currentFormEnd = this.reader.offset + length;
-
-
-	},
-
-	// collect attributes from all nodes at the top level of a surface
-	parseConnections( length ) {
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		this.currentForm = this.currentSurface.connections;
-
-	},
-
-	// surface node attribute data, e.g. specular, roughness etc
-	parseEntryForm( length ) {
-
-		this.reader.skip( 8 ); // NAME + length
-		var name = this.reader.getString();
-		this.currentForm = this.currentNode.attributes;
-
-		this.setupForm( name, length );
-
-	},
-
-	// parse values from material - doesn't match up to other LWO3 data types
-	// sub form of entry form
-	parseValueForm() {
-
-		this.reader.skip( 8 ); // unknown + length
-
-		var valueType = this.reader.getString();
-
-		if ( valueType === 'double' ) {
-
-			this.currentForm.value = this.reader.getUint64();
-
-		} else if ( valueType === 'int' ) {
-
-			this.currentForm.value = this.reader.getUint32();
-
-		} else if ( valueType === 'vparam' ) {
-
-			this.reader.skip( 24 );
-			this.currentForm.value = this.reader.getFloat64();
-
-		} else if ( valueType === 'vparam3' ) {
-
-			this.reader.skip( 24 );
-			this.currentForm.value = this.reader.getFloat64Array( 3 );
-
-		}
-
-	},
-
-	// holds various data about texture node image state
-	// Data other thanmipMapLevel unknown
-	parseImageStateForm() {
-
-		this.reader.skip( 8 ); // unknown
-
-		this.currentForm.mipMapLevel = this.reader.getFloat32();
-
-	},
-
-	// LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node)
-	parseImageMap( length ) {
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		if ( ! this.currentForm.maps ) this.currentForm.maps = [];
-
-		var map = {};
-		this.currentForm.maps.push( map );
-		this.currentForm = map;
-
-		this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX
-
-	},
-
-	parseTextureNodeAttribute( type ) {
-
-		this.reader.skip( 28 ); // FORM + length + VPRM + unknown + Uint32 x2 + float32
-
-		this.reader.skip( 20 ); // FORM + length + VPVL + float32 + Uint32
-
-		switch ( type ) {
-
-			case 'ISCL':
-				this.currentNode.scale = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IPOS':
-				this.currentNode.position = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IROT':
-				this.currentNode.rotation = this.reader.getFloat32Array( 3 );
-				break;
-			case 'IFAL':
-				this.currentNode.falloff = this.reader.getFloat32Array( 3 );
-				break;
-
-			case 'IBMP':
-				this.currentNode.amplitude = this.reader.getFloat32();
-				break;
-			case 'IUTD':
-				this.currentNode.uTiles = this.reader.getFloat32();
-				break;
-			case 'IVTD':
-				this.currentNode.vTiles = this.reader.getFloat32();
-				break;
-
-		}
-
-		this.reader.skip( 2 ); // unknown
-
-
-	},
-
-	// ENVL forms are currently ignored
-	parseEnvelope( length ) {
-
-		this.reader.skip( length - 4 ); // skipping  entirely for now
-
-	},
-
-	///
-	// CHUNK PARSING METHODS
-	///
-
-	// clips can either be defined inside a surface node, or at the top
-	// level and they have a different format in each case
-	parseClip( length ) {
-
-		var tag = this.reader.getIDTag();
-
-		// inside surface node
-		if ( tag === 'FORM' ) {
-
-			this.reader.skip( 16 );
-
-			this.currentNode.fileName = this.reader.getString();
-
-			return;
-
-		}
-
-		// otherwise top level
-		this.reader.setOffset( this.reader.offset - 4 );
-
-		this.currentFormEnd = this.reader.offset + length;
-		this.parentForm = this.currentForm;
-
-		this.reader.skip( 8 ); // unknown
-
-		var texture = {
-			index: this.reader.getUint32()
-		};
-		this.tree.textures.push( texture );
-		this.currentForm = texture;
-
-	},
-
-	parseClipLwo2( length ) {
-
-		var texture = {
-			index: this.reader.getUint32(),
-			fileName: ""
-		};
-
-		// seach STIL block
-		while ( true ) {
-
-			var tag = this.reader.getIDTag();
-			var n_length = this.reader.getUint16();
-			if ( tag === 'STIL' ) {
-
-				texture.fileName = this.reader.getString();
-				break;
-
-			}
-
-			if ( n_length >= length ) {
-
-				break;
-
-			}
-
-		}
-
-		this.tree.textures.push( texture );
-		this.currentForm = texture;
-
-	},
-
-	parseImage() {
-
-		this.reader.skip( 8 ); // unknown
-		this.currentForm.fileName = this.reader.getString();
-
-	},
-
-	parseXVAL( type, length ) {
-
-		var endOffset = this.reader.offset + length - 4;
-		this.reader.skip( 8 );
-
-		this.currentForm[ type ] = this.reader.getFloat32();
-
-		this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope
-
-	},
-
-	parseXVAL3( type, length ) {
-
-		var endOffset = this.reader.offset + length - 4;
-		this.reader.skip( 8 );
-
-		this.currentForm[ type ] = {
-			x: this.reader.getFloat32(),
-			y: this.reader.getFloat32(),
-			z: this.reader.getFloat32(),
-		};
-
-		this.reader.setOffset( endOffset );
-
-	},
-
-	// Tags associated with an object
-	// OTAG { type[ID4], tag-string[S0] }
-	parseObjectTag() {
-
-		if ( ! this.tree.objectTags ) this.tree.objectTags = {};
-
-		this.tree.objectTags[ this.reader.getIDTag() ] = {
-			tagString: this.reader.getString()
-		};
-
-	},
-
-	// Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered.
-	// LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2]
-	parseLayer( length ) {
-
-		var layer = {
-			number: this.reader.getUint16(),
-			flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden.
-			pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present
-			name: this.reader.getString(),
-		};
-
-		this.tree.layers.push( layer );
-		this.currentLayer = layer;
-
-		var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength
-
-		// if we have not reached then end of the layer block, there must be a parent defined
-		this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent
-
-	},
-
-	// VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors
-	// Converting from left to right handed coordinate system:
-	// x -> -x and switch material FrontSide -> BackSide
-	parsePoints( length ) {
-
-		this.currentPoints = [];
-		for ( var i = 0; i < length / 4; i += 3 ) {
-
-			// z -> -z to match three.js right handed coords
-			this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
-
-		}
-
-	},
-
-	// parse VMAP or VMAD
-	// Associates a set of floating-point vectors with a set of points.
-	// VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * }
-
-	// VMAD Associates a set of floating-point vectors with the vertices of specific polygons.
-	// Similar to VMAP UVs, but associates with polygon vertices rather than points
-	// to solve to problem of UV seams:  VMAD chunks are paired with VMAPs of the same name,
-	// if they exist. The vector values in the VMAD will then replace those in the
-	// corresponding VMAP, but only for calculations involving the specified polygons.
-	// VMAD { type[ID4], dimension[U2], name[S0], ( vert[VX], poly[VX], value[F4] # dimension ) * }
-	parseVertexMapping( length, discontinuous ) {
-
-		var finalOffset = this.reader.offset + length;
-
-		var channelName = this.reader.getString();
-
-		if ( this.reader.offset === finalOffset ) {
-
-			// then we are in a texture node and the VMAP chunk is just a reference to a UV channel name
-			this.currentForm.UVChannel = channelName;
-			return;
-
-		}
-
-		// otherwise reset to initial length and parse normal VMAP CHUNK
-		this.reader.setOffset( this.reader.offset - stringOffset( channelName ) );
-
-		var type = this.reader.getIDTag();
-
-		this.reader.getUint16(); // dimension
-		var name = this.reader.getString();
-
-		var remainingLength = length - 6 - stringOffset( name );
-
-		switch ( type ) {
-
-			case 'TXUV':
-				this.parseUVMapping( name, finalOffset, discontinuous );
-				break;
-			case 'MORF':
-			case 'SPOT':
-				this.parseMorphTargets( name, finalOffset, type ); // can't be discontinuous
-				break;
-			// unsupported VMAPs
-			case 'APSL':
-			case 'NORM':
-			case 'WGHT':
-			case 'MNVW':
-			case 'PICK':
-			case 'RGB ':
-			case 'RGBA':
-				this.reader.skip( remainingLength );
-				break;
-			default:
-				console.warn( 'LWOLoader: unknown vertex map type: ' + type );
-				this.reader.skip( remainingLength );
-
-		}
-
-	},
-
-	parseUVMapping( name, finalOffset, discontinuous ) {
-
-		var uvIndices = [];
-		var polyIndices = [];
-		var uvs = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			uvIndices.push( this.reader.getVariableLengthIndex() );
-
-			if ( discontinuous ) polyIndices.push( this.reader.getVariableLengthIndex() );
-
-			uvs.push( this.reader.getFloat32(), this.reader.getFloat32() );
-
-		}
-
-		if ( discontinuous ) {
-
-			if ( ! this.currentLayer.discontinuousUVs ) this.currentLayer.discontinuousUVs = {};
-
-			this.currentLayer.discontinuousUVs[ name ] = {
-				uvIndices: uvIndices,
-				polyIndices: polyIndices,
-				uvs: uvs,
-			};
-
-		} else {
-
-			if ( ! this.currentLayer.uvs ) this.currentLayer.uvs = {};
-
-			this.currentLayer.uvs[ name ] = {
-				uvIndices: uvIndices,
-				uvs: uvs,
-			};
-
-		}
-
-	},
-
-	parseMorphTargets( name, finalOffset, type ) {
-
-		var indices = [];
-		var points = [];
-
-		type = ( type === 'MORF' ) ? 'relative' : 'absolute';
-
-		while ( this.reader.offset < finalOffset ) {
-
-			indices.push( this.reader.getVariableLengthIndex() );
-			// z -> -z to match three.js right handed coords
-			points.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
-
-		}
-
-		if ( ! this.currentLayer.morphTargets ) this.currentLayer.morphTargets = {};
-
-		this.currentLayer.morphTargets[ name ] = {
-			indices: indices,
-			points: points,
-			type: type,
-		};
-
-	},
-
-	// A list of polygons for the current layer.
-	// POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * }
-	parsePolygonList( length ) {
-
-		var finalOffset = this.reader.offset + length;
-		var type = this.reader.getIDTag();
-
-		var indices = [];
-
-		// hold a list of polygon sizes, to be split up later
-		var polygonDimensions = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			var numverts = this.reader.getUint16();
-
-			//var flags = numverts & 64512; // 6 high order bits are flags - ignoring for now
-			numverts = numverts & 1023; // remaining ten low order bits are vertex num
-			polygonDimensions.push( numverts );
-
-			for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() );
-
-		}
-
-		var geometryData = {
-			type: type,
-			vertexIndices: indices,
-			polygonDimensions: polygonDimensions,
-			points: this.currentPoints
-		};
-
-		// Note: assuming that all polys will be lines or points if the first is
-		if ( polygonDimensions[ 0 ] === 1 ) geometryData.type = 'points';
-		else if ( polygonDimensions[ 0 ] === 2 ) geometryData.type = 'lines';
-
-		this.currentLayer.geometry = geometryData;
-
-	},
-
-	// Lists the tag strings that can be associated with polygons by the PTAG chunk.
-	// TAGS { tag-string[S0] * }
-	parseTagStrings( length ) {
-
-		this.tree.tags = this.reader.getStringArray( length );
-
-	},
-
-	// Associates tags of a given type with polygons in the most recent POLS chunk.
-	// PTAG { type[ID4], ( poly[VX], tag[U2] ) * }
-	parsePolygonTagMapping( length ) {
-
-		var finalOffset = this.reader.offset + length;
-		var type = this.reader.getIDTag();
-		if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset );
-		else { //PART, SMGP, COLR not supported
-
-			this.reader.skip( length - 4 );
-
-		}
-
-	},
-
-	parseMaterialIndices( finalOffset ) {
-
-		// array holds polygon index followed by material index
-		this.currentLayer.geometry.materialIndices = [];
-
-		while ( this.reader.offset < finalOffset ) {
-
-			var polygonIndex = this.reader.getVariableLengthIndex();
-			var materialIndex = this.reader.getUint16();
-
-			this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex );
-
-		}
-
-	},
-
-	parseUnknownCHUNK( blockID, length ) {
-
-		console.warn( 'LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length );
-
-		// print the chunk plus some bytes padding either side
-		// printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 );
-
-		var data = this.reader.getString( length );
-
-		this.currentForm[ blockID ] = data;
-
-	}
-
-};
-
-function DataViewReader( buffer ) {
-
-	this.dv = new DataView( buffer );
-	this.offset = 0;
-
-}
-
-DataViewReader.prototype = {
-
-	constructor: DataViewReader,
-
-	size: function () {
-
-		return this.dv.buffer.byteLength;
-
-	},
-
-	setOffset( offset ) {
-
-		if ( offset > 0 && offset < this.dv.buffer.byteLength ) {
-
-			this.offset = offset;
-
-		} else {
-
-			console.error( 'LWOLoader: invalid buffer offset' );
-
-		}
-
-	},
-
-	endOfFile: function () {
-
-		if ( this.offset >= this.size() ) return true;
-		return false;
-
-	},
-
-	skip: function ( length ) {
-
-		this.offset += length;
-
-	},
-
-	getUint8: function () {
-
-		var value = this.dv.getUint8( this.offset );
-		this.offset += 1;
-		return value;
-
-	},
-
-	getUint16: function () {
-
-		var value = this.dv.getUint16( this.offset );
-		this.offset += 2;
-		return value;
-
-	},
-
-	getInt32: function () {
-
-		var value = this.dv.getInt32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getUint32: function () {
-
-		var value = this.dv.getUint32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getUint64: function () {
-
-		var low, high;
-
-		high = this.getUint32();
-		low = this.getUint32();
-		return high * 0x100000000 + low;
-
-	},
-
-	getFloat32: function () {
-
-		var value = this.dv.getFloat32( this.offset, false );
-		this.offset += 4;
-		return value;
-
-	},
-
-	getFloat32Array: function ( size ) {
-
-		var a = [];
-
-		for ( var i = 0; i < size; i ++ ) {
-
-			a.push( this.getFloat32() );
-
-		}
-
-		return a;
-
-	},
-
-	getFloat64: function () {
-
-		var value = this.dv.getFloat64( this.offset, this.littleEndian );
-		this.offset += 8;
-		return value;
-
-	},
-
-	getFloat64Array: function ( size ) {
-
-		var a = [];
-
-		for ( var i = 0; i < size; i ++ ) {
-
-			a.push( this.getFloat64() );
-
-		}
-
-		return a;
-
-	},
-
-	// get variable-length index data type
-	// VX ::= index[U2] | (index + 0xFF000000)[U4]
-	// If the index value is less than 65,280 (0xFF00),then VX === U2
-	// otherwise VX === U4 with bits 24-31 set
-	// When reading an index, if the first byte encountered is 255 (0xFF), then
-	// the four-byte form is being used and the first byte should be discarded or masked out.
-	getVariableLengthIndex() {
-
-		var firstByte = this.getUint8();
-
-		if ( firstByte === 255 ) {
-
-			return this.getUint8() * 65536 + this.getUint8() * 256 + this.getUint8();
-
-		}
-
-		return firstByte * 256 + this.getUint8();
-
-	},
-
-	// An ID tag is a sequence of 4 bytes containing 7-bit ASCII values
-	getIDTag() {
-
-		return this.getString( 4 );
-
-	},
-
-	getString: function ( size ) {
-
-		if ( size === 0 ) return;
-
-		// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
-		var a = [];
-
-		if ( size ) {
-
-			for ( var i = 0; i < size; i ++ ) {
-
-				a[ i ] = this.getUint8();
-
-			}
-
-		} else {
-
-			var currentChar;
-			var len = 0;
-
-			while ( currentChar !== 0 ) {
-
-				currentChar = this.getUint8();
-				if ( currentChar !== 0 ) a.push( currentChar );
-				len ++;
-
-			}
-
-			if ( ! isEven( len + 1 ) ) this.getUint8(); // if string with terminating nullbyte is uneven, extra nullbyte is added
-
-		}
-
-		return LoaderUtils.decodeText( new Uint8Array( a ) );
-
-	},
-
-	getStringArray: function ( size ) {
-
-		var a = this.getString( size );
-		a = a.split( '\0' );
-
-		return a.filter( Boolean ); // return array with any empty strings removed
-
-	}
-
-};
-
-// ************** DEBUGGER  **************
-
-function Debugger( ) {
-
-	this.active = false;
-	this.depth = 0;
-	this.formList = [];
-
-}
-
-Debugger.prototype = {
-
-	constructor: Debugger,
-
-	enable: function () {
-
-		this.active = true;
-
-	},
-
-	log: function () {
-
-		if ( ! this.active ) return;
-
-		var nodeType;
-
-		switch ( this.node ) {
-
-			case 0:
-				nodeType = "FORM";
-				break;
-
-			case 1:
-				nodeType = "CHK";
-				break;
-
-			case 2:
-				nodeType = "S-CHK";
-				break;
-
-		}
-
-		console.log(
-			"| ".repeat( this.depth ) +
-			nodeType,
-			this.nodeID,
-			`( ${this.offset} ) -> ( ${this.dataOffset + this.length} )`,
-			( ( this.node == 0 ) ? " {" : "" ),
-			( ( this.skipped ) ? "SKIPPED" : "" ),
-			( ( this.node == 0 && this.skipped ) ? "}" : "" )
-		);
-
-		if ( this.node == 0 && ! this.skipped ) {
-
-			this.depth += 1;
-			this.formList.push( this.dataOffset + this.length );
-
-		}
-
-		this.skipped = false;
-
-	},
-
-	closeForms: function () {
-
-		if ( ! this.active ) return;
-
-		for ( var i = this.formList.length - 1; i >= 0; i -- ) {
-
-			if ( this.offset >= this.formList[ i ] ) {
-
-				this.depth -= 1;
-				console.log( "| ".repeat( this.depth ) + "}" );
-				this.formList.splice( - 1, 1 );
-
-			}
-
-		}
-
-	}
-
-};
-
-// ************** UTILITY FUNCTIONS **************
-
-function isEven( num ) {
-
-	return num % 2;
-
-}
-
-// calculate the length of the string in the buffer
-// this will be string.length + nullbyte + optional padbyte to make the length even
-function stringOffset( string ) {
-
-	return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 );
-
-}
-
-// for testing purposes, dump buffer to console
-// printBuffer( this.reader.dv.buffer, this.reader.offset, length );
-function printBuffer( buffer, from, to ) {
-
-	console.log( LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ) );
-
-}
+import { IFFParser } from "./lwo/IFFParser.js";
 
 
 var lwoTree;
 var lwoTree;
 
 

+ 73 - 17
examples/jsm/loaders/OBJLoader.js

@@ -14,7 +14,8 @@ import {
 	Mesh,
 	Mesh,
 	MeshPhongMaterial,
 	MeshPhongMaterial,
 	Points,
 	Points,
-	PointsMaterial
+	PointsMaterial,
+	Vector3
 } from "../../../build/three.module.js";
 } from "../../../build/three.module.js";
 
 
 var OBJLoader = ( function () {
 var OBJLoader = ( function () {
@@ -28,6 +29,13 @@ var OBJLoader = ( function () {
 	// usemap map_name
 	// usemap map_name
 	var map_use_pattern = /^usemap /;
 	var map_use_pattern = /^usemap /;
 
 
+	var vA = new Vector3();
+	var vB = new Vector3();
+	var vC = new Vector3();
+
+	var ab = new Vector3();
+	var cb = new Vector3();
+
 	function ParserState() {
 	function ParserState() {
 
 
 		var state = {
 		var state = {
@@ -70,7 +78,9 @@ var OBJLoader = ( function () {
 						vertices: [],
 						vertices: [],
 						normals: [],
 						normals: [],
 						colors: [],
 						colors: [],
-						uvs: []
+						uvs: [],
+						hasNormalIndices: false,
+						hasUVIndices: false
 					},
 					},
 					materials: [],
 					materials: [],
 					smooth: true,
 					smooth: true,
@@ -263,6 +273,27 @@ var OBJLoader = ( function () {
 
 
 			},
 			},
 
 
+			addFaceNormal: function ( a, b, c ) {
+
+				var src = this.vertices;
+				var dst = this.object.geometry.normals;
+
+				vA.fromArray( src, a );
+				vB.fromArray( src, b );
+				vC.fromArray( src, c );
+
+				cb.subVectors( vC, vB );
+				ab.subVectors( vA, vB );
+				cb.cross( ab );
+
+				cb.normalize();
+
+				dst.push( cb.x, cb.y, cb.z );
+				dst.push( cb.x, cb.y, cb.z );
+				dst.push( cb.x, cb.y, cb.z );
+
+			},
+
 			addColor: function ( a, b, c ) {
 			addColor: function ( a, b, c ) {
 
 
 				var src = this.colors;
 				var src = this.colors;
@@ -285,6 +316,16 @@ var OBJLoader = ( function () {
 
 
 			},
 			},
 
 
+			addDefaultUV: function () {
+
+				var dst = this.object.geometry.uvs;
+
+				dst.push( 0, 0 );
+				dst.push( 0, 0 );
+				dst.push( 0, 0 );
+
+			},
+
 			addUVLine: function ( a ) {
 			addUVLine: function ( a ) {
 
 
 				var src = this.uvs;
 				var src = this.uvs;
@@ -305,26 +346,45 @@ var OBJLoader = ( function () {
 				this.addVertex( ia, ib, ic );
 				this.addVertex( ia, ib, ic );
 				this.addColor( ia, ib, ic );
 				this.addColor( ia, ib, ic );
 
 
+				// normals
+
+				if ( na !== undefined && na !== '' ) {
+
+					var nLen = this.normals.length;
+
+					ia = this.parseNormalIndex( na, nLen );
+					ib = this.parseNormalIndex( nb, nLen );
+					ic = this.parseNormalIndex( nc, nLen );
+
+					this.addNormal( ia, ib, ic );
+
+					this.object.geometry.hasNormalIndices = true;
+
+				} else {
+
+					this.addFaceNormal( ia, ib, ic );
+
+				}
+
+				// uvs
+
 				if ( ua !== undefined && ua !== '' ) {
 				if ( ua !== undefined && ua !== '' ) {
 
 
 					var uvLen = this.uvs.length;
 					var uvLen = this.uvs.length;
+
 					ia = this.parseUVIndex( ua, uvLen );
 					ia = this.parseUVIndex( ua, uvLen );
 					ib = this.parseUVIndex( ub, uvLen );
 					ib = this.parseUVIndex( ub, uvLen );
 					ic = this.parseUVIndex( uc, uvLen );
 					ic = this.parseUVIndex( uc, uvLen );
-					this.addUV( ia, ib, ic );
 
 
-				}
+					this.addUV( ia, ib, ic );
 
 
-				if ( na !== undefined && na !== '' ) {
+					this.object.geometry.hasUVIndices = true;
 
 
-					// Normals are many times the same. If so, skip function call and parseInt.
-					var nLen = this.normals.length;
-					ia = this.parseNormalIndex( na, nLen );
+				} else {
 
 
-					ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
-					ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
+					// add placeholder values (for inconsistent face definitions)
 
 
-					this.addNormal( ia, ib, ic );
+					this.addDefaultUV();
 
 
 				}
 				}
 
 
@@ -667,14 +727,10 @@ var OBJLoader = ( function () {
 
 
 				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
 				buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) );
 
 
-				if ( geometry.normals.length > 0 ) {
+				if ( geometry.hasNormalIndices === true ) {
 
 
 					buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
 					buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) );
 
 
-				} else {
-
-					buffergeometry.computeVertexNormals();
-
 				}
 				}
 
 
 				if ( geometry.colors.length > 0 ) {
 				if ( geometry.colors.length > 0 ) {
@@ -684,7 +740,7 @@ var OBJLoader = ( function () {
 
 
 				}
 				}
 
 
-				if ( geometry.uvs.length > 0 ) {
+				if ( geometry.hasUVIndices === true ) {
 
 
 					buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
 					buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) );
 
 

+ 646 - 18
examples/jsm/loaders/VRMLLoader.js

@@ -27,10 +27,12 @@ import {
 	Object3D,
 	Object3D,
 	Points,
 	Points,
 	PointsMaterial,
 	PointsMaterial,
+	Quaternion,
 	RGBAFormat,
 	RGBAFormat,
 	RGBFormat,
 	RGBFormat,
 	RepeatWrapping,
 	RepeatWrapping,
 	Scene,
 	Scene,
+	ShapeUtils,
 	SphereBufferGeometry,
 	SphereBufferGeometry,
 	TextureLoader,
 	TextureLoader,
 	Vector2,
 	Vector2,
@@ -365,13 +367,13 @@ var VRMLLoader = ( function () {
 
 
 					def: function ( ctx ) {
 					def: function ( ctx ) {
 
 
-						return ctx.Identifier[ 0 ].image;
+						return ( ctx.Identifier || ctx.NodeName )[ 0 ].image;
 
 
 					},
 					},
 
 
 					use: function ( ctx ) {
 					use: function ( ctx ) {
 
 
-						return { USE: ctx.Identifier[ 0 ].image };
+						return { USE: ( ctx.Identifier || ctx.NodeName )[ 0 ].image };
 
 
 					},
 					},
 
 
@@ -551,6 +553,8 @@ var VRMLLoader = ( function () {
 
 
 					if ( object instanceof Object3D ) scene.add( object );
 					if ( object instanceof Object3D ) scene.add( object );
 
 
+					if ( node.name === 'WorldInfo' ) scene.userData.worldInfo = object;
+
 				}
 				}
 
 
 				return scene;
 				return scene;
@@ -618,6 +622,7 @@ var VRMLLoader = ( function () {
 
 
 					case 'Group':
 					case 'Group':
 					case 'Transform':
 					case 'Transform':
+					case 'Collision':
 						build = buildGroupingNode( node );
 						build = buildGroupingNode( node );
 						break;
 						break;
 
 
@@ -677,6 +682,14 @@ var VRMLLoader = ( function () {
 						build = buildSphereNode( node );
 						build = buildSphereNode( node );
 						break;
 						break;
 
 
+					case 'ElevationGrid':
+						build = buildElevationGridNode( node );
+						break;
+
+					case 'Extrusion':
+						build = buildExtrusionNode( node );
+						break;
+
 					case 'Color':
 					case 'Color':
 					case 'Coordinate':
 					case 'Coordinate':
 					case 'Normal':
 					case 'Normal':
@@ -684,9 +697,12 @@ var VRMLLoader = ( function () {
 						build = buildGeometricNode( node );
 						build = buildGeometricNode( node );
 						break;
 						break;
 
 
+					case 'WorldInfo':
+						build = buildWorldInfoNode( node );
+						break;
+
 					case 'Anchor':
 					case 'Anchor':
 					case 'Billboard':
 					case 'Billboard':
-					case 'Collision':
 
 
 					case 'Inline':
 					case 'Inline':
 					case 'LOD':
 					case 'LOD':
@@ -698,7 +714,6 @@ var VRMLLoader = ( function () {
 					case 'Script':
 					case 'Script':
 					case 'Sound':
 					case 'Sound':
 					case 'SpotLight':
 					case 'SpotLight':
-					case 'WorldInfo':
 
 
 					case 'CylinderSensor':
 					case 'CylinderSensor':
 					case 'PlaneSensor':
 					case 'PlaneSensor':
@@ -708,8 +723,6 @@ var VRMLLoader = ( function () {
 					case 'TouchSensor':
 					case 'TouchSensor':
 					case 'VisibilitySensor':
 					case 'VisibilitySensor':
 
 
-					case 'ElevationGrid':
-					case 'Extrusion':
 					case 'Text':
 					case 'Text':
 
 
 					case 'FontStyle':
 					case 'FontStyle':
@@ -754,6 +767,14 @@ var VRMLLoader = ( function () {
 
 
 					switch ( fieldName ) {
 					switch ( fieldName ) {
 
 
+						case 'bboxCenter':
+							// field not supported
+							break;
+
+						case 'bboxSize':
+							// field not supported
+							break;
+
 						case 'center':
 						case 'center':
 							// field not supported
 							// field not supported
 							break;
 							break;
@@ -762,6 +783,10 @@ var VRMLLoader = ( function () {
 							parseFieldChildren( fieldValues, object );
 							parseFieldChildren( fieldValues, object );
 							break;
 							break;
 
 
+						case 'collide':
+							// field not supported
+							break;
+
 						case 'rotation':
 						case 'rotation':
 							var axis = new Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							var axis = new Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							var angle = fieldValues[ 3 ];
 							var angle = fieldValues[ 3 ];
@@ -780,11 +805,7 @@ var VRMLLoader = ( function () {
 							object.position.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							object.position.set( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] );
 							break;
 							break;
 
 
-						case 'bboxCenter':
-							// field not supported
-							break;
-
-						case 'bboxSize':
+						case 'proxy':
 							// field not supported
 							// field not supported
 							break;
 							break;
 
 
@@ -1475,6 +1496,40 @@ var VRMLLoader = ( function () {
 
 
 			}
 			}
 
 
+			function buildWorldInfoNode( node ) {
+
+				var worldInfo = {};
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'title':
+							worldInfo.title = fieldValues[ 0 ];
+							break;
+
+						case 'info':
+							worldInfo.info = fieldValues;
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				return worldInfo;
+
+			}
+
 			function buildIndexedFaceSetNode( node ) {
 			function buildIndexedFaceSetNode( node ) {
 
 
 				var color, coord, normal, texCoord;
 				var color, coord, normal, texCoord;
@@ -2073,6 +2128,557 @@ var VRMLLoader = ( function () {
 
 
 			}
 			}
 
 
+			function buildElevationGridNode( node ) {
+
+				var color;
+				var normal;
+				var texCoord;
+				var height;
+
+				var colorPerVertex = true;
+				var normalPerVertex = true;
+				var solid = true;
+				var ccw = true;
+				var creaseAngle = 0;
+				var xDimension = 2;
+				var zDimension = 2;
+				var xSpacing = 1;
+				var zSpacing = 1;
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'color':
+							var colorNode = fieldValues[ 0 ];
+
+							if ( colorNode !== null ) {
+
+								color = getNode( colorNode );
+
+							}
+
+							break;
+
+						case 'normal':
+							var normalNode = fieldValues[ 0 ];
+
+							if ( normalNode !== null ) {
+
+								normal = getNode( normalNode );
+
+							}
+
+							break;
+
+						case 'texCoord':
+							var texCoordNode = fieldValues[ 0 ];
+
+							if ( texCoordNode !== null ) {
+
+								texCoord = getNode( texCoordNode );
+
+							}
+
+							break;
+
+						case 'height':
+							height = fieldValues;
+							break;
+
+						case 'ccw':
+							ccw = fieldValues[ 0 ];
+							break;
+
+						case 'colorPerVertex':
+							colorPerVertex = fieldValues[ 0 ];
+							break;
+
+						case 'creaseAngle':
+							creaseAngle = fieldValues[ 0 ];
+							break;
+
+						case 'normalPerVertex':
+							normalPerVertex = fieldValues[ 0 ];
+							break;
+
+						case 'solid':
+							solid = fieldValues[ 0 ];
+							break;
+
+						case 'xDimension':
+							xDimension = fieldValues[ 0 ];
+							break;
+
+						case 'xSpacing':
+							xSpacing = fieldValues[ 0 ];
+							break;
+
+						case 'zDimension':
+							zDimension = fieldValues[ 0 ];
+							break;
+
+						case 'zSpacing':
+							zSpacing = fieldValues[ 0 ];
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				// vertex data
+
+				var vertices = [];
+				var normals = [];
+				var colors = [];
+				var uvs = [];
+
+				for ( var i = 0; i < zDimension; i ++ ) {
+
+					for ( var j = 0; j < xDimension; j ++ ) {
+
+						// compute a row major index
+
+						var index = ( i * xDimension ) + j;
+
+						// vertices
+
+						var x = xSpacing * i;
+						var y = height[ index ];
+						var z = zSpacing * j;
+
+						vertices.push( x, y, z );
+
+						// colors
+
+						if ( color && colorPerVertex === true ) {
+
+							var r = color[ index * 3 + 0 ];
+							var g = color[ index * 3 + 1 ];
+							var b = color[ index * 3 + 2 ];
+
+							colors.push( r, g, b );
+
+						}
+
+						// normals
+
+						if ( normal && normalPerVertex === true ) {
+
+							var xn = normal[ index * 3 + 0 ];
+							var yn = normal[ index * 3 + 1 ];
+							var zn = normal[ index * 3 + 2 ];
+
+							normals.push( xn, yn, zn );
+
+						}
+
+						// uvs
+
+						if ( texCoord ) {
+
+							var s = texCoord[ index * 2 + 0 ];
+							var t = texCoord[ index * 2 + 1 ];
+
+							uvs.push( s, t );
+
+
+						} else {
+
+							uvs.push( i / ( xDimension - 1 ), j / ( zDimension - 1 ) );
+
+						}
+
+					}
+
+				}
+
+				// indices
+
+				var indices = [];
+
+				for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+					for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+						// from https://tecfa.unige.ch/guides/vrml/vrml97/spec/part1/nodesRef.html#ElevationGrid
+
+						var a = i + j * xDimension;
+						var b = i + ( j + 1 ) * xDimension;
+						var c = ( i + 1 ) + ( j + 1 ) * xDimension;
+						var d = ( i + 1 ) + j * xDimension;
+
+						// faces
+
+						if ( ccw === true ) {
+
+							indices.push( a, c, b );
+							indices.push( c, a, d );
+
+						} else {
+
+							indices.push( a, b, c );
+							indices.push( c, d, a );
+
+						}
+
+					}
+
+				}
+
+				//
+
+				var positionAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( vertices, 3 ) );
+				var uvAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( uvs, 2 ) );
+				var colorAttribute;
+				var normalAttribute;
+
+				// color attribute
+
+				if ( color ) {
+
+					if ( colorPerVertex === false ) {
+
+						for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+							for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+								var index = i + j * ( xDimension - 1 );
+
+								var r = color[ index * 3 + 0 ];
+								var g = color[ index * 3 + 1 ];
+								var b = color[ index * 3 + 2 ];
+
+								// one color per quad
+
+								colors.push( r, g, b ); colors.push( r, g, b ); colors.push( r, g, b );
+								colors.push( r, g, b ); colors.push( r, g, b ); colors.push( r, g, b );
+
+							}
+
+						}
+
+						colorAttribute = new Float32BufferAttribute( colors, 3 );
+
+					} else {
+
+						colorAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( colors, 3 ) );
+
+					}
+
+				}
+
+				// normal attribute
+
+				if ( normal ) {
+
+					if ( normalPerVertex === false ) {
+
+						for ( var i = 0; i < xDimension - 1; i ++ ) {
+
+							for ( var j = 0; j < zDimension - 1; j ++ ) {
+
+								var index = i + j * ( xDimension - 1 );
+
+								var xn = normal[ index * 3 + 0 ];
+								var yn = normal[ index * 3 + 1 ];
+								var zn = normal[ index * 3 + 2 ];
+
+								// one normal per quad
+
+								normals.push( xn, yn, zn ); normals.push( xn, yn, zn ); normals.push( xn, yn, zn );
+								normals.push( xn, yn, zn ); normals.push( xn, yn, zn ); normals.push( xn, yn, zn );
+
+							}
+
+						}
+
+						normalAttribute = new Float32BufferAttribute( normals, 3 );
+
+					} else {
+
+						normalAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( normals, 3 ) );
+
+					}
+
+				} else {
+
+					normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle );
+
+				}
+
+				// build geometry
+
+				var geometry = new BufferGeometry();
+				geometry.setAttribute( 'position', positionAttribute );
+				geometry.setAttribute( 'normal', normalAttribute );
+				geometry.setAttribute( 'uv', uvAttribute );
+
+				if ( colorAttribute ) geometry.setAttribute( 'color', colorAttribute );
+
+				// "solid" influences the material so let's store it for later use
+
+				geometry._solid = solid;
+				geometry._type = 'mesh';
+
+				return geometry;
+
+			}
+
+			function buildExtrusionNode( node ) {
+
+				var crossSection = [ 1, 1, 1, - 1, - 1, - 1, - 1, 1, 1, 1 ];
+				var spine = [ 0, 0, 0, 0, 1, 0 ];
+				var scale;
+				var orientation;
+
+				var beginCap = true;
+				var ccw = true;
+				var creaseAngle = 0;
+				var endCap = true;
+				var solid = true;
+
+				var fields = node.fields;
+
+				for ( var i = 0, l = fields.length; i < l; i ++ ) {
+
+					var field = fields[ i ];
+					var fieldName = field.name;
+					var fieldValues = field.values;
+
+					switch ( fieldName ) {
+
+						case 'beginCap':
+							beginCap = fieldValues[ 0 ];
+							break;
+
+						case 'ccw':
+							ccw = fieldValues[ 0 ];
+							break;
+
+						case 'convex':
+							// field not supported
+							break;
+
+						case 'creaseAngle':
+							creaseAngle = fieldValues[ 0 ];
+							break;
+
+						case 'crossSection':
+							crossSection = fieldValues;
+							break;
+
+						case 'endCap':
+							endCap = fieldValues[ 0 ];
+							break;
+
+						case 'orientation':
+							orientation = fieldValues;
+							break;
+
+						case 'scale':
+							scale = fieldValues;
+							break;
+
+						case 'solid':
+							solid = fieldValues[ 0 ];
+							break;
+
+						case 'spine':
+							spine = fieldValues; // only extrusion along the Y-axis are supported so far
+							break;
+
+						default:
+							console.warn( 'THREE.VRMLLoader: Unknown field:', fieldName );
+							break;
+
+					}
+
+				}
+
+				var crossSectionClosed = ( crossSection[ 0 ] === crossSection[ crossSection.length - 2 ] && crossSection[ 1 ] === crossSection[ crossSection.length - 1 ] );
+
+				// vertices
+
+				var vertices = [];
+				var spineVector = new Vector3();
+				var scaling = new Vector3();
+
+				var axis = new Vector3();
+				var vertex = new Vector3();
+				var quaternion = new Quaternion();
+
+				for ( var i = 0, j = 0, o = 0, il = spine.length; i < il; i += 3, j += 2, o += 4 ) {
+
+					spineVector.fromArray( spine, i );
+
+					scaling.x = scale ? scale[ j + 0 ] : 1;
+					scaling.y = 1;
+					scaling.z = scale ? scale[ j + 1 ] : 1;
+
+					axis.x = orientation ? orientation[ o + 0 ] : 0;
+					axis.y = orientation ? orientation[ o + 1 ] : 0;
+					axis.z = orientation ? orientation[ o + 2 ] : 1;
+					var angle = orientation ? orientation[ o + 3 ] : 0;
+
+					for ( var k = 0, kl = crossSection.length; k < kl; k += 2 ) {
+
+						vertex.x = crossSection[ k + 0 ];
+						vertex.y = 0;
+						vertex.z = crossSection[ k + 1 ];
+
+						// scale
+
+						vertex.multiply( scaling );
+
+						// rotate
+
+						quaternion.setFromAxisAngle( axis, angle );
+						vertex.applyQuaternion( quaternion );
+
+						// translate
+
+						vertex.add( spineVector );
+
+						vertices.push( vertex.x, vertex.y, vertex.z );
+
+					}
+
+				}
+
+				// indices
+
+				var indices = [];
+
+				var spineCount = spine.length / 3;
+				var crossSectionCount = crossSection.length / 2;
+
+				for ( var i = 0; i < spineCount - 1; i ++ ) {
+
+					for ( var j = 0; j < crossSectionCount - 1; j ++ ) {
+
+						var a = j + i * crossSectionCount;
+						var b = ( j + 1 ) + i * crossSectionCount;
+						var c = j + ( i + 1 ) * crossSectionCount;
+						var d = ( j + 1 ) + ( i + 1 ) * crossSectionCount;
+
+						if ( ( j === crossSectionCount - 2 ) && ( crossSectionClosed === true ) ) {
+
+							b = i * crossSectionCount;
+							d = ( i + 1 ) * crossSectionCount;
+
+						}
+
+						if ( ccw === true ) {
+
+							indices.push( a, b, c );
+							indices.push( c, b, d );
+
+						} else {
+
+							indices.push( a, c, b );
+							indices.push( c, d, b );
+
+						}
+
+					}
+
+				}
+
+				// triangulate cap
+
+				if ( beginCap === true || endCap === true ) {
+
+					var contour = [];
+
+					for ( var i = 0, l = crossSection.length; i < l; i += 2 ) {
+
+						contour.push( new Vector2( crossSection[ i ], crossSection[ i + 1 ] ) );
+
+					}
+
+					var faces = ShapeUtils.triangulateShape( contour, [] );
+					var capIndices = [];
+
+					for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+						var face = faces[ i ];
+
+						capIndices.push( face[ 0 ], face[ 1 ], face[ 2 ] );
+
+					}
+
+					// begin cap
+
+					if ( beginCap === true ) {
+
+						for ( var i = 0, l = capIndices.length; i < l; i += 3 ) {
+
+							if ( ccw === true ) {
+
+								indices.push( capIndices[ i + 0 ], capIndices[ i + 1 ], capIndices[ i + 2 ] );
+
+							} else {
+
+								indices.push( capIndices[ i + 0 ], capIndices[ i + 2 ], capIndices[ i + 1 ] );
+
+							}
+
+						}
+
+					}
+
+					// end cap
+
+					if ( endCap === true ) {
+
+						var indexOffset = crossSectionCount * ( spineCount - 1 ); // references to the first vertex of the last cross section
+
+						for ( var i = 0, l = capIndices.length; i < l; i += 3 ) {
+
+							if ( ccw === true ) {
+
+								indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 2 ], indexOffset + capIndices[ i + 1 ] );
+
+							} else {
+
+								indices.push( indexOffset + capIndices[ i + 0 ], indexOffset + capIndices[ i + 1 ], indexOffset + capIndices[ i + 2 ] );
+
+							}
+
+						}
+
+					}
+
+				}
+
+				var positionAttribute = toNonIndexedAttribute( indices, new Float32BufferAttribute( vertices, 3 ) );
+				var normalAttribute = computeNormalAttribute( indices, vertices, creaseAngle );
+
+				var geometry = new BufferGeometry();
+				geometry.setAttribute( 'position', positionAttribute );
+				geometry.setAttribute( 'normal', normalAttribute );
+				// no uvs yet
+
+				// "solid" influences the material so let's store it for later use
+
+				geometry._solid = solid;
+				geometry._type = 'mesh';
+
+				return geometry;
+
+			}
+
 			// helper functions
 			// helper functions
 
 
 			function resolveUSE( identifier ) {
 			function resolveUSE( identifier ) {
@@ -2569,18 +3175,18 @@ var VRMLLoader = ( function () {
 			var textureLoader = new TextureLoader( this.manager );
 			var textureLoader = new TextureLoader( this.manager );
 			textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
 			textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
 
 
-			// create JSON representing the tree structure of the VRML asset
-
-			var tree = generateVRMLTree( data );
-
 			// check version (only 2.0 is supported)
 			// check version (only 2.0 is supported)
 
 
-			if ( tree.version.indexOf( 'V2.0' ) === - 1 ) {
+			if ( data.indexOf( '#VRML V2.0' ) === - 1 ) {
 
 
 				throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' );
 				throw Error( 'THREE.VRMLLexer: Version of VRML asset not supported.' );
 
 
 			}
 			}
 
 
+			// create JSON representing the tree structure of the VRML asset
+
+			var tree = generateVRMLTree( data );
+
 			// parse the tree structure to a three.js scene
 			// parse the tree structure to a three.js scene
 
 
 			var scene = parseTree( tree );
 			var scene = parseTree( tree );
@@ -2707,14 +3313,36 @@ var VRMLLoader = ( function () {
 		$.RULE( 'def', function () {
 		$.RULE( 'def', function () {
 
 
 			$.CONSUME( DEF );
 			$.CONSUME( DEF );
-			$.CONSUME( Identifier );
+			$.OR( [
+				{ ALT: function () {
+
+					$.CONSUME( Identifier );
+
+				} },
+				{ ALT: function () {
+
+					$.CONSUME( NodeName );
+
+				} }
+			] );
 
 
 		} );
 		} );
 
 
 		$.RULE( 'use', function () {
 		$.RULE( 'use', function () {
 
 
 			$.CONSUME( USE );
 			$.CONSUME( USE );
-			$.CONSUME( Identifier );
+			$.OR( [
+				{ ALT: function () {
+
+					$.CONSUME( Identifier );
+
+				} },
+				{ ALT: function () {
+
+					$.CONSUME( NodeName );
+
+				} }
+			] );
 
 
 		} );
 		} );
 
 

+ 1221 - 0
examples/jsm/loaders/lwo/IFFParser.js

@@ -0,0 +1,1221 @@
+/**
+ * @author Lewy Blue / https://github.com/looeee
+ * @author Guilherme Avila / https://github/sciecode
+ *
+ * === IFFParser ===
+ * - Parses data from the IFF buffer.
+ * - LWO3 files are in IFF format and can contain the following data types, referred to by shorthand codes
+ *
+ * ATOMIC DATA TYPES
+ *  ID Tag - 4x 7 bit uppercase ASCII chars: ID4
+ *  signed integer, 1, 2, or 4 byte length: I1, I2, I4
+ *  unsigned integer, 1, 2, or 4 byte length: U1, U2, U4
+ *  float, 4 byte length: F4
+ *  string, series of ASCII chars followed by null byte (If the length of the string including the null terminating byte is odd, an extra null is added so that the data that follows will begin on an even byte boundary): S0
+ *
+ * COMPOUND DATA TYPES
+ *  Variable-length Index (index into an array or collection): U2 or U4 : VX
+ *  Color (RGB): F4 + F4 + F4: COL12
+ *  Coordinate (x, y, z): F4 + F4 + F4: VEC12
+ *  Percentage F4 data type from 0->1 with 1 = 100%: FP4
+ *  Angle in radian F4: ANG4
+ *  Filename (string) S0: FNAM0
+ *  XValue F4 + index (VX) + optional envelope( ENVL ): XVAL
+ *  XValue vector VEC12 + index (VX) + optional envelope( ENVL ): XVAL3
+ *
+ *  The IFF file is arranged in chunks:
+ *  CHUNK = ID4 + length (U4) + length X bytes of data + optional 0 pad byte
+ *  optional 0 pad byte is there to ensure chunk ends on even boundary, not counted in size
+ *
+ * COMPOUND DATA TYPES
+ * - Chunks are combined in Forms (collections of chunks)
+ * - FORM = string 'FORM' (ID4) + length (U4) + type (ID4) + optional ( CHUNK | FORM )
+ * - CHUNKS and FORMS are collectively referred to as blocks
+ * - The entire file is contained in one top level FORM
+ *
+ **/
+
+import { LoaderUtils } from "../../../../build/three.module.js";
+import { LWO2Parser } from "./LWO2Parser.js";
+import { LWO3Parser } from "./LWO3Parser.js";
+
+function IFFParser( ) {
+
+	this.debugger = new Debugger();
+	// this.debugger.enable(); // un-comment to log IFF hierarchy.
+
+}
+
+IFFParser.prototype = {
+
+	constructor: IFFParser,
+
+	parse: function ( buffer ) {
+
+		this.reader = new DataViewReader( buffer );
+
+		this.tree = {
+			materials: {},
+			layers: [],
+			tags: [],
+			textures: [],
+		};
+
+		// start out at the top level to add any data before first layer is encountered
+		this.currentLayer = this.tree;
+		this.currentForm = this.tree;
+
+		this.parseTopForm();
+
+		if ( this.tree.format === undefined ) return;
+
+		if ( this.tree.format === 'LWO2' ) {
+
+			this.parser = new LWO2Parser( this );
+			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
+
+		} else if ( this.tree.format === 'LWO3' ) {
+
+			this.parser = new LWO3Parser( this );
+			while ( ! this.reader.endOfFile() ) this.parser.parseBlock();
+
+		}
+
+		this.debugger.offset = this.reader.offset;
+		this.debugger.closeForms();
+
+		return this.tree;
+
+	},
+
+	parseTopForm() {
+
+		this.debugger.offset = this.reader.offset;
+
+		var topForm = this.reader.getIDTag();
+
+		if ( topForm !== 'FORM' ) {
+
+			console.warn( "LWOLoader: Top-level FORM missing." );
+			return;
+
+		}
+
+		var length = this.reader.getUint32();
+
+		this.debugger.dataOffset = this.reader.offset;
+		this.debugger.length = length;
+
+		var type = this.reader.getIDTag();
+
+		if ( type === 'LWO2' ) {
+
+			this.tree.format = type;
+
+		} else if ( type === 'LWO3' ) {
+
+			this.tree.format = type;
+
+		}
+
+		this.debugger.node = 0;
+		this.debugger.nodeID = type;
+		this.debugger.log();
+
+		return;
+
+	},
+
+
+	///
+	// FORM PARSING METHODS
+	///
+
+	// Forms are organisational and can contain any number of sub chunks and sub forms
+	// FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * }
+	parseForm( length ) {
+
+		var type = this.reader.getIDTag();
+
+		switch ( type ) {
+
+			// SKIPPED FORMS
+			// if skipForm( length ) is called, the entire form and any sub forms and chunks are skipped
+
+			case 'ISEQ': // Image sequence
+			case 'ANIM': // plug in animation
+			case 'STCC': // Color-cycling Still
+			case 'VPVL':
+			case 'VPRM':
+			case 'NROT':
+			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
+			case 'WRPH': // image wrap h
+			case 'FUNC':
+			case 'FALL':
+			case 'OPAC':
+			case 'GRAD': // gradient texture
+			case 'ENVS':
+			case 'VMOP':
+			case 'VMBG':
+
+			// Car Material FORMS
+			case 'OMAX':
+			case 'STEX':
+			case 'CKBG':
+			case 'CKEY':
+			case 'VMLA':
+			case 'VMLB':
+				this.debugger.skipped = true;
+				this.skipForm( length ); // not currently supported
+				break;
+
+			// if break; is called directly, the position in the lwoTree is not created
+			// any sub chunks and forms are added to the parent form instead
+			case 'META':
+			case 'NNDS':
+			case 'NODS':
+			case 'NDTA':
+			case 'ADAT':
+			case 'AOVS':
+			case 'BLOK':
+
+			// used by texture nodes
+			case 'IBGC': // imageBackgroundColor
+			case 'IOPC': // imageOpacity
+			case 'IIMG': // hold reference to image path
+			case 'TXTR':
+				// this.setupForm( type, length );
+				this.debugger.length = 4;
+				this.debugger.skipped = true;
+				break;
+
+			case 'IFAL': // imageFallof
+			case 'ISCL': // imageScale
+			case 'IPOS': // imagePosition
+			case 'IROT': // imageRotation
+			case 'IBMP':
+			case 'IUTD':
+			case 'IVTD':
+				this.parseTextureNodeAttribute( type );
+				break;
+
+			case 'ENVL':
+				this.parseEnvelope( length );
+				break;
+
+				// CLIP FORM AND SUB FORMS
+
+			case 'CLIP':
+				if ( this.tree.format === 'LWO2' ) {
+
+					this.parseForm( length );
+
+				} else {
+
+					this.parseClip( length );
+
+				}
+
+				break;
+
+			case 'STIL':
+				this.parseImage();
+				break;
+
+			case 'XREF': // clone of another STIL
+				this.reader.skip( 8 ); // unknown
+				this.currentForm.referenceTexture = {
+					index: this.reader.getUint32(),
+					refName: this.reader.getString() // internal unique ref
+				};
+				break;
+
+				// Not in spec, used by texture nodes
+
+			case 'IMST':
+				this.parseImageStateForm( length );
+				break;
+
+				// SURF FORM AND SUB FORMS
+
+			case 'SURF':
+				this.parseSurfaceForm( length );
+				break;
+
+			case 'VALU': // Not in spec
+				this.parseValueForm( length );
+				break;
+
+			case 'NTAG':
+				this.parseSubNode( length );
+				break;
+
+			case 'ATTR': // BSDF Node Attributes
+			case 'SATR': // Standard Node Attributes
+				this.setupForm( 'attributes', length );
+				break;
+
+			case 'NCON':
+				this.parseConnections( length );
+				break;
+
+			case 'SSHA':
+				this.parentForm = this.currentForm;
+				this.currentForm = this.currentSurface;
+				this.setupForm( 'surfaceShader', length );
+				break;
+
+			case 'SSHD':
+				this.setupForm( 'surfaceShaderData', length );
+				break;
+
+			case 'ENTR': // Not in spec
+				this.parseEntryForm( length );
+				break;
+
+				// Image Map Layer
+
+			case 'IMAP':
+				this.parseImageMap( length );
+				break;
+
+			case 'TAMP':
+				this.parseXVAL( 'amplitude', length );
+				break;
+
+				//Texture Mapping Form
+
+			case 'TMAP':
+				this.setupForm( 'textureMap', length );
+				break;
+
+			case 'CNTR':
+				this.parseXVAL3( 'center', length );
+				break;
+
+			case 'SIZE':
+				this.parseXVAL3( 'scale', length );
+				break;
+
+			case 'ROTA':
+				this.parseXVAL3( 'rotation', length );
+				break;
+
+			default:
+				this.parseUnknownForm( type, length );
+
+		}
+
+		this.debugger.node = 0;
+		this.debugger.nodeID = type;
+		this.debugger.log();
+
+	},
+
+	setupForm( type, length ) {
+
+		if ( ! this.currentForm ) this.currentForm = this.currentNode;
+
+		this.currentFormEnd = this.reader.offset + length;
+		this.parentForm = this.currentForm;
+
+		if ( ! this.currentForm[ type ] ) {
+
+			this.currentForm[ type ] = {};
+			this.currentForm = this.currentForm[ type ];
+
+
+		} else {
+
+			// should never see this unless there's a bug in the reader
+			console.warn( 'LWOLoader: form already exists on parent: ', type, this.currentForm );
+
+			this.currentForm = this.currentForm[ type ];
+
+		}
+
+
+	},
+
+	skipForm( length ) {
+
+		this.reader.skip( length - 4 );
+
+	},
+
+	parseUnknownForm( type, length ) {
+
+		console.warn( 'LWOLoader: unknown FORM encountered: ' + type, length );
+
+		printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 );
+		this.reader.skip( length - 4 );
+
+	},
+
+	parseSurfaceForm( length ) {
+
+		this.reader.skip( 8 ); // unknown Uint32 x2
+
+		var name = this.reader.getString();
+
+		var surface = {
+			attributes: {}, // LWO2 style non-node attributes will go here
+			connections: {},
+			name: name,
+			inputName: name,
+			nodes: {},
+			source: this.reader.getString(),
+		};
+
+		this.tree.materials[ name ] = surface;
+		this.currentSurface = surface;
+
+		this.parentForm = this.tree.materials;
+		this.currentForm = surface;
+		this.currentFormEnd = this.reader.offset + length;
+
+	},
+
+	parseSurfaceLwo2( length ) {
+
+		var name = this.reader.getString();
+
+		var surface = {
+			attributes: {}, // LWO2 style non-node attributes will go here
+			connections: {},
+			name: name,
+			nodes: {},
+			source: this.reader.getString(),
+		};
+
+		this.tree.materials[ name ] = surface;
+		this.currentSurface = surface;
+
+		this.parentForm = this.tree.materials;
+		this.currentForm = surface;
+		this.currentFormEnd = this.reader.offset + length;
+
+	},
+
+	parseSubNode( length ) {
+
+		// parse the NRNM CHUNK of the subnode FORM to get
+		// a meaningful name for the subNode
+		// some subnodes can be renamed, but Input and Surface cannot
+
+		this.reader.skip( 8 ); // NRNM + length
+		var name = this.reader.getString();
+
+		var node = {
+			name: name
+		};
+		this.currentForm = node;
+		this.currentNode = node;
+
+		this.currentFormEnd = this.reader.offset + length;
+
+
+	},
+
+	// collect attributes from all nodes at the top level of a surface
+	parseConnections( length ) {
+
+		this.currentFormEnd = this.reader.offset + length;
+		this.parentForm = this.currentForm;
+
+		this.currentForm = this.currentSurface.connections;
+
+	},
+
+	// surface node attribute data, e.g. specular, roughness etc
+	parseEntryForm( length ) {
+
+		this.reader.skip( 8 ); // NAME + length
+		var name = this.reader.getString();
+		this.currentForm = this.currentNode.attributes;
+
+		this.setupForm( name, length );
+
+	},
+
+	// parse values from material - doesn't match up to other LWO3 data types
+	// sub form of entry form
+	parseValueForm() {
+
+		this.reader.skip( 8 ); // unknown + length
+
+		var valueType = this.reader.getString();
+
+		if ( valueType === 'double' ) {
+
+			this.currentForm.value = this.reader.getUint64();
+
+		} else if ( valueType === 'int' ) {
+
+			this.currentForm.value = this.reader.getUint32();
+
+		} else if ( valueType === 'vparam' ) {
+
+			this.reader.skip( 24 );
+			this.currentForm.value = this.reader.getFloat64();
+
+		} else if ( valueType === 'vparam3' ) {
+
+			this.reader.skip( 24 );
+			this.currentForm.value = this.reader.getFloat64Array( 3 );
+
+		}
+
+	},
+
+	// holds various data about texture node image state
+	// Data other thanmipMapLevel unknown
+	parseImageStateForm() {
+
+		this.reader.skip( 8 ); // unknown
+
+		this.currentForm.mipMapLevel = this.reader.getFloat32();
+
+	},
+
+	// LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node)
+	parseImageMap( length ) {
+
+		this.currentFormEnd = this.reader.offset + length;
+		this.parentForm = this.currentForm;
+
+		if ( ! this.currentForm.maps ) this.currentForm.maps = [];
+
+		var map = {};
+		this.currentForm.maps.push( map );
+		this.currentForm = map;
+
+		this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX
+
+	},
+
+	parseTextureNodeAttribute( type ) {
+
+		this.reader.skip( 28 ); // FORM + length + VPRM + unknown + Uint32 x2 + float32
+
+		this.reader.skip( 20 ); // FORM + length + VPVL + float32 + Uint32
+
+		switch ( type ) {
+
+			case 'ISCL':
+				this.currentNode.scale = this.reader.getFloat32Array( 3 );
+				break;
+			case 'IPOS':
+				this.currentNode.position = this.reader.getFloat32Array( 3 );
+				break;
+			case 'IROT':
+				this.currentNode.rotation = this.reader.getFloat32Array( 3 );
+				break;
+			case 'IFAL':
+				this.currentNode.falloff = this.reader.getFloat32Array( 3 );
+				break;
+
+			case 'IBMP':
+				this.currentNode.amplitude = this.reader.getFloat32();
+				break;
+			case 'IUTD':
+				this.currentNode.uTiles = this.reader.getFloat32();
+				break;
+			case 'IVTD':
+				this.currentNode.vTiles = this.reader.getFloat32();
+				break;
+
+		}
+
+		this.reader.skip( 2 ); // unknown
+
+
+	},
+
+	// ENVL forms are currently ignored
+	parseEnvelope( length ) {
+
+		this.reader.skip( length - 4 ); // skipping  entirely for now
+
+	},
+
+	///
+	// CHUNK PARSING METHODS
+	///
+
+	// clips can either be defined inside a surface node, or at the top
+	// level and they have a different format in each case
+	parseClip( length ) {
+
+		var tag = this.reader.getIDTag();
+
+		// inside surface node
+		if ( tag === 'FORM' ) {
+
+			this.reader.skip( 16 );
+
+			this.currentNode.fileName = this.reader.getString();
+
+			return;
+
+		}
+
+		// otherwise top level
+		this.reader.setOffset( this.reader.offset - 4 );
+
+		this.currentFormEnd = this.reader.offset + length;
+		this.parentForm = this.currentForm;
+
+		this.reader.skip( 8 ); // unknown
+
+		var texture = {
+			index: this.reader.getUint32()
+		};
+		this.tree.textures.push( texture );
+		this.currentForm = texture;
+
+	},
+
+	parseClipLwo2( length ) {
+
+		var texture = {
+			index: this.reader.getUint32(),
+			fileName: ""
+		};
+
+		// seach STIL block
+		while ( true ) {
+
+			var tag = this.reader.getIDTag();
+			var n_length = this.reader.getUint16();
+			if ( tag === 'STIL' ) {
+
+				texture.fileName = this.reader.getString();
+				break;
+
+			}
+
+			if ( n_length >= length ) {
+
+				break;
+
+			}
+
+		}
+
+		this.tree.textures.push( texture );
+		this.currentForm = texture;
+
+	},
+
+	parseImage() {
+
+		this.reader.skip( 8 ); // unknown
+		this.currentForm.fileName = this.reader.getString();
+
+	},
+
+	parseXVAL( type, length ) {
+
+		var endOffset = this.reader.offset + length - 4;
+		this.reader.skip( 8 );
+
+		this.currentForm[ type ] = this.reader.getFloat32();
+
+		this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope
+
+	},
+
+	parseXVAL3( type, length ) {
+
+		var endOffset = this.reader.offset + length - 4;
+		this.reader.skip( 8 );
+
+		this.currentForm[ type ] = {
+			x: this.reader.getFloat32(),
+			y: this.reader.getFloat32(),
+			z: this.reader.getFloat32(),
+		};
+
+		this.reader.setOffset( endOffset );
+
+	},
+
+	// Tags associated with an object
+	// OTAG { type[ID4], tag-string[S0] }
+	parseObjectTag() {
+
+		if ( ! this.tree.objectTags ) this.tree.objectTags = {};
+
+		this.tree.objectTags[ this.reader.getIDTag() ] = {
+			tagString: this.reader.getString()
+		};
+
+	},
+
+	// Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered.
+	// LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2]
+	parseLayer( length ) {
+
+		var layer = {
+			number: this.reader.getUint16(),
+			flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden.
+			pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present
+			name: this.reader.getString(),
+		};
+
+		this.tree.layers.push( layer );
+		this.currentLayer = layer;
+
+		var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength
+
+		// if we have not reached then end of the layer block, there must be a parent defined
+		this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent
+
+	},
+
+	// VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors
+	// Converting from left to right handed coordinate system:
+	// x -> -x and switch material FrontSide -> BackSide
+	parsePoints( length ) {
+
+		this.currentPoints = [];
+		for ( var i = 0; i < length / 4; i += 3 ) {
+
+			// z -> -z to match three.js right handed coords
+			this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
+
+		}
+
+	},
+
+	// parse VMAP or VMAD
+	// Associates a set of floating-point vectors with a set of points.
+	// VMAP: { type[ID4], dimension[U2], name[S0], ( vert[VX], value[F4] # dimension ) * }
+
+	// VMAD Associates a set of floating-point vectors with the vertices of specific polygons.
+	// Similar to VMAP UVs, but associates with polygon vertices rather than points
+	// to solve to problem of UV seams:  VMAD chunks are paired with VMAPs of the same name,
+	// if they exist. The vector values in the VMAD will then replace those in the
+	// corresponding VMAP, but only for calculations involving the specified polygons.
+	// VMAD { type[ID4], dimension[U2], name[S0], ( vert[VX], poly[VX], value[F4] # dimension ) * }
+	parseVertexMapping( length, discontinuous ) {
+
+		var finalOffset = this.reader.offset + length;
+
+		var channelName = this.reader.getString();
+
+		if ( this.reader.offset === finalOffset ) {
+
+			// then we are in a texture node and the VMAP chunk is just a reference to a UV channel name
+			this.currentForm.UVChannel = channelName;
+			return;
+
+		}
+
+		// otherwise reset to initial length and parse normal VMAP CHUNK
+		this.reader.setOffset( this.reader.offset - stringOffset( channelName ) );
+
+		var type = this.reader.getIDTag();
+
+		this.reader.getUint16(); // dimension
+		var name = this.reader.getString();
+
+		var remainingLength = length - 6 - stringOffset( name );
+
+		switch ( type ) {
+
+			case 'TXUV':
+				this.parseUVMapping( name, finalOffset, discontinuous );
+				break;
+			case 'MORF':
+			case 'SPOT':
+				this.parseMorphTargets( name, finalOffset, type ); // can't be discontinuous
+				break;
+			// unsupported VMAPs
+			case 'APSL':
+			case 'NORM':
+			case 'WGHT':
+			case 'MNVW':
+			case 'PICK':
+			case 'RGB ':
+			case 'RGBA':
+				this.reader.skip( remainingLength );
+				break;
+			default:
+				console.warn( 'LWOLoader: unknown vertex map type: ' + type );
+				this.reader.skip( remainingLength );
+
+		}
+
+	},
+
+	parseUVMapping( name, finalOffset, discontinuous ) {
+
+		var uvIndices = [];
+		var polyIndices = [];
+		var uvs = [];
+
+		while ( this.reader.offset < finalOffset ) {
+
+			uvIndices.push( this.reader.getVariableLengthIndex() );
+
+			if ( discontinuous ) polyIndices.push( this.reader.getVariableLengthIndex() );
+
+			uvs.push( this.reader.getFloat32(), this.reader.getFloat32() );
+
+		}
+
+		if ( discontinuous ) {
+
+			if ( ! this.currentLayer.discontinuousUVs ) this.currentLayer.discontinuousUVs = {};
+
+			this.currentLayer.discontinuousUVs[ name ] = {
+				uvIndices: uvIndices,
+				polyIndices: polyIndices,
+				uvs: uvs,
+			};
+
+		} else {
+
+			if ( ! this.currentLayer.uvs ) this.currentLayer.uvs = {};
+
+			this.currentLayer.uvs[ name ] = {
+				uvIndices: uvIndices,
+				uvs: uvs,
+			};
+
+		}
+
+	},
+
+	parseMorphTargets( name, finalOffset, type ) {
+
+		var indices = [];
+		var points = [];
+
+		type = ( type === 'MORF' ) ? 'relative' : 'absolute';
+
+		while ( this.reader.offset < finalOffset ) {
+
+			indices.push( this.reader.getVariableLengthIndex() );
+			// z -> -z to match three.js right handed coords
+			points.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() );
+
+		}
+
+		if ( ! this.currentLayer.morphTargets ) this.currentLayer.morphTargets = {};
+
+		this.currentLayer.morphTargets[ name ] = {
+			indices: indices,
+			points: points,
+			type: type,
+		};
+
+	},
+
+	// A list of polygons for the current layer.
+	// POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * }
+	parsePolygonList( length ) {
+
+		var finalOffset = this.reader.offset + length;
+		var type = this.reader.getIDTag();
+
+		var indices = [];
+
+		// hold a list of polygon sizes, to be split up later
+		var polygonDimensions = [];
+
+		while ( this.reader.offset < finalOffset ) {
+
+			var numverts = this.reader.getUint16();
+
+			//var flags = numverts & 64512; // 6 high order bits are flags - ignoring for now
+			numverts = numverts & 1023; // remaining ten low order bits are vertex num
+			polygonDimensions.push( numverts );
+
+			for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() );
+
+		}
+
+		var geometryData = {
+			type: type,
+			vertexIndices: indices,
+			polygonDimensions: polygonDimensions,
+			points: this.currentPoints
+		};
+
+		// Note: assuming that all polys will be lines or points if the first is
+		if ( polygonDimensions[ 0 ] === 1 ) geometryData.type = 'points';
+		else if ( polygonDimensions[ 0 ] === 2 ) geometryData.type = 'lines';
+
+		this.currentLayer.geometry = geometryData;
+
+	},
+
+	// Lists the tag strings that can be associated with polygons by the PTAG chunk.
+	// TAGS { tag-string[S0] * }
+	parseTagStrings( length ) {
+
+		this.tree.tags = this.reader.getStringArray( length );
+
+	},
+
+	// Associates tags of a given type with polygons in the most recent POLS chunk.
+	// PTAG { type[ID4], ( poly[VX], tag[U2] ) * }
+	parsePolygonTagMapping( length ) {
+
+		var finalOffset = this.reader.offset + length;
+		var type = this.reader.getIDTag();
+		if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset );
+		else { //PART, SMGP, COLR not supported
+
+			this.reader.skip( length - 4 );
+
+		}
+
+	},
+
+	parseMaterialIndices( finalOffset ) {
+
+		// array holds polygon index followed by material index
+		this.currentLayer.geometry.materialIndices = [];
+
+		while ( this.reader.offset < finalOffset ) {
+
+			var polygonIndex = this.reader.getVariableLengthIndex();
+			var materialIndex = this.reader.getUint16();
+
+			this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex );
+
+		}
+
+	},
+
+	parseUnknownCHUNK( blockID, length ) {
+
+		console.warn( 'LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length );
+
+		// print the chunk plus some bytes padding either side
+		// printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 );
+
+		var data = this.reader.getString( length );
+
+		this.currentForm[ blockID ] = data;
+
+	}
+
+};
+
+function DataViewReader( buffer ) {
+
+	this.dv = new DataView( buffer );
+	this.offset = 0;
+
+}
+
+DataViewReader.prototype = {
+
+	constructor: DataViewReader,
+
+	size: function () {
+
+		return this.dv.buffer.byteLength;
+
+	},
+
+	setOffset( offset ) {
+
+		if ( offset > 0 && offset < this.dv.buffer.byteLength ) {
+
+			this.offset = offset;
+
+		} else {
+
+			console.error( 'LWOLoader: invalid buffer offset' );
+
+		}
+
+	},
+
+	endOfFile: function () {
+
+		if ( this.offset >= this.size() ) return true;
+		return false;
+
+	},
+
+	skip: function ( length ) {
+
+		this.offset += length;
+
+	},
+
+	getUint8: function () {
+
+		var value = this.dv.getUint8( this.offset );
+		this.offset += 1;
+		return value;
+
+	},
+
+	getUint16: function () {
+
+		var value = this.dv.getUint16( this.offset );
+		this.offset += 2;
+		return value;
+
+	},
+
+	getInt32: function () {
+
+		var value = this.dv.getInt32( this.offset, false );
+		this.offset += 4;
+		return value;
+
+	},
+
+	getUint32: function () {
+
+		var value = this.dv.getUint32( this.offset, false );
+		this.offset += 4;
+		return value;
+
+	},
+
+	getUint64: function () {
+
+		var low, high;
+
+		high = this.getUint32();
+		low = this.getUint32();
+		return high * 0x100000000 + low;
+
+	},
+
+	getFloat32: function () {
+
+		var value = this.dv.getFloat32( this.offset, false );
+		this.offset += 4;
+		return value;
+
+	},
+
+	getFloat32Array: function ( size ) {
+
+		var a = [];
+
+		for ( var i = 0; i < size; i ++ ) {
+
+			a.push( this.getFloat32() );
+
+		}
+
+		return a;
+
+	},
+
+	getFloat64: function () {
+
+		var value = this.dv.getFloat64( this.offset, this.littleEndian );
+		this.offset += 8;
+		return value;
+
+	},
+
+	getFloat64Array: function ( size ) {
+
+		var a = [];
+
+		for ( var i = 0; i < size; i ++ ) {
+
+			a.push( this.getFloat64() );
+
+		}
+
+		return a;
+
+	},
+
+	// get variable-length index data type
+	// VX ::= index[U2] | (index + 0xFF000000)[U4]
+	// If the index value is less than 65,280 (0xFF00),then VX === U2
+	// otherwise VX === U4 with bits 24-31 set
+	// When reading an index, if the first byte encountered is 255 (0xFF), then
+	// the four-byte form is being used and the first byte should be discarded or masked out.
+	getVariableLengthIndex() {
+
+		var firstByte = this.getUint8();
+
+		if ( firstByte === 255 ) {
+
+			return this.getUint8() * 65536 + this.getUint8() * 256 + this.getUint8();
+
+		}
+
+		return firstByte * 256 + this.getUint8();
+
+	},
+
+	// An ID tag is a sequence of 4 bytes containing 7-bit ASCII values
+	getIDTag() {
+
+		return this.getString( 4 );
+
+	},
+
+	getString: function ( size ) {
+
+		if ( size === 0 ) return;
+
+		// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
+		var a = [];
+
+		if ( size ) {
+
+			for ( var i = 0; i < size; i ++ ) {
+
+				a[ i ] = this.getUint8();
+
+			}
+
+		} else {
+
+			var currentChar;
+			var len = 0;
+
+			while ( currentChar !== 0 ) {
+
+				currentChar = this.getUint8();
+				if ( currentChar !== 0 ) a.push( currentChar );
+				len ++;
+
+			}
+
+			if ( ! isEven( len + 1 ) ) this.getUint8(); // if string with terminating nullbyte is uneven, extra nullbyte is added
+
+		}
+
+		return LoaderUtils.decodeText( new Uint8Array( a ) );
+
+	},
+
+	getStringArray: function ( size ) {
+
+		var a = this.getString( size );
+		a = a.split( '\0' );
+
+		return a.filter( Boolean ); // return array with any empty strings removed
+
+	}
+
+};
+
+// ************** DEBUGGER  **************
+
+function Debugger( ) {
+
+	this.active = false;
+	this.depth = 0;
+	this.formList = [];
+
+}
+
+Debugger.prototype = {
+
+	constructor: Debugger,
+
+	enable: function () {
+
+		this.active = true;
+
+	},
+
+	log: function () {
+
+		if ( ! this.active ) return;
+
+		var nodeType;
+
+		switch ( this.node ) {
+
+			case 0:
+				nodeType = "FORM";
+				break;
+
+			case 1:
+				nodeType = "CHK";
+				break;
+
+			case 2:
+				nodeType = "S-CHK";
+				break;
+
+		}
+
+		console.log(
+			"| ".repeat( this.depth ) +
+			nodeType,
+			this.nodeID,
+			`( ${this.offset} ) -> ( ${this.dataOffset + this.length} )`,
+			( ( this.node == 0 ) ? " {" : "" ),
+			( ( this.skipped ) ? "SKIPPED" : "" ),
+			( ( this.node == 0 && this.skipped ) ? "}" : "" )
+		);
+
+		if ( this.node == 0 && ! this.skipped ) {
+
+			this.depth += 1;
+			this.formList.push( this.dataOffset + this.length );
+
+		}
+
+		this.skipped = false;
+
+	},
+
+	closeForms: function () {
+
+		if ( ! this.active ) return;
+
+		for ( var i = this.formList.length - 1; i >= 0; i -- ) {
+
+			if ( this.offset >= this.formList[ i ] ) {
+
+				this.depth -= 1;
+				console.log( "| ".repeat( this.depth ) + "}" );
+				this.formList.splice( - 1, 1 );
+
+			}
+
+		}
+
+	}
+
+};
+
+// ************** UTILITY FUNCTIONS **************
+
+function isEven( num ) {
+
+	return num % 2;
+
+}
+
+// calculate the length of the string in the buffer
+// this will be string.length + nullbyte + optional padbyte to make the length even
+function stringOffset( string ) {
+
+	return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 );
+
+}
+
+// for testing purposes, dump buffer to console
+// printBuffer( this.reader.dv.buffer, this.reader.offset, length );
+function printBuffer( buffer, from, to ) {
+
+	console.log( LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ) );
+
+}
+
+export { IFFParser };

+ 420 - 0
examples/jsm/loaders/lwo/LWO2Parser.js

@@ -0,0 +1,420 @@
+/**
+ * @author Lewy Blue / https://github.com/looeee
+ * @author Guilherme Avila / https://github/sciecode
+ */
+
+function LWO2Parser( IFFParser ) {
+
+	this.IFF = IFFParser;
+
+}
+
+LWO2Parser.prototype = {
+
+	constructor: LWO2Parser,
+
+	parseBlock: function () {
+
+		this.IFF.debugger.offset = this.IFF.reader.offset;
+		this.IFF.debugger.closeForms();
+
+		var blockID = this.IFF.reader.getIDTag();
+		var length = this.IFF.reader.getUint32(); // size of data in bytes
+		if ( length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset ) {
+
+			this.IFF.reader.offset -= 4;
+			length = this.IFF.reader.getUint16();
+
+		}
+
+		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
+		this.IFF.debugger.length = length;
+
+		// Data types may be found in either LWO2 OR LWO3 spec
+		switch ( blockID ) {
+
+			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
+				this.IFF.parseForm( length );
+				break;
+
+			// SKIPPED CHUNKS
+			// if break; is called directly, the position in the lwoTree is not created
+			// any sub chunks and forms are added to the parent form instead
+			// MISC skipped
+			case 'ICON': // Thumbnail Icon Image
+			case 'VMPA': // Vertex Map Parameter
+			case 'BBOX': // bounding box
+			// case 'VMMD':
+			// case 'VTYP':
+
+			// normal maps can be specified, normally on models imported from other applications. Currently ignored
+			case 'NORM':
+
+			// ENVL FORM skipped
+			case 'PRE ':
+			case 'POST':
+			case 'KEY ':
+			case 'SPAN':
+
+			// CLIP FORM skipped
+			case 'TIME':
+			case 'CLRS':
+			case 'CLRA':
+			case 'FILT':
+			case 'DITH':
+			case 'CONT':
+			case 'BRIT':
+			case 'SATR':
+			case 'HUE ':
+			case 'GAMM':
+			case 'NEGA':
+			case 'IFLT':
+			case 'PFLT':
+
+			// Image Map Layer skipped
+			case 'PROJ':
+			case 'AXIS':
+			case 'AAST':
+			case 'PIXB':
+			case 'AUVO':
+			case 'STCK':
+
+			// Procedural Textures skipped
+			case 'PROC':
+			case 'VALU':
+			case 'FUNC':
+
+			// Gradient Textures skipped
+			case 'PNAM':
+			case 'INAM':
+			case 'GRST':
+			case 'GREN':
+			case 'GRPT':
+			case 'FKEY':
+			case 'IKEY':
+
+			// Texture Mapping Form skipped
+			case 'CSYS':
+
+			// Surface CHUNKs skipped
+			case 'OPAQ': // top level 'opacity' checkbox
+			case 'CMAP': // clip map
+
+			// Surface node CHUNKS skipped
+			// These mainly specify the node editor setup in LW
+			case 'NLOC':
+			case 'NZOM':
+			case 'NVER':
+			case 'NSRV':
+			case 'NVSK': // unknown
+			case 'NCRD':
+			case 'WRPW': // image wrap w ( for cylindrical and spherical projections)
+			case 'WRPH': // image wrap h
+			case 'NMOD':
+			case 'NPRW':
+			case 'NPLA':
+			case 'NODS':
+			case 'VERS':
+			case 'ENUM':
+			case 'TAG ':
+			case 'OPAC':
+
+			// Car Material CHUNKS
+			case 'CGMD':
+			case 'CGTY':
+			case 'CGST':
+			case 'CGEN':
+			case 'CGTS':
+			case 'CGTE':
+			case 'OSMP':
+			case 'OMDE':
+			case 'OUTR':
+			case 'FLAG':
+
+			case 'TRNL':
+			case 'GLOW':
+			case 'GVAL': // glow intensity
+			case 'SHRP':
+			case 'RFOP':
+			case 'RSAN':
+			case 'TROP':
+			case 'RBLR':
+			case 'TBLR':
+			case 'CLRH':
+			case 'CLRF':
+			case 'ADTR':
+			case 'LINE':
+			case 'ALPH':
+			case 'VCOL':
+			case 'ENAB':
+				this.IFF.debugger.skipped = true;
+				this.IFF.reader.skip( length );
+				break;
+
+			case 'SURF':
+				this.IFF.parseSurfaceLwo2( length );
+				break;
+
+			case 'CLIP':
+				this.IFF.parseClipLwo2( length );
+				break;
+
+			// Texture node chunks (not in spec)
+			case 'IPIX': // usePixelBlending
+			case 'IMIP': // useMipMaps
+			case 'IMOD': // imageBlendingMode
+			case 'AMOD': // unknown
+			case 'IINV': // imageInvertAlpha
+			case 'INCR': // imageInvertColor
+			case 'IAXS': // imageAxis ( for non-UV maps)
+			case 'IFOT': // imageFallofType
+			case 'ITIM': // timing for animated textures
+			case 'IWRL':
+			case 'IUTI':
+			case 'IINX':
+			case 'IINY':
+			case 'IINZ':
+			case 'IREF': // possibly a VX for reused texture nodes
+				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
+				else this.IFF.reader.skip( length );
+				break;
+
+			case 'OTAG':
+				this.IFF.parseObjectTag();
+				break;
+
+			case 'LAYR':
+				this.IFF.parseLayer( length );
+				break;
+
+			case 'PNTS':
+				this.IFF.parsePoints( length );
+				break;
+
+			case 'VMAP':
+				this.IFF.parseVertexMapping( length );
+				break;
+
+			case 'AUVU':
+			case 'AUVN':
+				this.IFF.reader.skip( length - 1 );
+				this.IFF.reader.getVariableLengthIndex(); // VX
+				break;
+
+			case 'POLS':
+				this.IFF.parsePolygonList( length );
+				break;
+
+			case 'TAGS':
+				this.IFF.parseTagStrings( length );
+				break;
+
+			case 'PTAG':
+				this.IFF.parsePolygonTagMapping( length );
+				break;
+
+			case 'VMAD':
+				this.IFF.parseVertexMapping( length, true );
+				break;
+
+			// Misc CHUNKS
+			case 'DESC': // Description Line
+				this.IFF.currentForm.description = this.IFF.reader.getString();
+				break;
+
+			case 'TEXT':
+			case 'CMNT':
+			case 'NCOM':
+				this.IFF.currentForm.comment = this.IFF.reader.getString();
+				break;
+
+			// Envelope Form
+			case 'NAME':
+				this.IFF.currentForm.channelName = this.IFF.reader.getString();
+				break;
+
+			// Image Map Layer
+			case 'WRAP':
+				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
+				break;
+
+			case 'IMAG':
+				var index = this.IFF.reader.getVariableLengthIndex();
+				this.IFF.currentForm.imageIndex = index;
+				break;
+
+			// Texture Mapping Form
+			case 'OREF':
+				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
+				break;
+
+			case 'ROID':
+				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
+				break;
+
+			// Surface Blocks
+			case 'SSHN':
+				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
+				break;
+
+			case 'AOVN':
+				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
+				break;
+
+			// Nodal Blocks
+			case 'NSTA':
+				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
+				break;
+
+			case 'NRNM':
+				this.IFF.currentForm.realName = this.IFF.reader.getString();
+				break;
+
+			case 'NNME':
+				this.IFF.currentForm.refName = this.IFF.reader.getString();
+				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
+				break;
+
+			// Nodal Blocks : connections
+			case 'INME':
+				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
+				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IINN':
+				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
+				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IINM':
+				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
+				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IONM':
+				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
+				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'FNAM':
+				this.IFF.currentForm.fileName = this.IFF.reader.getString();
+				break;
+
+			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
+				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
+				else this.IFF.reader.skip( length );
+				break;
+
+			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
+			case 'SMAN':
+				var maxSmoothingAngle = this.IFF.reader.getFloat32();
+				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
+				break;
+
+			// LWO2: Basic Surface Parameters
+			case 'COLR':
+				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
+				this.IFF.reader.skip( 2 ); // VX: envelope
+				break;
+
+			case 'LUMI':
+				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'SPEC':
+				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'DIFF':
+				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'REFL':
+				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'GLOS':
+				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'TRAN':
+				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'BUMP':
+				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'SIDE':
+				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
+				break;
+
+			case 'RIMG':
+				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
+				break;
+
+			case 'RIND':
+				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'TIMG':
+				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
+				break;
+
+			case 'IMAP':
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'TMAP':
+				this.IFF.debugger.skipped = true;
+				this.IFF.reader.skip( length ); // needs implementing
+				break;
+
+			case 'IUVI': // uv channel name
+				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
+				break;
+
+			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
+				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
+				break;
+			case 'IVTL': // heightWrappingMode
+				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
+				break;
+
+			// LWO2 USE
+			case 'BLOK':
+				// skip
+				break;
+
+			default:
+				this.IFF.parseUnknownCHUNK( blockID, length );
+
+		}
+
+		if ( blockID != 'FORM' ) {
+
+			this.IFF.debugger.node = 1;
+			this.IFF.debugger.nodeID = blockID;
+			this.IFF.debugger.log();
+
+		}
+
+		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
+
+			this.IFF.currentForm = this.IFF.parentForm;
+
+		}
+
+	}
+
+};
+
+export { LWO2Parser };

+ 380 - 0
examples/jsm/loaders/lwo/LWO3Parser.js

@@ -0,0 +1,380 @@
+/**
+ * @author Lewy Blue / https://github.com/looeee
+ * @author Guilherme Avila / https://github/sciecode
+ */
+
+function LWO3Parser( IFFParser ) {
+
+	this.IFF = IFFParser;
+
+}
+
+LWO3Parser.prototype = {
+
+	constructor: LWO3Parser,
+
+	parseBlock: function () {
+
+		this.IFF.debugger.offset = this.IFF.reader.offset;
+		this.IFF.debugger.closeForms();
+
+		var blockID = this.IFF.reader.getIDTag();
+		var length = this.IFF.reader.getUint32(); // size of data in bytes
+
+		this.IFF.debugger.dataOffset = this.IFF.reader.offset;
+		this.IFF.debugger.length = length;
+
+		// Data types may be found in either LWO2 OR LWO3 spec
+		switch ( blockID ) {
+
+			case 'FORM': // form blocks may consist of sub -chunks or sub-forms
+				this.IFF.parseForm( length );
+				break;
+
+			// SKIPPED CHUNKS
+			// MISC skipped
+			case 'ICON': // Thumbnail Icon Image
+			case 'VMPA': // Vertex Map Parameter
+			case 'BBOX': // bounding box
+			// case 'VMMD':
+			// case 'VTYP':
+
+			// normal maps can be specified, normally on models imported from other applications. Currently ignored
+			case 'NORM':
+
+			// ENVL FORM skipped
+			case 'PRE ':
+			case 'POST':
+			case 'KEY ':
+			case 'SPAN':
+
+			// CLIP FORM skipped
+			case 'TIME':
+			case 'CLRS':
+			case 'CLRA':
+			case 'FILT':
+			case 'DITH':
+			case 'CONT':
+			case 'BRIT':
+			case 'SATR':
+			case 'HUE ':
+			case 'GAMM':
+			case 'NEGA':
+			case 'IFLT':
+			case 'PFLT':
+
+			// Image Map Layer skipped
+			case 'PROJ':
+			case 'AXIS':
+			case 'AAST':
+			case 'PIXB':
+			case 'STCK':
+
+			// Procedural Textures skipped
+			case 'VALU':
+
+			// Gradient Textures skipped
+			case 'PNAM':
+			case 'INAM':
+			case 'GRST':
+			case 'GREN':
+			case 'GRPT':
+			case 'FKEY':
+			case 'IKEY':
+
+			// Texture Mapping Form skipped
+			case 'CSYS':
+
+				// Surface CHUNKs skipped
+			case 'OPAQ': // top level 'opacity' checkbox
+			case 'CMAP': // clip map
+
+			// Surface node CHUNKS skipped
+			// These mainly specify the node editor setup in LW
+			case 'NLOC':
+			case 'NZOM':
+			case 'NVER':
+			case 'NSRV':
+			case 'NCRD':
+			case 'NMOD':
+			case 'NSEL':
+			case 'NPRW':
+			case 'NPLA':
+			case 'VERS':
+			case 'ENUM':
+			case 'TAG ':
+
+			// Car Material CHUNKS
+			case 'CGMD':
+			case 'CGTY':
+			case 'CGST':
+			case 'CGEN':
+			case 'CGTS':
+			case 'CGTE':
+			case 'OSMP':
+			case 'OMDE':
+			case 'OUTR':
+			case 'FLAG':
+
+			case 'TRNL':
+			case 'SHRP':
+			case 'RFOP':
+			case 'RSAN':
+			case 'TROP':
+			case 'RBLR':
+			case 'TBLR':
+			case 'CLRH':
+			case 'CLRF':
+			case 'ADTR':
+			case 'GLOW':
+			case 'LINE':
+			case 'ALPH':
+			case 'VCOL':
+			case 'ENAB':
+				this.IFF.debugger.skipped = true;
+				this.IFF.reader.skip( length );
+				break;
+
+			// Texture node chunks (not in spec)
+			case 'IPIX': // usePixelBlending
+			case 'IMIP': // useMipMaps
+			case 'IMOD': // imageBlendingMode
+			case 'AMOD': // unknown
+			case 'IINV': // imageInvertAlpha
+			case 'INCR': // imageInvertColor
+			case 'IAXS': // imageAxis ( for non-UV maps)
+			case 'IFOT': // imageFallofType
+			case 'ITIM': // timing for animated textures
+			case 'IWRL':
+			case 'IUTI':
+			case 'IINX':
+			case 'IINY':
+			case 'IINZ':
+			case 'IREF': // possibly a VX for reused texture nodes
+				if ( length === 4 ) this.IFF.currentNode[ blockID ] = this.IFF.reader.getInt32();
+				else this.IFF.reader.skip( length );
+				break;
+
+			case 'OTAG':
+				this.IFF.parseObjectTag();
+				break;
+
+			case 'LAYR':
+				this.IFF.parseLayer( length );
+				break;
+
+			case 'PNTS':
+				this.IFF.parsePoints( length );
+				break;
+
+			case 'VMAP':
+				this.IFF.parseVertexMapping( length );
+				break;
+
+			case 'POLS':
+				this.IFF.parsePolygonList( length );
+				break;
+
+			case 'TAGS':
+				this.IFF.parseTagStrings( length );
+				break;
+
+			case 'PTAG':
+				this.IFF.parsePolygonTagMapping( length );
+				break;
+
+			case 'VMAD':
+				this.IFF.parseVertexMapping( length, true );
+				break;
+
+			// Misc CHUNKS
+			case 'DESC': // Description Line
+				this.IFF.currentForm.description = this.IFF.reader.getString();
+				break;
+
+			case 'TEXT':
+			case 'CMNT':
+			case 'NCOM':
+				this.IFF.currentForm.comment = this.IFF.reader.getString();
+				break;
+
+			// Envelope Form
+			case 'NAME':
+				this.IFF.currentForm.channelName = this.IFF.reader.getString();
+				break;
+
+			// Image Map Layer
+			case 'WRAP':
+				this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() };
+				break;
+
+			case 'IMAG':
+				var index = this.IFF.reader.getVariableLengthIndex();
+				this.IFF.currentForm.imageIndex = index;
+				break;
+
+			// Texture Mapping Form
+			case 'OREF':
+				this.IFF.currentForm.referenceObject = this.IFF.reader.getString();
+				break;
+
+			case 'ROID':
+				this.IFF.currentForm.referenceObjectID = this.IFF.reader.getUint32();
+				break;
+
+			// Surface Blocks
+			case 'SSHN':
+				this.IFF.currentSurface.surfaceShaderName = this.IFF.reader.getString();
+				break;
+
+			case 'AOVN':
+				this.IFF.currentSurface.surfaceCustomAOVName = this.IFF.reader.getString();
+				break;
+
+			// Nodal Blocks
+			case 'NSTA':
+				this.IFF.currentForm.disabled = this.IFF.reader.getUint16();
+				break;
+
+			case 'NRNM':
+				this.IFF.currentForm.realName = this.IFF.reader.getString();
+				break;
+
+			case 'NNME':
+				this.IFF.currentForm.refName = this.IFF.reader.getString();
+				this.IFF.currentSurface.nodes[ this.IFF.currentForm.refName ] = this.IFF.currentForm;
+				break;
+
+			// Nodal Blocks : connections
+			case 'INME':
+				if ( ! this.IFF.currentForm.nodeName ) this.IFF.currentForm.nodeName = [];
+				this.IFF.currentForm.nodeName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IINN':
+				if ( ! this.IFF.currentForm.inputNodeName ) this.IFF.currentForm.inputNodeName = [];
+				this.IFF.currentForm.inputNodeName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IINM':
+				if ( ! this.IFF.currentForm.inputName ) this.IFF.currentForm.inputName = [];
+				this.IFF.currentForm.inputName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'IONM':
+				if ( ! this.IFF.currentForm.inputOutputName ) this.IFF.currentForm.inputOutputName = [];
+				this.IFF.currentForm.inputOutputName.push( this.IFF.reader.getString() );
+				break;
+
+			case 'FNAM':
+				this.IFF.currentForm.fileName = this.IFF.reader.getString();
+				break;
+
+			case 'CHAN': // NOTE: ENVL Forms may also have CHAN chunk, however ENVL is currently ignored
+				if ( length === 4 ) this.IFF.currentForm.textureChannel = this.IFF.reader.getIDTag();
+				else this.IFF.reader.skip( length );
+				break;
+
+			// LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format
+			case 'SMAN':
+				var maxSmoothingAngle = this.IFF.reader.getFloat32();
+				this.IFF.currentSurface.attributes.smooth = ( maxSmoothingAngle < 0 ) ? false : true;
+				break;
+
+			// LWO2: Basic Surface Parameters
+			case 'COLR':
+				this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array( 3 ) };
+				this.IFF.reader.skip( 2 ); // VX: envelope
+				break;
+
+			case 'LUMI':
+				this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'SPEC':
+				this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'DIFF':
+				this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'REFL':
+				this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'GLOS':
+				this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() };
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'TRAN':
+				this.IFF.currentSurface.attributes.opacity = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'BUMP':
+				this.IFF.currentSurface.attributes.bumpStrength = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'SIDE':
+				this.IFF.currentSurface.attributes.side = this.IFF.reader.getUint16();
+				break;
+
+			case 'RIMG':
+				this.IFF.currentSurface.attributes.reflectionMap = this.IFF.reader.getVariableLengthIndex();
+				break;
+
+			case 'RIND':
+				this.IFF.currentSurface.attributes.refractiveIndex = this.IFF.reader.getFloat32();
+				this.IFF.reader.skip( 2 );
+				break;
+
+			case 'TIMG':
+				this.IFF.currentSurface.attributes.refractionMap = this.IFF.reader.getVariableLengthIndex();
+				break;
+
+			case 'IMAP':
+				this.IFF.currentSurface.attributes.imageMapIndex = this.IFF.reader.getUint32();
+				break;
+
+			case 'IUVI': // uv channel name
+				this.IFF.currentNode.UVChannel = this.IFF.reader.getString( length );
+				break;
+
+			case 'IUTL': // widthWrappingMode: 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
+				this.IFF.currentNode.widthWrappingMode = this.IFF.reader.getUint32();
+				break;
+			case 'IVTL': // heightWrappingMode
+				this.IFF.currentNode.heightWrappingMode = this.IFF.reader.getUint32();
+				break;
+
+			default:
+				this.IFF.parseUnknownCHUNK( blockID, length );
+
+		}
+
+		if ( blockID != 'FORM' ) {
+
+			this.IFF.debugger.node = 1;
+			this.IFF.debugger.nodeID = blockID;
+			this.IFF.debugger.log();
+
+		}
+
+		if ( this.IFF.reader.offset >= this.IFF.currentFormEnd ) {
+
+			this.IFF.currentForm = this.IFF.parentForm;
+
+		}
+
+	}
+
+};
+
+export { LWO3Parser };

+ 21 - 13
examples/jsm/loaders/obj2/OBJLoader2Parser.js

@@ -344,9 +344,13 @@ OBJLoader2Parser.prototype = {
 		this.globalCounts.totalBytes = length;
 		this.globalCounts.totalBytes = length;
 		let buffer = new Array( 128 );
 		let buffer = new Array( 128 );
 
 
-		for ( let code, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i ++ ) {
+		let bufferPointer = 0;
+		let slashesCount = 0;
+		let word = '';
+		let currentByte = 0;
+		for ( let code, currentByte = 0; currentByte < length; currentByte ++ ) {
 
 
-			code = arrayBufferView[ i ];
+			code = arrayBufferView[ currentByte ];
 			switch ( code ) {
 			switch ( code ) {
 
 
 				// space
 				// space
@@ -363,11 +367,8 @@ OBJLoader2Parser.prototype = {
 
 
 				// LF
 				// LF
 				case 10:
 				case 10:
-					if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
+					this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
 					word = '';
 					word = '';
-					this.globalCounts.lineByte = this.globalCounts.currentByte;
-					this.globalCounts.currentByte = i;
-					this._processLine( buffer, bufferPointer, slashesCount );
 					bufferPointer = 0;
 					bufferPointer = 0;
 					slashesCount = 0;
 					slashesCount = 0;
 					break;
 					break;
@@ -384,6 +385,7 @@ OBJLoader2Parser.prototype = {
 
 
 		}
 		}
 
 
+		this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
 		this._finalizeParsing();
 		this._finalizeParsing();
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.execute' );
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.execute' );
 
 
@@ -404,9 +406,13 @@ OBJLoader2Parser.prototype = {
 		this.globalCounts.totalBytes = length;
 		this.globalCounts.totalBytes = length;
 		let buffer = new Array( 128 );
 		let buffer = new Array( 128 );
 
 
-		for ( let char, word = '', bufferPointer = 0, slashesCount = 0, i = 0; i < length; i ++ ) {
+		let bufferPointer = 0;
+		let slashesCount = 0;
+		let word = '';
+		let currentByte = 0;
+		for ( let char; currentByte < length; currentByte ++ ) {
 
 
-			char = text[ i ];
+			char = text[ currentByte ];
 			switch ( char ) {
 			switch ( char ) {
 
 
 				case ' ':
 				case ' ':
@@ -421,11 +427,8 @@ OBJLoader2Parser.prototype = {
 					break;
 					break;
 
 
 				case '\n':
 				case '\n':
-					if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
+					this._processLine( buffer, bufferPointer, slashesCount, word, currentByte );
 					word = '';
 					word = '';
-					this.globalCounts.lineByte = this.globalCounts.currentByte;
-					this.globalCounts.currentByte = i;
-					this._processLine( buffer, bufferPointer, slashesCount );
 					bufferPointer = 0;
 					bufferPointer = 0;
 					slashesCount = 0;
 					slashesCount = 0;
 					break;
 					break;
@@ -440,15 +443,20 @@ OBJLoader2Parser.prototype = {
 
 
 		}
 		}
 
 
+		this._processLine( buffer, bufferPointer, word, slashesCount );
 		this._finalizeParsing();
 		this._finalizeParsing();
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.executeLegacy' );
 		if ( this.logging.enabled ) console.timeEnd( 'OBJLoader2Parser.executeLegacy' );
 
 
 	},
 	},
 
 
-	_processLine: function ( buffer, bufferPointer, slashesCount ) {
+	_processLine: function ( buffer, bufferPointer, slashesCount, word, currentByte ) {
 
 
+		this.globalCounts.lineByte = this.globalCounts.currentByte;
+		this.globalCounts.currentByte = currentByte;
 		if ( bufferPointer < 1 ) return;
 		if ( bufferPointer < 1 ) return;
 
 
+		if ( word.length > 0 ) buffer[ bufferPointer ++ ] = word;
+
 		let reconstructString = function ( content, legacyMode, start, stop ) {
 		let reconstructString = function ( content, legacyMode, start, stop ) {
 
 
 			let line = '';
 			let line = '';

+ 1 - 1
examples/jsm/loaders/obj2/worker/main/WorkerExecutionSupport.d.ts

@@ -58,6 +58,6 @@ export class WorkerExecutionSupport {
 	updateCallbacks( onAssetAvailable: Function, onLoad: Function ): void;
 	updateCallbacks( onAssetAvailable: Function, onLoad: Function ): void;
 	buildWorker( codeBuilderInstructions: CodeBuilderInstructions ): void;
 	buildWorker( codeBuilderInstructions: CodeBuilderInstructions ): void;
 	isWorkerLoaded( requireJsmWorker: boolean ): boolean;
 	isWorkerLoaded( requireJsmWorker: boolean ): boolean;
-	executeParallel( payload:object, transferables?: object[] );
+	executeParallel( payload:object, transferables?: object[] ): void;
 
 
 }
 }

+ 1 - 1
examples/jsm/misc/GPUComputationRenderer.d.ts

@@ -13,7 +13,7 @@ import {
 export interface Variable {
 export interface Variable {
 	name: string;
 	name: string;
 	initialValueTexture: Texture;
 	initialValueTexture: Texture;
-	material: Material;
+	material: ShaderMaterial;
 	dependencies: Variable[];
 	dependencies: Variable[];
 	renderTargets: RenderTarget[];
 	renderTargets: RenderTarget[];
 	wrapS: number;
 	wrapS: number;

+ 1 - 1
examples/jsm/nodes/core/NodeBuilder.d.ts

@@ -114,7 +114,7 @@ export class NodeBuilder {
 	isDefined( name: string ): boolean;
 	isDefined( name: string ): boolean;
 	getVar( uuid: string, type: string, ns: string, shader?: string, prefix?: string, label?: string ): object;
 	getVar( uuid: string, type: string, ns: string, shader?: string, prefix?: string, label?: string ): object;
 	getVar( uuid: string, type: string, ns: string, label: string ): object;
 	getVar( uuid: string, type: string, ns: string, label: string ): object;
-	getAttribute( name: string, type: string );
+	getAttribute( name: string, type: string ): any;
 	getCode( shader: string ): string;
 	getCode( shader: string ): string;
 	getVarListCode( vars: object[], prefix?: string ): string;
 	getVarListCode( vars: object[], prefix?: string ): string;
 	getVars( shader: string ): object[];
 	getVars( shader: string ): object[];

+ 7 - 7
examples/jsm/nodes/misc/TextureCubeUVNode.js

@@ -66,17 +66,17 @@ TextureCubeUVNode.Nodes = ( function () {
 		`vec2 getUV(vec3 direction, float face) {
 		`vec2 getUV(vec3 direction, float face) {
 				vec2 uv;
 				vec2 uv;
 				if (face == 0.0) {
 				if (face == 0.0) {
-					uv = vec2(-direction.z, direction.y) / abs(direction.x);
+					uv = vec2(direction.z, direction.y) / abs(direction.x); // pos x
 				} else if (face == 1.0) {
 				} else if (face == 1.0) {
-					uv = vec2(direction.x, -direction.z) / abs(direction.y);
+					uv = vec2(-direction.x, -direction.z) / abs(direction.y); // pos y
 				} else if (face == 2.0) {
 				} else if (face == 2.0) {
-					uv = direction.xy / abs(direction.z);
+					uv = vec2(-direction.x, direction.y) / abs(direction.z); // pos z
 				} else if (face == 3.0) {
 				} else if (face == 3.0) {
-					uv = vec2(direction.z, direction.y) / abs(direction.x);
+					uv = vec2(-direction.z, direction.y) / abs(direction.x); // neg x
 				} else if (face == 4.0) {
 				} else if (face == 4.0) {
-					uv = direction.xz / abs(direction.y);
+					uv = vec2(-direction.x, direction.z) / abs(direction.y); // neg y
 				} else {
 				} else {
-					uv = vec2(-direction.x, direction.y) / abs(direction.z);
+					uv = vec2(direction.x, direction.y) / abs(direction.z); // neg z
 				}
 				}
 				return 0.5 * (uv + 1.0);
 				return 0.5 * (uv + 1.0);
 		}` );
 		}` );
@@ -106,7 +106,7 @@ TextureCubeUVNode.Nodes = ( function () {
 			uv.y += filterInt * 2.0 * cubeUV_minTileSize;
 			uv.y += filterInt * 2.0 * cubeUV_minTileSize;
 			uv.x += 3.0 * max(0.0, cubeUV_maxTileSize - 2.0 * faceSize);
 			uv.x += 3.0 * max(0.0, cubeUV_maxTileSize - 2.0 * faceSize);
 			uv *= texelSize;
 			uv *= texelSize;
- 
+
 			vec4 tl = texture2D(envMap, uv);
 			vec4 tl = texture2D(envMap, uv);
 			uv.x += texelSize;
 			uv.x += texelSize;
 			vec4 tr = texture2D(envMap, uv);
 			vec4 tr = texture2D(envMap, uv);

+ 2 - 2
examples/jsm/nodes/utils/ColorSpaceNode.d.ts

@@ -10,8 +10,8 @@ export class ColorSpaceNode extends TempNode {
 	method: string | undefined;
 	method: string | undefined;
 	nodeType: string;
 	nodeType: string;
 
 
-	fromEncoding( encoding: number );
-	fromDecoding( encoding: number );
+	fromEncoding( encoding: number ): void;
+	fromDecoding( encoding: number ): void;
 	copy( source: ColorSpaceNode ): this;
 	copy( source: ColorSpaceNode ): this;
 
 
 	static Nodes: {
 	static Nodes: {

+ 1 - 0
examples/jsm/postprocessing/EffectComposer.js

@@ -97,6 +97,7 @@ Object.assign( EffectComposer.prototype, {
 	insertPass: function ( pass, index ) {
 	insertPass: function ( pass, index ) {
 
 
 		this.passes.splice( index, 0, pass );
 		this.passes.splice( index, 0, pass );
+		pass.setSize( this._width * this._pixelRatio, this._height * this._pixelRatio );
 
 
 	},
 	},
 
 

+ 2 - 4
examples/jsm/postprocessing/SAOPass.d.ts

@@ -62,9 +62,7 @@ export class SAOPass extends Pass {
 	fsQuad: object;
 	fsQuad: object;
 	params: SAOPassParams;
 	params: SAOPassParams;
 
 
-	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color, clearAlpha?: number ): void;
-	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: number, clearAlpha?: number ): void;
-	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color, clearAlpha?: number ): void;
-	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: number, clearAlpha?: number ): void;
+	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color | string | number, clearAlpha?: number ): void;
+	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color | string | number, clearAlpha?: number ): void;
 
 
 }
 }

+ 2 - 4
examples/jsm/postprocessing/SSAOPass.d.ts

@@ -52,9 +52,7 @@ export class SSAOPass extends Pass {
 	dipose(): void;
 	dipose(): void;
 	generateSampleKernel(): Vector3[];
 	generateSampleKernel(): Vector3[];
 	generateRandomKernelRotations(): void;
 	generateRandomKernelRotations(): void;
-	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color, clearAlpha?: number ): void;
-	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: number, clearAlpha?: number ): void;
-	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color, clearAlpha?: number ): void;
-	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: number, clearAlpha?: number ): void;
+	renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color | string | number, clearAlpha?: number ): void;
+	renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, clearColor?: Color | string | number, clearAlpha?: number ): void;
 
 
 }
 }

+ 1 - 1
examples/jsm/postprocessing/ShaderPass.d.ts

@@ -8,7 +8,7 @@ export class ShaderPass extends Pass {
 
 
 	constructor( shader: object, textureID?: string );
 	constructor( shader: object, textureID?: string );
 	textureID: string;
 	textureID: string;
-	uniforms: object;
+	uniforms: { [name: string]: { value: any } };
 	material: Material;
 	material: Material;
 	fsQuad: object;
 	fsQuad: object;
 
 

+ 1 - 1
examples/jsm/renderers/Projector.d.ts

@@ -76,6 +76,6 @@ export class Projector {
 
 
 	constructor();
 	constructor();
 
 
-	projectScene( scene: Scene, camera: Camera, sortObjects: boolean, sortElements: boolean );
+	projectScene( scene: Scene, camera: Camera, sortObjects: boolean, sortElements: boolean ): any;
 
 
 }
 }

+ 1 - 1
examples/jsm/shaders/BokehShader.js

@@ -22,7 +22,7 @@ var BokehShader = {
 		"focus": { value: 1.0 },
 		"focus": { value: 1.0 },
 		"aspect": { value: 1.0 },
 		"aspect": { value: 1.0 },
 		"aperture": { value: 0.025 },
 		"aperture": { value: 0.025 },
-		"maxblur": { value: 1.0 },
+		"maxblur": { value: 0.01 },
 		"nearClip": { value: 1.0 },
 		"nearClip": { value: 1.0 },
 		"farClip": { value: 1000.0 },
 		"farClip": { value: 1000.0 },
 
 

+ 0 - 1
examples/jsm/shaders/GodRaysShader.d.ts

@@ -25,7 +25,6 @@ export const GodRaysCombineShader: {
 		tColors: Uniform;
 		tColors: Uniform;
 		tGodRays: Uniform;
 		tGodRays: Uniform;
 		fGodRayIntensity: Uniform;
 		fGodRayIntensity: Uniform;
-		vSunPositionScreenSpace: Uniform;
 	};
 	};
 	vertexShader: string;
 	vertexShader: string;
 	fragmentShader: string;
 	fragmentShader: string;

+ 16 - 19
examples/jsm/shaders/GodRaysShader.js

@@ -20,7 +20,7 @@
 
 
 import {
 import {
 	Color,
 	Color,
-	Vector2
+	Vector3
 } from "../../../build/three.module.js";
 } from "../../../build/three.module.js";
 
 
 var GodRaysDepthMaskShader = {
 var GodRaysDepthMaskShader = {
@@ -90,7 +90,7 @@ var GodRaysGenerateShader = {
 			value: 1.0
 			value: 1.0
 		},
 		},
 		vSunPositionScreenSpace: {
 		vSunPositionScreenSpace: {
-			value: new Vector2( 0.5, 0.5 )
+			value: new Vector3()
 		}
 		}
 
 
 	},
 	},
@@ -116,14 +116,14 @@ var GodRaysGenerateShader = {
 
 
 		"uniform sampler2D tInput;",
 		"uniform sampler2D tInput;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
+		"uniform vec3 vSunPositionScreenSpace;",
 		"uniform float fStepSize;", // filter step size
 		"uniform float fStepSize;", // filter step size
 
 
 		"void main() {",
 		"void main() {",
 
 
 		// delta from current pixel to "sun" position
 		// delta from current pixel to "sun" position
 
 
-		"	vec2 delta = vSunPositionScreenSpace - vUv;",
+		"	vec2 delta = vSunPositionScreenSpace.xy - vUv;",
 		"	float dist = length( delta );",
 		"	float dist = length( delta );",
 
 
 		// Step vector (uv space)
 		// Step vector (uv space)
@@ -162,22 +162,24 @@ var GodRaysGenerateShader = {
 
 
 		// Unrolling loop manually makes it work in ANGLE
 		// Unrolling loop manually makes it work in ANGLE
 
 
-		"	if ( 0.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	float f = min( 1.0, max( vSunPositionScreenSpace.z / 1000.0, 0.0 ) );", // used to fade out godrays
+
+		"	if ( 0.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 1.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 1.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 2.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 2.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 3.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 3.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 4.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 4.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
-		"	if ( 5.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r;",
+		"	if ( 5.0 <= iters && uv.y < 1.0 ) col += texture2D( tInput, uv ).r * f;",
 		"	uv += stepv;",
 		"	uv += stepv;",
 
 
 		// Should technically be dividing by 'iters', but 'TAPS_PER_PASS' smooths out
 		// Should technically be dividing by 'iters', but 'TAPS_PER_PASS' smooths out
@@ -215,10 +217,6 @@ var GodRaysCombineShader = {
 
 
 		fGodRayIntensity: {
 		fGodRayIntensity: {
 			value: 0.69
 			value: 0.69
-		},
-
-		vSunPositionScreenSpace: {
-			value: new Vector2( 0.5, 0.5 )
 		}
 		}
 
 
 	},
 	},
@@ -243,7 +241,6 @@ var GodRaysCombineShader = {
 		"uniform sampler2D tColors;",
 		"uniform sampler2D tColors;",
 		"uniform sampler2D tGodRays;",
 		"uniform sampler2D tGodRays;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
 		"uniform float fGodRayIntensity;",
 		"uniform float fGodRayIntensity;",
 
 
 		"void main() {",
 		"void main() {",
@@ -272,7 +269,7 @@ var GodRaysFakeSunShader = {
 	uniforms: {
 	uniforms: {
 
 
 		vSunPositionScreenSpace: {
 		vSunPositionScreenSpace: {
-			value: new Vector2( 0.5, 0.5 )
+			value: new Vector3()
 		},
 		},
 
 
 		fAspect: {
 		fAspect: {
@@ -306,7 +303,7 @@ var GodRaysFakeSunShader = {
 
 
 		"varying vec2 vUv;",
 		"varying vec2 vUv;",
 
 
-		"uniform vec2 vSunPositionScreenSpace;",
+		"uniform vec3 vSunPositionScreenSpace;",
 		"uniform float fAspect;",
 		"uniform float fAspect;",
 
 
 		"uniform vec3 sunColor;",
 		"uniform vec3 sunColor;",
@@ -314,7 +311,7 @@ var GodRaysFakeSunShader = {
 
 
 		"void main() {",
 		"void main() {",
 
 
-		"	vec2 diff = vUv - vSunPositionScreenSpace;",
+		"	vec2 diff = vUv - vSunPositionScreenSpace.xy;",
 
 
 		// Correct for aspect ratio
 		// Correct for aspect ratio
 
 
@@ -323,7 +320,7 @@ var GodRaysFakeSunShader = {
 		"	float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );",
 		"	float prop = clamp( length( diff ) / 0.5, 0.0, 1.0 );",
 		"	prop = 0.35 * pow( 1.0 - prop, 3.0 );",
 		"	prop = 0.35 * pow( 1.0 - prop, 3.0 );",
 
 
-		"	gl_FragColor.xyz = mix( sunColor, bgColor, 1.0 - prop );",
+		"	gl_FragColor.xyz = ( vSunPositionScreenSpace.z > 0.0 ) ? mix( sunColor, bgColor, 1.0 - prop ) : bgColor;",
 		"	gl_FragColor.w = 1.0;",
 		"	gl_FragColor.w = 1.0;",
 
 
 		"}"
 		"}"

+ 3 - 2
examples/jsm/utils/TypedArrayUtils.d.ts

@@ -1,6 +1,7 @@
 export namespace TypedArrayUtils {
 export namespace TypedArrayUtils {
 	export function quicksortIP( arr: any[], eleSize: number, orderElement: number ): any[];
 	export function quicksortIP( arr: any[], eleSize: number, orderElement: number ): any[];
 
 
+	type Points = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Float32Array | Float64Array | Uint8ClampedArray;
 
 
 	export class Kdtree {
 	export class Kdtree {
 
 
@@ -8,9 +9,9 @@ export namespace TypedArrayUtils {
 		root: Node;
 		root: Node;
 		private maxDepth: number;
 		private maxDepth: number;
 
 
-		constructor( points: Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array | Float32Array | Float64Array | Uint8ClampedArray, metric: ( a: any, b: any ) => number, eleSize: number );
+		constructor( points: Points, metric: ( a: any, b: any ) => number, eleSize: number );
 
 
-		getPointSet( points: any, pos: number );
+		getPointSet( points: Points, pos: number ): Points;
 
 
 		buildTree(): Node;
 		buildTree(): Node;
 
 

+ 1 - 6
examples/misc_animation_authoring.html

@@ -161,11 +161,6 @@
 
 
 		</script>
 		</script>
 
 
-		<script>
-			if (navigator.webdriver) { // off for testing
-				setTimeout(() => { document.getElementsByTagName('div')[1].style.display = 'none' }, 750)
-			}
-		</script>
-
+		<script>if ( typeof TESTING !== 'undefined' ) { setTimeout(() => { var a = document.querySelectorAll( 'body > div' ); a[1].style.display = 'none'; }, 750); }</script>
 	</body>
 	</body>
 </html>
 </html>

+ 1 - 1
examples/misc_animation_keys.html

@@ -31,7 +31,7 @@
 				//
 				//
 
 
 				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
 				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
-				camera.position.set( 50, 50, 100 );
+				camera.position.set( 25, 25, 50 );
 				camera.lookAt( scene.position );
 				camera.lookAt( scene.position );
 
 
 				//
 				//

BIN
examples/models/gltf/Xbot.blend


BIN
examples/models/gltf/Xbot.glb


+ 1 - 1
examples/models/obj/verify/verify.obj

@@ -212,4 +212,4 @@ v 290 40 -10
 v 310 40 -10
 v 310 40 -10
 v 310 60 -10
 v 310 60 -10
 
 
-l -8/-2 -7/-1 -6/-2 -5/-1
+l -8/-2 -7/-1 -6/-2 -5/-1

+ 7 - 6
examples/models/vrml/test/creaseAngle.wrl → examples/models/vrml/creaseAngle.wrl

@@ -3,7 +3,8 @@
 #Created by Cinema 4D
 #Created by Cinema 4D
 
 
 DEF Plane Transform {
 DEF Plane Transform {
-  children [ 
+	scale 0.1 0.1 0.1
+  children [
     Shape {
     Shape {
       appearance DEF MAT_Mat Appearance {
       appearance DEF MAT_Mat Appearance {
         material Material {
         material Material {
@@ -40,11 +41,11 @@ DEF Plane Transform {
   ]
   ]
 }
 }
 DEF Null Transform {
 DEF Null Transform {
-  translation 0 -200 0
-  children [ 
+  translation 0 5 0
+  children [
     DEF Platonic Transform {
     DEF Platonic Transform {
-      translation 0 400 0
-      children [ 
+			scale 0.02 0.02 0.02
+      children [
         Shape {
         Shape {
           appearance USE MAT_Mat
           appearance USE MAT_Mat
           geometry DEF FACESET_Platonic IndexedFaceSet {
           geometry DEF FACESET_Platonic IndexedFaceSet {
@@ -83,4 +84,4 @@ DEF Null Transform {
       ]
       ]
     }
     }
   ]
   ]
-}
+}

+ 0 - 0
examples/models/vrml/test/crystal.wrl → examples/models/vrml/crystal.wrl


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