Преглед на файлове

Merge branch 'dev' into gtao-quadratic-ray-stepping-falloff

Marco Fugaro преди 1 месец
родител
ревизия
a08e9b223c
променени са 100 файла, в които са добавени 2172 реда и са изтрити 2104 реда
  1. 89 66
      build/three.cjs
  2. 10 23
      build/three.core.js
  3. 0 0
      build/three.core.min.js
  4. 79 43
      build/three.module.js
  5. 0 0
      build/three.module.min.js
  6. 505 622
      build/three.webgpu.js
  7. 0 0
      build/three.webgpu.min.js
  8. 505 622
      build/three.webgpu.nodes.js
  9. 0 0
      build/three.webgpu.nodes.min.js
  10. 1 1
      docs/TSL.md
  11. 2 2
      docs/index.html
  12. 5 5
      docs/llms-full.txt
  13. 2 2
      docs/pages/TSL.html
  14. 0 1
      editor/examples/arkanoid.app.json
  15. 1 1
      editor/index.html
  16. 15 6
      editor/js/Animation.js
  17. 5 1
      editor/js/Config.js
  18. 66 0
      editor/js/Editor.js
  19. 30 6
      editor/js/EditorControls.js
  20. 3 1
      editor/js/Menubar.Add.js
  21. 120 1
      editor/js/Menubar.File.js
  22. 17 2
      editor/js/Menubar.Render.js
  23. 31 2
      editor/js/Script.js
  24. 35 2
      editor/js/Sidebar.Project.Renderer.js
  25. 2 0
      editor/js/Sidebar.Scene.js
  26. 13 1
      editor/js/Sidebar.Settings.Shortcuts.js
  27. 9 0
      editor/js/Strings.js
  28. 12 1
      editor/js/Viewport.XR.js
  29. 21 4
      editor/js/Viewport.js
  30. 23 6
      editor/js/libs/app.js
  31. 233 0
      editor/js/libs/tern-threejs/build-defs.js
  32. 0 268
      editor/js/libs/tern-threejs/threejs.js
  33. 1 1
      examples/files.json
  34. 16 6
      examples/jsm/helpers/ViewHelper.js
  35. 3 3
      examples/jsm/libs/demuxer_mp4.js
  36. 0 8
      examples/jsm/libs/rhino3dm/rhino3dm.js
  37. 0 8
      examples/jsm/libs/rhino3dm/rhino3dm.module.js
  38. BIN
      examples/jsm/libs/rhino3dm/rhino3dm.wasm
  39. 1 1
      examples/jsm/loaders/3DMLoader.js
  40. 38 28
      examples/jsm/loaders/DRACOLoader.js
  41. 4 18
      examples/jsm/loaders/EXRLoader.js
  42. 6 28
      examples/jsm/loaders/HDRLoader.js
  43. 7 3
      examples/jsm/loaders/KTX2Loader.js
  44. 4 0
      examples/jsm/loaders/LWOLoader.js
  45. 31 9
      examples/jsm/loaders/MaterialXLoader.js
  46. 11 12
      examples/jsm/tsl/display/FXAANode.js
  47. 2 2
      examples/jsm/tsl/display/SSSNode.js
  48. 3 3
      examples/jsm/tsl/lighting/ClusteredLightsNode.js
  49. 1 1
      examples/jsm/tsl/lighting/DynamicLightsNode.js
  50. 2 2
      examples/misc_exporter_gcode.html
  51. BIN
      examples/models/lwo/Images/environments/kiara_8_sunset.jpg
  52. BIN
      examples/models/lwo/Images/environments/kloofendal_48d_partly_cloudy.jpg
  53. BIN
      examples/models/lwo/Images/material-PBR-abstract/Abstract_008_basecolor.jpg
  54. BIN
      examples/models/lwo/Images/material-PBR-abstract/Abstract_008_bump.jpg
  55. BIN
      examples/models/lwo/Images/material-PBR-abstract/Abstract_008_metallic.jpg
  56. BIN
      examples/models/lwo/Images/material-PBR-abstract/Abstract_008_normal.jpg
  57. BIN
      examples/models/lwo/Images/material-PBR-abstract/Abstract_008_roughness.jpg
  58. BIN
      examples/models/lwo/Images/material-Phong-metal/167_BUMP.jpg
  59. BIN
      examples/models/lwo/Images/material-Phong-metal/167_COLOR.JPG
  60. BIN
      examples/models/lwo/Images/material-Phong-metal/167_GLOSS.jpg
  61. BIN
      examples/models/lwo/Images/material-Phong-metal/167_SPEC.jpg
  62. 0 3
      examples/models/lwo/LICENSE.txt
  63. BIN
      examples/models/lwo/Objects/LWO3/Demo.lwo
  64. BIN
      examples/screenshots/webgl_batch_lod_bvh.jpg
  65. BIN
      examples/screenshots/webgl_loader_lwo.jpg
  66. BIN
      examples/screenshots/webgl_morphtargets_webcam.jpg
  67. BIN
      examples/screenshots/webgpu_lights_clustered.jpg
  68. BIN
      examples/screenshots/webgpu_postprocessing_motion_blur.jpg
  69. BIN
      examples/screenshots/webgpu_skinning_instancing_individual.jpg
  70. 1 0
      examples/tags.json
  71. 2 2
      examples/webgl_animation_keyframes.html
  72. 8 8
      examples/webgl_batch_lod_bvh.html
  73. 1 1
      examples/webgl_geometry_csg.html
  74. 2 2
      examples/webgl_gpgpu_water.html
  75. 1 2
      examples/webgl_loader_3dm.html
  76. 12 12
      examples/webgl_loader_3dtiles.html
  77. 3 3
      examples/webgl_loader_gltf_animation_pointer.html
  78. 2 2
      examples/webgl_loader_gltf_avif.html
  79. 2 2
      examples/webgl_loader_gltf_transmission.html
  80. 0 114
      examples/webgl_loader_lwo.html
  81. 2 2
      examples/webgl_materials_car.html
  82. 2 2
      examples/webgl_materials_envmaps_groundprojected.html
  83. 1 20
      examples/webgl_materials_matcap.html
  84. 1 1
      examples/webgl_modifier_subdivision.html
  85. 97 49
      examples/webgl_morphtargets_webcam.html
  86. 2 2
      examples/webgl_postprocessing_godrays.html
  87. 2 2
      examples/webgl_random_uv.html
  88. 1 1
      examples/webgl_raycaster_bvh.html
  89. 1 1
      examples/webgl_renderer_pathtracer.html
  90. 2 2
      examples/webgl_watch.html
  91. 0 2
      examples/webgpu_clipping.html
  92. 2 2
      examples/webgpu_compute_water.html
  93. 46 11
      examples/webgpu_lights_clustered.html
  94. 2 2
      examples/webgpu_loader_gltf_transmission.html
  95. 2 2
      examples/webgpu_materials_envmaps_groundprojected.html
  96. 1 20
      examples/webgpu_materials_matcap.html
  97. 2 2
      examples/webgpu_mesh_batch.html
  98. 2 2
      examples/webgpu_mrt.html
  99. 4 4
      examples/webgpu_multisampled_renderbuffers.html
  100. 2 2
      examples/webgpu_performance.html

+ 89 - 66
build/three.cjs

