rayzee 9.4.1 → 9.5.0

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 (145) hide show
  1. package/README.md +226 -25
  2. package/dist/ActiveColor-YvuQq44O.js +13 -0
  3. package/dist/ArchiveImporter-xuUJiklP.js +4301 -0
  4. package/dist/AssetConfig-DSEntE65.js +30 -0
  5. package/dist/BidirectionalIntegrator-C5PKjJoT.js +934 -0
  6. package/dist/ColorManagement-CheBCPaW.js +1488 -0
  7. package/dist/EngineDefaults-DH3bI5t-.js +844 -0
  8. package/dist/EngineEvents-Ci8dVWCP.js +73 -0
  9. package/dist/EngineIssues-CIZnXHsF.js +72 -0
  10. package/dist/FileFormats-sPGJeCRc.js +102 -0
  11. package/dist/Logger-BnHghprG.js +97 -0
  12. package/dist/{NeuralRetouch-vkdXXyK2.js → NeuralRetouch-CW11yLZ_.js} +8 -6
  13. package/dist/{NeuralSuperRes-FVqRJKQV.js → NeuralSuperRes-BekaecYg.js} +55 -120
  14. package/dist/PathTracer-Ccl9KOuF.js +29729 -0
  15. package/dist/PhysicalSky-Dv2PMFGW.js +1219 -0
  16. package/dist/RayzeeRenderer-BQxnzCTD.js +3542 -0
  17. package/dist/SolarLimb-ken-JbDK.js +721 -0
  18. package/dist/ToneMapGPU-BHi5JzIq.js +177 -0
  19. package/dist/ViewTransforms-D0rTLBsX.js +255 -0
  20. package/dist/WorkingMatrix-C6y233hT.js +27 -0
  21. package/dist/ZipReader-gDSWr0I5.js +98 -0
  22. package/dist/addons/archives.es.js +3 -0
  23. package/dist/addons/bidirectional.es.js +2 -0
  24. package/dist/addons/color.es.js +3 -0
  25. package/dist/addons/formats.es.js +2 -0
  26. package/dist/addons/physical-sky.es.js +2 -0
  27. package/dist/addons/storage.es.js +3 -0
  28. package/dist/archiveFormats-BZw7TiDp.js +75 -0
  29. package/dist/identity-DrQsGT2o.js +39 -0
  30. package/dist/openStorage-DwcBRUj7.js +820 -0
  31. package/dist/rayzee-core.es.js +12 -0
  32. package/dist/rayzee.es.js +2266 -44125
  33. package/dist/rayzee.umd.js +485 -473
  34. package/dist/sceneCachePolicy-BmwYOYDu.js +183 -0
  35. package/dist/shared-BGBcSxBQ.js +17 -0
  36. package/package.json +23 -2
  37. package/src/Color/ActiveColor.js +34 -0
  38. package/src/Color/BasicColor.js +147 -0
  39. package/src/Color/ColorManagement.js +7 -31
  40. package/src/EngineDefaults.js +23 -14
  41. package/src/EngineEvents.js +2 -0
  42. package/src/EngineIssues.js +1 -0
  43. package/src/PathTracerApp.js +617 -4016
  44. package/src/Pipeline/PipelineContext.js +0 -4
  45. package/src/Platform.js +36 -0
  46. package/src/Processor/ArchiveCache.js +1 -1
  47. package/src/Processor/ArchiveImporter.js +1259 -0
  48. package/src/Processor/AssetLoader.js +322 -1746
  49. package/src/Processor/AtmosphereModel.js +1 -2
  50. package/src/Processor/BVHBuilder.js +4 -95
  51. package/src/Processor/EnvironmentExactTable.js +187 -0
  52. package/src/Processor/EquirectHDRInfo.js +30 -223
  53. package/src/Processor/FileFormats.js +116 -0
  54. package/src/Processor/GLTFDecoders.js +85 -0
  55. package/src/Processor/GLTFDiffuseTransmission.js +63 -0
  56. package/src/Processor/GeometryExtractor.js +12 -1
  57. package/src/Processor/IESParser.js +47 -35
  58. package/src/Processor/KernelManager.js +35 -2
  59. package/src/Processor/MaterialPacking.js +8 -7
  60. package/src/Processor/PBRT/PBRTMaterials.js +129 -41
  61. package/src/Processor/PBRT/PBRTParser.js +30 -17
  62. package/src/Processor/PBRT/PBRTSceneBuilder.js +548 -37
  63. package/src/Processor/PBRT/PBRTTextureBake.js +154 -0
  64. package/src/Processor/PBRT/PFM.js +59 -0
  65. package/src/Processor/PackedRayBuffer.js +116 -9
  66. package/src/Processor/ParallelBVHBuilder.js +3 -6
  67. package/src/Processor/PhysicalSky.js +5 -4
  68. package/src/Processor/PlatformImageLoader.js +50 -6
  69. package/src/Processor/QueueManager.js +12 -3
  70. package/src/Processor/ReinsertionOptimizer.js +2 -2
  71. package/src/Processor/SceneProcessor.js +15 -55
  72. package/src/Processor/ShaderBuilder.js +6 -16
  73. package/src/Processor/SimpleSky.js +1 -62
  74. package/src/Processor/SolarLimb.js +3 -0
  75. package/src/Processor/TextureCreator.js +1 -1
  76. package/src/Processor/Workers/BVHSubtreeWorker.js +1 -34
  77. package/src/Processor/Workers/BVHWorker.js +5 -18
  78. package/src/Processor/Workers/CDFWorker.js +6 -183
  79. package/src/Processor/archiveFormats.js +7 -0
  80. package/src/Processor/utils.js +2 -1
  81. package/src/RayzeeRenderer.js +3902 -0
  82. package/src/RenderSettings.js +49 -41
  83. package/src/Stages/ASVGF.js +5 -7
  84. package/src/Stages/AutoExposure.js +3 -12
  85. package/src/Stages/Compositor.js +8 -23
  86. package/src/Stages/NRD.js +9 -2
  87. package/src/Stages/NormalDepth.js +9 -6
  88. package/src/Stages/PathTracer.js +254 -38
  89. package/src/Stages/PathTracerStage.js +11 -16
  90. package/src/Storage/BLASCache.js +1 -1
  91. package/src/Storage/CDFCache.js +18 -17
  92. package/src/Storage/DownloadCache.js +1 -1
  93. package/src/Storage/SceneGraphCodec.js +46 -3
  94. package/src/Storage/SpillStore.js +1 -1
  95. package/src/Storage/StorageManager.js +2 -4
  96. package/src/Storage/areas.js +4 -0
  97. package/src/TSL/BVHTraversal.js +62 -24
  98. package/src/TSL/Bidirectional.js +204 -0
  99. package/src/TSL/BidirectionalLamps.js +132 -0
  100. package/src/TSL/Clearcoat.js +15 -6
  101. package/src/TSL/Common.js +19 -14
  102. package/src/TSL/ConnectKernel.js +147 -0
  103. package/src/TSL/Debugger.js +1 -1
  104. package/src/TSL/Displacement.js +4 -3
  105. package/src/TSL/EmissiveSampling.js +64 -17
  106. package/src/TSL/Environment.js +77 -106
  107. package/src/TSL/EnvironmentCDF.js +85 -39
  108. package/src/TSL/ExtendKernel.js +8 -3
  109. package/src/TSL/FinalWriteKernel.js +4 -4
  110. package/src/TSL/GenerateKernel.js +18 -4
  111. package/src/TSL/HitFacet.js +35 -5
  112. package/src/TSL/LightBVHSampling.js +40 -9
  113. package/src/TSL/LightGenerateKernel.js +293 -0
  114. package/src/TSL/LightGuide.js +198 -0
  115. package/src/TSL/LightSplatKernel.js +165 -0
  116. package/src/TSL/LightsCore.js +13 -38
  117. package/src/TSL/LightsDirect.js +28 -29
  118. package/src/TSL/LightsIndirect.js +3 -1
  119. package/src/TSL/LightsSampling.js +90 -38
  120. package/src/TSL/MaterialEvaluation.js +113 -31
  121. package/src/TSL/MaterialProperties.js +44 -17
  122. package/src/TSL/MaterialTransmission.js +52 -47
  123. package/src/TSL/MergeKernel.js +218 -0
  124. package/src/TSL/PathTracerCore.js +65 -29
  125. package/src/TSL/Random.js +4 -0
  126. package/src/TSL/SceneResources.js +51 -0
  127. package/src/TSL/ShadeKernel.js +881 -103
  128. package/src/TSL/Struct.js +10 -0
  129. package/src/TSL/Sun.js +1 -1
  130. package/src/TSL/TextureSampling.js +65 -60
  131. package/src/addons/archives.js +14 -0
  132. package/src/addons/bidirectional.js +13 -0
  133. package/src/addons/color.js +32 -0
  134. package/src/addons/formats.js +16 -0
  135. package/src/addons/physicalSky.js +14 -0
  136. package/src/addons/storage.js +19 -0
  137. package/src/core.js +35 -0
  138. package/src/index.js +3 -1
  139. package/src/integrators/BidirectionalIntegrator.js +832 -0
  140. package/src/managers/EnvironmentManager.js +94 -91
  141. package/src/managers/MaterialDataManager.js +42 -0
  142. package/src/managers/UniformManager.js +0 -1
  143. package/dist/ToneMapGPU-DKQM1pMd.js +0 -552
  144. package/dist/rolldown-runtime-CAFD8bLK.js +0 -11
  145. package/src/Processor/TreeletOptimizer.js +0 -574
