@plastic-software/three 0.183.4 → 0.184.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/build/three.cjs +775 -287
  2. package/build/three.core.js +372 -110
  3. package/build/three.core.min.js +1 -1
  4. package/build/three.module.js +428 -181
  5. package/build/three.module.min.js +1 -1
  6. package/build/three.tsl.js +7 -1
  7. package/build/three.tsl.min.js +1 -1
  8. package/build/three.webgpu.js +2979 -1281
  9. package/build/three.webgpu.min.js +1 -1
  10. package/build/three.webgpu.nodes.js +2942 -1281
  11. package/build/three.webgpu.nodes.min.js +1 -1
  12. package/examples/jsm/Addons.js +11 -0
  13. package/examples/jsm/animation/CCDIKSolver.js +5 -1
  14. package/examples/jsm/controls/ArcballControls.js +4 -1
  15. package/examples/jsm/controls/DragControls.js +2 -2
  16. package/examples/jsm/controls/FirstPersonControls.js +58 -54
  17. package/examples/jsm/controls/FlyControls.js +4 -0
  18. package/examples/jsm/controls/OrbitControls.js +2 -2
  19. package/examples/jsm/controls/TrackballControls.js +2 -2
  20. package/examples/jsm/controls/TransformControls.js +34 -2
  21. package/examples/jsm/csm/CSMShadowNode.js +6 -2
  22. package/examples/jsm/exporters/GLTFExporter.js +21 -5
  23. package/examples/jsm/geometries/TextGeometry.js +18 -0
  24. package/examples/jsm/helpers/LightProbeGridHelper.js +221 -0
  25. package/examples/jsm/inspector/Extension.js +13 -0
  26. package/examples/jsm/inspector/Inspector.js +169 -114
  27. package/examples/jsm/inspector/RendererInspector.js +2 -2
  28. package/examples/jsm/inspector/extensions/extensions.json +6 -0
  29. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +916 -0
  30. package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +281 -0
  31. package/examples/jsm/inspector/tabs/Memory.js +128 -0
  32. package/examples/jsm/inspector/tabs/Parameters.js +34 -2
  33. package/examples/jsm/inspector/tabs/Performance.js +2 -2
  34. package/examples/jsm/inspector/tabs/Settings.js +264 -0
  35. package/examples/jsm/inspector/tabs/Timeline.js +1611 -0
  36. package/examples/jsm/inspector/tabs/Viewer.js +105 -3
  37. package/examples/jsm/inspector/ui/Graph.js +2 -2
  38. package/examples/jsm/inspector/ui/List.js +1 -1
  39. package/examples/jsm/inspector/ui/Profiler.js +273 -176
  40. package/examples/jsm/inspector/ui/Style.js +64 -10
  41. package/examples/jsm/inspector/ui/Tab.js +39 -7
  42. package/examples/jsm/inspector/ui/Values.js +39 -2
  43. package/examples/jsm/inspector/ui/utils.js +13 -0
  44. package/examples/jsm/interaction/InteractionManager.js +226 -0
  45. package/examples/jsm/libs/meshopt_decoder.module.js +8 -8
  46. package/examples/jsm/lighting/DynamicLighting.js +82 -0
  47. package/examples/jsm/lighting/LightProbeGrid.js +651 -0
  48. package/examples/jsm/lines/LineMaterial.js +1 -1
  49. package/examples/jsm/loaders/EXRLoader.js +682 -43
  50. package/examples/jsm/loaders/FBXLoader.js +233 -33
  51. package/examples/jsm/loaders/GLTFLoader.js +24 -7
  52. package/examples/jsm/loaders/HDRLoader.js +1 -1
  53. package/examples/jsm/loaders/KTX2Loader.js +8 -2
  54. package/examples/jsm/loaders/LDrawLoader.js +39 -47
  55. package/examples/jsm/loaders/SVGLoader.js +1 -1
  56. package/examples/jsm/loaders/VTKLoader.js +5 -1
  57. package/examples/jsm/loaders/collada/ColladaComposer.js +101 -7
  58. package/examples/jsm/loaders/collada/ColladaParser.js +19 -4
  59. package/examples/jsm/loaders/usd/USDAParser.js +6 -0
  60. package/examples/jsm/loaders/usd/USDCParser.js +26 -0
  61. package/examples/jsm/loaders/usd/USDComposer.js +656 -103
  62. package/examples/jsm/misc/GPUComputationRenderer.js +2 -0
  63. package/examples/jsm/misc/RollerCoaster.js +42 -4
  64. package/examples/jsm/modifiers/TessellateModifier.js +1 -1
  65. package/examples/jsm/objects/Reflector.js +73 -25
  66. package/examples/jsm/objects/Sky.js +14 -2
  67. package/examples/jsm/objects/SkyMesh.js +23 -6
  68. package/examples/jsm/renderers/Projector.js +18 -38
  69. package/examples/jsm/renderers/SVGRenderer.js +6 -25
  70. package/examples/jsm/transpiler/GLSLDecoder.js +2 -2
  71. package/examples/jsm/tsl/WebGLNodesHandler.js +605 -0
  72. package/examples/jsm/tsl/display/AfterImageNode.js +10 -0
  73. package/examples/jsm/tsl/display/AnamorphicNode.js +11 -0
  74. package/examples/jsm/tsl/display/BilateralBlurNode.js +10 -0
  75. package/examples/jsm/tsl/display/ChromaticAberrationNode.js +3 -36
  76. package/examples/jsm/tsl/display/FSR1Node.js +477 -0
  77. package/examples/jsm/tsl/display/GTAONode.js +2 -1
  78. package/examples/jsm/tsl/display/GaussianBlurNode.js +10 -0
  79. package/examples/jsm/tsl/display/GodraysNode.js +2 -11
  80. package/examples/jsm/tsl/display/OutlineNode.js +66 -16
  81. package/examples/jsm/tsl/display/SSGINode.js +0 -4
  82. package/examples/jsm/tsl/display/SharpenNode.js +283 -0
  83. package/examples/jsm/tsl/display/TAAUNode.js +835 -0
  84. package/examples/jsm/tsl/display/TRAANode.js +48 -7
  85. package/examples/jsm/tsl/lighting/DynamicLightsNode.js +300 -0
  86. package/examples/jsm/tsl/lighting/data/AmbientLightDataNode.js +61 -0
  87. package/examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js +111 -0
  88. package/examples/jsm/tsl/lighting/data/HemisphereLightDataNode.js +99 -0
  89. package/examples/jsm/tsl/lighting/data/PointLightDataNode.js +134 -0
  90. package/examples/jsm/tsl/lighting/data/SpotLightDataNode.js +161 -0
  91. package/examples/jsm/tsl/math/Bayer.js +13 -2
  92. package/examples/jsm/utils/BufferGeometryUtils.js +2 -3
  93. package/examples/jsm/utils/ColorUtils.js +76 -0
  94. package/examples/jsm/utils/SkeletonUtils.js +14 -8
  95. package/examples/jsm/webxr/XRHandMeshModel.js +36 -10
  96. package/examples/jsm/webxr/XRHandModelFactory.js +2 -1
  97. package/package.json +4 -4
  98. package/src/Three.Core.js +1 -0
  99. package/src/Three.TSL.js +6 -0
  100. package/src/Three.WebGPU.Nodes.js +3 -0
  101. package/src/Three.WebGPU.js +6 -0
  102. package/src/animation/AnimationAction.js +11 -1
  103. package/src/audio/AudioContext.js +2 -2
  104. package/src/constants.js +1 -1
  105. package/src/core/BufferAttribute.js +13 -1
  106. package/src/core/Clock.js +1 -1
  107. package/src/core/Object3D.js +1 -5
  108. package/src/core/RenderTarget.js +1 -0
  109. package/src/extras/curves/CatmullRomCurve3.js +3 -2
  110. package/src/loaders/AudioLoader.js +11 -1
  111. package/src/loaders/DataTextureLoader.js +6 -4
  112. package/src/loaders/FileLoader.js +1 -2
  113. package/src/loaders/ImageBitmapLoader.js +4 -6
  114. package/src/loaders/MaterialLoader.js +1 -1
  115. package/src/loaders/ObjectLoader.js +25 -4
  116. package/src/loaders/nodes/NodeObjectLoader.js +18 -0
  117. package/src/materials/MeshToonMaterial.js +1 -1
  118. package/src/materials/nodes/Line2NodeMaterial.js +27 -0
  119. package/src/materials/nodes/NodeMaterial.js +0 -27
  120. package/src/materials/nodes/manager/NodeMaterialObserver.js +188 -89
  121. package/src/math/Line3.js +3 -0
  122. package/src/math/Matrix2.js +13 -9
  123. package/src/math/Matrix3.js +13 -9
  124. package/src/math/Matrix4.js +13 -9
  125. package/src/math/Plane.js +4 -3
  126. package/src/math/Triangle.js +1 -1
  127. package/src/math/Vector2.js +11 -7
  128. package/src/math/Vector3.js +12 -8
  129. package/src/math/Vector4.js +13 -9
  130. package/src/nodes/Nodes.js +0 -1
  131. package/src/nodes/TSL.js +1 -1
  132. package/src/nodes/accessors/BufferAttributeNode.js +9 -3
  133. package/src/nodes/accessors/CubeTextureNode.js +7 -1
  134. package/src/nodes/accessors/MaterialProperties.js +2 -5
  135. package/src/nodes/accessors/Object3DNode.js +1 -1
  136. package/src/nodes/accessors/ReferenceBaseNode.js +2 -2
  137. package/src/nodes/accessors/ReferenceNode.js +4 -4
  138. package/src/nodes/accessors/SceneProperties.js +2 -8
  139. package/src/nodes/accessors/StorageBufferNode.js +10 -4
  140. package/src/nodes/accessors/StorageTextureNode.js +4 -9
  141. package/src/nodes/accessors/TextureNode.js +10 -2
  142. package/src/nodes/accessors/UniformArrayNode.js +2 -2
  143. package/src/nodes/code/FunctionCallNode.js +1 -1
  144. package/src/nodes/code/FunctionNode.js +1 -1
  145. package/src/nodes/core/ArrayNode.js +1 -1
  146. package/src/nodes/core/AssignNode.js +1 -1
  147. package/src/nodes/core/AttributeNode.js +1 -1
  148. package/src/nodes/core/BypassNode.js +1 -1
  149. package/src/nodes/core/ContextNode.js +1 -1
  150. package/src/nodes/core/IndexNode.js +2 -1
  151. package/src/nodes/core/InputNode.js +1 -1
  152. package/src/nodes/core/InspectorNode.js +1 -1
  153. package/src/nodes/core/IsolateNode.js +1 -1
  154. package/src/nodes/core/Node.js +83 -12
  155. package/src/nodes/core/NodeBuilder.js +117 -16
  156. package/src/nodes/core/NodeUtils.js +1 -1
  157. package/src/nodes/core/OutputStructNode.js +1 -1
  158. package/src/nodes/core/ParameterNode.js +1 -1
  159. package/src/nodes/core/StackNode.js +1 -1
  160. package/src/nodes/core/StructNode.js +1 -1
  161. package/src/nodes/core/StructTypeNode.js +1 -1
  162. package/src/nodes/core/SubBuildNode.js +1 -1
  163. package/src/nodes/core/UniformGroupNode.js +36 -6
  164. package/src/nodes/core/VarNode.js +1 -1
  165. package/src/nodes/core/VaryingNode.js +1 -1
  166. package/src/nodes/display/NormalMapNode.js +2 -2
  167. package/src/nodes/display/PassNode.js +27 -7
  168. package/src/nodes/display/RenderOutputNode.js +4 -4
  169. package/src/nodes/display/ScreenNode.js +1 -1
  170. package/src/nodes/display/ViewportDepthTextureNode.js +11 -15
  171. package/src/nodes/display/ViewportTextureNode.js +18 -7
  172. package/src/nodes/functions/BSDF/V_GGX_SmithCorrelated_Anisotropic.js +2 -2
  173. package/src/nodes/geometry/RangeNode.js +1 -1
  174. package/src/nodes/gpgpu/AtomicFunctionNode.js +1 -1
  175. package/src/nodes/gpgpu/BarrierNode.js +9 -0
  176. package/src/nodes/gpgpu/ComputeBuiltinNode.js +1 -1
  177. package/src/nodes/gpgpu/ComputeNode.js +69 -44
  178. package/src/nodes/gpgpu/SubgroupFunctionNode.js +1 -1
  179. package/src/nodes/lighting/LightsNode.js +6 -27
  180. package/src/nodes/lighting/ShadowNode.js +24 -2
  181. package/src/nodes/math/BitcastNode.js +1 -1
  182. package/src/nodes/math/ConditionalNode.js +1 -1
  183. package/src/nodes/math/MathNode.js +73 -1
  184. package/src/nodes/math/OperatorNode.js +1 -1
  185. package/src/nodes/math/PackFloatNode.js +1 -1
  186. package/src/nodes/math/UnpackFloatNode.js +1 -1
  187. package/src/nodes/tsl/TSLBase.js +1 -1
  188. package/src/nodes/tsl/TSLCore.js +21 -3
  189. package/src/nodes/utils/ArrayElementNode.js +1 -1
  190. package/src/nodes/utils/ConvertNode.js +1 -1
  191. package/src/nodes/utils/DebugNode.js +1 -1
  192. package/src/nodes/utils/EventNode.js +30 -0
  193. package/src/nodes/utils/FlipNode.js +1 -1
  194. package/src/nodes/utils/FunctionOverloadingNode.js +1 -1
  195. package/src/nodes/utils/JoinNode.js +1 -1
  196. package/src/nodes/utils/MemberNode.js +1 -1
  197. package/src/nodes/utils/Remap.js +48 -0
  198. package/src/nodes/utils/RotateNode.js +1 -1
  199. package/src/nodes/utils/SetNode.js +1 -1
  200. package/src/nodes/utils/SplitNode.js +1 -1
  201. package/src/objects/BatchedMesh.js +17 -2
  202. package/src/objects/InstancedMesh.js +19 -3
  203. package/src/objects/SkinnedMesh.js +26 -9
  204. package/src/renderers/WebGLRenderer.js +147 -48
  205. package/src/renderers/common/Animation.js +3 -3
  206. package/src/renderers/common/Attributes.js +15 -1
  207. package/src/renderers/common/Backend.js +0 -8
  208. package/src/renderers/common/Background.js +2 -2
  209. package/src/renderers/common/BindGroup.js +1 -8
  210. package/src/renderers/common/Bindings.js +2 -2
  211. package/src/renderers/common/ComputePipeline.js +1 -1
  212. package/src/renderers/common/CubeRenderTarget.js +1 -1
  213. package/src/renderers/common/Info.js +333 -4
  214. package/src/renderers/common/InspectorBase.js +6 -1
  215. package/src/renderers/common/Pipelines.js +36 -3
  216. package/src/renderers/common/ReadbackBuffer.js +78 -0
  217. package/src/renderers/common/RenderBundle.js +3 -1
  218. package/src/renderers/common/RenderBundles.js +5 -2
  219. package/src/renderers/common/RenderObject.js +2 -2
  220. package/src/renderers/common/RenderObjects.js +3 -3
  221. package/src/renderers/common/RenderPipeline.js +35 -6
  222. package/src/renderers/common/Renderer.js +232 -53
  223. package/src/renderers/common/Textures.js +72 -3
  224. package/src/renderers/common/UniformsGroup.js +1 -1
  225. package/src/renderers/common/XRManager.js +34 -27
  226. package/src/renderers/common/extras/PMREMGenerator.js +23 -15
  227. package/src/renderers/common/nodes/NodeBuilderState.js +1 -1
  228. package/src/renderers/common/nodes/NodeManager.js +230 -99
  229. package/src/renderers/shaders/ShaderChunk/envmap_common_pars_fragment.glsl.js +0 -1
  230. package/src/renderers/shaders/ShaderChunk/envmap_fragment.glsl.js +1 -1
  231. package/src/renderers/shaders/ShaderChunk/lightprobes_pars_fragment.glsl.js +80 -0
  232. package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +8 -0
  233. package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +2 -0
  234. package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +1 -3
  235. package/src/renderers/shaders/ShaderChunk/normal_fragment_maps.glsl.js +7 -0
  236. package/src/renderers/shaders/ShaderChunk/premultiplied_alpha_fragment.glsl.js +0 -1
  237. package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +12 -2
  238. package/src/renderers/shaders/ShaderChunk.js +2 -0
  239. package/src/renderers/shaders/ShaderLib/backgroundCube.glsl.js +1 -2
  240. package/src/renderers/shaders/ShaderLib.js +0 -1
  241. package/src/renderers/shaders/UniformsLib.js +7 -2
  242. package/src/renderers/shaders/UniformsUtils.js +27 -5
  243. package/src/renderers/webgl/WebGLAnimation.js +2 -1
  244. package/src/renderers/webgl/WebGLBackground.js +13 -13
  245. package/src/renderers/webgl/WebGLBufferRenderer.js +0 -32
  246. package/src/renderers/webgl/WebGLCapabilities.js +6 -0
  247. package/src/renderers/webgl/WebGLIndexedBufferRenderer.js +0 -32
  248. package/src/renderers/webgl/WebGLMaterials.js +12 -13
  249. package/src/renderers/webgl/WebGLOutput.js +4 -1
  250. package/src/renderers/webgl/WebGLProgram.js +4 -0
  251. package/src/renderers/webgl/WebGLPrograms.js +21 -4
  252. package/src/renderers/webgl/WebGLRenderStates.js +13 -2
  253. package/src/renderers/webgl/WebGLState.js +43 -0
  254. package/src/renderers/webgl/WebGLTextures.js +129 -26
  255. package/src/renderers/webgl/WebGLUniformsGroups.js +19 -0
  256. package/src/renderers/webgl-fallback/WebGLBackend.js +106 -65
  257. package/src/renderers/webgl-fallback/WebGLBufferRenderer.js +0 -41
  258. package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +29 -51
  259. package/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +53 -19
  260. package/src/renderers/webgl-fallback/utils/WebGLCapabilities.js +25 -0
  261. package/src/renderers/webgl-fallback/utils/WebGLState.js +42 -1
  262. package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +63 -50
  263. package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +1 -1
  264. package/src/renderers/webgpu/WebGPUBackend.js +160 -146
  265. package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +55 -33
  266. package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +103 -17
  267. package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +1 -1
  268. package/src/renderers/webgpu/utils/WebGPUCapabilities.js +48 -0
  269. package/src/renderers/webgpu/utils/WebGPUConstants.js +8 -0
  270. package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +91 -17
  271. package/src/renderers/webgpu/utils/WebGPUUtils.js +18 -2
  272. package/src/renderers/webxr/WebXRController.js +12 -0
  273. package/src/textures/HTMLTexture.js +74 -0
  274. package/src/textures/Source.js +1 -1
  275. package/src/textures/Texture.js +13 -2
  276. package/src/utils.js +23 -1
  277. package/src/nodes/utils/RemapNode.js +0 -125