@@ -24072,15 +24072,6 @@ class Skeleton {
 		 */
 		this.boneMatrices = null;
 
-		/**
-		 * An array buffer holding the bone data of the previous frame.
-		 * Required for computing velocity. Maintained in {@link SkinningNode}.
-		 *
-		 * @type {?Float32Array}
-		 * @default null
-		 */
-		this.previousBoneMatrices = null;
-
 		/**
 		 * A texture holding the bone data for use
 		 * in the vertex shader.
@@ -24506,15 +24497,6 @@ class InstancedMesh extends Mesh {
 		 */
 		this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
 
-		/**
-		 * Represents the local transformation of all instances of the previous frame.
-		 * Required for computing velocity. Maintained in {@link InstanceNode}.
-		 *
-		 * @type {?InstancedBufferAttribute}
-		 * @default null
-		 */
-		this.previousInstanceMatrix = null;
-
 		/**
 		 * Represents the color of all instances. You have to set its
 		 * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data
@@ -24644,8 +24626,6 @@ class InstancedMesh extends Mesh {
 
 		this.instanceMatrix.copy( source.instanceMatrix );
 
-		if ( source.previousInstanceMatrix !== null ) this.previousInstanceMatrix = source.previousInstanceMatrix.clone();
-
 		if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone();
 		if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
 
@@ -36309,6 +36289,10 @@ class SphereGeometry extends BufferGeometry {
 			const verticesRow = [];
 
 			const v = iy / heightSegments;
+			const theta = thetaStart + v * thetaLength;
+
+			const y = radius * Math.cos( theta );
+			const ringRadius = Math.sqrt( radius * radius - y * y );
 
 			// special case for the poles
 
@@ -36327,12 +36311,13 @@ class SphereGeometry extends BufferGeometry {
 			for ( let ix = 0; ix <= widthSegments; ix ++ ) {
 
 				const u = ix / widthSegments;
+				const phi = phiStart + u * phiLength;
 
 				// vertex
 
-				vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
-				vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
-				vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+				vertex.x = - ringRadius * Math.cos( phi );
+				vertex.y = y;
+				vertex.z = ringRadius * Math.sin( phi );
 
 				vertices.push( vertex.x, vertex.y, vertex.z );
 
@@ -48814,6 +48799,8 @@ class ObjectLoader extends Loader {
 
 		function loadImage( url ) {
 
+			url = scope.manager.resolveURL( url );
+
 			scope.manager.itemStart( url );
 
 			return loader.load( url, function () {
@@ -60413,7 +60400,7 @@ const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\
 
 const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#include <morphinstance_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}";
 
-const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = vec4( dist, 0.0, 0.0, 1.0 );\n}";
+const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = vec4( dist, 0.0, 0.0, 1.0 );\n}";
 
 const vertex$c = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n}";
 
@@ -68153,12 +68140,20 @@ function WebGLRenderList() {
 
 	}
 
-	function sort( customOpaqueSort, customTransparentSort ) {
+	function sort( customOpaqueSort, customTransparentSort, reversedDepth ) {
 
 		if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
 		if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable );
 		if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
 
+		if ( reversedDepth ) {
+
+			opaque.reverse();
+			transmissive.reverse();
+			transparent.reverse();
+
+		}
+
 	}
 
 	function finish() {
@@ -75524,73 +75519,101 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) {
 
 		for ( let i = 0, il = uniforms.length; i < il; i ++ ) {
 
-			const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ];
+			const uniformItem = uniforms[ i ];
 
-			for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) {
+			if ( Array.isArray( uniformItem ) ) {
 
-				const uniform = uniformArray[ j ];
+				for ( let j = 0, jl = uniformItem.length; j < jl; j ++ ) {
 
-				if ( hasUniformChanged( uniform, i, j, cache ) === true ) {
+					updateUniform( uniformItem[ j ], i, j, cache );
 
-					const offset = uniform.__offset;
+				}
 
-					const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ];
+			} else {
 
-					let arrayOffset = 0;
+				updateUniform( uniformItem, i, 0, cache );
 
-					for ( let k = 0; k < values.length; k ++ ) {
+			}
 
-						const value = values[ k ];
+		}
 
-						const info = getUniformSize( value );
+		gl.bindBuffer( gl.UNIFORM_BUFFER, null );
 
-						// TODO add integer and struct support
-						if ( typeof value === 'number' || typeof value === 'boolean' ) {
+	}
 
-							uniform.__data[ 0 ] = value;
-							gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data );
+	function updateUniform( uniform, index, indexArray, cache ) {
 
-						} else if ( value.isMatrix3 ) {
+		if ( hasUniformChanged( uniform, index, indexArray, cache ) === true ) {
 
-							// manually converting 3x3 to 3x4
+			const offset = uniform.__offset;
+			const value = uniform.value;
 
-							uniform.__data[ 0 ] = value.elements[ 0 ];
-							uniform.__data[ 1 ] = value.elements[ 1 ];
-							uniform.__data[ 2 ] = value.elements[ 2 ];
-							uniform.__data[ 3 ] = 0;
-							uniform.__data[ 4 ] = value.elements[ 3 ];
-							uniform.__data[ 5 ] = value.elements[ 4 ];
-							uniform.__data[ 6 ] = value.elements[ 5 ];
-							uniform.__data[ 7 ] = 0;
-							uniform.__data[ 8 ] = value.elements[ 6 ];
-							uniform.__data[ 9 ] = value.elements[ 7 ];
-							uniform.__data[ 10 ] = value.elements[ 8 ];
-							uniform.__data[ 11 ] = 0;
+			if ( Array.isArray( value ) ) {
 
-						} else if ( ArrayBuffer.isView( value ) ) {
+				let arrayOffset = 0;
 
-							// copy the buffer data using "set"
-							uniform.__data.set( new value.constructor( value.buffer, value.byteOffset, uniform.__data.length ) );
+				for ( let k = 0; k < value.length; k ++ ) {
 
-						} else {
+					const val = value[ k ];
+					const info = getUniformSize( val );
 
-							value.toArray( uniform.__data, arrayOffset );
+					writeUniformValue( val, uniform.__data, arrayOffset );
 
-							arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT;
+					// only toArray() values advance arrayOffset
+					if ( typeof val !== 'number' && typeof val !== 'boolean' && ! val.isMatrix3 && ! ArrayBuffer.isView( val ) ) {
 
-						}
+						arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT;
 
 					}
 
-					gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data );
-
 				}
 
+			} else {
+
+				writeUniformValue( value, uniform.__data, 0 );
+
 			}
 
+			gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data );
+
 		}
 
-		gl.bindBuffer( gl.UNIFORM_BUFFER, null );
+	}
+
+	function writeUniformValue( value, data, offset ) {
+
+		// TODO add integer and struct support
+		if ( typeof value === 'number' || typeof value === 'boolean' ) {
+
+			data[ 0 ] = value;
+
+		} else if ( value.isMatrix3 ) {
+
+			// manually converting 3x3 to 3x4
+
+			data[ 0 ] = value.elements[ 0 ];
+			data[ 1 ] = value.elements[ 1 ];
+			data[ 2 ] = value.elements[ 2 ];
+			data[ 3 ] = 0;
+			data[ 4 ] = value.elements[ 3 ];
+			data[ 5 ] = value.elements[ 4 ];
+			data[ 6 ] = value.elements[ 5 ];
+			data[ 7 ] = 0;
+			data[ 8 ] = value.elements[ 6 ];
+			data[ 9 ] = value.elements[ 7 ];
+			data[ 10 ] = value.elements[ 8 ];
+			data[ 11 ] = 0;
+
+		} else if ( ArrayBuffer.isView( value ) ) {
+
+			// copy the buffer data using "set"
+			data.set( new value.constructor( value.buffer, value.byteOffset, data.length ) );
+
+		} else {
+
+			value.toArray( data, offset );
+
+		}
 
 	}
 
@@ -77512,7 +77535,7 @@ class WebGLRenderer {
 
 			if ( _this.sortObjects === true ) {
 
-				currentRenderList.sort( _opaqueSort, _transparentSort );
+				currentRenderList.sort( _opaqueSort, _transparentSort, camera.reversedDepth );
 
 			}
 
@@ -77527,6 +77550,8 @@ class WebGLRenderer {
 
 			this.info.render.frame ++;
 
+			if ( this.info.autoReset === true ) this.info.reset();
+
 			if ( _clippingEnabled === true ) clipping.beginShadows();
 
 			const shadowsArray = currentRenderState.state.shadowsArray;
@@ -77537,8 +77562,6 @@ class WebGLRenderer {
 
 			//
 
-			if ( this.info.autoReset === true ) this.info.reset();
-
 			// render scene (skip if first effect is a render pass - it will render the scene itself)
 
 			const skipSceneRender = useOutput && output.hasRenderPass();

+ 10 - 23
build/three.core.js

@@ -24092,15 +24092,6 @@ class Skeleton {
 		 */
 		this.boneMatrices = null;
 
-		/**
-		 * An array buffer holding the bone data of the previous frame.
-		 * Required for computing velocity. Maintained in {@link SkinningNode}.
-		 *
-		 * @type {?Float32Array}
-		 * @default null
-		 */
-		this.previousBoneMatrices = null;
-
 		/**
 		 * A texture holding the bone data for use
 		 * in the vertex shader.
@@ -24526,15 +24517,6 @@ class InstancedMesh extends Mesh {
 		 */
 		this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
 
-		/**
-		 * Represents the local transformation of all instances of the previous frame.
-		 * Required for computing velocity. Maintained in {@link InstanceNode}.
-		 *
-		 * @type {?InstancedBufferAttribute}
-		 * @default null
-		 */
-		this.previousInstanceMatrix = null;
-
 		/**
 		 * Represents the color of all instances. You have to set its
 		 * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data
@@ -24664,8 +24646,6 @@ class InstancedMesh extends Mesh {
 
 		this.instanceMatrix.copy( source.instanceMatrix );
 
-		if ( source.previousInstanceMatrix !== null ) this.previousInstanceMatrix = source.previousInstanceMatrix.clone();
-
 		if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone();
 		if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
 
@@ -36329,6 +36309,10 @@ class SphereGeometry extends BufferGeometry {
 			const verticesRow = [];
 
 			const v = iy / heightSegments;
+			const theta = thetaStart + v * thetaLength;
+
+			const y = radius * Math.cos( theta );
+			const ringRadius = Math.sqrt( radius * radius - y * y );
 
 			// special case for the poles
 
@@ -36347,12 +36331,13 @@ class SphereGeometry extends BufferGeometry {
 			for ( let ix = 0; ix <= widthSegments; ix ++ ) {
 
 				const u = ix / widthSegments;
+				const phi = phiStart + u * phiLength;
 
 				// vertex
 
-				vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
-				vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
-				vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+				vertex.x = - ringRadius * Math.cos( phi );
+				vertex.y = y;
+				vertex.z = ringRadius * Math.sin( phi );
 
 				vertices.push( vertex.x, vertex.y, vertex.z );
 
@@ -48834,6 +48819,8 @@ class ObjectLoader extends Loader {
 
 		function loadImage( url ) {
 
+			url = scope.manager.resolveURL( url );
+
 			scope.manager.itemStart( url );
 
 			return loader.load( url, function () {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/three.core.min.js


+ 79 - 43
build/three.module.js

@@ -528,7 +528,7 @@ const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\
 
 const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <batching_pars_vertex>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <batching_vertex>\n\t#include <skinbase_vertex>\n\t#include <morphinstance_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}";
 
-const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = vec4( dist, 0.0, 0.0, 1.0 );\n}";
+const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <alphatest_pars_fragment>\n#include <alphahash_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <clipping_planes_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <alphahash_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = vec4( dist, 0.0, 0.0, 1.0 );\n}";
 
 const vertex$c = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n}";
 
@@ -8268,12 +8268,20 @@ function WebGLRenderList() {
 
 	}
 
-	function sort( customOpaqueSort, customTransparentSort ) {
+	function sort( customOpaqueSort, customTransparentSort, reversedDepth ) {
 
 		if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
 		if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable );
 		if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
 
+		if ( reversedDepth ) {
+
+			opaque.reverse();
+			transmissive.reverse();
+			transparent.reverse();
+
+		}
+
 	}
 
 	function finish() {
@@ -15639,73 +15647,101 @@ function WebGLUniformsGroups( gl, info, capabilities, state ) {
 
 		for ( let i = 0, il = uniforms.length; i < il; i ++ ) {
 
-			const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ];
+			const uniformItem = uniforms[ i ];
 
-			for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) {
+			if ( Array.isArray( uniformItem ) ) {
 
-				const uniform = uniformArray[ j ];
+				for ( let j = 0, jl = uniformItem.length; j < jl; j ++ ) {
 
-				if ( hasUniformChanged( uniform, i, j, cache ) === true ) {
+					updateUniform( uniformItem[ j ], i, j, cache );
 
-					const offset = uniform.__offset;
+				}
 
-					const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ];
+			} else {
 
-					let arrayOffset = 0;
+				updateUniform( uniformItem, i, 0, cache );
 
-					for ( let k = 0; k < values.length; k ++ ) {
+			}
 
-						const value = values[ k ];
+		}
 
-						const info = getUniformSize( value );
+		gl.bindBuffer( gl.UNIFORM_BUFFER, null );
 
-						// TODO add integer and struct support
-						if ( typeof value === 'number' || typeof value === 'boolean' ) {
+	}
 
-							uniform.__data[ 0 ] = value;
-							gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data );
+	function updateUniform( uniform, index, indexArray, cache ) {
 
-						} else if ( value.isMatrix3 ) {
+		if ( hasUniformChanged( uniform, index, indexArray, cache ) === true ) {
 
-							// manually converting 3x3 to 3x4
+			const offset = uniform.__offset;
+			const value = uniform.value;
 
-							uniform.__data[ 0 ] = value.elements[ 0 ];
-							uniform.__data[ 1 ] = value.elements[ 1 ];
-							uniform.__data[ 2 ] = value.elements[ 2 ];
-							uniform.__data[ 3 ] = 0;
-							uniform.__data[ 4 ] = value.elements[ 3 ];
-							uniform.__data[ 5 ] = value.elements[ 4 ];
-							uniform.__data[ 6 ] = value.elements[ 5 ];
-							uniform.__data[ 7 ] = 0;
-							uniform.__data[ 8 ] = value.elements[ 6 ];
-							uniform.__data[ 9 ] = value.elements[ 7 ];
-							uniform.__data[ 10 ] = value.elements[ 8 ];
-							uniform.__data[ 11 ] = 0;
+			if ( Array.isArray( value ) ) {
 
-						} else if ( ArrayBuffer.isView( value ) ) {
+				let arrayOffset = 0;
 
-							// copy the buffer data using "set"
-							uniform.__data.set( new value.constructor( value.buffer, value.byteOffset, uniform.__data.length ) );
+				for ( let k = 0; k < value.length; k ++ ) {
 
-						} else {
+					const val = value[ k ];
+					const info = getUniformSize( val );
 
-							value.toArray( uniform.__data, arrayOffset );
+					writeUniformValue( val, uniform.__data, arrayOffset );
 
-							arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT;
+					// only toArray() values advance arrayOffset
+					if ( typeof val !== 'number' && typeof val !== 'boolean' && ! val.isMatrix3 && ! ArrayBuffer.isView( val ) ) {
 
-						}
+						arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT;
 
 					}
 
-					gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data );
-
 				}
 
+			} else {
+
+				writeUniformValue( value, uniform.__data, 0 );
+
 			}
 
+			gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data );
+
 		}
 
-		gl.bindBuffer( gl.UNIFORM_BUFFER, null );
+	}
+
+	function writeUniformValue( value, data, offset ) {
+
+		// TODO add integer and struct support
+		if ( typeof value === 'number' || typeof value === 'boolean' ) {
+
+			data[ 0 ] = value;
+
+		} else if ( value.isMatrix3 ) {
+
+			// manually converting 3x3 to 3x4
+
+			data[ 0 ] = value.elements[ 0 ];
+			data[ 1 ] = value.elements[ 1 ];
+			data[ 2 ] = value.elements[ 2 ];
+			data[ 3 ] = 0;
+			data[ 4 ] = value.elements[ 3 ];
+			data[ 5 ] = value.elements[ 4 ];
+			data[ 6 ] = value.elements[ 5 ];
+			data[ 7 ] = 0;
+			data[ 8 ] = value.elements[ 6 ];
+			data[ 9 ] = value.elements[ 7 ];
+			data[ 10 ] = value.elements[ 8 ];
+			data[ 11 ] = 0;
+
+		} else if ( ArrayBuffer.isView( value ) ) {
+
+			// copy the buffer data using "set"
+			data.set( new value.constructor( value.buffer, value.byteOffset, data.length ) );
+
+		} else {
+
+			value.toArray( data, offset );
+
+		}
 
 	}
 
@@ -17627,7 +17663,7 @@ class WebGLRenderer {
 
 			if ( _this.sortObjects === true ) {
 
-				currentRenderList.sort( _opaqueSort, _transparentSort );
+				currentRenderList.sort( _opaqueSort, _transparentSort, camera.reversedDepth );
 
 			}
 
@@ -17642,6 +17678,8 @@ class WebGLRenderer {
 
 			this.info.render.frame ++;
 
+			if ( this.info.autoReset === true ) this.info.reset();
+
 			if ( _clippingEnabled === true ) clipping.beginShadows();
 
 			const shadowsArray = currentRenderState.state.shadowsArray;
@@ -17652,8 +17690,6 @@ class WebGLRenderer {
 
 			//
 
-			if ( this.info.autoReset === true ) this.info.reset();
-
 			// render scene (skip if first effect is a render pass - it will render the scene itself)
 
 			const skipSceneRender = useOutput && output.hasRenderPass();

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/three.module.min.js


Файловите разлики са ограничени, защото са твърде много
+ 505 - 622
build/three.webgpu.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/three.webgpu.min.js


Файловите разлики са ограничени, защото са твърде много
+ 505 - 622
build/three.webgpu.nodes.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/three.webgpu.nodes.min.js


+ 1 - 1
docs/TSL.md

@@ -490,7 +490,7 @@ It's possible use `xyzw`, `rgba` or `stpq`.
 | Name | Description |
 | -- | -- |
 | `.add( node \| value, ... )` | Return the addition of two or more value. |
-| `.sub( node \| value )` | Return the subraction of two or more value. |
+| `.sub( node \| value )` | Return the subtraction of two or more value. |
 | `.mul( node \| value )` | Return the multiplication of two or more value. |
 | `.div( node \| value )` | Return the division of two or more value. |
 | `.mod( node \| value )` | Computes the remainder of dividing the first node by the second. |

+ 2 - 2
docs/index.html

@@ -1091,7 +1091,7 @@
 						<li><a href="TSL.html#code">code</a></li>
 						<li><a href="TSL.html#colorBleeding">colorBleeding</a></li>
 						<li><a href="TSL.html#colorSpaceToWorking">colorSpaceToWorking</a></li>
-						<li><a href="TSL.html#colorToDirection">colorToDirection</a></li>
+						<li><a href="TSL.html#unpackRGBToNormal">unpackRGBToNormal</a></li>
 						<li><a href="TSL.html#compute">compute</a></li>
 						<li><a href="TSL.html#computeBuiltin">computeBuiltin</a></li>
 						<li><a href="TSL.html#computeKernel">computeKernel</a></li>
@@ -1126,7 +1126,7 @@
 						<li><a href="TSL.html#difference">difference</a></li>
 						<li><a href="TSL.html#diffuseColor">diffuseColor</a></li>
 						<li><a href="TSL.html#diffuseContribution">diffuseContribution</a></li>
-						<li><a href="TSL.html#directionToColor">directionToColor</a></li>
+						<li><a href="TSL.html#packNormalToRGB">packNormalToRGB</a></li>
 						<li><a href="TSL.html#directionToFaceDirection">directionToFaceDirection</a></li>
 						<li><a href="TSL.html#dispersion">dispersion</a></li>
 						<li><a href="TSL.html#distance">distance</a></li>

+ 5 - 5
docs/llms-full.txt

@@ -1403,8 +1403,8 @@ const matcap = texture( matcapMap, matcapUV );
 
 | Variable | Description | Type |
 | -- | -- | -- |
-| `directionToColor( value )` | Converts direction vector to color. | `color` |
-| `colorToDirection( value )` | Converts color to direction vector. | `vec3` |
+| `packNormalToRGB( value )` | Converts normal vector to color. | `color` |
+| `unpackRGBToNormal(  value )` | Converts color to normal vector. | `vec3` |
 
 ## Render Pipeline
 
@@ -1442,18 +1442,18 @@ MRT allows capturing multiple outputs from a single render pass. Instead of rend
 Use `setMRT()` with the `mrt()` function to define which outputs to capture:
 
 ```js
-import { pass, mrt, output, normalView, velocity, directionToColor } from 'three/tsl';
+import { pass, mrt, output, normalView, velocity, packNormalToRGB } from 'three/tsl';
 
 const scenePass = pass( scene, camera );
 
 scenePass.setMRT( mrt( {
 	output: output,                          // Final color output
-	normal: directionToColor( normalView ),  // View-space normals encoded as colors
+	normal: packNormalToRGB( normalView ),  // View-space normals encoded as colors
 	velocity: velocity                       // Motion vectors for temporal effects
 } ) );
 ```
 
-Each MRT entry accepts any TSL node, allowing you to customize outputs using formulas, encoders, or material accessors. For example, `directionToColor( normalView )` encodes view-space normals into RGB values. You can use any TSL function to transform, combine, or encode data before writing to the render target.
+Each MRT entry accepts any TSL node, allowing you to customize outputs using formulas, encoders, or material accessors. For example, `packNormalToRGB( normalView )` encodes view-space normals into RGB values. You can use any TSL function to transform, combine, or encode data before writing to the render target.
 
 Within a TSL function `Fn( ( { material, object } ) => { ... } )`, you have complete access to the current material and object being rendered, enabling full customization of outputs.
 

+ 2 - 2
docs/pages/TSL.html

@@ -3487,7 +3487,7 @@ Simulates the analog signal bleeding in CRT displays where colors
 							</tbody>
 						</table>
 					</div>
-					<h3 class="name name-method" id="colorToDirection" translate="no">.<a href="#colorToDirection">colorToDirection</a><span class="signature">( node : <span class="param-type"><a href="Node.html">Node</a>.&lt;vec3></span> )</span><span class="type-signature"> : <a href="Node.html">Node</a>.&lt;vec3></span> </h3>
+					<h3 class="name name-method" id="unpackRGBToNormal" translate="no">.<a href="#unpackRGBToNormal">unpackRGBToNormal</a><span class="signature">( node : <span class="param-type"><a href="Node.html">Node</a>.&lt;vec3></span> )</span><span class="type-signature"> : <a href="Node.html">Node</a>.&lt;vec3></span> </h3>
 					<div class="method">
 						<div class="description">
 							<p>Unpacks a color value into a direction vector.</p>
@@ -4251,7 +4251,7 @@ densening fog farther from the camera.</p>
 							</tbody>
 						</table>
 					</div>
-					<h3 class="name name-method" id="directionToColor" translate="no">.<a href="#directionToColor">directionToColor</a><span class="signature">( node : <span class="param-type"><a href="Node.html">Node</a>.&lt;vec3></span> )</span><span class="type-signature"> : <a href="Node.html">Node</a>.&lt;vec3></span> </h3>
+					<h3 class="name name-method" id="packNormalToRGB" translate="no">.<a href="#packNormalToRGB">packNormalToRGB</a><span class="signature">( node : <span class="param-type"><a href="Node.html">Node</a>.&lt;vec3></span> )</span><span class="type-signature"> : <a href="Node.html">Node</a>.&lt;vec3></span> </h3>
 					<div class="method">
 						<div class="description">
 							<p>Packs a direction vector into a color value.</p>

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
editor/examples/arkanoid.app.json


+ 1 - 1
editor/index.html

@@ -54,7 +54,6 @@
 		<script src="js/libs/ternjs/comment.js"></script>
 		<script src="js/libs/ternjs/infer.js"></script>
 		<script src="js/libs/ternjs/doc_comment.js"></script>
-		<script src="js/libs/tern-threejs/threejs.js"></script>
 		<script src="js/libs/signals.min.js"></script>
 
 		<script type="module">
@@ -196,6 +195,7 @@
 
 				const signals = editor.signals;
 
+				signals.cameraResetted.add( saveState );
 				signals.geometryChanged.add( saveState );
 				signals.objectAdded.add( saveState );
 				signals.objectChanged.add( saveState );

+ 15 - 6
editor/js/Animation.js

@@ -367,7 +367,14 @@ function Animation( editor ) {
 
 			clipRow.addEventListener( 'click', function () {
 
-				editor.select( root );
+				if ( editor.selected !== root ) {
+
+					signals.objectSelected.remove( selectDefaultClip );
+					editor.select( root );
+					signals.objectSelected.add( selectDefaultClip );
+
+				}
+
 				selectClip( clip, root );
 				update(); // Refresh to update highlighting
 
@@ -578,10 +585,7 @@ function Animation( editor ) {
 
 	}
 
-	updateTime();
-
-	// Auto-select clip when an object with animations is selected
-	signals.objectSelected.add( function ( object ) {
+	function selectDefaultClip( object ) {
 
 		if ( object !== null && object.animations && object.animations.length > 0 ) {
 
@@ -590,7 +594,12 @@ function Animation( editor ) {
 
 		}
 
-	} );
+	}
+
+	updateTime();
+
+	// Auto-select clip when an object with animations is selected
+	signals.objectSelected.add( selectDefaultClip );
 
 	// Update when scene changes
 	signals.editorCleared.add( clear );

+ 5 - 1
editor/js/Config.js

@@ -15,6 +15,8 @@ function Config() {
 		'project/editable': false,
 		'project/vr': false,
 
+		'project/camera': 'perspective',
+
 		'project/renderer/type': 'WebGLRenderer',
 		'project/renderer/antialias': true,
 		'project/renderer/shadows': true,
@@ -28,7 +30,9 @@ function Config() {
 		'settings/shortcuts/rotate': 'e',
 		'settings/shortcuts/scale': 'r',
 		'settings/shortcuts/undo': 'z',
-		'settings/shortcuts/focus': 'f'
+		'settings/shortcuts/focus': 'f',
+		'settings/shortcuts/perspective': 'p',
+		'settings/shortcuts/orthographic': 'o'
 	};
 
 	if ( window.localStorage[ name ] === undefined ) {

+ 66 - 0
editor/js/Editor.js

@@ -11,6 +11,7 @@ var _DEFAULT_CAMERA = new THREE.PerspectiveCamera( 50, 1, 0.001, 1e10 );
 _DEFAULT_CAMERA.name = 'Camera';
 _DEFAULT_CAMERA.position.set( 0, 5, 10 );
 _DEFAULT_CAMERA.lookAt( new THREE.Vector3() );
+const _ORTHOGRAPHIC_FRUSTUM_SIZE = 100;
 
 function Editor() {
 
@@ -551,6 +552,68 @@ Editor.prototype = {
 
 	},
 
+	setCameraType: function ( type ) {
+
+		const oldCamera = this.camera;
+
+		const isOrthographic = oldCamera.isOrthographicCamera === true;
+
+		if ( ( type === 'orthographic' && isOrthographic ) || ( type === 'perspective' && ! isOrthographic ) ) return;
+
+		// the orbit point the framing should be preserved around
+
+		const center = this.controls ? this.controls.center : new THREE.Vector3();
+		const distance = oldCamera.position.distanceTo( center );
+
+		let newCamera;
+
+		if ( type === 'orthographic' ) {
+
+			const halfSize = _ORTHOGRAPHIC_FRUSTUM_SIZE / 2;
+			newCamera = new THREE.OrthographicCamera( - halfSize, halfSize, halfSize, - halfSize, 0, 10000 );
+			newCamera.position.copy( oldCamera.position );
+			newCamera.quaternion.copy( oldCamera.quaternion );
+
+			// derive the zoom so the orthographic framing matches the perspective view at the orbit center
+
+			const halfFOV = THREE.MathUtils.DEG2RAD * oldCamera.fov / 2;
+			newCamera.zoom = ( newCamera.top - newCamera.bottom ) / ( 2 * Math.max( distance, 0.0001 ) * Math.tan( halfFOV ) );
+
+		} else {
+
+			newCamera = new THREE.PerspectiveCamera( 50, 1, 0.001, 1e10 );
+			newCamera.quaternion.copy( oldCamera.quaternion );
+
+			// reposition along the view direction so the perspective framing matches the orthographic view
+
+			const halfFOV = THREE.MathUtils.DEG2RAD * newCamera.fov / 2;
+			const targetDistance = ( oldCamera.top - oldCamera.bottom ) / ( 2 * oldCamera.zoom * Math.tan( halfFOV ) );
+
+			const offset = new THREE.Vector3().subVectors( oldCamera.position, center );
+			if ( offset.lengthSq() === 0 ) offset.set( 0, 0, 1 ).applyQuaternion( oldCamera.quaternion );
+			offset.normalize().multiplyScalar( targetDistance );
+
+			newCamera.position.copy( center ).add( offset );
+
+		}
+
+		newCamera.name = oldCamera.name;
+		newCamera.uuid = oldCamera.uuid;
+		newCamera.updateProjectionMatrix();
+
+		this.camera = newCamera;
+		this.cameras[ newCamera.uuid ] = newCamera;
+
+		if ( this.viewportCamera === oldCamera ) this.viewportCamera = newCamera;
+
+		this.signals.cameraResetted.dispatch();
+
+		// keep the selection (and thus the sidebar) in sync with the new camera instance
+
+		if ( this.selected === oldCamera ) this.select( newCamera );
+
+	},
+
 	setViewportCamera: function ( uuid ) {
 
 		this.viewportCamera = this.cameras[ uuid ] || this.camera;
@@ -629,6 +692,7 @@ Editor.prototype = {
 		this.history.clear();
 		this.storage.clear();
 
+		this.setCameraType( 'perspective' );
 		this.camera.copy( _DEFAULT_CAMERA );
 		this.signals.cameraResetted.dispatch();
 
@@ -676,6 +740,8 @@ Editor.prototype = {
 		var loader = new THREE.ObjectLoader();
 		var camera = await loader.parseAsync( json.camera );
 
+		this.setCameraType( camera.isOrthographicCamera ? 'orthographic' : 'perspective' );
+
 		const existingUuid = this.camera.uuid;
 		const incomingUuid = camera.uuid;
 

+ 30 - 6
editor/js/EditorControls.js

@@ -40,6 +40,12 @@ class EditorControls extends THREE.EventDispatcher {
 
 		var changeEvent = { type: 'change' };
 
+		this.setCamera = function ( camera ) {
+
+			object = camera;
+
+		};
+
 		this.focus = function ( target ) {
 
 			var distance;
@@ -66,13 +72,22 @@ class EditorControls extends THREE.EventDispatcher {
 
 			object.position.copy( center ).add( delta );
 
+			if ( object.isOrthographicCamera ) {
+
+				object.zoom = ( object.top - object.bottom ) / ( distance * 2 );
+				object.updateProjectionMatrix();
+
+			}
+
 			scope.dispatchEvent( changeEvent );
 
 		};
 
 		this.pan = function ( delta ) {
 
-			var distance = object.position.distanceTo( center );
+			var distance = object.isOrthographicCamera
+				? ( object.top - object.bottom ) / object.zoom
+				: object.position.distanceTo( center );
 
 			delta.multiplyScalar( distance * scope.panSpeed );
 			delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
@@ -86,15 +101,24 @@ class EditorControls extends THREE.EventDispatcher {
 
 		this.zoom = function ( delta ) {
 
-			var distance = object.position.distanceTo( center );
+			if ( object.isOrthographicCamera ) {
 
-			delta.multiplyScalar( distance * scope.zoomSpeed );
+				object.zoom = Math.max( 0.0001, object.zoom * Math.pow( 0.95, delta.z ) );
+				object.updateProjectionMatrix();
 
-			if ( delta.length() > distance ) return;
+			} else {
 
-			delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+				var distance = object.position.distanceTo( center );
 
-			object.position.add( delta );
+				delta.multiplyScalar( distance * scope.zoomSpeed );
+
+				if ( delta.length() > distance ) return;
+
+				delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+
+				object.position.add( delta );
+
+			}
 
 			scope.dispatchEvent( changeEvent );
 

+ 3 - 1
editor/js/Menubar.Add.js

@@ -526,7 +526,9 @@ function MenubarAdd( editor ) {
 	option.setTextContent( strings.getKey( 'menubar/add/camera/orthographic' ) );
 	option.onClick( function () {
 
-		const aspect = editor.camera.aspect;
+		const aspect = editor.camera.isPerspectiveCamera
+			? editor.camera.aspect
+			: ( editor.camera.right - editor.camera.left ) / ( editor.camera.top - editor.camera.bottom );
 		const camera = new THREE.OrthographicCamera( - aspect, aspect );
 		camera.name = 'OrthographicCamera';
 

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

@@ -1,5 +1,5 @@
 import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js';
-import { FileLoader } from 'three';
+import { FileLoader, PropertyBinding } from 'three';
 
 function MenubarFile( editor ) {
 
@@ -276,6 +276,15 @@ function MenubarFile( editor ) {
 	option.onClick( async function () {
 
 		const scene = editor.scene;
+
+		if ( needsUniqueNames( scene ) ) { // see #25179
+
+			if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return;
+
+			ensureUniqueNames( scene );
+
+		}
+
 		const animations = getAnimations( scene );
 
 		const optimizedAnimations = [];
@@ -307,6 +316,15 @@ function MenubarFile( editor ) {
 	option.onClick( async function () {
 
 		const scene = editor.scene;
+
+		if ( needsUniqueNames( scene ) ) { // see #25179
+
+			if ( confirm( strings.getKey( 'prompt/file/export/duplicateNames' ) ) === false ) return;
+
+			ensureUniqueNames( scene );
+
+		}
+
 		const animations = getAnimations( scene );
 
 		const optimizedAnimations = [];
@@ -460,6 +478,107 @@ function MenubarFile( editor ) {
 
 	}
 
+	function needsUniqueNames( scene ) {
+
+		const usedNames = new Set();
+		let duplicate = false;
+		let animated = false;
+
+		scene.traverse( function ( object ) {
+
+			if ( object.animations.length > 0 ) animated = true;
+
+			if ( object.name === '' ) return;
+
+			if ( usedNames.has( object.name ) ) duplicate = true;
+
+			usedNames.add( object.name );
+
+		} );
+
+		return duplicate && animated;
+
+	}
+
+	// Gives every object a unique name and keeps the animation tracks that
+	// reference them by name in sync. The renamed scene mirrors the result of a
+	// glTF round-trip, where the loader makes all names unique, too.
+
+	function ensureUniqueNames( scene ) {
+
+		// Resolve each track's target object up front, scoped to the object that
+		// owns the clip. This disambiguates colliding names before they change.
+
+		const trackBindings = [];
+
+		scene.traverse( function ( owner ) {
+
+			for ( const clip of owner.animations ) {
+
+				for ( const track of clip.tracks ) {
+
+					const nodeName = PropertyBinding.parseTrackName( track.name ).nodeName;
+					const target = PropertyBinding.findNode( owner, nodeName );
+
+					// References by UUID stay valid, so only track name-based ones.
+
+					if ( target !== null && target.name === nodeName ) {
+
+						trackBindings.push( { track, target, nodeName } );
+
+					}
+
+				}
+
+			}
+
+		} );
+
+		// Assign a unique name to every named object.
+
+		let changed = false;
+		const usedNames = new Set();
+
+		scene.traverse( function ( object ) {
+
+			if ( object.name === '' ) return;
+
+			if ( usedNames.has( object.name ) ) {
+
+				let suffix = 1, name;
+				do {
+
+					name = object.name + '_' + ( suffix ++ );
+
+				} while ( usedNames.has( name ) );
+
+				object.name = name;
+				changed = true;
+
+			}
+
+			usedNames.add( object.name );
+
+		} );
+
+		if ( changed === false ) return;
+
+		// Point the affected tracks at their renamed targets.
+
+		for ( const { track, target, nodeName } of trackBindings ) {
+
+			if ( target.name !== nodeName ) {
+
+				track.name = target.name + track.name.slice( nodeName.length );
+
+			}
+
+		}
+
+		editor.signals.sceneGraphChanged.dispatch();
+
+	}
+
 	return container;
 
 }

+ 17 - 2
editor/js/Menubar.Render.js

@@ -202,13 +202,28 @@ class RenderImageDialog {
 			const loader = new THREE.ObjectLoader();
 
 			const camera = await loader.parseAsync( json.camera );
-			camera.aspect = imageWidth.getValue() / imageHeight.getValue();
+
+			const aspect = imageWidth.getValue() / imageHeight.getValue();
+
+			if ( camera.isPerspectiveCamera ) {
+
+				camera.aspect = aspect;
+
+			} else {
+
+				const frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
+
+			}
+
 			camera.updateProjectionMatrix();
 			camera.updateMatrixWorld();
 
 			const scene = await loader.parseAsync( json.scene );
 
-			const renderer = new THREE.WebGLRenderer( { antialias: true, logarithmicDepthBuffer: true } );
+			const renderer = new THREE.WebGLRenderer( { antialias: true, reversedDepthBuffer: true } );
 			renderer.setSize( imageWidth.getValue(), imageHeight.getValue() );
 			renderer.setClearColor( editor.viewportColor );
 

+ 31 - 2
editor/js/Script.js

@@ -3,6 +3,8 @@ import { UIElement, UIPanel, UIText } from './libs/ui.js';
 import { SetScriptValueCommand } from './commands/SetScriptValueCommand.js';
 import { SetMaterialValueCommand } from './commands/SetMaterialValueCommand.js';
 
+import buildThreeDefs from './libs/tern-threejs/build-defs.js';
+
 function Script( editor ) {
 
 	const signals = editor.signals;
@@ -291,10 +293,35 @@ function Script( editor ) {
 	// tern js autocomplete
 
 	const server = new CodeMirror.TernServer( {
-		caseInsensitive: true,
-		plugins: { threejs: null }
+		caseInsensitive: true
 	} );
 
+	// The three.js API definitions are built lazily from the JSDoc in the library
+	// build the first time the script editor is opened.
+
+	let threeDefsRequested = false;
+
+	async function loadThreeDefs() {
+
+		if ( threeDefsRequested ) return;
+		threeDefsRequested = true;
+
+		try {
+
+			const url = new URL( '../build/three.core.js', document.baseURI ).href;
+			const source = await ( await fetch( url ) ).text();
+
+			server.server.defs.push( buildThreeDefs( source ) );
+			server.server.reset();
+
+		} catch ( error ) {
+
+			console.warn( 'Script: Failed to build three.js autocomplete defs.', error );
+
+		}
+
+	}
+
 	codemirror.setOption( 'extraKeys', {
 		'Ctrl-Space': function ( cm ) {
 
@@ -448,6 +475,8 @@ function Script( editor ) {
 		currentScript = script;
 		currentObject = object;
 
+		if ( mode === 'javascript' ) loadThreeDefs();
+
 		container.setDisplay( '' );
 		codemirror.setValue( source );
 		codemirror.clearHistory();

+ 35 - 2
editor/js/Sidebar.Project.Renderer.js

@@ -15,6 +15,30 @@ function SidebarProjectRenderer( editor ) {
 	const container = new UIPanel();
 	container.setBorderTop( '0px' );
 
+	// Camera
+
+	const cameraRow = new UIRow();
+	container.add( cameraRow );
+
+	cameraRow.add( new UIText( strings.getKey( 'sidebar/project/camera' ) ).setClass( 'Label' ) );
+
+	const cameraTypeSelect = new UISelect().setOptions( {
+		'perspective': 'Perspective',
+		'orthographic': 'Orthographic'
+	} ).setWidth( '150px' ).onChange( function () {
+
+		editor.setCameraType( this.getValue() );
+
+	} );
+	cameraTypeSelect.setValue( config.getKey( 'project/camera' ) );
+	cameraRow.add( cameraTypeSelect );
+
+	if ( config.getKey( 'project/camera' ) === 'orthographic' ) {
+
+		editor.setCameraType( 'orthographic' );
+
+	}
+
 	// Renderer
 
 	const rendererRow = new UIRow();
@@ -111,12 +135,12 @@ function SidebarProjectRenderer( editor ) {
 
 		if ( rendererType === 'WebGPURenderer' ) {
 
-			currentRenderer = new WebGPURenderer( { antialias: antialias, logarithmicDepthBuffer: true } );
+			currentRenderer = new WebGPURenderer( { antialias: antialias, reversedDepthBuffer: true } );
 			await currentRenderer.init();
 
 		} else {
 
-			currentRenderer = new THREE.WebGLRenderer( { antialias: antialias, logarithmicDepthBuffer: true } );
+			currentRenderer = new THREE.WebGLRenderer( { antialias: antialias, reversedDepthBuffer: true } );
 
 		}
 
@@ -135,6 +159,15 @@ function SidebarProjectRenderer( editor ) {
 
 	// Signals
 
+	signals.cameraResetted.add( function () {
+
+		const type = editor.camera.isOrthographicCamera ? 'orthographic' : 'perspective';
+
+		cameraTypeSelect.setValue( type );
+		config.setKey( 'project/camera', type );
+
+	} );
+
 	signals.editorCleared.add( function () {
 
 		currentRenderer.shadowMap.enabled = true;

+ 2 - 0
editor/js/Sidebar.Scene.js

@@ -503,6 +503,8 @@ function SidebarScene( editor ) {
 
 	signals.sceneGraphChanged.add( refreshUI );
 
+	signals.cameraResetted.add( refreshUI );
+
 	signals.objectChanged.add( function ( object ) {
 
 		const options = outliner.options;

+ 13 - 1
editor/js/Sidebar.Settings.Shortcuts.js

@@ -24,7 +24,7 @@ function SidebarSettingsShortcuts( editor ) {
 	headerRow.add( new UIText( strings.getKey( 'sidebar/settings/shortcuts' ).toUpperCase() ) );
 	container.add( headerRow );
 
-	const shortcuts = [ 'translate', 'rotate', 'scale', 'undo', 'focus' ];
+	const shortcuts = [ 'translate', 'rotate', 'scale', 'undo', 'focus', 'perspective', 'orthographic' ];
 
 	function createShortcutInput( name ) {
 
@@ -175,6 +175,18 @@ function SidebarSettingsShortcuts( editor ) {
 
 				break;
 
+			case config.getKey( 'settings/shortcuts/perspective' ):
+
+				editor.setCameraType( 'perspective' );
+
+				break;
+
+			case config.getKey( 'settings/shortcuts/orthographic' ):
+
+				editor.setCameraType( 'orthographic' );
+
+				break;
+
 		}
 
 	} );

+ 9 - 0
editor/js/Strings.js

@@ -8,6 +8,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': 'خطایی در باز کردن پروژه پیش آمده',
 			'prompt/file/export/noMeshSelected': 'هیچ Mesh ای انتخاب نکردید',
 			'prompt/file/export/noObjectSelected': 'هیچ آبجکتی انتخاب نکردید!',
+			'prompt/file/export/duplicateNames': 'Some objects share the same name. They will be renamed to ensure unique names. Are you sure?',
 			'prompt/script/remove': 'آیا اطمینان دارید؟',
 			'prompt/history/clear': 'هیستوری قبل و بعد (undo / redo) پاک خواهند شد آیا مطمئنید؟',
 			'prompt/history/preserve': 'The history will be preserved across sessions.\nThis can have an impact on performance when working with textures.',
@@ -456,6 +457,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': 'Failed to open project!',
 			'prompt/file/export/noMeshSelected': 'No Mesh selected!',
 			'prompt/file/export/noObjectSelected': 'No Object selected!',
+			'prompt/file/export/duplicateNames': 'Some objects share the same name. They will be renamed to ensure unique names. Are you sure?',
 			'prompt/script/remove': 'Are you sure?',
 			'prompt/history/clear': 'The Undo/Redo History will be cleared. Are you sure?',
 			'prompt/history/preserve': 'The history will be preserved across sessions.\nThis can have an impact on performance when working with textures.',
@@ -813,6 +815,7 @@ function Strings( config ) {
 			'sidebar/script/remove': 'Remove',
 
 			'sidebar/project': 'Project',
+			'sidebar/project/camera': 'Camera',
 			'sidebar/project/renderer': 'Renderer',
 			'sidebar/project/antialias': 'Antialias',
 			'sidebar/project/shadows': 'Shadows',
@@ -847,6 +850,8 @@ function Strings( config ) {
 			'sidebar/settings/shortcuts/scale': 'Scale',
 			'sidebar/settings/shortcuts/undo': 'Undo',
 			'sidebar/settings/shortcuts/focus': 'Focus',
+			'sidebar/settings/shortcuts/perspective': 'Perspective',
+			'sidebar/settings/shortcuts/orthographic': 'Orthographic',
 
 			'sidebar/history': 'History',
 			'sidebar/history/clear': 'Clear',
@@ -905,6 +910,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': 'Échec de l\'ouverture du projet !',
 			'prompt/file/export/noMeshSelected': 'Aucun maillage sélectionné !',
 			'prompt/file/export/noObjectSelected': 'Aucun objet sélectionné !',
+			'prompt/file/export/duplicateNames': 'Certains objets portent le même nom. Ils seront renommés afin de garantir des noms uniques. Êtes-vous sûr ?',
 			'prompt/script/remove': 'Es-tu sûr?',
 			'prompt/history/clear': 'L\'historique d\'annulation/rétablissement sera effacé Êtes-vous sûr ?',
 			'prompt/history/preserve': 'L\'histoire sera conservée entre les sessions.\nCela peut avoir un impact sur les performances lors de la manipulation des textures.',
@@ -1354,6 +1360,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': '无法打开项目!',
 			'prompt/file/export/noMeshSelected': '未选择网格!',
 			'prompt/file/export/noObjectSelected': '未选择对象!',
+			'prompt/file/export/duplicateNames': '部分对象具有相同的名称。它们将被重命名以确保名称唯一。确定吗?',
 			'prompt/script/remove': '你确定吗?',
 			'prompt/history/clear': '撤销/重做历史记录将被清除。您确定吗?',
 			'prompt/history/preserve': '历史将在会话之间保留。\n这可能会影响在处理纹理时的性能。',
@@ -1803,6 +1810,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': 'プロジェクトを開くことができませんでした!',
 			'prompt/file/export/noMeshSelected': 'メッシュが選択されていません!',
 			'prompt/file/export/noObjectSelected': 'オブジェクトが選択されていません!',
+			'prompt/file/export/duplicateNames': '一部のオブジェクトの名前が重複しています。名前を一意にするために変更します。よろしいですか?',
 			'prompt/script/remove': '本気ですか?',
 			'prompt/history/clear': '元に戻す/やり直しの履歴が消去されます。 本気ですか?',
 			'prompt/history/preserve': '履歴はセッションをまたいで保存されます。\nこれは、テクスチャを操作する際のパフォーマンスに影響を与える可能性があります。',
@@ -2251,6 +2259,7 @@ function Strings( config ) {
 			'prompt/file/failedToOpenProject': '프로젝트를 여는 데 실패했습니다!',
 			'prompt/file/export/noMeshSelected': '메시가 선택되지 않았습니다!',
 			'prompt/file/export/noObjectSelected': '객체가 선택되지 않았습니다!',
+			'prompt/file/export/duplicateNames': '일부 객체의 이름이 중복됩니다. 이름을 고유하게 만들기 위해 변경됩니다. 계속하시겠습니까?',
 			'prompt/script/remove': '삭제하시겠습니까?',
 			'prompt/history/clear': '되돌리기/다시하기 기록이 지워집니다. 진행하시겠습니까?',
 			'prompt/history/preserve': '기록은 세션을 통해 저장됩니다. 이는 텍스처를 조작할 때 성능에 영향을 미칠 수 있습니다.',

+ 12 - 1
editor/js/Viewport.XR.js

@@ -20,7 +20,18 @@ class XR {
 
 		const onSessionStarted = async ( session ) => {
 
-			camera.copy( editor.camera );
+			if ( editor.camera.isPerspectiveCamera ) {
+
+				camera.copy( editor.camera );
+
+			} else {
+
+				// an orthographic default camera can't be mirrored into a perspective XR camera
+
+				camera.position.copy( editor.camera.position );
+				camera.quaternion.copy( editor.camera.quaternion );
+
+			}
 
 			const sidebar = document.getElementById( 'sidebar' );
 			sidebar.style.width = '350px';

+ 21 - 4
editor/js/Viewport.js

@@ -39,7 +39,7 @@ function Viewport( editor ) {
 	let pmremGenerator = null;
 	let pathtracer = null;
 
-	const camera = editor.camera;
+	let camera = editor.camera;
 	const scene = editor.scene;
 	const sceneHelpers = editor.sceneHelpers;
 
@@ -166,8 +166,10 @@ function Viewport( editor ) {
 
 			} else {
 
-				camera.left = - aspect;
-				camera.right = aspect;
+				const frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
 
 			}
 
@@ -812,7 +814,22 @@ function Viewport( editor ) {
 
 	} );
 
-	signals.cameraResetted.add( updateAspectRatio );
+	signals.cameraResetted.add( function () {
+
+		if ( camera !== editor.camera ) {
+
+			camera = editor.camera;
+
+			controls.setCamera( camera );
+			transformControls.camera = camera;
+			viewHelper.camera = camera;
+
+		}
+
+		updateAspectRatio();
+		render();
+
+	} );
 
 	// animations
 

+ 23 - 6
editor/js/libs/app.js

@@ -34,12 +34,12 @@ const APP = {
 			if ( project.renderer === 'WebGPURenderer' ) {
 
 				const { WebGPURenderer } = await import( 'three/webgpu' );
-				renderer = new WebGPURenderer( { antialias: true, logarithmicDepthBuffer: true } );
+				renderer = new WebGPURenderer( { antialias: true, reversedDepthBuffer: true } );
 				await renderer.init();
 
 			} else {
 
-				renderer = new THREE.WebGLRenderer( { antialias: true, logarithmicDepthBuffer: true } );
+				renderer = new THREE.WebGLRenderer( { antialias: true, reversedDepthBuffer: true } );
 
 			}
 
@@ -125,8 +125,7 @@ const APP = {
 		this.setCamera = function ( value ) {
 
 			camera = value;
-			camera.aspect = this.width / this.height;
-			camera.updateProjectionMatrix();
+			setCameraAspect( camera, this.width / this.height );
 
 		};
 
@@ -155,8 +154,7 @@ const APP = {
 
 			if ( camera ) {
 
-				camera.aspect = this.width / this.height;
-				camera.updateProjectionMatrix();
+				setCameraAspect( camera, this.width / this.height );
 
 			}
 
@@ -168,6 +166,25 @@ const APP = {
 
 		};
 
+		function setCameraAspect( camera, aspect ) {
+
+			if ( camera.isPerspectiveCamera ) {
+
+				camera.aspect = aspect;
+
+			} else {
+
+				const frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
+
+			}
+
+			camera.updateProjectionMatrix();
+
+		}
+
 		function dispatch( array, event ) {
 
 			for ( let i = 0, l = array.length; i < l; i ++ ) {

+ 233 - 0
editor/js/libs/tern-threejs/build-defs.js

@@ -0,0 +1,233 @@
+// Builds Tern definitions for the three.js API by scanning the JSDoc comments
+// in a three.js build file (e.g. build/three.core.js).
+//
+// The build output is machine-formatted (one declaration per line, tab
+// indentation, every documented symbol preceded by a `/** ... */` block), so a
+// lightweight line scanner is enough — no full JS parser is required.
+//
+// Usage:  const defs = buildDefs( sourceText );
+//
+// `defs` is a Tern definitions object of the shape consumed by the editor's
+// TernServer:  { "!name": "threejs", "THREE": { Box3: { prototype: { ... } } } }
+
+// --- JSDoc type -> Tern type ------------------------------------------------
+
+function mapType( raw, classes ) {
+
+	if ( ! raw ) return null;
+
+	let type = raw.trim();
+
+	// strip a single leading/trailing brace pair if present
+	type = type.replace( /^\{/, '' ).replace( /\}$/, '' ).trim();
+
+	// unions: Tern has no union type, take the first member
+	if ( type.includes( '|' ) ) type = type.split( '|' )[ 0 ].trim();
+
+	// nullable / non-nullable / rest markers
+	type = type.replace( /^[?!]/, '' ).replace( /^\.\.\./, '' ).trim();
+
+	// Array forms: Array<X>, Array.<X>, X[]
+	let m = type.match( /^Array\.?<(.+)>$/ );
+	if ( m ) return '[' + ( mapType( m[ 1 ], classes ) || '?' ) + ']';
+	m = type.match( /^(.+)\[\]$/ );
+	if ( m ) return '[' + ( mapType( m[ 1 ], classes ) || '?' ) + ']';
+
+	switch ( type ) {
+
+		case 'number': case 'string': case 'boolean': return type;
+		case 'function': case 'Function': return 'fn()';
+		case '*': case 'any': case 'Object': case 'object':
+		case 'undefined': case 'null': case 'void': return '?';
+
+	}
+
+	if ( classes.has( type ) ) return '+THREE.' + type;
+
+	return '?'; // unknown (TypedArray, external types, generics, …)
+
+}
+
+// --- JSDoc block parser -----------------------------------------------------
+
+function parseDoc( lines ) {
+
+	const params = [];
+	let returns = null, atType = null, readonly = false;
+	const desc = [];
+
+	for ( let raw of lines ) {
+
+		const line = raw.replace( /^\s*\*?\s?/, '' ); // strip ` * `
+
+		const pm = line.match( /^@param\s+(\{[^}]*\})?\s*\[?([\w.]+)/ );
+		if ( pm ) { params.push( { type: pm[ 1 ] || null, name: pm[ 2 ].split( '.' )[ 0 ] } ); continue; }
+
+		const rm = line.match( /^@returns?\s+(\{[^}]*\})/ );
+		if ( rm ) { returns = rm[ 1 ]; continue; }
+
+		const tm = line.match( /^@type\s+(\{[^}]*\})/ );
+		if ( tm ) { atType = tm[ 1 ]; continue; }
+
+		if ( /^@readonly/.test( line ) ) { readonly = true; continue; }
+		if ( /^@/.test( line ) ) continue; // other tags ignored
+
+		if ( line.trim() ) desc.push( line.trim() );
+
+	}
+
+	// de-duplicate rest params that share a base name
+	const seen = new Set(), uniqueParams = [];
+	for ( const p of params ) if ( ! seen.has( p.name ) ) { seen.add( p.name ); uniqueParams.push( p ); }
+
+	return { params: uniqueParams, returns, atType, readonly, doc: desc.join( ' ' ) };
+
+}
+
+function fnType( doc, classes ) {
+
+	const args = doc.params.map( p => p.name + ': ' + ( mapType( p.type, classes ) || '?' ) ).join( ', ' );
+	let t = 'fn(' + args + ')';
+	const ret = mapType( doc.returns, classes );
+	if ( ret ) t += ' -> ' + ret;
+	return t;
+
+}
+
+function entry( type, doc ) {
+
+	const e = { '!type': type };
+	if ( doc.doc ) e[ '!doc' ] = doc.doc;
+	return e;
+
+}
+
+// --- main scan --------------------------------------------------------------
+
+export default function buildDefs( source ) {
+
+	const lines = source.split( '\n' );
+
+	// pass 1: collect class names (so types can resolve to +THREE.X)
+	const classes = new Set();
+	for ( const line of lines ) {
+
+		const m = line.match( /^class\s+(\w+)/ );
+		if ( m ) classes.add( m[ 1 ] );
+
+	}
+
+	const THREE = {};
+	let cur = null;        // current class def object
+	let curName = null;    // current class name
+	let pending = null;    // most recent parsed JSDoc block
+
+	for ( let i = 0; i < lines.length; i ++ ) {
+
+		const line = lines[ i ];
+
+		// JSDoc block: collect then resolve against the next code line below
+		const t = line.trim();
+		if ( t.startsWith( '/**' ) ) {
+
+			const block = [];
+			if ( ! t.endsWith( '*/' ) ) {
+
+				i ++;
+				for ( ; i < lines.length; i ++ ) {
+
+					if ( lines[ i ].trim().endsWith( '*/' ) ) break;
+					block.push( lines[ i ] );
+
+				}
+
+			}
+
+			pending = parseDoc( block );
+			continue;
+
+		}
+
+		// class declaration
+		const cm = line.match( /^class\s+(\w+)(?:\s+extends\s+(\w+))?/ );
+		if ( cm ) {
+
+			curName = cm[ 1 ];
+			cur = THREE[ curName ] || ( THREE[ curName ] = {} );
+			// Declaring the class with a function `!type` makes Tern treat it as a
+			// constructor, so `new THREE.X()` resolves to X.prototype. The signature
+			// is refined once the constructor's JSDoc is read.
+			cur[ '!type' ] = 'fn()';
+			cur.prototype = cur.prototype || {};
+			if ( cm[ 2 ] && classes.has( cm[ 2 ] ) ) cur.prototype[ '!proto' ] = 'THREE.' + cm[ 2 ] + '.prototype';
+			if ( pending && pending.doc ) cur[ '!doc' ] = pending.doc;
+			pending = null;
+			continue;
+
+		}
+
+		// end of a top-level class
+		if ( line === '}' ) { cur = null; curName = null; pending = null; continue; }
+
+		if ( ! cur || ! pending ) { if ( t === '' ) continue; pending = null; continue; }
+
+		// inside a class, the line right after a JSDoc block:
+
+		// instance field:  \t\tthis.name =
+		let m = line.match( /^\t\tthis\.(\w+)\s*=/ );
+		if ( m ) {
+
+			const ty = mapType( pending.atType, classes ) || '?';
+			cur.prototype[ m[ 1 ] ] = entry( ty, pending );
+			pending = null;
+			continue;
+
+		}
+
+		// static method:  \tstatic name(
+		m = line.match( /^\tstatic\s+(\w+)\s*\(/ );
+		if ( m ) {
+
+			cur[ m[ 1 ] ] = entry( fnType( pending, classes ), pending );
+			pending = null;
+			continue;
+
+		}
+
+		// accessor:  \tget name()  /  \tset name(
+		m = line.match( /^\t(?:get|set)\s+(\w+)\s*\(/ );
+		if ( m ) {
+
+			const ty = mapType( pending.atType || pending.returns, classes ) || '?';
+			if ( ! cur.prototype[ m[ 1 ] ] ) cur.prototype[ m[ 1 ] ] = entry( ty, pending );
+			pending = null;
+			continue;
+
+		}
+
+		// constructor:  \tconstructor(   -> refine the class signature
+		if ( /^\tconstructor\s*\(/.test( line ) ) {
+
+			cur[ '!type' ] = 'fn(' + pending.params.map( p => p.name + ': ' + ( mapType( p.type, classes ) || '?' ) ).join( ', ' ) + ')';
+			pending = null;
+			continue;
+
+		}
+
+		// instance method:  \t[async ]name(
+		m = line.match( /^\t(?:async\s+)?(\w+)\s*\(/ );
+		if ( m ) {
+
+			cur.prototype[ m[ 1 ] ] = entry( fnType( pending, classes ), pending );
+			pending = null;
+			continue;
+
+		}
+
+		pending = null;
+
+	}
+
+	return { '!name': 'threejs', THREE };
+
+}

Файловите разлики са ограничени, защото са твърде много
+ 0 - 268
editor/js/libs/tern-threejs/threejs.js


+ 1 - 1
examples/files.json

@@ -100,7 +100,6 @@
 		"webgl_loader_imagebitmap",
 		"webgl_loader_kmz",
 		"webgl_loader_ldraw",
-		"webgl_loader_lwo",
 		"webgl_loader_md2",
 		"webgl_loader_md2_control",
 		"webgl_loader_mdd",
@@ -471,6 +470,7 @@
 		"webgpu_shadowmap_vsm",
 		"webgpu_skinning",
 		"webgpu_skinning_instancing",
+		"webgpu_skinning_instancing_individual",
 		"webgpu_skinning_points",
 		"webgpu_sky",
 		"webgpu_sprites",

+ 16 - 6
examples/jsm/helpers/ViewHelper.js

@@ -49,6 +49,14 @@ class ViewHelper extends Object3D {
 		 */
 		this.isViewHelper = true;
 
+		/**
+		 * The camera whose transformation is visualized. It can be reassigned at
+		 * any time to rebind the helper to a different camera.
+		 *
+		 * @type {Camera}
+		 */
+		this.camera = camera;
+
 		/**
 		 * Whether the helper is currently animating or not.
 		 *
@@ -86,6 +94,8 @@ class ViewHelper extends Object3D {
 
 		const options = {};
 
+		const scope = this;
+
 		const interactiveObjects = [];
 		const raycaster = new Raycaster();
 		const mouse = new Vector2();
@@ -163,11 +173,11 @@ class ViewHelper extends Object3D {
 		 */
 		this.render = function ( renderer ) {
 
-			this.quaternion.copy( camera.quaternion ).invert();
+			this.quaternion.copy( this.camera.quaternion ).invert();
 			this.updateMatrixWorld();
 
 			point.set( 0, 0, 1 );
-			point.applyQuaternion( camera.quaternion );
+			point.applyQuaternion( this.camera.quaternion );
 
 			//
 
@@ -325,11 +335,11 @@ class ViewHelper extends Object3D {
 			// animate position by doing a slerp and then scaling the position on the unit sphere
 
 			q1.rotateTowards( q2, step );
-			camera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( this.center );
+			this.camera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( this.center );
 
 			// animate orientation
 
-			camera.quaternion.rotateTowards( targetQuaternion, step );
+			this.camera.quaternion.rotateTowards( targetQuaternion, step );
 
 			if ( q1.angleTo( q2 ) === 0 ) {
 
@@ -408,12 +418,12 @@ class ViewHelper extends Object3D {
 
 			//
 
-			radius = camera.position.distanceTo( focusPoint );
+			radius = scope.camera.position.distanceTo( focusPoint );
 			targetPosition.multiplyScalar( radius ).add( focusPoint );
 
 			dummy.position.copy( focusPoint );
 
-			dummy.lookAt( camera.position );
+			dummy.lookAt( scope.camera.position );
 			q1.copy( dummy.quaternion );
 
 			dummy.lookAt( targetPosition );

+ 3 - 3
examples/jsm/libs/demuxer_mp4.js

@@ -1,4 +1,4 @@
-import MP4Box from 'https://cdn.jsdelivr.net/npm/mp4box@0.5.3/+esm';
+import { createFile, DataStream } from 'https://cdn.jsdelivr.net/npm/mp4box@2.3.0/+esm';
 
 // From: https://w3c.github.io/webcodecs/samples/video-decode-display/
 
@@ -47,7 +47,7 @@ export class MP4Demuxer {
     this.#setStatus = setStatus;
 
     // Configure an MP4Box File for demuxing.
-    this.#file = MP4Box.createFile();
+    this.#file = createFile();
     this.#file.onError = error => setStatus("demux", error);
     this.#file.onReady = this.#onReady.bind(this);
     this.#file.onSamples = this.#onSamples.bind(this);
@@ -68,7 +68,7 @@ export class MP4Demuxer {
     for (const entry of trak.mdia.minf.stbl.stsd.entries) {
       const box = entry.avcC || entry.hvcC || entry.vpcC || entry.av1C;
       if (box) {
-        const stream = new MP4Box.DataStream(undefined, 0, MP4Box.DataStream.BIG_ENDIAN);
+        const stream = new DataStream(undefined, 0, DataStream.BIG_ENDIAN);
         box.write(stream);
         return new Uint8Array(stream.buffer, 8);  // Remove the box header.
       }

Файловите разлики са ограничени, защото са твърде много
+ 0 - 8
examples/jsm/libs/rhino3dm/rhino3dm.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 8
examples/jsm/libs/rhino3dm/rhino3dm.module.js


BIN
examples/jsm/libs/rhino3dm/rhino3dm.wasm


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

@@ -42,7 +42,7 @@ const _taskCache = new WeakMap();
  *
  * ```js
  * const loader = new Rhino3dmLoader();
- * loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' );
+ * loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.17.0/' );
  *
  * const object = await loader.loadAsync( 'models/3dm/Rhino_Logo.3dm' );
  * scene.add( object );

+ 38 - 28
examples/jsm/loaders/DRACOLoader.js

@@ -8,7 +8,8 @@ import {
 	LinearSRGBColorSpace,
 	SRGBColorSpace,
 	InterleavedBuffer,
-	InterleavedBufferAttribute
+	InterleavedBufferAttribute,
+	LoaderUtils
 } from 'three';
 
 const _taskCache = new WeakMap();
@@ -17,6 +18,11 @@ const WASM_BIN_URL = new URL( '../libs/draco/draco_decoder.wasm', import.meta.ur
 const WASM_JS_URL = new URL( '../libs/draco/draco_wasm_wrapper.js', import.meta.url ).toString();
 const JS_URL = new URL( '../libs/draco/draco_decoder.js', import.meta.url ).toString();
 
+const DRACO_GLTF_CONFIG = {
+	js: new URL( '../libs/draco/gltf/draco_wasm_wrapper.js', import.meta.url ).toString(),
+	wasm: new URL( '../libs/draco/gltf/draco_decoder.wasm', import.meta.url ).toString(),
+};
+
 /**
  * A loader for the Draco format.
  *
@@ -38,12 +44,10 @@ const JS_URL = new URL( '../libs/draco/draco_decoder.js', import.meta.url ).toSt
  *
  * ```js
  * const loader = new DRACOLoader();
- * loader.setDecoderPath( '/examples/jsm/libs/draco/' );
- *
- * const geometry = await dracoLoader.loadAsync( 'models/draco/bunny.drc' );
+ * const geometry = await loader.loadAsync( 'models/draco/bunny.drc' );
  * geometry.computeVertexNormals(); // optional
  *
- * dracoLoader.dispose();
+ * loader.dispose();
  * ```
  *
  * @augments Loader
@@ -60,7 +64,11 @@ class DRACOLoader extends Loader {
 
 		super( manager );
 
-		this.decoderPath = '';
+		this.decoderPaths = {
+			js: WASM_JS_URL,
+			wasm: WASM_BIN_URL,
+			dep_js: JS_URL,
+		};
 		this.decoderConfig = {};
 		this.decoderBinary = null;
 		this.decoderPending = null;
@@ -88,12 +96,25 @@ class DRACOLoader extends Loader {
 	/**
 	 * Provides configuration for the decoder libraries. Configuration cannot be changed after decoding begins.
 	 *
-	 * @param {string} path - The decoder path.
+	 * @param {string|{js:string, wasm:string}} path - The decoder path, or a config object with explicit URLs for each decoder file.
 	 * @return {DRACOLoader} A reference to this loader.
 	 */
 	setDecoderPath( path ) {
 
-		this.decoderPath = path;
+		const { decoderPaths } = this;
+		if ( typeof path === 'object' ) {
+
+			decoderPaths.js = path.js;
+			decoderPaths.wasm = path.wasm;
+			decoderPaths.dep_js = null;
+
+		} else {
+
+			decoderPaths.js = LoaderUtils.resolveURL( 'draco_wasm_wrapper.js', path );
+			decoderPaths.wasm = LoaderUtils.resolveURL( 'draco_decoder.wasm', path );
+			decoderPaths.dep_js = LoaderUtils.resolveURL( 'draco_decoder.js', path );
+
+		}
 
 		return this;
 
@@ -337,7 +358,6 @@ class DRACOLoader extends Loader {
 	_loadLibrary( url, responseType ) {
 
 		const loader = new FileLoader( this.manager );
-		loader.setPath( this.decoderPath );
 		loader.setResponseType( responseType );
 		loader.setWithCredentials( this.withCredentials );
 
@@ -364,31 +384,21 @@ class DRACOLoader extends Loader {
 		const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
 		const librariesPending = [];
 
-		if ( this.decoderPath === '' ) {
+		const { decoderPaths } = this;
+		if ( useJS ) {
 
-			if ( useJS ) {
+			if ( decoderPaths.dep_js === null ) {
 
-				librariesPending.push( this._loadLibrary( JS_URL, 'text' ) );
-
-			} else {
-
-				librariesPending.push( this._loadLibrary( WASM_JS_URL, 'text' ) );
-				librariesPending.push( this._loadLibrary( WASM_BIN_URL, 'arraybuffer' ) );
+				throw new Error( 'THREE.DRACOLoader: WebAssembly is required when using a custom decoder paths.' );
 
 			}
 
-		} else {
-
-			if ( useJS ) {
-
-				librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
+			librariesPending.push( this._loadLibrary( decoderPaths.dep_js, 'text' ) );
 
-			} else {
-
-				librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
-				librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
+		} else {
 
-			}
+			librariesPending.push( this._loadLibrary( decoderPaths.js, 'text' ) );
+			librariesPending.push( this._loadLibrary( decoderPaths.wasm, 'arraybuffer' ) );
 
 		}
 
@@ -759,4 +769,4 @@ function DRACOWorker() {
 
 }
 
-export { DRACOLoader };
+export { DRACOLoader, DRACO_GLTF_CONFIG };

+ 4 - 18
examples/jsm/loaders/EXRLoader.js

@@ -3343,6 +3343,10 @@ class EXRLoader extends DataTextureLoader {
 			format: EXRDecoder.format,
 			colorSpace: EXRDecoder.colorSpace,
 			type: this.type,
+			minFilter: LinearFilter,
+			magFilter: LinearFilter,
+			generateMipmaps: false,
+			flipY: false,
 		};
 
 	}
@@ -3386,24 +3390,6 @@ class EXRLoader extends DataTextureLoader {
 
 	}
 
-	load( url, onLoad, onProgress, onError ) {
-
-		function onLoadCallback( texture, texData ) {
-
-			texture.colorSpace = texData.colorSpace;
-			texture.minFilter = LinearFilter;
-			texture.magFilter = LinearFilter;
-			texture.generateMipmaps = false;
-			texture.flipY = false;
-
-			if ( onLoad ) onLoad( texture, texData );
-
-		}
-
-		return super.load( url, onLoadCallback, onProgress, onError );
-
-	}
-
 }
 
 export { EXRLoader };

+ 6 - 28
examples/jsm/loaders/HDRLoader.js

@@ -433,7 +433,12 @@ class HDRLoader extends DataTextureLoader {
 			header: rgbe_header_info.string,
 			gamma: rgbe_header_info.gamma,
 			exposure: rgbe_header_info.exposure,
-			type: type
+			type: type,
+			colorSpace: LinearSRGBColorSpace,
+			minFilter: LinearFilter,
+			magFilter: LinearFilter,
+			generateMipmaps: false,
+			flipY: true
 		};
 
 	}
@@ -451,33 +456,6 @@ class HDRLoader extends DataTextureLoader {
 
 	}
 
-	load( url, onLoad, onProgress, onError ) {
-
-		function onLoadCallback( texture, texData ) {
-
-			switch ( texture.type ) {
-
-				case FloatType:
-				case HalfFloatType:
-
-					texture.colorSpace = LinearSRGBColorSpace;
-					texture.minFilter = LinearFilter;
-					texture.magFilter = LinearFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = true;
-
-					break;
-
-			}
-
-			if ( onLoad ) onLoad( texture, texData );
-
-		}
-
-		return super.load( url, onLoadCallback, onProgress, onError );
-
-	}
-
 }
 
 export { HDRLoader };

+ 7 - 3
examples/jsm/loaders/KTX2Loader.js

@@ -257,16 +257,17 @@ class KTX2Loader extends Loader {
 
 			if ( typeof navigator !== 'undefined' &&
 				typeof navigator.platform !== 'undefined' && typeof navigator.userAgent !== 'undefined' &&
-				navigator.platform.indexOf( 'Linux' ) >= 0 && navigator.userAgent.indexOf( 'Firefox' ) >= 0 &&
+				navigator.platform.indexOf( 'Linux' ) >= 0 && navigator.userAgent.indexOf( 'Android' ) < 0 &&
 				this.workerConfig.astcSupported && this.workerConfig.etc2Supported &&
 				this.workerConfig.bptcSupported && this.workerConfig.dxtSupported ) {
 
-				// On Linux, Mesa drivers for AMD and Intel GPUs expose ETC2 and ASTC even though the hardware doesn't support these.
+				// On Linux, Mesa drivers for AMD and Intel GPUs expose ETC1,ETC2 and ASTC even though the hardware doesn't support these.
 				// Using these extensions will result in expensive software decompression on the main thread inside the driver, causing performance issues.
-				// When using ANGLE (e.g. via Chrome), these extensions are not exposed except for some specific Intel GPU models - however, Firefox doesn't perform this filtering.
+				// In general, browsers should not expose extensions for emulated formats, but Chrome and Firefox currently do so on Linux.
 				// Since a granular filter is a little too fragile and we can transcode into other GPU formats, disable formats that are likely to be emulated.
 
 				this.workerConfig.astcSupported = false;
+				this.workerConfig.etc1Supported = false;
 				this.workerConfig.etc2Supported = false;
 
 			}
@@ -971,6 +972,8 @@ KTX2Loader.BasisWorker = function () {
 
 const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGBFormat, RGFormat, RedFormat ] );
 
+const NORMALIZED_VK_FORMATS = new Set( [ VK_FORMAT_R16G16B16A16_UNORM ] );
+
 const FORMAT_MAP = {
 
 	[ VK_FORMAT_R32G32B32A32_SFLOAT ]: RGBAFormat,
@@ -1220,6 +1223,7 @@ async function createRawTexture( container ) {
 		texture.minFilter = useMipmaps ? NearestMipmapNearestFilter : NearestFilter;
 		texture.magFilter = NearestFilter;
 		texture.generateMipmaps = container.levelCount === 0;
+		texture.normalized = NORMALIZED_VK_FORMATS.has( vkFormat );
 
 	} else {
 

+ 4 - 0
examples/jsm/loaders/LWOLoader.js

@@ -50,6 +50,7 @@ let _lwoTree;
  *
  * @augments Loader
  * @three_import import { LWOLoader } from 'three/addons/loaders/LWOLoader.js';
+ * @deprecated since r185.
  */
 class LWOLoader extends Loader {
 
@@ -57,11 +58,14 @@ class LWOLoader extends Loader {
 	 * Constructs a new LWO loader.
 	 *
 	 * @param {LoadingManager} [manager] - The loading manager.
+	 * @deprecated since r185.
 	 */
 	constructor( manager ) {
 
 		super( manager );
 
+		console.warn( 'THREE.LWOLoader: The loader has been deprecated and will be removed with r195. Export your LWO files to glTF before using them on the web.' ); // @deprecated, r185
+
 	}
 
 	/**

+ 31 - 9
examples/jsm/loaders/MaterialXLoader.js

@@ -886,17 +886,39 @@ class MaterialXNode {
 		material.specularIntensityNode = specularIntensityNode || float( 0.5 );
 		material.specularColorNode = specularColorNode || color( 1.0, 1.0, 1.0 );
 		material.iorNode = iorNode || float( 1.5 );
-		material.anisotropyNode = anisotropyNode || float( 0 );
-		material.anisotropyRotationNode = anisotropyRotationNode || float( 0 );
-		material.transmissionNode = transmissionNode || float( 0 );
-		material.transmissionColorNode = transmissionColorNode || color( 1.0, 1.0, 1.0 );
+
+		if ( anisotropyNode !== null ) {
+
+			material.anisotropyNode = anisotropyNode;
+			material.anisotropyRotationNode = anisotropyRotationNode || float( 0 );
+
+		}
+
+		if ( transmissionNode !== null ) {
+
+			material.transmissionNode = transmissionNode;
+			material.transmissionColorNode = transmissionColorNode || color( 1.0, 1.0, 1.0 );
+
+		}
+
 		material.thinFilmThicknessNode = thinFilmThicknessNode || float( 0 );
 		material.thinFilmIorNode = thinFilmIorNode || float( 1.5 );
-		material.sheenNode = sheenNode || float( 0 );
-		material.sheenColorNode = sheenColorNode || color( 1.0, 1.0, 1.0 );
-		material.sheenRoughnessNode = sheenRoughnessNode || float( 0.5 );
-		material.clearcoatNode = clearcoatNode || float( 0 );
-		material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
+
+		if ( sheenNode !== null ) {
+
+			material.sheenNode = sheenNode;
+			material.sheenColorNode = sheenColorNode || color( 1.0, 1.0, 1.0 );
+			material.sheenRoughnessNode = sheenRoughnessNode || float( 0.5 );
+
+		}
+
+		if ( clearcoatNode !== null ) {
+
+			material.clearcoatNode = clearcoatNode;
+			material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 );
+
+		}
+
 		if ( normalNode ) material.normalNode = normalNode;
 		if ( emissiveNode ) material.emissiveNode = emissiveNode;
 

+ 11 - 12
examples/jsm/tsl/display/FXAANode.js

@@ -304,26 +304,25 @@ class FXAANode extends TempNode {
 		const ApplyFXAA = Fn( ( [ uv, texSize ] ) => {
 
 			const luminance = SampleLuminanceNeighborhood( texSize, uv );
-			If( ShouldSkipPixel( luminance ), () => {
+			const finalUv = vec2( uv ).toVar();
 
-				return Sample( uv );
+			If( ShouldSkipPixel( luminance ).not(), () => {
 
-			} );
+				const pixelBlend = DeterminePixelBlendFactor( luminance );
+				const edge = DetermineEdge( texSize, luminance );
+				const edgeBlend = DetermineEdgeBlendFactor( texSize, luminance, edge, uv );
 
-			const pixelBlend = DeterminePixelBlendFactor( luminance );
-			const edge = DetermineEdge( texSize, luminance );
-			const edgeBlend = DetermineEdgeBlendFactor( texSize, luminance, edge, uv );
+				const finalBlend = max( pixelBlend, edgeBlend );
 
-			const finalBlend = max( pixelBlend, edgeBlend );
-			const finalUv = uv.toVar();
+				If( edge.isHorizontal, () => {
 
-			If( edge.isHorizontal, () => {
+					finalUv.y.addAssign( edge.pixelStep.mul( finalBlend ) );
 
-				finalUv.y.addAssign( edge.pixelStep.mul( finalBlend ) );
+				} ).Else( () => {
 
-			} ).Else( () => {
+					finalUv.x.addAssign( edge.pixelStep.mul( finalBlend ) );
 
-				finalUv.x.addAssign( edge.pixelStep.mul( finalBlend ) );
+				} );
 
 			} );
 

+ 2 - 2
examples/jsm/tsl/display/SSSNode.js

@@ -30,7 +30,7 @@ let _rendererState;
  *
  * - Ideally the maximum shadow length should not exceed `1` meter. Otherwise the effect gets
  * computationally very expensive since more samples during the ray marching process are evaluated.
- * You can mitigate this issue by reducing the `quality` paramter.
+ * You can mitigate this issue by reducing the `quality` parameter.
  * - The effect can only be used with a single directional light, the main light of your scene.
  * This main light usually represents the sun or daylight.
  * - Like other Screen-Space techniques SSS can only honor objects in the shadowing computation that
@@ -121,7 +121,7 @@ class SSSNode extends TempNode {
 
 		/**
 		 * Whether to use temporal filtering or not. Setting this property to
-		 * `true` requires the usage of `TRAANode`. This will help to reduce noice
+		 * `true` requires the usage of `TRAANode`. This will help to reduce noise
 		 * although it introduces typical TAA artifacts like ghosting and temporal
 		 * instabilities.
 		 *

+ 3 - 3
examples/jsm/tsl/lighting/ClusteredLightsNode.js

@@ -1,7 +1,7 @@
 import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType } from 'three/webgpu';
 
 import {
-	attributeArray, nodeProxy, int, float, vec3, ivec2, ivec4, uniform, Break, Loop, positionView,
+	attributeArray, nodeProxy, int, float, vec3, vec4, ivec2, ivec4, uniform, Break, Loop, positionView,
 	Fn, If, Return, textureLoad, instanceIndex, screenCoordinate, directPointLight,
 	renderGroup,
 	min, max, pow, log, clamp, dot
@@ -79,7 +79,7 @@ class ClusteredLightsNode extends LightsNode {
 
 	customCacheKey() {
 
-		return this._compute.getCacheKey() + super.customCacheKey();
+		return ( this._compute ? this._compute.getCacheKey() : 0 ) + super.customCacheKey();
 
 	}
 
@@ -306,7 +306,7 @@ class ClusteredLightsNode extends LightsNode {
 		const dataB = textureLoad( this._lightsTexture, ivec2( index, 1 ) );
 
 		const position = dataA.xyz;
-		const viewPosition = this._cameraViewMatrix.mul( position );
+		const viewPosition = this._cameraViewMatrix.mul( vec4( position, 1.0 ) ).xyz;
 		const distance = dataA.w;
 		const color = dataB.rgb;
 		const decay = dataB.w;

+ 1 - 1
examples/jsm/tsl/lighting/DynamicLightsNode.js

@@ -230,7 +230,7 @@ class DynamicLightsNode extends LightsNode {
 
 		}
 
-		this._lightNodes = lightNodes;
+		return lightNodes;
 
 	}
 

+ 2 - 2
examples/misc_exporter_gcode.html

@@ -29,7 +29,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import Polyslice from 'https://unpkg.com/@jgphilpott/polyslice@25.12.8/dist/index.browser.esm.js';
+			import Polyslice from 'https://cdn.jsdelivr.net/npm/@jgphilpott/polyslice@26.4.0/dist/index.browser.esm.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
@@ -129,7 +129,7 @@
 					// Check if Polyslice is loaded
 					if ( typeof Polyslice === 'undefined' ) {
 
-						alert( 'Polyslice library failed to load from CDN.\n\nPossible solutions:\n1. Disable ad blockers or browser extensions\n2. Check your network connection\n3. Ensure unpkg.com is not blocked by your firewall' );
+						alert( 'Polyslice library failed to load from CDN.\n\nPossible solutions:\n1. Disable ad blockers or browser extensions\n2. Check your network connection\n3. Ensure cdn.jsdelivr.net is not blocked by your firewall' );
 						return;
 
 					}

BIN
examples/models/lwo/Images/environments/kiara_8_sunset.jpg


BIN
examples/models/lwo/Images/environments/kloofendal_48d_partly_cloudy.jpg


BIN
examples/models/lwo/Images/material-PBR-abstract/Abstract_008_basecolor.jpg


BIN
examples/models/lwo/Images/material-PBR-abstract/Abstract_008_bump.jpg


BIN
examples/models/lwo/Images/material-PBR-abstract/Abstract_008_metallic.jpg


BIN
examples/models/lwo/Images/material-PBR-abstract/Abstract_008_normal.jpg


BIN
examples/models/lwo/Images/material-PBR-abstract/Abstract_008_roughness.jpg


BIN
examples/models/lwo/Images/material-Phong-metal/167_BUMP.jpg


BIN
examples/models/lwo/Images/material-Phong-metal/167_COLOR.JPG


BIN
examples/models/lwo/Images/material-Phong-metal/167_GLOSS.jpg


BIN
examples/models/lwo/Images/material-Phong-metal/167_SPEC.jpg


+ 0 - 3
examples/models/lwo/LICENSE.txt

@@ -1,3 +0,0 @@
-Models by on the z. Licensed under Creative Commons Attribution 4.0 International License
-Environment images by HDRI Haven. Licensed under Creative Commons CC0 Public Domain
-Texture images by CC0 Textures. Licensed under Creative Commons CC0 Public Domain

BIN
examples/models/lwo/Objects/LWO3/Demo.lwo


BIN
examples/screenshots/webgl_batch_lod_bvh.jpg


BIN
examples/screenshots/webgl_loader_lwo.jpg


BIN
examples/screenshots/webgl_morphtargets_webcam.jpg


BIN
examples/screenshots/webgpu_lights_clustered.jpg


BIN
examples/screenshots/webgpu_postprocessing_motion_blur.jpg


BIN
examples/screenshots/webgpu_skinning_instancing_individual.jpg


+ 1 - 0
examples/tags.json

@@ -52,6 +52,7 @@
 	"webgl_lines_fat": [ "gpu", "stats", "panel" ],
 	"webgl_lines_fat_raycasting": [ "gpu", "stats", "panel", "raycast" ],
 	"webgl_loader_gltf_animation_pointer": [ "community", "animation", "gltf" ],
+	"webgl_loader_3dm": [ "rhino", "community" ],
 	"webgl_loader_3dtiles": [ "community", "3dtiles", "3d-tiles", "maps", "tiles", "globe", "earth", "cesium" ],
 	"webgl_loader_gltf_dispersion": [ "transmission" ],
 	"webgl_loader_gltf_progressive_lod": [ "community", "performance", "plugin", "library", "level", "details" ],

+ 2 - 2
examples/webgl_animation_keyframes.html

@@ -50,7 +50,7 @@
 			import { Sky } from 'three/addons/objects/Sky.js';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			let mixer;
 
@@ -95,7 +95,7 @@
 			controls.update();
 
 			const dracoLoader = new DRACOLoader();
-			dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+			dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 			const loader = new GLTFLoader();
 			loader.setDRACOLoader( dracoLoader );

+ 8 - 8
examples/webgl_batch_lod_bvh.html

@@ -30,13 +30,13 @@
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
 
-					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.9/build/index.module.js",
+					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.10/build/index.module.js",
 
-					"@three.ez/batched-mesh-extensions": "https://cdn.jsdelivr.net/npm/@three.ez/batched-mesh-extensions@0.0.8/build/webgl.js",
+					"@three.ez/batched-mesh-extensions": "https://cdn.jsdelivr.net/npm/@three.ez/batched-mesh-extensions@0.0.11/build/webgl.js",
 					"bvh.js": "https://cdn.jsdelivr.net/npm/bvh.js@0.0.13/build/index.js",
 
 					"@three.ez/simplify-geometry": "https://cdn.jsdelivr.net/npm/@three.ez/simplify-geometry@0.0.1/build/index.js",
-					"meshoptimizer": "https://cdn.jsdelivr.net/npm/meshoptimizer@0.23.0/+esm"
+					"meshoptimizer": "https://cdn.jsdelivr.net/npm/meshoptimizer@1.1.1/+esm"
 				}
 			}
 		</script>
@@ -131,7 +131,7 @@
 				// create BatchedMesh
 				const { vertexCount, indexCount, LODIndexCount } = getBatchedMeshLODCount( geometriesLODArray );
 				batchedMesh = new THREE.BatchedMesh( instancesCount, vertexCount, indexCount, new THREE.MeshStandardMaterial( { metalness: 1, roughness: 0.8 } ) );
-			
+
 				// enable radix sort for better performance
 				batchedMesh.customSort = createRadixSort( batchedMesh );
 
@@ -140,10 +140,10 @@
 
 					const geometryLOD = geometriesLODArray[ i ];
 					const geometryId = batchedMesh.addGeometry( geometryLOD[ 0 ], - 1, LODIndexCount[ i ] );
-					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 1 ], 50 );
-					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 2 ], 100 );
-					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 3 ], 125 );
-					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 4 ], 200 );
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 1 ], 0.08 ); // used when below ~8% of the screen height
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 2 ], 0.04 ); // below ~4%
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 3 ], 0.033 ); // below ~3.3%
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 4 ], 0.02 ); // below ~2%
 			
 				}
 

+ 1 - 1
examples/webgl_geometry_csg.html

@@ -33,7 +33,7 @@
 				"imports": {
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
-					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.9/build/index.module.js",
+					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.10/build/index.module.js",
 					"three-bvh-csg": "https://cdn.jsdelivr.net/npm/three-bvh-csg@0.0.18/build/index.module.js"
 				}
 			}

+ 2 - 2
examples/webgl_gpgpu_water.html

@@ -151,7 +151,7 @@
 			import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			// Texture width for simulation
 			const WIDTH = 128;
@@ -242,7 +242,7 @@
 
 				const hdrLoader = new HDRLoader().setPath( './textures/equirectangular/' );
 				const glbloader = new GLTFLoader().setPath( 'models/gltf/' );
-				glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+				glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) );
 
 				const [ env, model ] = await Promise.all( [ hdrLoader.loadAsync( 'blouberg_sunrise_2_1k.hdr' ), glbloader.loadAsync( 'duck.glb' ) ] );
 				env.mapping = THREE.EquirectangularReflectionMapping;

+ 1 - 2
examples/webgl_loader_3dm.html

@@ -79,8 +79,7 @@
 				scene.add( directionalLight );
 
 				const loader = new Rhino3dmLoader();
-				//generally, use this for the Library Path: https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1
-				loader.setLibraryPath( 'jsm/libs/rhino3dm/' );
+				loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.17.0/' );
 				loader.load( 'models/3dm/Rhino_Logo.3dm', function ( object ) {
 
 					scene.add( object );

+ 12 - 12
examples/webgl_loader_3dtiles.html

@@ -56,16 +56,16 @@
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
 					"three/examples/": "./",
-					"3d-tiles-renderer": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.js",
-					"3d-tiles-renderer/core/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.core-plugins.js",
-					"3d-tiles-renderer/three/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.24/build/index.three-plugins.js",
-					"postprocessing": "https://cdn.jsdelivr.net/npm/postprocessing@6.39.0/build/index.js",
-					"@takram/three-clouds": "https://cdn.jsdelivr.net/npm/@takram/three-clouds@0.7.3/build/index.js",
-					"@takram/three-atmosphere": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.17.1/build/index.js",
-					"@takram/three-geospatial": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.7.1/build/index.js",
-					"@takram/three-geospatial/shaders": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.7.1/build/shaders.js",
-					"@takram/three-geospatial-effects": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial-effects@0.6.1/build/index.js",
-					"@takram/three-atmosphere/shaders/bruneton": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.17.1/build/shaders/bruneton.js"
+					"3d-tiles-renderer": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.27/build/index.js",
+					"3d-tiles-renderer/core/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.27/build/index.core-plugins.js",
+					"3d-tiles-renderer/three/plugins": "https://cdn.jsdelivr.net/npm/3d-tiles-renderer@0.4.27/build/index.three-plugins.js",
+					"postprocessing": "https://cdn.jsdelivr.net/npm/postprocessing@6.39.1/build/index.js",
+					"@takram/three-clouds": "https://cdn.jsdelivr.net/npm/@takram/three-clouds@0.7.6/build/index.js",
+					"@takram/three-atmosphere": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.19.1/build/index.js",
+					"@takram/three-geospatial": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.9.1/build/index.js",
+					"@takram/three-geospatial/shaders": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial@0.9.1/build/shaders.js",
+					"@takram/three-geospatial-effects": "https://cdn.jsdelivr.net/npm/@takram/three-geospatial-effects@0.6.4/build/index.js",
+					"@takram/three-atmosphere/shaders/bruneton": "https://cdn.jsdelivr.net/npm/@takram/three-atmosphere@0.19.1/build/shaders/bruneton.js"
 				}
 			}
 		</script>
@@ -73,7 +73,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { toCreasedNormals } from 'three/addons/utils/BufferGeometryUtils.js';
 			import { TilesRenderer, GlobeControls, CAMERA_FRAME } from '3d-tiles-renderer';
 			import { CesiumIonAuthPlugin } from '3d-tiles-renderer/core/plugins';
@@ -122,7 +122,7 @@
 
 				// loader
 				const dracoLoader = new DRACOLoader();
-				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+				dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 				const DEG2RAD = Math.PI / 180;
 

+ 3 - 3
examples/webgl_loader_gltf_animation_pointer.html

@@ -35,7 +35,7 @@
 				"imports": {
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
-          			"@needle-tools/three-animation-pointer": "https://cdn.jsdelivr.net/npm/@needle-tools/three-animation-pointer@1.0.1"
+          			"@needle-tools/three-animation-pointer": "https://cdn.jsdelivr.net/npm/@needle-tools/three-animation-pointer@1.0.7"
 				}
 			}
 		</script>
@@ -50,7 +50,7 @@
 			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
 			import { GLTFAnimationPointerExtension } from '@needle-tools/three-animation-pointer';
 
@@ -84,7 +84,7 @@
 			controls.enableDamping = true;
 
 			const dracoLoader = new DRACOLoader();
-			dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+			dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 			
 
 			const ktx2Loader = new KTX2Loader()

+ 2 - 2
examples/webgl_loader_gltf_avif.html

@@ -42,7 +42,7 @@
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			let camera, scene, renderer;
 
@@ -60,7 +60,7 @@
 				//
 
 				const dracoLoader = new DRACOLoader();
-				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+				dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 				const loader = new GLTFLoader();
 				loader.setDRACOLoader( dracoLoader );

+ 2 - 2
examples/webgl_loader_gltf_transmission.html

@@ -35,7 +35,7 @@
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
 
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			let camera, scene, renderer, controls, timer, mixer;
 
@@ -69,7 +69,7 @@
 
 						new GLTFLoader()
 							.setPath( 'models/gltf/' )
-							.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) )
+							.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) )
 							.load( 'IridescentDishWithOlives.glb', function ( gltf ) {
 
 								mixer = new THREE.AnimationMixer( gltf.scene );

+ 0 - 114
examples/webgl_loader_lwo.html

@@ -1,114 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - LWOLoader</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<meta property="og:title" content="three.js webgl - LWOLoader">
-		<meta property="og:type" content="website">
-		<meta property="og:url" content="https://threejs.org/examples/webgl_loader_lwo.html">
-		<meta property="og:image" content="https://threejs.org/examples/screenshots/webgl_loader_lwo.jpg">
-
-		<link type="text/css" rel="stylesheet" href="main.css">
-	</head>
-
-	<body>
-		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - LWOLoader
-			<P>Lightwave Object loader by <a href="https://discoverthreejs.com/" target="_blank" rel="noopener">Discover three.js</a></P>
-			Models by <a href="https://onthez.com/" target="_blank" rel="noopener">on the z</a> - Environment images from <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
-		</div>
-
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js",
-					"three/addons/": "./jsm/"
-				}
-			}
-		</script>
-
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-			import { LWOLoader } from 'three/addons/loaders/LWOLoader.js';
-
-			let camera, scene, renderer;
-
-			init();
-
-			function init() {
-
-				const container = document.createElement( 'div' );
-				document.body.appendChild( container );
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 200 );
-				camera.position.set( 0.7, 14.6, - 43.2 );
-
-				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0xa0a0a0 );
-
-				const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
-				scene.add( ambientLight );
-
-				const light1 = new THREE.DirectionalLight( 0xc1c1c1, 3 );
-				light1.position.set( 0, 200, - 100 );
-				scene.add( light1 );
-
-				const grid = new THREE.GridHelper( 200, 20, 0x000000, 0x000000 );
-				grid.material.opacity = 0.3;
-				grid.material.transparent = true;
-				scene.add( grid );
-
-				const loader = new LWOLoader();
-				loader.load( 'models/lwo/Objects/LWO3/Demo.lwo', function ( object ) {
-
-					const phong = object.meshes[ 0 ];
-					phong.position.set( 2, 12, 0 );
-
-					const standard = object.meshes[ 1 ];
-					standard.position.set( - 2, 12, 0 );
-
-					const rocket = object.meshes[ 2 ];
-					rocket.position.set( 0, 10.5, 1 );
-
-					scene.add( phong, standard, rocket );
-
-				} );
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setAnimationLoop( animate );
-				renderer.toneMapping = THREE.ACESFilmicToneMapping;
-				container.appendChild( renderer.domElement );
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.target.set( - 1.33, 10, 6.7 );
-				controls.update();
-
-				window.addEventListener( 'resize', onWindowResize );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function animate() {
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 2 - 2
examples/webgl_materials_car.html

@@ -54,7 +54,7 @@
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 
 			let camera, scene, renderer;
@@ -145,7 +145,7 @@
 				const shadow = new THREE.TextureLoader().load( 'models/gltf/ferrari_ao.png' );
 
 				const dracoLoader = new DRACOLoader();
-				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+				dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 				const loader = new GLTFLoader();
 				loader.setDRACOLoader( dracoLoader );

+ 2 - 2
examples/webgl_materials_envmaps_groundprojected.html

@@ -39,7 +39,7 @@
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 
 			const params = {
@@ -76,7 +76,7 @@
 				scene.environment = envMap;
 
 				const dracoLoader = new DRACOLoader();
-				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+				dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 				const loader = new GLTFLoader();
 				loader.setDRACOLoader( dracoLoader );

+ 1 - 20
examples/webgl_materials_matcap.html

@@ -190,27 +190,8 @@
 
 			function handleEXR( event ) {
 
-				const contents = event.target.result;
-
 				const loader = new EXRLoader();
-
-				loader.setDataType( THREE.HalfFloatType );
-
-				const texData = loader.parse( contents );
-
-				const texture = new THREE.DataTexture();
-
-				texture.image.width = texData.width;
-				texture.image.height = texData.height;
-				texture.image.data = texData.data;
-
-				texture.format = texData.format;
-				texture.type = texData.type;
-				texture.colorSpace = THREE.LinearSRGBColorSpace;
-				texture.minFilter = THREE.LinearFilter;
-				texture.magFilter = THREE.LinearFilter;
-				texture.generateMipmaps = false;
-				texture.flipY = false;
+				const texture = loader.createDataTexture( event.target.result );
 
 				updateMatcap( texture );
 

+ 1 - 1
examples/webgl_modifier_subdivision.html

@@ -22,7 +22,7 @@
 				"imports": {
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
-					"three-subdivide": "https://cdn.jsdelivr.net/npm/three-subdivide@1.1.2/build/index.module.js"
+					"three-subdivide": "https://cdn.jsdelivr.net/npm/three-subdivide@1.1.5/build/index.module.js"
 				}
 			}
 		</script>

+ 97 - 49
examples/webgl_morphtargets_webcam.html

@@ -13,6 +13,11 @@
 			body {
 				background-color: #666666;
 			}
+			canvas {
+				position: absolute;
+				inset: 0;
+				margin: auto;
+			}
 		</style>
 	</head>
 	<body>
@@ -35,8 +40,6 @@
 
 			import * as THREE from 'three';
 
-			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
 			import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
@@ -45,9 +48,7 @@
 
 			// Mediapipe
 
-			import vision from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0';
-
-			const { FaceLandmarker, FilesetResolver } = vision;
+			import { FaceLandmarker, FilesetResolver } from 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35';
 
 			const blendshapesMap = {
 				// '_neutral': '',
@@ -105,6 +106,18 @@
 				// '': 'tongueOut'
 			};
 
+			// MediaPipe returns the head pose in a metric 3D space that assumes a
+			// fixed virtual camera: right-handed, at the origin, looking down -Z, with
+			// units in centimeters and a vertical field of view of 63 degrees. The
+			// camera, the video plane and the model all have to share that frame for
+			// the rendered face to register with the webcam image.
+
+			const MP_FOV = 63; // vertical field of view, in degrees
+			const MP_NEAR = 1; // 1 cm
+			const MP_FAR = 10000; // 100 m
+
+			const VIDEO_DISTANCE = 100; // depth of the video plane, in cm
+
 			//
 
 			const renderer = new THREE.WebGLRenderer( { antialias: true } );
@@ -113,22 +126,41 @@
 			renderer.toneMapping = THREE.ACESFilmicToneMapping;
 			document.body.appendChild( renderer.domElement );
 
-			const camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 100 );
-			camera.position.z = 5;
+			// The render camera matches MediaPipe's virtual camera: at the origin,
+			// looking down -Z. It must not be moved, otherwise the overlay drifts. Its
+			// aspect switches to the video's once the webcam is running.
+			const camera = new THREE.PerspectiveCamera( MP_FOV, window.innerWidth / window.innerHeight, MP_NEAR, MP_FAR );
 
 			const scene = new THREE.Scene();
 			scene.background = new THREE.Color( 0x666666 );
-			scene.scale.x = - 1;
+			scene.scale.x = - 1; // mirror the whole scene for a selfie view ( flips video and pose together )
 
 			scene.add( new THREE.AmbientLight( 0xffffff, 5 ) );
 
-			const controls = new OrbitControls( camera, renderer.domElement );
-
 			// Face
 
 			let face, eyeL, eyeR;
 			const eyeRotationLimit = THREE.MathUtils.degToRad( 30 );
 
+			// MediaPipe's facial transformation matrix is copied here verbatim. Until
+			// the webcam delivers one, the face rests at a default frontal pose ( in
+			// front of the camera, in centimeters ) so it is framed before tracking.
+			const faceContainer = new THREE.Object3D();
+			faceContainer.matrixAutoUpdate = false;
+			faceContainer.matrix.makeTranslation( 0, 0, - 50 );
+			faceContainer.matrixWorldNeedsUpdate = true;
+			scene.add( faceContainer );
+
+			// The Face Cap model is not MediaPipe's canonical face mesh, so this fixed
+			// transform registers it into the canonical frame ( centimeters, +Y up,
+			// +Z out of the face ) before the pose matrix is applied. The values are
+			// derived from the model's eye positions.
+			const registration = new THREE.Object3D();
+			registration.scale.setScalar( 0.958 );
+			registration.rotation.x = Math.PI / 2;
+			registration.position.set( 0, 0.12, 1.18 );
+			faceContainer.add( registration );
+
 			const ktx2Loader = new KTX2Loader()
 				.detectSupport( renderer );
 
@@ -137,18 +169,19 @@
 				.setMeshoptDecoder( MeshoptDecoder )
 				.load( 'models/gltf/facecap.glb', ( gltf ) => {
 
-					const mesh = gltf.scene.children[ 0 ];
-					scene.add( mesh );
+					// Reparent the head/eyes/teeth and drop the model's own scale rig.
+					const group = gltf.scene.getObjectByName( 'grp_transform' );
+					registration.add( group );
 
-					const head = mesh.getObjectByName( 'mesh_2' );
+					const head = group.getObjectByName( 'mesh_2' );
 					head.material = new THREE.MeshNormalMaterial();
 
-					const teeth = mesh.getObjectByName( 'mesh_3' );
+					const teeth = group.getObjectByName( 'mesh_3' );
 					teeth.material = new THREE.MeshNormalMaterial();
 
-					face = mesh.getObjectByName( 'mesh_2' );
-					eyeL = mesh.getObjectByName( 'eyeLeft' );
-  					eyeR = mesh.getObjectByName( 'eyeRight' );
+					face = head;
+					eyeL = group.getObjectByName( 'eyeLeft' );
+					eyeR = group.getObjectByName( 'eyeRight' );
 
 					// GUI
 
@@ -177,14 +210,16 @@
 			texture.colorSpace = THREE.SRGBColorSpace;
 
 			const geometry = new THREE.PlaneGeometry( 1, 1 );
-			const material = new THREE.MeshBasicMaterial( { map: texture, depthWrite: false } );
+			const material = new THREE.MeshBasicMaterial( { map: texture, depthTest: false, depthWrite: false } );
 			const videomesh = new THREE.Mesh( geometry, material );
+			videomesh.position.z = - VIDEO_DISTANCE;
+			videomesh.renderOrder = - 1;
 			scene.add( videomesh );
 
 			// MediaPipe
 
 			const filesetResolver = await FilesetResolver.forVisionTasks(
-				'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm'
+				'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35/wasm'
 			);
 
 			const faceLandmarker = await FaceLandmarker.createFromOptions( filesetResolver, {
@@ -209,50 +244,55 @@
 					} )
 					.catch( function ( error ) {
 
-						console.error( 'Unable to access the camera/webcam.', error );
+						console.warn( 'Unable to access the camera/webcam.', error );
 
 					} );
 
 			}
 
-			const transform = new THREE.Object3D();
+			// The camera matches the video aspect; the canvas is sized to that aspect
+			// and centered, so the grey body shows through as letterbox/pillarbox bars.
 
-			function animate() {
+			video.addEventListener( 'loadedmetadata', function () {
 
-				if ( video.readyState >= HTMLMediaElement.HAVE_METADATA ) {
+				const aspect = video.videoWidth / video.videoHeight;
 
-					const results = faceLandmarker.detectForVideo( video, Date.now() );
+				camera.aspect = aspect;
+				camera.updateProjectionMatrix();
 
-					if ( results.facialTransformationMatrixes.length > 0 ) {
+				// Size the plane so it exactly fills the frustum at its depth.
+				const height = 2 * VIDEO_DISTANCE * Math.tan( THREE.MathUtils.degToRad( MP_FOV / 2 ) );
+				videomesh.scale.set( height * aspect, height, 1 );
 
-						const facialTransformationMatrixes = results.facialTransformationMatrixes[ 0 ].data;
+				resize();
 
-						transform.matrix.fromArray( facialTransformationMatrixes );
-						transform.matrix.decompose( transform.position, transform.quaternion, transform.scale );
+			} );
 
-						const object = scene.getObjectByName( 'grp_transform' );
+			function animate() {
 
-						object.position.x = transform.position.x;
-						object.position.y = transform.position.z + 40;
-						object.position.z = - transform.position.y;
+				if ( video.readyState >= HTMLMediaElement.HAVE_METADATA ) {
 
-						object.rotation.x = transform.rotation.x;
-						object.rotation.y = transform.rotation.z;
-						object.rotation.z = - transform.rotation.y;
+					const results = faceLandmarker.detectForVideo( video, Date.now() );
+
+					if ( results.facialTransformationMatrixes.length > 0 ) {
+
+						// Apply MediaPipe's metric pose matrix directly.
+						faceContainer.matrix.fromArray( results.facialTransformationMatrixes[ 0 ].data );
+						faceContainer.matrixWorldNeedsUpdate = true;
 
 					}
 
 					if ( results.faceBlendshapes.length > 0 ) {
-			
+
 						const faceBlendshapes = results.faceBlendshapes[ 0 ].categories;
-			
+
 						// Morph values does not exist on the eye meshes, so we map the eyes blendshape score into rotation values
 						const eyeScore = {
 							leftHorizontal: 0,
 							rightHorizontal: 0,
 							leftVertical: 0,
 							rightVertical: 0,
-      						};
+						};
 
 						for ( const blendshape of faceBlendshapes ) {
 
@@ -304,28 +344,36 @@
 						eyeR.rotation.z = eyeScore.rightHorizontal * eyeRotationLimit;
 						eyeL.rotation.x = eyeScore.leftVertical * eyeRotationLimit;
 						eyeR.rotation.x = eyeScore.rightVertical * eyeRotationLimit;
-			
+
 					}
 
 				}
 
-				videomesh.scale.x = video.videoWidth / 100;
-				videomesh.scale.y = video.videoHeight / 100;
-
 				renderer.render( scene, camera );
 
-				controls.update();
-
 			}
 
-			window.addEventListener( 'resize', function () {
+			function resize() {
 
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
+				// Largest video-aspect rectangle that fits inside the window.
+				let width = window.innerWidth;
+				let height = window.innerHeight;
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				if ( width / height > camera.aspect ) {
 
-			} );
+					width = height * camera.aspect;
+
+				} else {
+
+					height = width / camera.aspect;
+
+				}
+
+				renderer.setSize( width, height );
+
+			}
+
+			window.addEventListener( 'resize', resize );
 
 		</script>
 	</body>

+ 2 - 2
examples/webgl_postprocessing_godrays.html

@@ -21,8 +21,8 @@
 				"imports": {
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
-					"postprocessing": "https://unpkg.com/postprocessing@6.38.2/build/index.js",
-					"goodrays": "https://unpkg.com/three-good-godrays@0.8.1/build/three-good-godrays.esm.js"
+					"postprocessing": "https://cdn.jsdelivr.net/npm/postprocessing@6.39.1/build/index.js",
+					"goodrays": "https://cdn.jsdelivr.net/npm/three-good-godrays@0.12.0/build/three-good-godrays.esm.js"
 				}
 			}
 		</script>

+ 2 - 2
examples/webgl_random_uv.html

@@ -33,7 +33,7 @@
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -114,7 +114,7 @@
 						// model
 
 						const loader = new GLTFLoader().setPath( 'models/gltf/' );
-						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) );
 						loader.load( 'ShaderBall2.glb', function ( gltf ) {
 
 							const shaderBall = gltf.scene.children[ 0 ];

+ 1 - 1
examples/webgl_raycaster_bvh.html

@@ -33,7 +33,7 @@
 				"imports": {
 					"three": "../build/three.module.js",
 					"three/addons/": "./jsm/",
-					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.9/build/index.module.js"
+					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.10/build/index.module.js"
 				}
 			}
 		</script>

+ 1 - 1
examples/webgl_renderer_pathtracer.html

@@ -49,7 +49,7 @@
 					"three/addons/": "./jsm/",
 					"three/examples/": "./",
 					"three-gpu-pathtracer": "https://cdn.jsdelivr.net/npm/three-gpu-pathtracer@0.0.24/build/index.module.js",
-					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.9/build/index.module.js"
+					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.10/build/index.module.js"
 				}
 			}
 		</script>

+ 2 - 2
examples/webgl_watch.html

@@ -35,7 +35,7 @@
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
@@ -102,7 +102,7 @@
 						// model
 
 						const loader = new GLTFLoader().setPath( 'models/gltf/' );
-						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+						loader.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) );
 						loader.load( 'rolex.glb', function ( gltf ) {
 
 							gltf.scene.rotation.x = Math.PI * 0.25;

+ 0 - 2
examples/webgpu_clipping.html

@@ -107,8 +107,6 @@
 					color: 0x80ee10,
 					shininess: 0,
 					side: THREE.DoubleSide,
-
-					// ***** Clipping setup (material): *****
 					alphaToCoverage: true
 				} );
 

+ 2 - 2
examples/webgpu_compute_water.html

@@ -46,7 +46,7 @@
 			import { SimplexNoise } from 'three/addons/math/SimplexNoise.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
- 			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+ 			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
 			import WebGPU from 'three/addons/capabilities/WebGPU.js';
@@ -438,7 +438,7 @@
 
 				const hdrLoader = new HDRLoader().setPath( './textures/equirectangular/' );
  				const glbloader = new GLTFLoader().setPath( 'models/gltf/' );
- 				glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) );
+ 				glbloader.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) );
 
  				const [ env, model ] = await Promise.all( [ hdrLoader.loadAsync( 'blouberg_sunrise_2_1k.hdr' ), glbloader.loadAsync( 'duck.glb' ) ] );
  				env.mapping = THREE.EquirectangularReflectionMapping;

+ 46 - 11
examples/webgpu_lights_clustered.html

@@ -38,7 +38,7 @@
 		<script type="module">
 
 			import * as THREE from 'three/webgpu';
-			import { pass, uniform, vec3, float, mix, step } from 'three/tsl';
+			import { pass, uniform, vec3, float, mix, step, uv, instancedBufferAttribute } from 'three/tsl';
 
 			import { ClusteredLighting } from 'three/addons/lighting/ClusteredLighting.js';
 
@@ -54,6 +54,7 @@
 
 			let camera, scene, renderer,
 				lights, lightDummy,
+				lightPositionAttribute,
 				controls,
 				scenePass, clusterInfluence, debugZSliceNode,
 				lighting,
@@ -102,12 +103,37 @@
 				const modelSize = box.getSize( new THREE.Vector3() );
 				const modelCenter = box.getCenter( new THREE.Vector3() );
 
-				const material = new THREE.MeshBasicMaterial();
+				const lightPositions = new Float32Array( maxCount * 3 );
+				const lightColors = new Float32Array( maxCount * 3 );
 
-				lightDummy = new THREE.InstancedMesh( new THREE.SphereGeometry( 0.05, 16, 8 ), material, maxCount );
-				lightDummy.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
-				lightDummy.frustumCulled = false;
+				lightPositionAttribute = new THREE.InstancedBufferAttribute( lightPositions, 3 );
+				lightPositionAttribute.setUsage( THREE.DynamicDrawUsage );
+
+				const lightColorAttribute = new THREE.InstancedBufferAttribute( lightColors, 3 );
+
+				// Physical 1/r² point-source falloff (Plummer profile): I = peak * r0² / ( r² + r0² )
+				// Subtract the value at r = 0.5 so the soft tail reaches exactly 0 at the sprite edge.
+				const coreRadius = 0.02;
+				const peak = 16;
+				const r0sq = coreRadius * coreRadius;
+				const edgeBias = r0sq / ( 0.25 + r0sq );
+
+				const offset = uv().sub( 0.5 );
+				const r2 = offset.dot( offset );
+				const glow = float( r0sq ).div( r2.add( r0sq ) ).sub( edgeBias ).max( 0 ).mul( peak );
+
+				const spriteMaterial = new THREE.SpriteNodeMaterial( {
+					transparent: true,
+					depthWrite: false,
+					blending: THREE.AdditiveBlending
+				} );
+				spriteMaterial.positionNode = instancedBufferAttribute( lightPositionAttribute );
+				spriteMaterial.colorNode = glow.mul( instancedBufferAttribute( lightColorAttribute ) );
+				spriteMaterial.scaleNode = uniform( 0.2 );
+
+				lightDummy = new THREE.Sprite( spriteMaterial );
 				lightDummy.count = count;
+				lightDummy.frustumCulled = false;
 				scene.add( lightDummy );
 
 				// lights
@@ -115,7 +141,7 @@
 				lights = new THREE.Group();
 				scene.add( lights );
 
-				const addLight = ( hexColor, power = 30, distance = 1 ) => {
+				const addLight = ( hexColor, power = 10, distance = 1 ) => {
 
 					const light = new THREE.PointLight( hexColor, 1, distance );
 					light.position.set(
@@ -128,9 +154,10 @@
 					light.userData.fixedPosition = light.position.clone();
 					light.visible = ( lights.children.length < count );
 
-					light.updateMatrixWorld();
-
-					lightDummy.setMatrixAt( lights.children.length, light.matrixWorld );
+					const i = lights.children.length;
+					lightPositions[ i * 3 + 0 ] = light.position.x;
+					lightPositions[ i * 3 + 1 ] = light.position.y;
+					lightPositions[ i * 3 + 2 ] = light.position.z;
 
 					lights.add( light );
 
@@ -144,7 +171,10 @@
 
 					const hex = ( Math.random() * 0x888888 ) + 0x888888;
 
-					lightDummy.setColorAt( i, color.setHex( hex ) );
+					color.setHex( hex );
+					lightColors[ i * 3 + 0 ] = color.r;
+					lightColors[ i * 3 + 1 ] = color.g;
+					lightColors[ i * 3 + 2 ] = color.b;
 
 					addLight( hex );
 
@@ -279,6 +309,7 @@
 			function animate() {
 
 				const time = performance.now() / 1000;
+				const positions = lightPositionAttribute.array;
 
 				for ( let i = 0; i < lights.children.length; i ++ ) {
 
@@ -290,10 +321,14 @@
 					light.position.y += Math.cos( lightTime * 0.5 ) * .3;
 					light.position.z += Math.cos( lightTime * 0.3 ) * 1.5;
 
-					lightDummy.setMatrixAt( i, light.matrixWorld );
+					positions[ i * 3 + 0 ] = light.position.x;
+					positions[ i * 3 + 1 ] = light.position.y;
+					positions[ i * 3 + 2 ] = light.position.z;
 
 				}
 
+				lightPositionAttribute.needsUpdate = true;
+
 				renderPipeline.render();
 
 			}

+ 2 - 2
examples/webgpu_loader_gltf_transmission.html

@@ -44,7 +44,7 @@
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
 
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			let camera, scene, renderer, controls, timer, mixer;
 
@@ -78,7 +78,7 @@
 
 						new GLTFLoader()
 							.setPath( 'models/gltf/' )
-							.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) )
+							.setDRACOLoader( new DRACOLoader().setDecoderPath( DRACO_GLTF_CONFIG ) )
 							.load( 'IridescentDishWithOlives.glb', function ( gltf ) {
 
 								mixer = new THREE.AnimationMixer( gltf.scene );

+ 2 - 2
examples/webgpu_materials_envmaps_groundprojected.html

@@ -41,7 +41,7 @@
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 			import { HDRLoader } from 'three/addons/loaders/HDRLoader.js';
 			import { getGroundProjectedNormal } from 'three/addons/tsl/utils/GroundedSkybox.js';
 
@@ -77,7 +77,7 @@
 				scene.environment = envMap;
 
 				const dracoLoader = new DRACOLoader();
-				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+				dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 				const loader = new GLTFLoader();
 				loader.setDRACOLoader( dracoLoader );

+ 1 - 20
examples/webgpu_materials_matcap.html

@@ -191,27 +191,8 @@
 
 			function handleEXR( event ) {
 
-				const contents = event.target.result;
-
 				const loader = new EXRLoader();
-
-				loader.setDataType( THREE.HalfFloatType );
-
-				const texData = loader.parse( contents );
-
-				const texture = new THREE.DataTexture();
-
-				texture.image.width = texData.width;
-				texture.image.height = texData.height;
-				texture.image.data = texData.data;
-
-				texture.format = texData.format;
-				texture.type = texData.type;
-				texture.colorSpace = THREE.LinearSRGBColorSpace;
-				texture.minFilter = THREE.LinearFilter;
-				texture.magFilter = THREE.LinearFilter;
-				texture.generateMipmaps = false;
-				texture.flipY = false;
+				const texture = loader.createDataTexture( event.target.result );
 
 				updateMatcap( texture );
 

+ 2 - 2
examples/webgpu_mesh_batch.html

@@ -42,7 +42,7 @@
 		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 		import { radixSort } from 'three/addons/utils/SortUtils.js';
 
-		import { normalView, directionToColor, diffuseColor, vec4 } from 'three/tsl';
+		import { normalView, packNormalToRGB, diffuseColor, vec4 } from 'three/tsl';
 
 		let camera, scene, renderer;
 		let controls;
@@ -131,7 +131,7 @@
 
 				material = new THREE.MeshBasicNodeMaterial();
 				material.outputNode = vec4(
-					diffuseColor.mul( directionToColor( normalView ).y.add( 0.5 ) ).rgb,
+					diffuseColor.mul( packNormalToRGB( normalView ).y.add( 0.5 ) ).rgb,
 					diffuseColor.a,
 				);
 		

+ 2 - 2
examples/webgpu_mrt.html

@@ -38,7 +38,7 @@
 		<script type="module">
 
 			import * as THREE from 'three/webgpu';
-			import { output, normalView, pass, step, diffuseColor, emissive, directionToColor, screenUV, mix, mrt, Fn } from 'three/tsl';
+			import { output, normalView, pass, step, diffuseColor, emissive, packNormalToRGB, screenUV, mix, mrt, Fn } from 'three/tsl';
 
 			import { Inspector } from 'three/addons/inspector/Inspector.js';
 
@@ -99,7 +99,7 @@
 				const scenePass = pass( scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } );
 				scenePass.setMRT( mrt( {
 					output: output,
-					normal: directionToColor( normalView ),
+					normal: packNormalToRGB( normalView ),
 					diffuse: diffuseColor,
 					emissive: emissive
 				} ) );

+ 4 - 4
examples/webgpu_multisampled_renderbuffers.html

@@ -52,7 +52,7 @@
 
 			const params = {
 				animated: true,
-				samples: 4
+				multisampling: true
 			};
 
 			const mat4 = new THREE.Matrix4();
@@ -81,7 +81,7 @@
 			function initGUI() {
 
 				const gui = renderer.inspector.createParameters( 'Settings' );
-				gui.add( params, 'samples', 0, 4, 1 );
+				gui.add( params, 'multisampling' );
 				gui.add( params, 'animated' );
 
 			}
@@ -126,7 +126,7 @@
 				document.body.appendChild( renderer.domElement );
 
 				renderTarget = new THREE.RenderTarget( window.innerWidth * dpr, window.innerHeight * dpr, {
-					samples: params.samples,
+					samples: params.multisampling ? 4 : 1,
 					depthBuffer: true,
 				} );
 
@@ -172,7 +172,7 @@
 
 				}
 
-				renderTarget.samples = params.samples;
+				renderTarget.samples = params.multisampling ? 4 : 1;
 
 				renderer.setRenderTarget( renderTarget );
 				renderer.render( scene, camera );

+ 2 - 2
examples/webgpu_performance.html

@@ -47,7 +47,7 @@
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
-			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { DRACOLoader, DRACO_GLTF_CONFIG } from 'three/addons/loaders/DRACOLoader.js';
 
 			import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
 
@@ -105,7 +105,7 @@
 						// model
 
 						const dracoLoader = new DRACOLoader();
-						dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
+						dracoLoader.setDecoderPath( DRACO_GLTF_CONFIG );
 
 						const loader = new GLTFLoader().setPath( 'models/gltf/' );
 						loader.setDRACOLoader( dracoLoader );

Някои файлове не бяха показани, защото твърде много файлове са промени

粤ICP备19079148号