@@ -65,6 +65,13 @@ export function pString( params, name, dflt ) {
65
65
 
66
66
  }
67
67
 
68
+ export function pBool( params, name, dflt ) {
69
+
70
+ const v = params[ name ]?.value?.[ 0 ];
71
+ return v === undefined ? dflt : v === true || v === 'true';
72
+
73
+ }
74
+
68
75
  /**
69
76
  * Resolve a spectrum/color/float-valued parameter to an RGB triple and/or a
70
77
  * texture. Returns `{ rgb, texture }` — exactly one is typically non-null.
@@ -144,15 +151,74 @@ function namedMetalKey( s ) {
144
151
 
145
152
  }
146
153
 
147
- /** Roughness from `roughness` or anisotropic `uroughness`/`vroughness`. */
148
- function resolveRoughness( params, dflt ) {
154
+ // A float parameter's value, or a float texture's mean (`ctx.floatTextureMean`); null when absent or unreadable.
155
+ async function floatOrMean( params, name, ctx ) {
156
+
157
+ const p = params[ name ];
158
+ if ( ! p ) return null;
159
+ if ( typeof p.value[ 0 ] === 'number' ) return p.value[ 0 ];
160
+ if ( p.type === 'texture' ) return ( await ctx.floatTextureMean?.( p.value[ 0 ] ) ) ?? null;
161
+ return null;
162
+
163
+ }
164
+
165
+ /**
166
+ * The engine's roughness for pbrt's `roughness` or anisotropic `uroughness`/`vroughness` (their α averaged), under
167
+ * `prefix` for a layer (`conductor.`, `interface.`); a textured roughness counts as its mean. pbrt-v4 takes GGX
168
+ * α = √roughness (`remaproughness`, the default) or α = roughness; the engine's GGX takes α = roughness². Passing
169
+ * pbrt's number through made `remaproughness false` 0.1 an α of 0.01, a near mirror. `dflt` is the engine's.
170
+ */
171
+ async function resolveRoughness( params, dflt, ctx, prefix = '' ) {
172
+
173
+ const remap = params.remaproughness?.value?.[ 0 ];
174
+ const alpha = ( r ) => ( remap === false || remap === 'false' ? r : Math.sqrt( Math.max( r, 0 ) ) );
175
+ let a = null;
176
+ const r = await floatOrMean( params, `${prefix}roughness`, ctx );
177
+ if ( r !== null ) a = alpha( r );
178
+ else {
179
+
180
+ const u = await floatOrMean( params, `${prefix}uroughness`, ctx );
181
+ const v = await floatOrMean( params, `${prefix}vroughness`, ctx );
182
+ if ( u !== null ) a = v !== null ? ( alpha( u ) + alpha( v ) ) / 2 : alpha( u );
183
+
184
+ }
185
+
186
+ return a === null ? dflt : Math.sqrt( Math.max( a, 0 ) );
187
+
188
+ }
189
+
190
+ // A conductor's colour: `reflectance`, else normal-incidence Fresnel of `eta`/`k` (under `prefix` for a coated
191
+ // conductor). pbrt's default is copper.
192
+ async function applyConductorColor( mat, params, ctx, prefix, applyAlbedo, setColor ) {
193
+
194
+ const refl = await resolveSpectrum( params, 'reflectance', ctx, null );
195
+ const etaP = params[ `${prefix}eta` ], kP = params[ `${prefix}k` ];
196
+ const etaNamed = etaP && etaP.type === 'spectrum' && typeof etaP.value[ 0 ] === 'string';
197
+
198
+ if ( refl.rgb || refl.texture ) {
199
+
200
+ applyAlbedo( refl );
201
+
202
+ } else if ( etaP && kP && ! etaNamed ) {
203
+
204
+ // Normal-incidence reflectance from complex IOR: ((η-1)²+k²)/((η+1)²+k²).
205
+ const eta = ( await resolveSpectrum( params, `${prefix}eta`, ctx, [ 0.2, 0.92, 1.1 ] ) ).rgb;
206
+ const k = ( await resolveSpectrum( params, `${prefix}k`, ctx, [ 3.9, 2.45, 2.14 ] ) ).rgb;
207
+ const fr = ( n, kk ) => ( ( n - 1 ) ** 2 + kk ** 2 ) / ( ( n + 1 ) ** 2 + kk ** 2 );
208
+ setColor( [ fr( eta[ 0 ], k[ 0 ] ), fr( eta[ 1 ], k[ 1 ] ), fr( eta[ 2 ], k[ 2 ] ) ] );
209
+
210
+ } else if ( etaP ) {
149
211
 
150
- if ( params.roughness && typeof params.roughness.value[ 0 ] === 'number' ) return params.roughness.value[ 0 ];
151
- const u = pFloat( params, 'uroughness', null );
152
- const v = pFloat( params, 'vroughness', null );
153
- if ( u !== null && v !== null ) return ( u + v ) / 2;
154
- if ( u !== null ) return u;
155
- return dflt;
212
+ // Named conductor spectrum → metal albedo table.
213
+ setColor( ( await resolveSpectrum( params, `${prefix}eta`, ctx, DEFAULT_METAL ) ).rgb || DEFAULT_METAL );
214
+
215
+ } else {
216
+
217
+ setColor( METAL_ALBEDO.cu ); // pbrt-v4 default conductor is copper
218
+
219
+ }
220
+
221
+ mat.metalness = 1;
156
222
 
157
223
  }
158
224
 
@@ -193,9 +259,11 @@ export async function buildMaterial( def, ctx ) {
193
259
 
194
260
  case 'diffuse': {
195
261
 
262
+ // Lambertian: no dielectric specular layer, which MeshPhysicalMaterial has by default.
196
263
  applyAlbedo( await resolveSpectrum( params, 'reflectance', ctx, [ 0.5, 0.5, 0.5 ] ) );
197
264
  mat.roughness = 1;
198
265
  mat.metalness = 0;
266
+ mat.specularIntensity = 0;
199
267
  break;
200
268
 
201
269
  }
@@ -203,35 +271,19 @@ export async function buildMaterial( def, ctx ) {
203
271
  case 'conductor':
204
272
  case 'metal': {
205
273
 
206
- const refl = await resolveSpectrum( params, 'reflectance', ctx, null );
207
- const etaP = params.eta, kP = params.k;
208
- const etaNamed = etaP && etaP.type === 'spectrum' && typeof etaP.value[ 0 ] === 'string';
209
-
210
- if ( refl.rgb || refl.texture ) {
211
-
212
- applyAlbedo( refl );
213
-
214
- } else if ( etaP && kP && ! etaNamed ) {
215
-
216
- // Normal-incidence reflectance from complex IOR: ((η-1)²+k²)/((η+1)²+k²).
217
- const eta = ( await resolveSpectrum( params, 'eta', ctx, [ 0.2, 0.92, 1.1 ] ) ).rgb;
218
- const k = ( await resolveSpectrum( params, 'k', ctx, [ 3.9, 2.45, 2.14 ] ) ).rgb;
219
- const fr = ( n, kk ) => ( ( n - 1 ) ** 2 + kk ** 2 ) / ( ( n + 1 ) ** 2 + kk ** 2 );
220
- setColor( [ fr( eta[ 0 ], k[ 0 ] ), fr( eta[ 1 ], k[ 1 ] ), fr( eta[ 2 ], k[ 2 ] ) ] );
221
-
222
- } else if ( etaP ) {
223
-
224
- // Named conductor spectrum → metal albedo table.
225
- setColor( ( await resolveSpectrum( params, 'eta', ctx, DEFAULT_METAL ) ).rgb || DEFAULT_METAL );
226
-
227
- } else {
274
+ await applyConductorColor( mat, params, ctx, '', applyAlbedo, setColor );
275
+ mat.roughness = await resolveRoughness( params, 0.1, ctx );
276
+ break;
228
277
 
229
- setColor( METAL_ALBEDO.cu ); // pbrt-v4 default conductor is copper
278
+ }
230
279
 
231
- }
280
+ case 'coatedconductor': {
232
281
 
233
- mat.metalness = 1;
234
- mat.roughness = resolveRoughness( params, 0.1 );
282
+ // A conductor under a dielectric coat; pbrt's roughnesses default to 0 for both.
283
+ await applyConductorColor( mat, params, ctx, 'conductor.', applyAlbedo, setColor );
284
+ mat.roughness = await resolveRoughness( params, 0, ctx, 'conductor.' );
285
+ mat.clearcoat = 1;
286
+ mat.clearcoatRoughness = await resolveRoughness( params, 0, ctx, 'interface.' );
235
287
  break;
236
288
 
237
289
  }
@@ -243,7 +295,7 @@ export async function buildMaterial( def, ctx ) {
243
295
  mat.metalness = 0;
244
296
  mat.color.setRGB( 1, 1, 1 );
245
297
  mat.ior = pFloat( params, 'eta', 1.5 );
246
- mat.roughness = resolveRoughness( params, 0 );
298
+ mat.roughness = await resolveRoughness( params, 0, ctx );
247
299
  mat.thickness = type === 'thindielectric' ? 0 : pFloat( params, 'thickness', 0 );
248
300
  break;
249
301
 
@@ -255,18 +307,30 @@ export async function buildMaterial( def, ctx ) {
255
307
  mat.roughness = 0.6;
256
308
  mat.metalness = 0;
257
309
  mat.clearcoat = 1;
258
- mat.clearcoatRoughness = resolveRoughness( params, 0 );
310
+ mat.clearcoatRoughness = await resolveRoughness( params, 0, ctx );
259
311
  break;
260
312
 
261
313
  }
262
314
 
263
315
  case 'diffusetransmission': {
264
316
 
317
+ // pbrt reflects R and transmits T, both diffusely and both × `scale`, clamped to [0, 1]; no specular layer.
318
+ // The engine splits its diffuse lobe by `diffuseTransmission`: reflecting (1 − dt) · base, transmitting
319
+ // dt · colour. dt = max(T) makes both exact whenever max(R) + max(T) ≤ 1.
320
+ const scale = pFloat( params, 'scale', 1 );
321
+ const unit = ( v ) => Math.min( 1, Math.max( 0, scale * v ) );
265
322
  const trans = await resolveSpectrum( params, 'transmittance', ctx, [ 0.25, 0.25, 0.25 ] );
266
- applyAlbedo( await resolveSpectrum( params, 'reflectance', ctx, [ 0.25, 0.25, 0.25 ] ) );
267
- mat.transmission = trans.rgb ? ( trans.rgb[ 0 ] + trans.rgb[ 1 ] + trans.rgb[ 2 ] ) / 3 : 0.5;
323
+ const T = trans.rgb ? trans.rgb.map( unit ) : [ 0.5, 0.5, 0.5 ];
324
+ const dt = Math.min( 0.99, Math.max( ...T ) );
325
+ const refl = await resolveSpectrum( params, 'reflectance', ctx, [ 0.25, 0.25, 0.25 ] );
326
+ const base = ( v ) => Math.min( 1, unit( v ) / ( 1 - dt ) );
327
+ applyAlbedo( { texture: refl.texture, rgb: refl.rgb ? refl.rgb.map( base ) : ( refl.texture ? [ 1, 1, 1 ].map( base ) : null ) } );
328
+ if ( trans.texture ) ctx.warn( 'diffusetransmission: a textured "transmittance" is not supported — using 0.5' );
329
+ mat.diffuseTransmission = dt;
330
+ mat.diffuseTransmissionColor = new Color().setRGB( ...T.map( ( v ) => ( dt > 0 ? Math.min( 1, v / dt ) : 1 ) ) );
268
331
  mat.roughness = 1;
269
- mat.ior = 1.0;
332
+ mat.metalness = 0;
333
+ mat.specularIntensity = 0;
270
334
  break;
271
335
 
272
336
  }
@@ -292,13 +356,18 @@ export async function buildMaterial( def, ctx ) {
292
356
  // instead of warning per shape. Recursive: mix-in-mix terminates as the
293
357
  // chain bottoms out at a non-mix.
294
358
  const matNames = params.materials?.value || [];
295
- const t = Math.max( 0, Math.min( 1, pFloat( params, 'amount', 0.5 ) ) );
359
+ let t = Math.max( 0, Math.min( 1, pFloat( params, 'amount', 0.5 ) ) );
296
360
  const defA = matNames[ 0 ] ? ctx.namedMaterials?.get( matNames[ 0 ] ) : null;
297
361
  const defB = matNames[ 1 ] ? ctx.namedMaterials?.get( matNames[ 1 ] ) : null;
298
362
 
299
363
  if ( defA && defB ) {
300
364
 
301
365
  const [ matA, matB ] = await Promise.all( [ buildMaterial( defA, ctx ), buildMaterial( defB, ctx ) ] );
366
+ // pbrt picks the second material where a texture amount says so; its colours are baked by that
367
+ // texture (`ctx.bakeMaterialMix`) and the rest is weighed by the texture's mean.
368
+ const amountP = params.amount;
369
+ const baked = amountP?.type === 'texture' ? await ctx.bakeMaterialMix?.( matA, matB, amountP.value[ 0 ] ) : null;
370
+ if ( baked ) t = baked.mean;
302
371
  const lerp = ( a, b ) => a * ( 1 - t ) + b * t;
303
372
  mat.color.lerpColors( matA.color, matB.color, t );
304
373
  mat.roughness = lerp( matA.roughness, matB.roughness );
@@ -309,9 +378,25 @@ export async function buildMaterial( def, ctx ) {
309
378
  mat.clearcoat = lerp( matA.clearcoat ?? 0, matB.clearcoat ?? 0 );
310
379
  mat.clearcoatRoughness = lerp( matA.clearcoatRoughness ?? 0, matB.clearcoatRoughness ?? 0 );
311
380
  mat.emissive.lerpColors( matA.emissive, matB.emissive, t );
381
+ const dtA = matA.diffuseTransmission ?? 0, dtB = matB.diffuseTransmission ?? 0;
382
+ if ( dtA > 0 || dtB > 0 ) {
383
+
384
+ mat.diffuseTransmission = lerp( dtA, dtB );
385
+ mat.diffuseTransmissionColor = new Color().lerpColors( matA.diffuseTransmissionColor ?? new Color( 1, 1, 1 ), matB.diffuseTransmissionColor ?? new Color( 1, 1, 1 ), t );
386
+
387
+ }
388
+
312
389
  mat.emissiveIntensity = lerp( matA.emissiveIntensity ?? 0, matB.emissiveIntensity ?? 0 );
313
- // Maps can't be lerped — pick the dominant side.
390
+ // Maps can't be lerped — pick the dominant side, unless the amount's own texture baked them together.
314
391
  mat.map = ( t < 0.5 ? matA.map : matB.map ) || null;
392
+ mat.normalMap = ( t < 0.5 ? matA.normalMap : matB.normalMap ) || null;
393
+ if ( baked?.texture ) {
394
+
395
+ mat.map = baked.texture;
396
+ mat.color.setRGB( 1, 1, 1 );
397
+
398
+ }
399
+
315
400
  break;
316
401
 
317
402
  }
@@ -344,6 +429,9 @@ export async function buildMaterial( def, ctx ) {
344
429
 
345
430
  }
346
431
 
432
+ const normalMap = pString( params, 'normalmap', null );
433
+ if ( normalMap && ctx.resolveNormalMap ) mat.normalMap = await ctx.resolveNormalMap( normalMap );
434
+
347
435
  mat.roughness = Math.max( 0, Math.min( 1, mat.roughness ) );
348
436
  return mat;
349
437
 
@@ -111,6 +111,7 @@ export class PBRTParser {
111
111
  instanceCount: 0,
112
112
  skippedInstances: 0,
113
113
  objects: new Map(),
114
+ media: new Map(),
114
115
  transformTimes: { start: 0, end: 1 },
115
116
  hasMotion: false,
116
117
  warnings: []
@@ -122,14 +123,13 @@ export class PBRTParser {
122
123
  this.activeTransform = BOTH;
123
124
  this._ctmSource = null;
124
125
  this._ctmValue = null;
125
- this.state = { material: null, areaLight: null, reverseOrientation: false };
126
+ this.state = { material: null, areaLight: null, reverseOrientation: false, interior: null };
126
127
  this.attributeStack = [];
127
128
  this.transformStack = [];
128
129
  this.coordSystems = new Map();
129
130
 
130
131
  // Object capture (ObjectBegin/End)
131
132
  this.currentObject = null; // name being captured, or null
132
- this.objectBeginCTM = null; // CTM frame at ObjectBegin
133
133
 
134
134
  // Directory stack for resolving nested Includes
135
135
  this.dirStack = [ '' ];
@@ -275,7 +275,8 @@ export class PBRTParser {
275
275
  activeTransform: this.activeTransform,
276
276
  material: this.state.material,
277
277
  areaLight: this.state.areaLight,
278
- reverseOrientation: this.state.reverseOrientation
278
+ reverseOrientation: this.state.reverseOrientation,
279
+ interior: this.state.interior
279
280
  } );
280
281
 
281
282
  }
@@ -287,7 +288,7 @@ export class PBRTParser {
287
288
  this.ctm = s.ctm;
288
289
  this.ctmEnd = s.ctmEnd;
289
290
  this.activeTransform = s.activeTransform;
290
- this.state = { material: s.material, areaLight: s.areaLight, reverseOrientation: s.reverseOrientation };
291
+ this.state = { material: s.material, areaLight: s.areaLight, reverseOrientation: s.reverseOrientation, interior: s.interior };
291
292
 
292
293
  }
293
294
 
@@ -624,7 +625,7 @@ export class PBRTParser {
624
625
  this.ctm = M.identity();
625
626
  this.ctmEnd = null;
626
627
  this.activeTransform = BOTH;
627
- this.state = { material: null, areaLight: null, reverseOrientation: false };
628
+ this.state = { material: null, areaLight: null, reverseOrientation: false, interior: null };
628
629
  break;
629
630
  case 'WorldEnd': break; // legacy v3
630
631
 
@@ -673,14 +674,21 @@ export class PBRTParser {
673
674
 
674
675
  case 'MediumInterface': {
675
676
 
676
- // up to two strings (inside/outside)
677
- if ( this._peek() && this._peek().type === TokenType.STRING ) this._next();
678
- if ( this._peek() && this._peek().type === TokenType.STRING ) this._next();
677
+ // "interior" "exterior"; a single name is both. Only the interior colours a shape.
678
+ const interior = this._peek()?.type === TokenType.STRING ? this._next().value : '';
679
+ if ( this._peek()?.type === TokenType.STRING ) this._next();
680
+ this.state.interior = interior || null;
679
681
  break;
680
682
 
681
683
  }
682
684
 
683
- case 'MakeNamedMedium': this._skipNamedAndParams(); break;
685
+ case 'MakeNamedMedium': {
686
+
687
+ const mediumName = this._expectString( 'MakeNamedMedium name' );
688
+ this.ir.media.set( mediumName, this._parseParams() );
689
+ break;
690
+
691
+ }
684
692
 
685
693
  // ── materials ──
686
694
  case 'Material': {
@@ -748,13 +756,18 @@ export class PBRTParser {
748
756
 
749
757
  const type = this._expectString( 'Shape type' );
750
758
  const params = this._parseParams();
759
+ // pbrt: a shape of alpha 0 is never hit, and an area light on it emits nothing.
760
+ const alpha = params.alpha;
761
+ if ( alpha?.type === 'float' && alpha.value[ 0 ] <= 0 ) break;
762
+ if ( alpha && ! ( alpha.type === 'float' && alpha.value[ 0 ] >= 1 ) ) this._warnOnce( 'alpha', 'shape "alpha" between 0 and 1, or as a texture, is not supported — drawn opaque' );
751
763
  const shape = {
752
764
  type,
753
765
  params,
754
766
  ctm: this._ctmSnapshot(),
755
767
  material: this.state.material,
756
768
  areaLight: this.state.areaLight,
757
- reverseOrientation: this.state.reverseOrientation
769
+ reverseOrientation: this.state.reverseOrientation,
770
+ interior: this.state.interior
758
771
  };
759
772
  const end = this.currentObject === null ? this._ctmEndSnapshot() : null;
760
773
  if ( end ) shape.ctmEnd = end;
@@ -776,7 +789,6 @@ export class PBRTParser {
776
789
  // pbrt implicitly pushes graphics state.
777
790
  this._pushGraphicsState();
778
791
  this.currentObject = objName;
779
- this.objectBeginCTM = this.ctm.slice();
780
792
  if ( ! this.ir.objects.has( objName ) ) this.ir.objects.set( objName, [] );
781
793
  break;
782
794
 
@@ -785,7 +797,6 @@ export class PBRTParser {
785
797
  case 'ObjectEnd': {
786
798
 
787
799
  this.currentObject = null;
788
- this.objectBeginCTM = null;
789
800
  this._popGraphicsState();
790
801
  break;
791
802
 
@@ -824,8 +835,8 @@ export class PBRTParser {
824
835
 
825
836
  if ( this.currentObject !== null ) {
826
837
 
827
- // Store relative to the ObjectBegin frame so instances can re-place it.
828
- shape.relativeCTM = M.multiply( M.invert( this.objectBeginCTM ), shape.ctm );
838
+ // pbrt keeps a template shape's whole transform and puts each placement's on top of it,
839
+ // whatever the transform was at ObjectBegin (kroken defines under one and places at Identity).
829
840
  this.ir.objects.get( this.currentObject ).push( shape );
830
841
 
831
842
  } else {
@@ -880,7 +891,7 @@ export class PBRTParser {
880
891
 
881
892
  if ( ! this.instanceIncludes || this.currentObject !== null ) return null;
882
893
  if ( this.ctmEnd !== null || this.activeTransform !== BOTH || this.state.areaLight ) return null;
883
- return `${dir}\0${path}\0${this._stateId( this.state.material )}\0${this.state.reverseOrientation}`;
894
+ return `${dir}\0${path}\0${this._stateId( this.state.material )}\0${this.state.reverseOrientation}\0${this.state.interior}`;
884
895
 
885
896
  }
886
897
 
@@ -902,7 +913,8 @@ export class PBRTParser {
902
913
  attributes: this.attributeStack.length,
903
914
  transforms: this.transformStack.length,
904
915
  material: this.state.material,
905
- reverseOrientation: this.state.reverseOrientation
916
+ reverseOrientation: this.state.reverseOrientation,
917
+ interior: this.state.interior
906
918
  };
907
919
 
908
920
  }
@@ -912,7 +924,8 @@ export class PBRTParser {
912
924
  return this._effects === before.effects && this.ctmEnd === null && this.activeTransform === BOTH &&
913
925
  sameMatrix( this.ctm, before.ctm ) && this.attributeStack.length === before.attributes &&
914
926
  this.transformStack.length === before.transforms && this.state.material === before.material &&
915
- this.state.areaLight === null && this.state.reverseOrientation === before.reverseOrientation;
927
+ this.state.areaLight === null && this.state.reverseOrientation === before.reverseOrientation &&
928
+ this.state.interior === before.interior;
916
929
 
917
930
  }
918
931