@@ -83,7 +83,7 @@ import { unzlibSync } from '../libs/fflate.module.js';
83
83
  /**
84
84
  * A loader for the OpenEXR texture format.
85
85
  *
86
- * `EXRLoader` currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression.
86
+ * `EXRLoader` currently supports uncompressed, ZIP(S), RLE, PIZ, B44/A and DWA/B compression.
87
87
  * Supports reading as UnsignedByte, HalfFloat and Float type data texture.
88
88
  *
89
89
  * ```js
@@ -121,6 +121,14 @@ class EXRLoader extends DataTextureLoader {
121
121
  */
122
122
  this.outputFormat = RGBAFormat;
123
123
 
124
+ /**
125
+ * For multi-part EXR files, the index of the part to load.
126
+ *
127
+ * @type {number}
128
+ * @default 0
129
+ */
130
+ this.part = 0;
131
+
124
132
  }
125
133
 
126
134
  /**
@@ -164,6 +172,8 @@ class EXRLoader extends DataTextureLoader {
164
172
 
165
173
  const logBase = Math.pow( 2.7182818, 2.2 );
166
174
 
175
+ let b44LogTable = null; // lazily initialized for pLinear B44 channels
176
+
167
177
  function reverseLutFromBitmap( bitmap, lut ) {
168
178
 
169
179
  let k = 0;
@@ -1548,6 +1558,205 @@ class EXRLoader extends DataTextureLoader {
1548
1558
 
1549
1559
  }
1550
1560
 
1561
+ function uncompressB44( info ) {
1562
+
1563
+ const src = info.array;
1564
+ let srcOffset = info.offset.value;
1565
+
1566
+ const width = info.columns;
1567
+ const height = info.lines;
1568
+ const channels = info.inputChannels;
1569
+ const totalBytes = info.totalBytes;
1570
+
1571
+ // B44A allows 3-byte flat blocks; B44 always uses 14-byte blocks
1572
+ const isB44A = EXRHeader.compression === 'B44A_COMPRESSION';
1573
+
1574
+ // Output buffer organised as:
1575
+ // for each scanline y: [ ch0 pixels (w×2 bytes) | ch1 pixels | … ]
1576
+ const outBuffer = new Uint8Array( height * width * totalBytes );
1577
+
1578
+ // Reusable 4×4 block buffer
1579
+ const block = new Uint16Array( 16 );
1580
+
1581
+ // chByteOffset mirrors channelByteOffsets accumulation in setupDecoder
1582
+ let chByteOffset = 0;
1583
+
1584
+ for ( let c = 0; c < channels.length; c ++ ) {
1585
+
1586
+ const channel = channels[ c ];
1587
+ const pixelSize = channel.pixelType * 2; // HALF=2, FLOAT=4
1588
+
1589
+ // Effective dimensions for this channel (subsampled channels are smaller)
1590
+ const chanWidth = Math.ceil( width / channel.xSampling );
1591
+ const chanHeight = Math.ceil( height / channel.ySampling );
1592
+ const isFullRes = channel.xSampling === 1 && channel.ySampling === 1;
1593
+
1594
+ if ( channel.pixelType !== 1 ) {
1595
+
1596
+ // Non-HALF channels are stored raw, scanline by scanline
1597
+ for ( let y = 0; y < chanHeight; y ++ ) {
1598
+
1599
+ if ( isFullRes ) {
1600
+
1601
+ const lineBase = y * width * totalBytes + chByteOffset * width;
1602
+ for ( let x = 0; x < chanWidth * pixelSize; x ++ ) {
1603
+
1604
+ outBuffer[ lineBase + x ] = src[ srcOffset ++ ];
1605
+
1606
+ }
1607
+
1608
+ } else {
1609
+
1610
+ srcOffset += chanWidth * pixelSize;
1611
+
1612
+ }
1613
+
1614
+ }
1615
+
1616
+ chByteOffset += pixelSize;
1617
+ continue;
1618
+
1619
+ }
1620
+
1621
+ // HALF channel — process 4×4 blocks at effective channel dimensions
1622
+ const numBlocksX = Math.ceil( chanWidth / 4 );
1623
+ const numBlocksY = Math.ceil( chanHeight / 4 );
1624
+
1625
+ for ( let by = 0; by < numBlocksY; by ++ ) {
1626
+
1627
+ for ( let bx = 0; bx < numBlocksX; bx ++ ) {
1628
+
1629
+ // B44A only: flat-block when shift ≥ 13 (byte[2] ≥ 52)
1630
+ if ( isB44A && src[ srcOffset + 2 ] >= 52 ) {
1631
+
1632
+ // 3-byte flat block — all 16 pixels share one value
1633
+ const t = ( src[ srcOffset ] << 8 ) | src[ srcOffset + 1 ];
1634
+ const h = ( t & 0x8000 ) ? ( t & 0x7fff ) : ( ( ~ t ) & 0xffff );
1635
+ block.fill( h );
1636
+ srcOffset += 3;
1637
+
1638
+ } else {
1639
+
1640
+ // 14-byte B44 block
1641
+ const s0 = ( src[ srcOffset ] << 8 ) | src[ srcOffset + 1 ];
1642
+ const shift = src[ srcOffset + 2 ] >> 2;
1643
+ const bias = 0x20 << shift;
1644
+
1645
+ // Reconstruct 16 ordered-magnitude values from 6-bit running deltas.
1646
+ // Prediction structure (row = 4 pixels wide):
1647
+ // column 0 top-to-bottom: s0 → s4 → s8 → s12
1648
+ // then row-wise: s0 → s1 → s2 → s3
1649
+ // s4 → s5 → s6 → s7 etc.
1650
+
1651
+ const s4 = ( s0 + ( ( ( src[ srcOffset + 2 ] << 4 ) | ( src[ srcOffset + 3 ] >> 4 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1652
+ const s8 = ( s4 + ( ( ( src[ srcOffset + 3 ] << 2 ) | ( src[ srcOffset + 4 ] >> 6 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1653
+ const s12 = ( s8 + ( src[ srcOffset + 4 ] & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1654
+
1655
+ const s1 = ( s0 + ( ( src[ srcOffset + 5 ] >> 2 ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1656
+ const s5 = ( s4 + ( ( ( src[ srcOffset + 5 ] << 4 ) | ( src[ srcOffset + 6 ] >> 4 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1657
+ const s9 = ( s8 + ( ( ( src[ srcOffset + 6 ] << 2 ) | ( src[ srcOffset + 7 ] >> 6 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1658
+ const s13 = ( s12 + ( src[ srcOffset + 7 ] & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1659
+
1660
+ const s2 = ( s1 + ( ( src[ srcOffset + 8 ] >> 2 ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1661
+ const s6 = ( s5 + ( ( ( src[ srcOffset + 8 ] << 4 ) | ( src[ srcOffset + 9 ] >> 4 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1662
+ const s10 = ( s9 + ( ( ( src[ srcOffset + 9 ] << 2 ) | ( src[ srcOffset + 10 ] >> 6 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1663
+ const s14 = ( s13 + ( src[ srcOffset + 10 ] & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1664
+
1665
+ const s3 = ( s2 + ( ( src[ srcOffset + 11 ] >> 2 ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1666
+ const s7 = ( s6 + ( ( ( src[ srcOffset + 11 ] << 4 ) | ( src[ srcOffset + 12 ] >> 4 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1667
+ const s11 = ( s10 + ( ( ( src[ srcOffset + 12 ] << 2 ) | ( src[ srcOffset + 13 ] >> 6 ) ) & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1668
+ const s15 = ( s14 + ( src[ srcOffset + 13 ] & 0x3f ) * ( 1 << shift ) - bias ) & 0xffff;
1669
+
1670
+ // Convert ordered-magnitude → half-float:
1671
+ // positive (bit15=1): clear sign bit; negative (bit15=0): invert all bits
1672
+ const t = [ s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15 ];
1673
+ for ( let i = 0; i < 16; i ++ ) {
1674
+
1675
+ block[ i ] = ( t[ i ] & 0x8000 ) ? ( t[ i ] & 0x7fff ) : ( ( ~ t[ i ] ) & 0xffff );
1676
+
1677
+ }
1678
+
1679
+ srcOffset += 14;
1680
+
1681
+ }
1682
+
1683
+ // pLinear channels: data was stored as exp(x/8), convert back with 8·log(x)
1684
+ if ( channel.pLinear ) {
1685
+
1686
+ if ( b44LogTable === null ) {
1687
+
1688
+ b44LogTable = new Uint16Array( 65536 );
1689
+ for ( let i = 0; i < 65536; i ++ ) {
1690
+
1691
+ if ( ( i & 0x7c00 ) === 0x7c00 || i > 0x8000 ) {
1692
+
1693
+ b44LogTable[ i ] = 0;
1694
+
1695
+ } else {
1696
+
1697
+ const f = decodeFloat16( i );
1698
+ b44LogTable[ i ] = ( f <= 0 ) ? 0 : DataUtils.toHalfFloat( 8 * Math.log( f ) );
1699
+
1700
+ }
1701
+
1702
+ }
1703
+
1704
+ }
1705
+
1706
+ for ( let i = 0; i < 16; i ++ ) block[ i ] = b44LogTable[ block[ i ] ];
1707
+
1708
+ }
1709
+
1710
+ // Scatter the 16 pixels into the scanline-interleaved output buffer.
1711
+ // For subsampled channels (e.g. RY/BY with xSampling=ySampling=2) each decoded
1712
+ // pixel is replicated across its xSampling×ySampling footprint so the output
1713
+ // buffer has uniform full-resolution scanlines that parseScanline can read directly.
1714
+ for ( let py = 0; py < 4; py ++ ) {
1715
+
1716
+ const chanY = by * 4 + py;
1717
+ if ( chanY >= chanHeight ) continue;
1718
+
1719
+ for ( let px = 0; px < 4; px ++ ) {
1720
+
1721
+ const chanX = bx * 4 + px;
1722
+ if ( chanX >= chanWidth ) continue;
1723
+
1724
+ const val = block[ py * 4 + px ];
1725
+
1726
+ for ( let dy = 0; dy < channel.ySampling; dy ++ ) {
1727
+
1728
+ const fullY = chanY * channel.ySampling + dy;
1729
+ if ( fullY >= height ) continue;
1730
+
1731
+ for ( let dx = 0; dx < channel.xSampling; dx ++ ) {
1732
+
1733
+ const fullX = chanX * channel.xSampling + dx;
1734
+ if ( fullX >= width ) continue;
1735
+
1736
+ const outIdx = fullY * width * totalBytes + chByteOffset * width + fullX * 2;
1737
+ outBuffer[ outIdx ] = val & 0xff;
1738
+ outBuffer[ outIdx + 1 ] = ( val >> 8 ) & 0xff;
1739
+
1740
+ }
1741
+
1742
+ }
1743
+
1744
+ }
1745
+
1746
+ }
1747
+
1748
+ }
1749
+
1750
+ }
1751
+
1752
+ chByteOffset += 2; // HALF = 2 bytes per pixel
1753
+
1754
+ }
1755
+
1756
+ return new DataView( outBuffer.buffer );
1757
+
1758
+ }
1759
+
1551
1760
  function uncompressDWA( info ) {
1552
1761
 
1553
1762
  const inDataView = info.viewer;
@@ -1625,11 +1834,14 @@ class EXRLoader extends DataTextureLoader {
1625
1834
 
1626
1835
  const cd = channelData[ offset ];
1627
1836
 
1837
+ const dotIndex = cd.name.lastIndexOf( '.' );
1838
+ const suffix = dotIndex >= 0 ? cd.name.substring( dotIndex + 1 ) : cd.name;
1839
+
1628
1840
  for ( let i = 0; i < channelRules.length; ++ i ) {
1629
1841
 
1630
1842
  const rule = channelRules[ i ];
1631
1843
 
1632
- if ( cd.name == rule.name ) {
1844
+ if ( suffix === rule.name && cd.type === rule.type ) {
1633
1845
 
1634
1846
  cd.compression = rule.compression;
1635
1847
 
@@ -1870,17 +2082,7 @@ class EXRLoader extends DataTextureLoader {
1870
2082
 
1871
2083
  const parseInt64 = function ( dataView, offset ) {
1872
2084
 
1873
- let int;
1874
-
1875
- if ( 'getBigInt64' in DataView.prototype ) {
1876
-
1877
- int = Number( dataView.getBigInt64( offset.value, true ) );
1878
-
1879
- } else {
1880
-
1881
- int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 );
1882
-
1883
- }
2085
+ const int = Number( dataView.getBigInt64( offset.value, true ) );
1884
2086
 
1885
2087
  offset.value += ULONG_SIZE;
1886
2088
 
@@ -2145,8 +2347,9 @@ class EXRLoader extends DataTextureLoader {
2145
2347
 
2146
2348
  return parseTimecode( dataView, offset );
2147
2349
 
2148
- } else if ( type === 'preview' ) {
2350
+ } else if ( type === 'preview' || type === 'deepImageState' || type === 'idmanifest' ) {
2149
2351
 
2352
+ // Known metadata-only types: silently skip, they carry no pixel data.
2150
2353
  offset.value += size;
2151
2354
  return 'skipped';
2152
2355
 
@@ -2316,9 +2519,300 @@ class EXRLoader extends DataTextureLoader {
2316
2519
 
2317
2520
  }
2318
2521
 
2319
- function parseHeader( dataView, buffer, offset ) {
2522
+ function parseMultiPartScanline() {
2523
+
2524
+ const EXRDecoder = this;
2525
+ const chunkOffsets = EXRDecoder.chunkOffsets;
2526
+ const tmpOffset = { value: 0 };
2527
+
2528
+ for ( let chunkIdx = 0; chunkIdx < chunkOffsets.length; chunkIdx ++ ) {
2529
+
2530
+ const offset = { value: chunkOffsets[ chunkIdx ] };
2531
+
2532
+ offset.value += INT32_SIZE; // skip part number
2533
+
2534
+ const line = parseInt32( EXRDecoder.viewer, offset ) - EXRHeader.dataWindow.yMin;
2535
+ EXRDecoder.size = parseUint32( EXRDecoder.viewer, offset );
2536
+ EXRDecoder.lines = ( ( line + EXRDecoder.blockHeight > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.blockHeight );
2537
+
2538
+ const bytesPerLine = EXRDecoder.columns * EXRDecoder.totalBytes;
2539
+ const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesPerLine;
2540
+
2541
+ const savedOffset = EXRDecoder.offset;
2542
+ EXRDecoder.offset = offset;
2543
+ const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
2544
+ EXRDecoder.offset = savedOffset;
2545
+
2546
+ for ( let line_y = 0; line_y < EXRDecoder.blockHeight; line_y ++ ) {
2547
+
2548
+ const true_y = line_y + line;
2549
+ if ( true_y >= EXRDecoder.height ) continue;
2550
+
2551
+ const lineOffset = line_y * bytesPerLine;
2552
+ const outLineOffset = ( EXRDecoder.height - 1 - true_y ) * EXRDecoder.outLineWidth;
2553
+
2554
+ for ( let channelID = 0; channelID < EXRDecoder.inputChannels.length; channelID ++ ) {
2555
+
2556
+ const name = EXRHeader.channels[ channelID ].name;
2557
+ const lOff = EXRDecoder.channelByteOffsets[ name ] * EXRDecoder.columns;
2558
+ const cOff = EXRDecoder.decodeChannels[ name ];
2559
+
2560
+ if ( cOff === undefined ) continue;
2561
+
2562
+ tmpOffset.value = lineOffset + lOff;
2563
+
2564
+ for ( let x = 0; x < EXRDecoder.columns; x ++ ) {
2565
+
2566
+ const outIndex = outLineOffset + x * EXRDecoder.outputChannels + cOff;
2567
+ EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
2568
+
2569
+ }
2570
+
2571
+ }
2572
+
2573
+ }
2574
+
2575
+ }
2576
+
2577
+ }
2578
+
2579
+ function decompressDeepData( array, compressedOffset, compressedSize, compression ) {
2320
2580
 
2321
- const EXRHeader = {};
2581
+ if ( compressedSize === 0 ) return null;
2582
+
2583
+ const compressed = array.slice( compressedOffset, compressedOffset + compressedSize );
2584
+
2585
+ switch ( compression ) {
2586
+
2587
+ case 'NO_COMPRESSION':
2588
+ return new DataView( compressed.buffer, compressed.byteOffset, compressed.byteLength );
2589
+
2590
+ case 'RLE_COMPRESSION': {
2591
+
2592
+ const rawBuffer = new Uint8Array( decodeRunLength( compressed.buffer.slice( compressed.byteOffset, compressed.byteOffset + compressed.byteLength ) ) );
2593
+ const tmpBuffer = new Uint8Array( rawBuffer.length );
2594
+ predictor( rawBuffer );
2595
+ interleaveScalar( rawBuffer, tmpBuffer );
2596
+ return new DataView( tmpBuffer.buffer );
2597
+
2598
+ }
2599
+
2600
+ case 'ZIPS_COMPRESSION': {
2601
+
2602
+ const rawBuffer = unzlibSync( compressed );
2603
+ const tmpBuffer = new Uint8Array( rawBuffer.length );
2604
+ predictor( rawBuffer );
2605
+ interleaveScalar( rawBuffer, tmpBuffer );
2606
+ return new DataView( tmpBuffer.buffer );
2607
+
2608
+ }
2609
+
2610
+ default:
2611
+ throw new Error( 'EXRLoader.parse: ' + compression + ' is unsupported for deep data' );
2612
+
2613
+ }
2614
+
2615
+ }
2616
+
2617
+ function parseDeepScanline() {
2618
+
2619
+ const EXRDecoder = this;
2620
+ const chunkOffsets = EXRDecoder.chunkOffsets;
2621
+ const width = EXRDecoder.width;
2622
+ const height = EXRDecoder.height;
2623
+ const deepChannels = EXRDecoder.deepChannels;
2624
+ const compression = EXRHeader.compression;
2625
+ const isMultiPart = EXRDecoder.multiPart;
2626
+
2627
+ // Build a map from channel name to decode output slot
2628
+ const decodeChannels = EXRDecoder.decodeChannels;
2629
+ const outputChannels = EXRDecoder.outputChannels;
2630
+ const isHalfOutput = EXRDecoder.byteArray instanceof Uint16Array;
2631
+
2632
+ // Find the alpha channel index in deepChannels (for compositing)
2633
+ let alphaChannelIdx = - 1;
2634
+
2635
+ for ( let i = 0; i < deepChannels.length; i ++ ) {
2636
+
2637
+ if ( deepChannels[ i ].name === 'A' ) {
2638
+
2639
+ alphaChannelIdx = i;
2640
+ break;
2641
+
2642
+ }
2643
+
2644
+ }
2645
+
2646
+ for ( let chunkIdx = 0; chunkIdx < chunkOffsets.length; chunkIdx ++ ) {
2647
+
2648
+ const chunkOffset = { value: chunkOffsets[ chunkIdx ] };
2649
+
2650
+ // Multi-part files have a part number prefix per chunk
2651
+ if ( isMultiPart ) chunkOffset.value += INT32_SIZE;
2652
+
2653
+ const line = parseInt32( EXRDecoder.viewer, chunkOffset ) - EXRHeader.dataWindow.yMin;
2654
+
2655
+ // Read deep scanline sizes
2656
+ const sctCompressedSize = parseInt64( EXRDecoder.viewer, chunkOffset );
2657
+ const dataCompressedSize = parseInt64( EXRDecoder.viewer, chunkOffset );
2658
+ parseInt64( EXRDecoder.viewer, chunkOffset ); // uncompressed data size (unused)
2659
+
2660
+ // Decompress sample count table
2661
+ const sctView = decompressDeepData( EXRDecoder.array, chunkOffset.value, sctCompressedSize, compression );
2662
+ chunkOffset.value += sctCompressedSize;
2663
+
2664
+ if ( sctView === null ) continue;
2665
+
2666
+ // Parse cumulative sample counts
2667
+ const cumulativeCounts = new Uint32Array( width );
2668
+
2669
+ for ( let x = 0; x < width; x ++ ) {
2670
+
2671
+ cumulativeCounts[ x ] = sctView.getUint32( x * 4, true );
2672
+
2673
+ }
2674
+
2675
+ const totalSamples = cumulativeCounts[ width - 1 ];
2676
+
2677
+ if ( totalSamples === 0 ) {
2678
+
2679
+ chunkOffset.value += dataCompressedSize;
2680
+ continue;
2681
+
2682
+ }
2683
+
2684
+ // Decompress pixel data
2685
+ const pixelView = decompressDeepData( EXRDecoder.array, chunkOffset.value, dataCompressedSize, compression );
2686
+
2687
+ // Compute channel byte offsets within the decompressed pixel data.
2688
+ // Deep data layout: channels are contiguous, each has totalSamples values.
2689
+ const channelOffsets = [];
2690
+ let bytePos = 0;
2691
+
2692
+ for ( let i = 0; i < deepChannels.length; i ++ ) {
2693
+
2694
+ channelOffsets.push( bytePos );
2695
+ bytePos += totalSamples * deepChannels[ i ].bytesPerSample;
2696
+
2697
+ }
2698
+
2699
+ // Flatten deep samples: front-to-back composite with premultiplied alpha
2700
+ const outLineOffset = ( height - 1 - line ) * EXRDecoder.outLineWidth;
2701
+
2702
+ for ( let x = 0; x < width; x ++ ) {
2703
+
2704
+ const startSample = x === 0 ? 0 : cumulativeCounts[ x - 1 ];
2705
+ const endSample = cumulativeCounts[ x ];
2706
+ const numSamples = endSample - startSample;
2707
+
2708
+ if ( numSamples === 0 ) continue;
2709
+
2710
+ // Composite samples front-to-back (premultiplied alpha)
2711
+ const composited = new Float32Array( outputChannels );
2712
+ let compositedAlpha = 0;
2713
+
2714
+ for ( let s = 0; s < numSamples; s ++ ) {
2715
+
2716
+ const sampleIdx = startSample + s;
2717
+ const factor = 1 - compositedAlpha;
2718
+
2719
+ if ( factor <= 0 ) break;
2720
+
2721
+ // Read alpha for this sample
2722
+ let sampleAlpha = 1;
2723
+
2724
+ if ( alphaChannelIdx >= 0 ) {
2725
+
2726
+ const aBps = deepChannels[ alphaChannelIdx ].bytesPerSample;
2727
+ const aOff = channelOffsets[ alphaChannelIdx ] + sampleIdx * aBps;
2728
+
2729
+ sampleAlpha = aBps === 2
2730
+ ? decodeFloat16( pixelView.getUint16( aOff, true ) )
2731
+ : pixelView.getFloat32( aOff, true );
2732
+
2733
+ }
2734
+
2735
+ // Read and composite each output channel
2736
+ for ( let ci = 0; ci < deepChannels.length; ci ++ ) {
2737
+
2738
+ const ch = deepChannels[ ci ];
2739
+ const cOff = decodeChannels[ ch.name ];
2740
+
2741
+ if ( cOff === undefined ) continue;
2742
+
2743
+ const bps = ch.bytesPerSample;
2744
+ const dataOff = channelOffsets[ ci ] + sampleIdx * bps;
2745
+
2746
+ const value = bps === 2
2747
+ ? decodeFloat16( pixelView.getUint16( dataOff, true ) )
2748
+ : pixelView.getFloat32( dataOff, true );
2749
+
2750
+ composited[ cOff ] += value * factor;
2751
+
2752
+ }
2753
+
2754
+ compositedAlpha += sampleAlpha * factor;
2755
+
2756
+ }
2757
+
2758
+ // If alpha channel is being output, set it
2759
+ if ( decodeChannels[ 'A' ] !== undefined ) {
2760
+
2761
+ composited[ decodeChannels[ 'A' ] ] = compositedAlpha;
2762
+
2763
+ }
2764
+
2765
+ // Write to output buffer
2766
+ const outIndex = outLineOffset + x * outputChannels;
2767
+
2768
+ for ( let c = 0; c < outputChannels; c ++ ) {
2769
+
2770
+ EXRDecoder.byteArray[ outIndex + c ] = isHalfOutput
2771
+ ? DataUtils.toHalfFloat( composited[ c ] )
2772
+ : composited[ c ];
2773
+
2774
+ }
2775
+
2776
+ }
2777
+
2778
+ }
2779
+
2780
+ }
2781
+
2782
+ function parsePartHeader( dataView, buffer, offset ) {
2783
+
2784
+ const header = {};
2785
+ let hasAttributes = false;
2786
+
2787
+ while ( true ) {
2788
+
2789
+ const attributeName = parseNullTerminatedString( buffer, offset );
2790
+
2791
+ if ( attributeName === '' ) break;
2792
+
2793
+ hasAttributes = true;
2794
+
2795
+ const attributeType = parseNullTerminatedString( buffer, offset );
2796
+ const attributeSize = parseUint32( dataView, offset );
2797
+ const attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize );
2798
+
2799
+ if ( attributeValue === undefined ) {
2800
+
2801
+ console.warn( `THREE.EXRLoader: Skipped unknown header attribute type \'${attributeType}\'.` );
2802
+
2803
+ } else {
2804
+
2805
+ header[ attributeName ] = attributeValue;
2806
+
2807
+ }
2808
+
2809
+ }
2810
+
2811
+ return hasAttributes ? header : null;
2812
+
2813
+ }
2814
+
2815
+ function parseHeader( dataView, buffer, offset ) {
2322
2816
 
2323
2817
  if ( dataView.getUint32( 0, true ) != 20000630 ) { // magic
2324
2818
 
@@ -2326,11 +2820,11 @@ class EXRLoader extends DataTextureLoader {
2326
2820
 
2327
2821
  }
2328
2822
 
2329
- EXRHeader.version = dataView.getUint8( 4 );
2823
+ const version = dataView.getUint8( 4 );
2330
2824
 
2331
2825
  const spec = dataView.getUint8( 5 ); // fullMask
2332
2826
 
2333
- EXRHeader.spec = {
2827
+ const flags = {
2334
2828
  singleTile: !! ( spec & 2 ),
2335
2829
  longName: !! ( spec & 4 ),
2336
2830
  deepFormat: !! ( spec & 8 ),
@@ -2341,44 +2835,43 @@ class EXRLoader extends DataTextureLoader {
2341
2835
 
2342
2836
  offset.value = 8; // start at 8 - after pre-amble
2343
2837
 
2344
- let keepReading = true;
2345
-
2346
- while ( keepReading ) {
2838
+ const headers = [];
2347
2839
 
2348
- const attributeName = parseNullTerminatedString( buffer, offset );
2349
-
2350
- if ( attributeName === '' ) {
2840
+ if ( flags.multiPart ) {
2351
2841
 
2352
- keepReading = false;
2842
+ // Multi-part files: parse all part headers.
2843
+ // Each part header ends with an empty attribute name (null byte).
2844
+ // The header section ends when a null byte is read with no preceding attributes.
2353
2845
 
2354
- } else {
2846
+ while ( true ) {
2355
2847
 
2356
- const attributeType = parseNullTerminatedString( buffer, offset );
2357
- const attributeSize = parseUint32( dataView, offset );
2358
- const attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize );
2848
+ const header = parsePartHeader( dataView, buffer, offset );
2849
+ if ( header === null ) break;
2359
2850
 
2360
- if ( attributeValue === undefined ) {
2851
+ header.version = version;
2852
+ header.spec = flags;
2853
+ headers.push( header );
2361
2854
 
2362
- console.warn( `THREE.EXRLoader: Skipped unknown header attribute type \'${attributeType}\'.` );
2855
+ }
2363
2856
 
2364
- } else {
2857
+ if ( headers.length === 0 ) {
2365
2858
 
2366
- EXRHeader[ attributeName ] = attributeValue;
2367
-
2368
- }
2859
+ throw new Error( 'THREE.EXRLoader: No valid part headers found.' );
2369
2860
 
2370
2861
  }
2371
2862
 
2372
- }
2863
+ } else {
2373
2864
 
2374
- if ( ( spec & ~ 0x06 ) != 0 ) { // unsupported deep-image, multi-part
2865
+ // Single-part (standard or deep): one header
2375
2866
 
2376
- console.error( 'THREE.EXRHeader:', EXRHeader );
2377
- throw new Error( 'THREE.EXRLoader: Provided file is currently unsupported.' );
2867
+ const header = parsePartHeader( dataView, buffer, offset );
2868
+ header.version = version;
2869
+ header.spec = flags;
2870
+ headers.push( header );
2378
2871
 
2379
2872
  }
2380
2873
 
2381
- return EXRHeader;
2874
+ return headers;
2382
2875
 
2383
2876
  }
2384
2877
 
@@ -2394,6 +2887,7 @@ class EXRLoader extends DataTextureLoader {
2394
2887
  inputChannels: EXRHeader.channels,
2395
2888
  channelByteOffsets: {},
2396
2889
  shouldExpand: false,
2890
+ yCbCr: false,
2397
2891
  scanOrder: null,
2398
2892
  totalBytes: null,
2399
2893
  columns: null,
@@ -2437,6 +2931,12 @@ class EXRLoader extends DataTextureLoader {
2437
2931
  EXRDecoder.uncompress = uncompressPXR;
2438
2932
  break;
2439
2933
 
2934
+ case 'B44_COMPRESSION':
2935
+ case 'B44A_COMPRESSION':
2936
+ EXRDecoder.blockHeight = 32;
2937
+ EXRDecoder.uncompress = uncompressB44;
2938
+ break;
2939
+
2440
2940
  case 'DWAA_COMPRESSION':
2441
2941
  EXRDecoder.blockHeight = 32;
2442
2942
  EXRDecoder.uncompress = uncompressDWA;
@@ -2457,6 +2957,8 @@ class EXRLoader extends DataTextureLoader {
2457
2957
 
2458
2958
  switch ( channel.name ) {
2459
2959
 
2960
+ case 'BY':
2961
+ case 'RY':
2460
2962
  case 'Y':
2461
2963
  case 'R':
2462
2964
  case 'G':
@@ -2474,7 +2976,12 @@ class EXRLoader extends DataTextureLoader {
2474
2976
  let invalidOutput = false;
2475
2977
 
2476
2978
  // Validate if input texture contain supported channels
2477
- if ( channels.R && channels.G && channels.B ) {
2979
+ if ( channels.Y && channels.RY && channels.BY ) {
2980
+
2981
+ EXRDecoder.outputChannels = 4;
2982
+ EXRDecoder.yCbCr = true;
2983
+
2984
+ } else if ( channels.R && channels.G && channels.B ) {
2478
2985
 
2479
2986
  EXRDecoder.outputChannels = 4;
2480
2987
 
@@ -2565,6 +3072,16 @@ class EXRLoader extends DataTextureLoader {
2565
3072
 
2566
3073
  if ( invalidOutput ) throw new Error( 'EXRLoader.parse: invalid output format for specified file.' );
2567
3074
 
3075
+ // Luminance/chroma images always decode to RGBA; override whatever the output-format switch selected.
3076
+ if ( EXRDecoder.yCbCr ) {
3077
+
3078
+ EXRDecoder.format = RGBAFormat;
3079
+ EXRDecoder.outputChannels = 4;
3080
+ EXRDecoder.decodeChannels = { Y: 0, RY: 1, BY: 2 };
3081
+ fillAlpha = true;
3082
+
3083
+ }
3084
+
2568
3085
  if ( EXRDecoder.type == 1 ) {
2569
3086
 
2570
3087
  // half
@@ -2654,7 +3171,33 @@ class EXRLoader extends DataTextureLoader {
2654
3171
 
2655
3172
  }
2656
3173
 
2657
- if ( EXRHeader.spec.singleTile ) {
3174
+ if ( EXRHeader.spec.deepFormat ) {
3175
+
3176
+ // Deep format: offset tables are already parsed in the main flow.
3177
+ // Compute per-channel byte sizes for the deep pixel data layout.
3178
+
3179
+ EXRDecoder.deepChannels = [];
3180
+ let deepBytesPerSample = 0;
3181
+
3182
+ for ( const channel of EXRHeader.channels ) {
3183
+
3184
+ // UINT=0→4bytes, HALF=1→2bytes, FLOAT=2→4bytes
3185
+ const bytesPerSample = channel.pixelType === 0 ? 4 : channel.pixelType * 2;
3186
+ EXRDecoder.deepChannels.push( {
3187
+ name: channel.name,
3188
+ pixelType: channel.pixelType,
3189
+ bytesPerSample: bytesPerSample,
3190
+ } );
3191
+ deepBytesPerSample += bytesPerSample;
3192
+
3193
+ }
3194
+
3195
+ EXRDecoder.deepBytesPerSample = deepBytesPerSample;
3196
+ EXRDecoder.chunkOffsets = EXRHeader._chunkOffsets;
3197
+ EXRDecoder.multiPart = EXRHeader.spec.multiPart;
3198
+ EXRDecoder.decode = parseDeepScanline.bind( EXRDecoder );
3199
+
3200
+ } else if ( EXRHeader.spec.singleTile ) {
2658
3201
 
2659
3202
  EXRDecoder.blockHeight = EXRHeader.tiles.ySize;
2660
3203
  EXRDecoder.blockWidth = EXRHeader.tiles.xSize;
@@ -2674,6 +3217,13 @@ class EXRLoader extends DataTextureLoader {
2674
3217
 
2675
3218
  EXRDecoder.decode = parseTiles.bind( EXRDecoder );
2676
3219
 
3220
+ } else if ( EXRHeader.spec.multiPart ) {
3221
+
3222
+ // Multi-part scanline: offsets already parsed in main flow.
3223
+ EXRDecoder.blockWidth = EXRDecoder.width;
3224
+ EXRDecoder.chunkOffsets = EXRHeader._chunkOffsets;
3225
+ EXRDecoder.decode = parseMultiPartScanline.bind( EXRDecoder );
3226
+
2677
3227
  } else {
2678
3228
 
2679
3229
  EXRDecoder.blockWidth = EXRDecoder.width;
@@ -2696,7 +3246,38 @@ class EXRLoader extends DataTextureLoader {
2696
3246
  const uInt8Array = new Uint8Array( buffer );
2697
3247
 
2698
3248
  // get header information and validate format.
2699
- const EXRHeader = parseHeader( bufferDataView, buffer, offset );
3249
+ const EXRHeaders = parseHeader( bufferDataView, buffer, offset );
3250
+
3251
+ // select part to decode
3252
+ const partIndex = Math.max( 0, Math.min( this.part, EXRHeaders.length - 1 ) );
3253
+ const EXRHeader = EXRHeaders[ partIndex ];
3254
+
3255
+ // for multi-part deep files, skip offset tables for other parts
3256
+ if ( EXRHeader.spec.multiPart || EXRHeader.spec.deepFormat ) {
3257
+
3258
+ for ( let p = 0; p < EXRHeaders.length; p ++ ) {
3259
+
3260
+ const chunkCount = EXRHeaders[ p ].chunkCount;
3261
+
3262
+ if ( p === partIndex ) {
3263
+
3264
+ // store offset table for the selected part
3265
+ EXRHeader._chunkOffsets = [];
3266
+
3267
+ for ( let i = 0; i < chunkCount; i ++ )
3268
+ EXRHeader._chunkOffsets.push( parseInt64( bufferDataView, offset ) );
3269
+
3270
+ } else {
3271
+
3272
+ // skip other parts' offset tables
3273
+ for ( let i = 0; i < chunkCount; i ++ )
3274
+ parseInt64( bufferDataView, offset );
3275
+
3276
+ }
3277
+
3278
+ }
3279
+
3280
+ }
2700
3281
 
2701
3282
  // get input compression information and prepare decoding.
2702
3283
  const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type, this.outputFormat );
@@ -2723,6 +3304,51 @@ class EXRLoader extends DataTextureLoader {
2723
3304
 
2724
3305
  }
2725
3306
 
3307
+ // Luminance/chroma → RGB conversion (second pass).
3308
+ // Y/RY/BY were decoded into output slots 0/1/2; convert in-place using Rec.709 coefficients:
3309
+ // R = ( 1 + RY ) * Y, B = ( 1 + BY ) * Y, G = ( Y − R·0.2126 − B·0.0722 ) / 0.7152
3310
+ if ( EXRDecoder.yCbCr ) {
3311
+
3312
+ const byteArray = EXRDecoder.byteArray;
3313
+ const nPixels = EXRDecoder.width * EXRDecoder.height;
3314
+
3315
+ if ( this.type === HalfFloatType ) {
3316
+
3317
+ for ( let i = 0; i < nPixels; i ++ ) {
3318
+
3319
+ const base = i * 4;
3320
+ const Y = decodeFloat16( byteArray[ base ] );
3321
+ const RY = decodeFloat16( byteArray[ base + 1 ] );
3322
+ const BY = decodeFloat16( byteArray[ base + 2 ] );
3323
+ const R = ( 1 + RY ) * Y;
3324
+ const B = ( 1 + BY ) * Y;
3325
+ const G = ( Y - R * 0.2126 - B * 0.0722 ) / 0.7152;
3326
+ byteArray[ base ] = DataUtils.toHalfFloat( Math.max( 0, R ) );
3327
+ byteArray[ base + 1 ] = DataUtils.toHalfFloat( Math.max( 0, G ) );
3328
+ byteArray[ base + 2 ] = DataUtils.toHalfFloat( Math.max( 0, B ) );
3329
+
3330
+ }
3331
+
3332
+ } else {
3333
+
3334
+ for ( let i = 0; i < nPixels; i ++ ) {
3335
+
3336
+ const base = i * 4;
3337
+ const Y = byteArray[ base ];
3338
+ const RY = byteArray[ base + 1 ];
3339
+ const BY = byteArray[ base + 2 ];
3340
+ const R = ( 1 + RY ) * Y;
3341
+ const B = ( 1 + BY ) * Y;
3342
+ byteArray[ base ] = Math.max( 0, R );
3343
+ byteArray[ base + 1 ] = Math.max( 0, ( Y - R * 0.2126 - B * 0.0722 ) / 0.7152 );
3344
+ byteArray[ base + 2 ] = Math.max( 0, B );
3345
+
3346
+ }
3347
+
3348
+ }
3349
+
3350
+ }
3351
+
2726
3352
  return {
2727
3353
  header: EXRHeader,
2728
3354
  width: EXRDecoder.width,
@@ -2761,6 +3387,19 @@ class EXRLoader extends DataTextureLoader {
2761
3387
 
2762
3388
  }
2763
3389
 
3390
+ /**
3391
+ * For multi-part EXR files, sets which part to load.
3392
+ *
3393
+ * @param {number} value - The part index to load.
3394
+ * @return {EXRLoader} A reference to this loader.
3395
+ */
3396
+ setPart( value ) {
3397
+
3398
+ this.part = value;
3399
+ return this;
3400
+
3401
+ }
3402
+
2764
3403
  load( url, onLoad, onProgress, onError ) {
2765
3404
 
2766
3405
  function onLoadCallback( texture, texData ) {