@plasius/gpu-lighting 0.1.18 → 0.2.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.
package/dist/index.js CHANGED
@@ -123,6 +123,46 @@ var lightingProfileModeOrder = Object.freeze([
123
123
  "hybrid",
124
124
  "reference"
125
125
  ]);
126
+ var lightingEnvironmentTimeOfDayNames = Object.freeze([
127
+ "dawn",
128
+ "midday",
129
+ "dusk",
130
+ "night"
131
+ ]);
132
+ var lightingEnvironmentSceneNames = Object.freeze([
133
+ "studio",
134
+ "harbor",
135
+ "grass-field",
136
+ "forest",
137
+ "warehouse",
138
+ "cavern"
139
+ ]);
140
+ var lightingEnvironmentLightSourceKinds = Object.freeze([
141
+ "sky",
142
+ "sun",
143
+ "moon",
144
+ "stars",
145
+ "horizon-glow",
146
+ "ground-bounce",
147
+ "studio-softbox",
148
+ "canopy-transmission",
149
+ "window-portal",
150
+ "fluorescent-strip",
151
+ "sodium-door",
152
+ "emergency-beacon",
153
+ "cave-mouth",
154
+ "torch",
155
+ "bioluminescence",
156
+ "lava-fissure",
157
+ "crystal",
158
+ "custom"
159
+ ]);
160
+ var lightingEnvironmentPortalShapes = Object.freeze(["rectangle"]);
161
+ var lightingEnvironmentPortalModes = Object.freeze([
162
+ "disabled",
163
+ "guide",
164
+ "guide-and-gate"
165
+ ]);
126
166
  var defaultAdaptiveLightingProfilePolicy = Object.freeze({
127
167
  preferredProfile: "reference",
128
168
  minimumFrameRate: 30,
@@ -136,6 +176,809 @@ var lightingDistanceBands = Object.freeze([
136
176
  ]);
137
177
  var lightingWorkerQueueClass = "lighting";
138
178
  var lightingDebugOwner = "lighting";
179
+ function freezeVec4(value) {
180
+ return Object.freeze([value[0], value[1], value[2], value[3] ?? 1]);
181
+ }
182
+ function normalizeVector3(value, fallback) {
183
+ if (!Array.isArray(value) || value.length < 3) {
184
+ return [...fallback];
185
+ }
186
+ const vector = [
187
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
188
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
189
+ Number.isFinite(value[2]) ? value[2] : fallback[2]
190
+ ];
191
+ const length = Math.hypot(vector[0], vector[1], vector[2]);
192
+ if (!Number.isFinite(length) || length <= 1e-6) {
193
+ return [...fallback];
194
+ }
195
+ return vector.map((component) => component / length);
196
+ }
197
+ function readColor(value, fallback) {
198
+ if (!Array.isArray(value) || value.length < 3) {
199
+ return freezeVec4(fallback);
200
+ }
201
+ return freezeVec4([
202
+ Number.isFinite(value[0]) ? Math.max(0, value[0]) : fallback[0],
203
+ Number.isFinite(value[1]) ? Math.max(0, value[1]) : fallback[1],
204
+ Number.isFinite(value[2]) ? Math.max(0, value[2]) : fallback[2],
205
+ Number.isFinite(value[3]) ? Math.max(0, Math.min(1, value[3])) : fallback[3] ?? 1
206
+ ]);
207
+ }
208
+ function colorLuminance(value) {
209
+ return value[0] * 0.2126 + value[1] * 0.7152 + value[2] * 0.0722;
210
+ }
211
+ function readPositiveColor(value, fallback) {
212
+ const color = readColor(value, fallback);
213
+ const fallbackColor = readColor(fallback, [1, 1, 1, 1]);
214
+ return freezeVec4([
215
+ color[0] > 0 ? color[0] : Math.max(fallbackColor[0], 1e-4),
216
+ color[1] > 0 ? color[1] : Math.max(fallbackColor[1], 1e-4),
217
+ color[2] > 0 ? color[2] : Math.max(fallbackColor[2], 1e-4),
218
+ color[3]
219
+ ]);
220
+ }
221
+ function ensureNonNullColor(value, fallback = [1, 1, 1, 1]) {
222
+ const color = readColor(value, fallback);
223
+ if (colorLuminance(color) > 1e-6) {
224
+ return color;
225
+ }
226
+ return readPositiveColor(fallback, [1, 1, 1, 1]);
227
+ }
228
+ function readFinite(value, fallback) {
229
+ return Number.isFinite(value) ? value : fallback;
230
+ }
231
+ function readVector3(value, fallback) {
232
+ if (!Array.isArray(value) || value.length < 3) {
233
+ return [...fallback];
234
+ }
235
+ return [
236
+ Number.isFinite(value[0]) ? value[0] : fallback[0],
237
+ Number.isFinite(value[1]) ? value[1] : fallback[1],
238
+ Number.isFinite(value[2]) ? value[2] : fallback[2]
239
+ ];
240
+ }
241
+ function dot3(a, b) {
242
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
243
+ }
244
+ function cross3(a, b) {
245
+ return [
246
+ a[1] * b[2] - a[2] * b[1],
247
+ a[2] * b[0] - a[0] * b[2],
248
+ a[0] * b[1] - a[1] * b[0]
249
+ ];
250
+ }
251
+ function normalizeRawVector3(value, fallback) {
252
+ const length = Math.hypot(value[0], value[1], value[2]);
253
+ if (!Number.isFinite(length) || length <= 1e-6) {
254
+ return [...fallback];
255
+ }
256
+ return value.map((component) => component / length);
257
+ }
258
+ function orthogonalFallback(normal) {
259
+ if (Math.abs(normal[1]) < 0.92) {
260
+ return normalizeRawVector3(cross3([0, 1, 0], normal), [1, 0, 0]);
261
+ }
262
+ return normalizeRawVector3(cross3([1, 0, 0], normal), [0, 0, 1]);
263
+ }
264
+ function normalizePortalTangent(value, normal) {
265
+ const raw = normalizeVector3(value, orthogonalFallback(normal));
266
+ const projected = [
267
+ raw[0] - normal[0] * dot3(raw, normal),
268
+ raw[1] - normal[1] * dot3(raw, normal),
269
+ raw[2] - normal[2] * dot3(raw, normal)
270
+ ];
271
+ return normalizeRawVector3(projected, orthogonalFallback(normal));
272
+ }
273
+ function readPositiveFinite(value, fallback) {
274
+ const number = Number(value ?? fallback);
275
+ if (!Number.isFinite(number)) {
276
+ return fallback;
277
+ }
278
+ return Math.max(number, 1e-4);
279
+ }
280
+ function normalizeEnvironmentPortalMode(value, hasPortals) {
281
+ if (value == null) {
282
+ return hasPortals ? "guide-and-gate" : "disabled";
283
+ }
284
+ if (value === "gate") {
285
+ return "guide-and-gate";
286
+ }
287
+ if (lightingEnvironmentPortalModes.includes(value)) {
288
+ return value;
289
+ }
290
+ throw new Error(
291
+ `environmentPortalMode must be one of: ${lightingEnvironmentPortalModes.join(", ")}.`
292
+ );
293
+ }
294
+ function normalizeEnvironmentPortal(portal, index) {
295
+ if (!portal || typeof portal !== "object") {
296
+ throw new Error(`environmentPortals[${index}] must be an object.`);
297
+ }
298
+ const shape = portal.shape ?? portal.kind ?? "rectangle";
299
+ if (!lightingEnvironmentPortalShapes.includes(shape)) {
300
+ throw new Error(
301
+ `environmentPortals[${index}].shape must be one of: ${lightingEnvironmentPortalShapes.join(", ")}.`
302
+ );
303
+ }
304
+ const normal = Object.freeze(
305
+ normalizeVector3(portal.normal, [0, 0, 1])
306
+ );
307
+ const tangent = Object.freeze(normalizePortalTangent(portal.tangent, normal));
308
+ const bitangent = Object.freeze(
309
+ normalizeRawVector3(cross3(normal, tangent), [0, 1, 0])
310
+ );
311
+ const width = readPositiveFinite(
312
+ portal.width,
313
+ readPositiveFinite(portal.halfWidth, 0.5) * 2
314
+ );
315
+ const height = readPositiveFinite(
316
+ portal.height,
317
+ readPositiveFinite(portal.halfHeight, 0.5) * 2
318
+ );
319
+ const radianceScale = Math.max(
320
+ 0,
321
+ readFinite(portal.radianceScale ?? portal.intensity, 1)
322
+ );
323
+ return Object.freeze({
324
+ id: typeof portal.id === "string" && portal.id.length > 0 ? portal.id : `environment-portal-${index}`,
325
+ shape,
326
+ position: Object.freeze(readVector3(portal.position ?? portal.center, [0, 0, 0])),
327
+ normal,
328
+ tangent,
329
+ bitangent,
330
+ width,
331
+ height,
332
+ radianceScale,
333
+ color: readColor(portal.color, [1, 1, 1, 1]),
334
+ twoSided: portal.twoSided !== false
335
+ });
336
+ }
337
+ function normalizeEnvironmentPortals(value) {
338
+ if (value == null) {
339
+ return Object.freeze([]);
340
+ }
341
+ if (!Array.isArray(value)) {
342
+ throw new Error("environmentPortals must be an array when provided.");
343
+ }
344
+ return Object.freeze(value.map(normalizeEnvironmentPortal));
345
+ }
346
+ function freezeLightSourceSpec(source) {
347
+ return Object.freeze({
348
+ ...source,
349
+ color: source.color ? freezeVec4(source.color) : void 0,
350
+ direction: source.direction ? Object.freeze(normalizeVector3(source.direction, [0, 1, 0])) : void 0,
351
+ position: source.position ? Object.freeze(readVector3(source.position, [0, 0, 0])) : void 0
352
+ });
353
+ }
354
+ function defineEnvironmentPreset(spec) {
355
+ return Object.freeze({
356
+ ...spec,
357
+ scene: spec.scene ?? "studio",
358
+ timeOfDay: spec.timeOfDay ?? "midday",
359
+ horizonColor: freezeVec4(spec.horizonColor),
360
+ zenithColor: freezeVec4(spec.zenithColor),
361
+ sunDirection: Object.freeze(normalizeVector3(spec.sunDirection, [0, 1, 0])),
362
+ sunColor: freezeVec4(spec.sunColor),
363
+ ambientColor: freezeVec4(spec.ambientColor),
364
+ environmentLightSources: Object.freeze(
365
+ (spec.environmentLightSources ?? []).map(freezeLightSourceSpec)
366
+ )
367
+ });
368
+ }
369
+ function buildEnvironmentLightSourceFallback(config, preset) {
370
+ const firstPresetSource = preset.environmentLightSources[0] ?? {};
371
+ return {
372
+ kind: firstPresetSource.kind ?? "sky",
373
+ role: firstPresetSource.role ?? "fill",
374
+ color: firstPresetSource.color ?? config.sunColor,
375
+ intensity: firstPresetSource.intensity ?? Math.max(config.environmentIntensity, 1e-4),
376
+ direction: firstPresetSource.direction ?? config.sunDirection,
377
+ angularRadiusRadians: firstPresetSource.angularRadiusRadians ?? 0.25,
378
+ reach: firstPresetSource.reach ?? 1e3
379
+ };
380
+ }
381
+ function normalizeEnvironmentLightSource(source, index, fallback) {
382
+ if (!source || typeof source !== "object") {
383
+ throw new Error(`environmentLightSources[${index}] must be an object.`);
384
+ }
385
+ const requestedKind = source.kind ?? source.type ?? fallback.kind ?? "custom";
386
+ const kind = lightingEnvironmentLightSourceKinds.includes(requestedKind) ? requestedKind : "custom";
387
+ const color = readPositiveColor(source.color, fallback.color ?? [1, 1, 1, 1]);
388
+ const intensity = readPositiveFinite(
389
+ source.intensity ?? source.radianceScale,
390
+ fallback.intensity ?? 1
391
+ );
392
+ const radiance = freezeVec4([
393
+ color[0] * intensity,
394
+ color[1] * intensity,
395
+ color[2] * intensity,
396
+ color[3]
397
+ ]);
398
+ return Object.freeze({
399
+ id: typeof source.id === "string" && source.id.length > 0 ? source.id : `environment-light-source-${index}`,
400
+ kind,
401
+ type: kind,
402
+ role: typeof source.role === "string" && source.role.length > 0 ? source.role : fallback.role ?? "fill",
403
+ direction: Object.freeze(
404
+ normalizeVector3(source.direction, fallback.direction ?? [0, 1, 0])
405
+ ),
406
+ position: Object.freeze(
407
+ readVector3(source.position ?? source.origin, fallback.position ?? [0, 0, 0])
408
+ ),
409
+ color,
410
+ intensity,
411
+ radiance,
412
+ luminance: colorLuminance(radiance),
413
+ angularRadiusRadians: readPositiveFinite(
414
+ source.angularRadiusRadians ?? source.angularRadius,
415
+ fallback.angularRadiusRadians ?? 0.25
416
+ ),
417
+ reach: readPositiveFinite(
418
+ source.reach ?? source.distance,
419
+ fallback.reach ?? 1e3
420
+ ),
421
+ castsShadows: source.castsShadows !== false,
422
+ contributesToEnvironment: source.contributesToEnvironment !== false
423
+ });
424
+ }
425
+ function normalizeEnvironmentLightSources(value, preset, config) {
426
+ const baseSources = value ?? preset.environmentLightSources;
427
+ const fallback = buildEnvironmentLightSourceFallback(config, preset);
428
+ if (!Array.isArray(baseSources)) {
429
+ throw new Error("environmentLightSources must be an array when provided.");
430
+ }
431
+ const normalizedSources = baseSources.length > 0 ? baseSources.map(
432
+ (source, index) => normalizeEnvironmentLightSource(source, index, fallback)
433
+ ) : [normalizeEnvironmentLightSource(fallback, 0, fallback)];
434
+ return Object.freeze(normalizedSources);
435
+ }
436
+ function findDominantEnvironmentLightSource(sources) {
437
+ return sources.reduce(
438
+ (dominant, source) => source.luminance > dominant.luminance ? source : dominant
439
+ );
440
+ }
441
+ function createEnvironmentMissLighting(source, environmentColor) {
442
+ const fallbackRadiance = readPositiveColor(environmentColor, source.radiance);
443
+ const radiance = ensureNonNullColor(source.radiance, fallbackRadiance);
444
+ const color = readPositiveColor(source.color, environmentColor);
445
+ return Object.freeze({
446
+ sourceId: source.id,
447
+ kind: source.kind,
448
+ role: source.role,
449
+ contribution: "inferred-environment",
450
+ startingPoint: "environment-miss",
451
+ direction: source.direction,
452
+ position: source.position,
453
+ color,
454
+ intensity: Math.max(source.intensity, 1e-4),
455
+ radiance,
456
+ luminance: Math.max(colorLuminance(radiance), 1e-4)
457
+ });
458
+ }
459
+ var environmentLightingPresets = Object.freeze({
460
+ "moonlit-harbor": defineEnvironmentPreset({
461
+ preset: "moonlit-harbor",
462
+ scene: "harbor",
463
+ timeOfDay: "night",
464
+ environmentMode: 0,
465
+ environmentIntensity: 0.86,
466
+ exposure: 1,
467
+ horizonColor: freezeVec4([0.33, 0.43, 0.53, 1]),
468
+ zenithColor: freezeVec4([0.035, 0.07, 0.14, 1]),
469
+ sunDirection: Object.freeze(normalizeVector3([0.22, 0.88, 0.42], [0, 1, 0])),
470
+ sunColor: freezeVec4([2.1, 2.25, 2.65, 1]),
471
+ ambientColor: freezeVec4([0.018, 0.023, 0.03, 1]),
472
+ environmentLightSources: [
473
+ {
474
+ id: "harbor-moon",
475
+ kind: "moon",
476
+ role: "key",
477
+ direction: [0.22, 0.88, 0.42],
478
+ color: [0.7, 0.76, 0.9, 1],
479
+ intensity: 2.2,
480
+ angularRadiusRadians: 0.018
481
+ },
482
+ {
483
+ id: "harbor-sky",
484
+ kind: "sky",
485
+ role: "fill",
486
+ direction: [0, 1, 0],
487
+ color: [0.22, 0.31, 0.48, 1],
488
+ intensity: 0.35
489
+ }
490
+ ]
491
+ }),
492
+ "product-studio": defineEnvironmentPreset({
493
+ preset: "product-studio",
494
+ scene: "studio",
495
+ timeOfDay: "midday",
496
+ environmentMode: 1,
497
+ environmentIntensity: 1.05,
498
+ exposure: 1,
499
+ horizonColor: freezeVec4([0.52, 0.61, 0.65, 1]),
500
+ zenithColor: freezeVec4([0.18, 0.22, 0.26, 1]),
501
+ sunDirection: Object.freeze(normalizeVector3([0.18, 0.93, 0.24], [0, 1, 0])),
502
+ sunColor: freezeVec4([3.8, 3.55, 2.85, 1]),
503
+ ambientColor: freezeVec4([0.024, 0.027, 0.03, 1]),
504
+ environmentLightSources: [
505
+ {
506
+ id: "studio-key-softbox",
507
+ kind: "studio-softbox",
508
+ role: "key",
509
+ direction: [0.18, 0.93, 0.24],
510
+ color: [1, 0.94, 0.82, 1],
511
+ intensity: 4.1,
512
+ angularRadiusRadians: 0.42
513
+ },
514
+ {
515
+ id: "studio-fill-panel",
516
+ kind: "studio-softbox",
517
+ role: "fill",
518
+ direction: [-0.56, 0.62, -0.2],
519
+ color: [0.75, 0.84, 1, 1],
520
+ intensity: 1.3,
521
+ angularRadiusRadians: 0.55
522
+ }
523
+ ]
524
+ }),
525
+ "neutral-studio": defineEnvironmentPreset({
526
+ preset: "neutral-studio",
527
+ scene: "studio",
528
+ timeOfDay: "midday",
529
+ environmentMode: 2,
530
+ environmentIntensity: 0.95,
531
+ exposure: 1,
532
+ horizonColor: freezeVec4([0.48, 0.53, 0.55, 1]),
533
+ zenithColor: freezeVec4([0.24, 0.26, 0.29, 1]),
534
+ sunDirection: Object.freeze(normalizeVector3([-0.24, 0.86, 0.36], [0, 1, 0])),
535
+ sunColor: freezeVec4([2.4, 2.35, 2.2, 1]),
536
+ ambientColor: freezeVec4([0.028, 0.029, 0.03, 1]),
537
+ environmentLightSources: [
538
+ {
539
+ id: "neutral-studio-overhead",
540
+ kind: "studio-softbox",
541
+ role: "key",
542
+ direction: [-0.24, 0.86, 0.36],
543
+ color: [0.96, 0.97, 1, 1],
544
+ intensity: 2.5,
545
+ angularRadiusRadians: 0.5
546
+ },
547
+ {
548
+ id: "neutral-studio-wall-bounce",
549
+ kind: "ground-bounce",
550
+ role: "fill",
551
+ direction: [0.2, 0.3, -0.9],
552
+ color: [0.55, 0.58, 0.62, 1],
553
+ intensity: 0.8
554
+ }
555
+ ]
556
+ }),
557
+ "grass-field-dawn": defineEnvironmentPreset({
558
+ preset: "grass-field-dawn",
559
+ scene: "grass-field",
560
+ timeOfDay: "dawn",
561
+ environmentMode: 3,
562
+ environmentIntensity: 0.92,
563
+ exposure: 1.06,
564
+ horizonColor: [0.92, 0.54, 0.32, 1],
565
+ zenithColor: [0.16, 0.28, 0.5, 1],
566
+ sunDirection: [0.64, 0.32, 0.18],
567
+ sunColor: [5.6, 3.15, 1.55, 1],
568
+ ambientColor: [0.034, 0.047, 0.032, 1],
569
+ environmentLightSources: [
570
+ { id: "field-dawn-sun", kind: "sun", role: "key", direction: [0.64, 0.32, 0.18], color: [1, 0.58, 0.28, 1], intensity: 5.6, angularRadiusRadians: 0.012 },
571
+ { id: "field-dawn-sky", kind: "sky", role: "fill", direction: [0, 1, 0], color: [0.36, 0.52, 0.82, 1], intensity: 0.9 },
572
+ { id: "field-dawn-grass-bounce", kind: "ground-bounce", role: "bounce", direction: [0, 0.25, 0.1], color: [0.22, 0.44, 0.12, 1], intensity: 0.45 }
573
+ ]
574
+ }),
575
+ "grass-field-midday": defineEnvironmentPreset({
576
+ preset: "grass-field-midday",
577
+ scene: "grass-field",
578
+ timeOfDay: "midday",
579
+ environmentMode: 4,
580
+ environmentIntensity: 1.18,
581
+ exposure: 0.96,
582
+ horizonColor: [0.58, 0.78, 0.96, 1],
583
+ zenithColor: [0.1, 0.34, 0.82, 1],
584
+ sunDirection: [0.18, 0.98, 0.08],
585
+ sunColor: [9.8, 9.4, 8.55, 1],
586
+ ambientColor: [0.048, 0.062, 0.04, 1],
587
+ environmentLightSources: [
588
+ { id: "field-midday-sun", kind: "sun", role: "key", direction: [0.18, 0.98, 0.08], color: [1, 0.96, 0.86, 1], intensity: 9.8, angularRadiusRadians: 93e-4 },
589
+ { id: "field-midday-sky", kind: "sky", role: "fill", direction: [0, 1, 0], color: [0.48, 0.7, 1, 1], intensity: 1.8 },
590
+ { id: "field-midday-ground", kind: "ground-bounce", role: "bounce", direction: [0, 0.35, -0.15], color: [0.28, 0.56, 0.16, 1], intensity: 0.65 }
591
+ ]
592
+ }),
593
+ "grass-field-dusk": defineEnvironmentPreset({
594
+ preset: "grass-field-dusk",
595
+ scene: "grass-field",
596
+ timeOfDay: "dusk",
597
+ environmentMode: 5,
598
+ environmentIntensity: 0.82,
599
+ exposure: 1.12,
600
+ horizonColor: [1.08, 0.42, 0.24, 1],
601
+ zenithColor: [0.09, 0.1, 0.32, 1],
602
+ sunDirection: [-0.76, 0.24, 0.22],
603
+ sunColor: [4.8, 1.65, 0.72, 1],
604
+ ambientColor: [0.026, 0.026, 0.034, 1],
605
+ environmentLightSources: [
606
+ { id: "field-dusk-sun", kind: "sun", role: "key", direction: [-0.76, 0.24, 0.22], color: [1, 0.34, 0.16, 1], intensity: 4.8, angularRadiusRadians: 0.014 },
607
+ { id: "field-dusk-horizon", kind: "horizon-glow", role: "fill", direction: [-0.9, 0.08, 0.1], color: [0.92, 0.28, 0.16, 1], intensity: 1.2 },
608
+ { id: "field-dusk-grass", kind: "ground-bounce", role: "bounce", direction: [0, 0.22, 0.2], color: [0.12, 0.28, 0.11, 1], intensity: 0.35 }
609
+ ]
610
+ }),
611
+ "grass-field-night": defineEnvironmentPreset({
612
+ preset: "grass-field-night",
613
+ scene: "grass-field",
614
+ timeOfDay: "night",
615
+ environmentMode: 6,
616
+ environmentIntensity: 0.48,
617
+ exposure: 1.35,
618
+ horizonColor: [0.08, 0.13, 0.2, 1],
619
+ zenithColor: [0.018, 0.035, 0.09, 1],
620
+ sunDirection: [-0.22, 0.86, -0.34],
621
+ sunColor: [0.72, 0.82, 1.35, 1],
622
+ ambientColor: [0.012, 0.017, 0.026, 1],
623
+ environmentLightSources: [
624
+ { id: "field-night-moon", kind: "moon", role: "key", direction: [-0.22, 0.86, -0.34], color: [0.52, 0.62, 1, 1], intensity: 1.25, angularRadiusRadians: 0.018 },
625
+ { id: "field-night-stars", kind: "stars", role: "fill", direction: [0, 1, 0], color: [0.32, 0.38, 0.6, 1], intensity: 0.24 },
626
+ { id: "field-night-horizon", kind: "horizon-glow", role: "rim", direction: [0.8, 0.08, -0.15], color: [0.08, 0.14, 0.26, 1], intensity: 0.28 }
627
+ ]
628
+ }),
629
+ "forest-dawn": defineEnvironmentPreset({
630
+ preset: "forest-dawn",
631
+ scene: "forest",
632
+ timeOfDay: "dawn",
633
+ environmentMode: 7,
634
+ environmentIntensity: 0.78,
635
+ exposure: 1.14,
636
+ horizonColor: [0.72, 0.48, 0.28, 1],
637
+ zenithColor: [0.08, 0.18, 0.18, 1],
638
+ sunDirection: [0.58, 0.42, -0.24],
639
+ sunColor: [4.4, 2.65, 1.32, 1],
640
+ ambientColor: [0.024, 0.04, 0.026, 1],
641
+ environmentLightSources: [
642
+ { id: "forest-dawn-sun-shaft", kind: "sun", role: "key", direction: [0.58, 0.42, -0.24], color: [1, 0.62, 0.32, 1], intensity: 4.4, angularRadiusRadians: 0.018 },
643
+ { id: "forest-dawn-canopy", kind: "canopy-transmission", role: "filter", direction: [0.12, 0.78, 0.2], color: [0.34, 0.68, 0.24, 1], intensity: 0.86 },
644
+ { id: "forest-dawn-sky-gap", kind: "sky", role: "fill", direction: [-0.18, 0.92, 0.12], color: [0.28, 0.46, 0.62, 1], intensity: 0.48 }
645
+ ]
646
+ }),
647
+ "forest-midday": defineEnvironmentPreset({
648
+ preset: "forest-midday",
649
+ scene: "forest",
650
+ timeOfDay: "midday",
651
+ environmentMode: 8,
652
+ environmentIntensity: 0.96,
653
+ exposure: 1.02,
654
+ horizonColor: [0.38, 0.62, 0.42, 1],
655
+ zenithColor: [0.08, 0.28, 0.32, 1],
656
+ sunDirection: [0.08, 0.96, -0.18],
657
+ sunColor: [7.2, 6.9, 5.25, 1],
658
+ ambientColor: [0.034, 0.055, 0.032, 1],
659
+ environmentLightSources: [
660
+ { id: "forest-midday-sun-gap", kind: "sun", role: "key", direction: [0.08, 0.96, -0.18], color: [1, 0.96, 0.74, 1], intensity: 7.2, angularRadiusRadians: 0.013 },
661
+ { id: "forest-midday-leaves", kind: "canopy-transmission", role: "filter", direction: [0.32, 0.75, 0.12], color: [0.24, 0.72, 0.28, 1], intensity: 1.35 },
662
+ { id: "forest-midday-floor", kind: "ground-bounce", role: "bounce", direction: [-0.1, 0.25, 0.18], color: [0.18, 0.35, 0.13, 1], intensity: 0.42 }
663
+ ]
664
+ }),
665
+ "forest-dusk": defineEnvironmentPreset({
666
+ preset: "forest-dusk",
667
+ scene: "forest",
668
+ timeOfDay: "dusk",
669
+ environmentMode: 9,
670
+ environmentIntensity: 0.68,
671
+ exposure: 1.2,
672
+ horizonColor: [0.72, 0.28, 0.2, 1],
673
+ zenithColor: [0.04, 0.07, 0.18, 1],
674
+ sunDirection: [-0.7, 0.28, -0.18],
675
+ sunColor: [3.2, 1.18, 0.56, 1],
676
+ ambientColor: [0.018, 0.026, 0.024, 1],
677
+ environmentLightSources: [
678
+ { id: "forest-dusk-horizon", kind: "horizon-glow", role: "key", direction: [-0.7, 0.18, -0.18], color: [1, 0.34, 0.2, 1], intensity: 2.2, angularRadiusRadians: 0.1 },
679
+ { id: "forest-dusk-canopy", kind: "canopy-transmission", role: "filter", direction: [0.18, 0.7, 0.26], color: [0.18, 0.38, 0.2, 1], intensity: 0.52 },
680
+ { id: "forest-dusk-sky-gap", kind: "sky", role: "fill", direction: [0, 1, 0], color: [0.12, 0.16, 0.34, 1], intensity: 0.42 }
681
+ ]
682
+ }),
683
+ "forest-night": defineEnvironmentPreset({
684
+ preset: "forest-night",
685
+ scene: "forest",
686
+ timeOfDay: "night",
687
+ environmentMode: 10,
688
+ environmentIntensity: 0.42,
689
+ exposure: 1.42,
690
+ horizonColor: [0.035, 0.08, 0.1, 1],
691
+ zenithColor: [0.012, 0.025, 0.06, 1],
692
+ sunDirection: [0.2, 0.82, -0.46],
693
+ sunColor: [0.42, 0.56, 1.1, 1],
694
+ ambientColor: [0.01, 0.016, 0.02, 1],
695
+ environmentLightSources: [
696
+ { id: "forest-night-moon-gap", kind: "moon", role: "key", direction: [0.2, 0.82, -0.46], color: [0.42, 0.56, 1, 1], intensity: 0.95, angularRadiusRadians: 0.025 },
697
+ { id: "forest-night-canopy", kind: "canopy-transmission", role: "filter", direction: [-0.16, 0.66, 0.1], color: [0.08, 0.18, 0.12, 1], intensity: 0.28 },
698
+ { id: "forest-night-stars", kind: "stars", role: "fill", direction: [0, 1, 0], color: [0.22, 0.28, 0.5, 1], intensity: 0.18 }
699
+ ]
700
+ }),
701
+ "warehouse-dawn": defineEnvironmentPreset({
702
+ preset: "warehouse-dawn",
703
+ scene: "warehouse",
704
+ timeOfDay: "dawn",
705
+ environmentMode: 11,
706
+ environmentIntensity: 0.74,
707
+ exposure: 1.08,
708
+ horizonColor: [0.58, 0.44, 0.34, 1],
709
+ zenithColor: [0.16, 0.19, 0.24, 1],
710
+ sunDirection: [0.82, 0.28, 0.18],
711
+ sunColor: [2.8, 1.7, 0.92, 1],
712
+ ambientColor: [0.028, 0.03, 0.032, 1],
713
+ environmentLightSources: [
714
+ { id: "warehouse-dawn-loading-door", kind: "window-portal", role: "key", direction: [0.82, 0.28, 0.18], color: [1, 0.62, 0.34, 1], intensity: 2.8, angularRadiusRadians: 0.22 },
715
+ { id: "warehouse-dawn-fluorescent", kind: "fluorescent-strip", role: "fill", direction: [0, 1, 0], color: [0.78, 0.9, 1, 1], intensity: 1.1, angularRadiusRadians: 0.35 },
716
+ { id: "warehouse-dawn-concrete-bounce", kind: "ground-bounce", role: "bounce", direction: [0, 0.28, -0.2], color: [0.34, 0.36, 0.38, 1], intensity: 0.42 }
717
+ ]
718
+ }),
719
+ "warehouse-midday": defineEnvironmentPreset({
720
+ preset: "warehouse-midday",
721
+ scene: "warehouse",
722
+ timeOfDay: "midday",
723
+ environmentMode: 12,
724
+ environmentIntensity: 0.92,
725
+ exposure: 0.98,
726
+ horizonColor: [0.64, 0.7, 0.74, 1],
727
+ zenithColor: [0.28, 0.34, 0.42, 1],
728
+ sunDirection: [0.35, 0.86, 0.16],
729
+ sunColor: [4.2, 4, 3.45, 1],
730
+ ambientColor: [0.034, 0.036, 0.038, 1],
731
+ environmentLightSources: [
732
+ { id: "warehouse-midday-skylights", kind: "window-portal", role: "key", direction: [0.35, 0.86, 0.16], color: [0.92, 0.96, 1, 1], intensity: 4.2, angularRadiusRadians: 0.18 },
733
+ { id: "warehouse-midday-fluorescent", kind: "fluorescent-strip", role: "fill", direction: [-0.2, 0.92, 0.1], color: [0.78, 0.92, 1, 1], intensity: 1.6, angularRadiusRadians: 0.45 },
734
+ { id: "warehouse-midday-door-spill", kind: "sodium-door", role: "rim", direction: [-0.82, 0.18, -0.12], color: [1, 0.58, 0.24, 1], intensity: 0.68 }
735
+ ]
736
+ }),
737
+ "warehouse-dusk": defineEnvironmentPreset({
738
+ preset: "warehouse-dusk",
739
+ scene: "warehouse",
740
+ timeOfDay: "dusk",
741
+ environmentMode: 13,
742
+ environmentIntensity: 0.7,
743
+ exposure: 1.16,
744
+ horizonColor: [0.7, 0.32, 0.24, 1],
745
+ zenithColor: [0.08, 0.1, 0.18, 1],
746
+ sunDirection: [-0.78, 0.18, 0.16],
747
+ sunColor: [2.4, 0.94, 0.48, 1],
748
+ ambientColor: [0.022, 0.024, 0.03, 1],
749
+ environmentLightSources: [
750
+ { id: "warehouse-dusk-door-glow", kind: "sodium-door", role: "key", direction: [-0.78, 0.18, 0.16], color: [1, 0.42, 0.2, 1], intensity: 2.4, angularRadiusRadians: 0.18 },
751
+ { id: "warehouse-dusk-fluorescent", kind: "fluorescent-strip", role: "fill", direction: [0, 0.95, -0.08], color: [0.72, 0.88, 1, 1], intensity: 1.35, angularRadiusRadians: 0.4 },
752
+ { id: "warehouse-dusk-emergency", kind: "emergency-beacon", role: "accent", direction: [0.2, 0.35, -0.8], color: [1, 0.08, 0.04, 1], intensity: 0.32 }
753
+ ]
754
+ }),
755
+ "warehouse-night": defineEnvironmentPreset({
756
+ preset: "warehouse-night",
757
+ scene: "warehouse",
758
+ timeOfDay: "night",
759
+ environmentMode: 14,
760
+ environmentIntensity: 0.58,
761
+ exposure: 1.28,
762
+ horizonColor: [0.06, 0.08, 0.12, 1],
763
+ zenithColor: [0.02, 0.03, 0.055, 1],
764
+ sunDirection: [0.1, 0.94, -0.12],
765
+ sunColor: [1.2, 1.65, 2.25, 1],
766
+ ambientColor: [0.014, 0.018, 0.024, 1],
767
+ environmentLightSources: [
768
+ { id: "warehouse-night-fluorescent", kind: "fluorescent-strip", role: "key", direction: [0.1, 0.94, -0.12], color: [0.68, 0.88, 1, 1], intensity: 2.25, angularRadiusRadians: 0.5 },
769
+ { id: "warehouse-night-emergency", kind: "emergency-beacon", role: "accent", direction: [-0.4, 0.3, 0.7], color: [1, 0.05, 0.025, 1], intensity: 0.4 },
770
+ { id: "warehouse-night-door-leak", kind: "window-portal", role: "rim", direction: [0.82, 0.08, -0.2], color: [0.12, 0.22, 0.42, 1], intensity: 0.34 }
771
+ ]
772
+ }),
773
+ "cavern-dawn": defineEnvironmentPreset({
774
+ preset: "cavern-dawn",
775
+ scene: "cavern",
776
+ timeOfDay: "dawn",
777
+ environmentMode: 15,
778
+ environmentIntensity: 0.62,
779
+ exposure: 1.24,
780
+ horizonColor: [0.5, 0.3, 0.2, 1],
781
+ zenithColor: [0.04, 0.07, 0.09, 1],
782
+ sunDirection: [0.72, 0.32, 0.26],
783
+ sunColor: [2.1, 1.22, 0.64, 1],
784
+ ambientColor: [0.018, 0.018, 0.016, 1],
785
+ environmentLightSources: [
786
+ { id: "cavern-dawn-mouth", kind: "cave-mouth", role: "key", direction: [0.72, 0.32, 0.26], color: [1, 0.58, 0.3, 1], intensity: 2.1, angularRadiusRadians: 0.24 },
787
+ { id: "cavern-dawn-torch", kind: "torch", role: "emissive", direction: [-0.35, 0.28, -0.6], color: [1, 0.42, 0.16, 1], intensity: 1.35, reach: 18 },
788
+ { id: "cavern-dawn-crystal", kind: "crystal", role: "accent", direction: [0.08, 0.22, 0.9], color: [0.22, 0.72, 1, 1], intensity: 0.28, reach: 10 }
789
+ ]
790
+ }),
791
+ "cavern-midday": defineEnvironmentPreset({
792
+ preset: "cavern-midday",
793
+ scene: "cavern",
794
+ timeOfDay: "midday",
795
+ environmentMode: 16,
796
+ environmentIntensity: 0.72,
797
+ exposure: 1.16,
798
+ horizonColor: [0.6, 0.56, 0.48, 1],
799
+ zenithColor: [0.08, 0.12, 0.14, 1],
800
+ sunDirection: [0.36, 0.82, 0.14],
801
+ sunColor: [3.4, 3.05, 2.2, 1],
802
+ ambientColor: [0.02, 0.022, 0.02, 1],
803
+ environmentLightSources: [
804
+ { id: "cavern-midday-mouth", kind: "cave-mouth", role: "key", direction: [0.36, 0.82, 0.14], color: [1, 0.9, 0.66, 1], intensity: 3.4, angularRadiusRadians: 0.18 },
805
+ { id: "cavern-midday-biolume", kind: "bioluminescence", role: "fill", direction: [-0.25, 0.25, 0.7], color: [0.1, 0.82, 0.64, 1], intensity: 0.46, reach: 14 },
806
+ { id: "cavern-midday-wet-rock", kind: "ground-bounce", role: "bounce", direction: [0.1, 0.2, -0.3], color: [0.18, 0.2, 0.18, 1], intensity: 0.22 }
807
+ ]
808
+ }),
809
+ "cavern-dusk": defineEnvironmentPreset({
810
+ preset: "cavern-dusk",
811
+ scene: "cavern",
812
+ timeOfDay: "dusk",
813
+ environmentMode: 17,
814
+ environmentIntensity: 0.56,
815
+ exposure: 1.32,
816
+ horizonColor: [0.46, 0.18, 0.14, 1],
817
+ zenithColor: [0.035, 0.045, 0.08, 1],
818
+ sunDirection: [-0.62, 0.22, 0.22],
819
+ sunColor: [1.55, 0.56, 0.28, 1],
820
+ ambientColor: [0.014, 0.014, 0.018, 1],
821
+ environmentLightSources: [
822
+ { id: "cavern-dusk-mouth", kind: "cave-mouth", role: "rim", direction: [-0.62, 0.22, 0.22], color: [1, 0.36, 0.18, 1], intensity: 1.55, angularRadiusRadians: 0.22 },
823
+ { id: "cavern-dusk-torch", kind: "torch", role: "key", direction: [0.32, 0.34, -0.54], color: [1, 0.38, 0.12, 1], intensity: 1.85, reach: 20 },
824
+ { id: "cavern-dusk-biolume", kind: "bioluminescence", role: "fill", direction: [-0.18, 0.18, 0.74], color: [0.08, 0.58, 0.72, 1], intensity: 0.34, reach: 12 }
825
+ ]
826
+ }),
827
+ "cavern-night": defineEnvironmentPreset({
828
+ preset: "cavern-night",
829
+ scene: "cavern",
830
+ timeOfDay: "night",
831
+ environmentMode: 18,
832
+ environmentIntensity: 0.5,
833
+ exposure: 1.45,
834
+ horizonColor: [0.025, 0.035, 0.06, 1],
835
+ zenithColor: [8e-3, 0.014, 0.03, 1],
836
+ sunDirection: [0.18, 0.28, -0.68],
837
+ sunColor: [1.9, 0.72, 0.24, 1],
838
+ ambientColor: [0.01, 0.012, 0.018, 1],
839
+ environmentLightSources: [
840
+ { id: "cavern-night-torch", kind: "torch", role: "key", direction: [0.18, 0.28, -0.68], color: [1, 0.36, 0.12, 1], intensity: 1.9, reach: 18 },
841
+ { id: "cavern-night-biolume", kind: "bioluminescence", role: "fill", direction: [-0.32, 0.16, 0.72], color: [0.06, 0.62, 0.76, 1], intensity: 0.52, reach: 16 },
842
+ { id: "cavern-night-lava", kind: "lava-fissure", role: "emissive", direction: [0.42, 0.12, 0.28], color: [1, 0.18, 0.04, 1], intensity: 0.8, reach: 12 }
843
+ ]
844
+ })
845
+ });
846
+ var lightingEnvironmentPresetNames = Object.freeze(
847
+ Object.keys(environmentLightingPresets)
848
+ );
849
+ function resolveEnvironmentPreset(name, timeOfDay) {
850
+ const presetName = typeof name === "string" && name.length > 0 ? name : "product-studio";
851
+ const preset = environmentLightingPresets[presetName];
852
+ if (!preset) {
853
+ if (lightingEnvironmentSceneNames.includes(presetName)) {
854
+ if (timeOfDay != null && !lightingEnvironmentTimeOfDayNames.includes(timeOfDay)) {
855
+ throw new Error(
856
+ `timeOfDay must be one of: ${lightingEnvironmentTimeOfDayNames.join(", ")}.`
857
+ );
858
+ }
859
+ const scenePresetName = `${presetName}-${timeOfDay ?? "midday"}`;
860
+ const scenePreset = environmentLightingPresets[scenePresetName];
861
+ if (scenePreset) {
862
+ return scenePreset;
863
+ }
864
+ }
865
+ throw new Error(
866
+ `Unknown lighting environment preset "${presetName}". Expected one of: ${lightingEnvironmentPresetNames.join(", ")}.`
867
+ );
868
+ }
869
+ return preset;
870
+ }
871
+ function estimateEnvironmentColor(config) {
872
+ const horizonWeight = 0.58;
873
+ const zenithWeight = 1 - horizonWeight;
874
+ const glowWeight = 0.055;
875
+ const intensity = Math.max(config.environmentIntensity, 1e-4);
876
+ return ensureNonNullColor([
877
+ (config.horizonColor[0] * horizonWeight + config.zenithColor[0] * zenithWeight + config.sunColor[0] * glowWeight) * intensity,
878
+ (config.horizonColor[1] * horizonWeight + config.zenithColor[1] * zenithWeight + config.sunColor[1] * glowWeight) * intensity,
879
+ (config.horizonColor[2] * horizonWeight + config.zenithColor[2] * zenithWeight + config.sunColor[2] * glowWeight) * intensity,
880
+ 1
881
+ ], config.dominantLightSource?.radiance ?? config.sunColor);
882
+ }
883
+ function createEnvironmentLightingConfig(options = {}) {
884
+ const preset = resolveEnvironmentPreset(
885
+ options.preset ?? options.name ?? options.scene,
886
+ options.timeOfDay
887
+ );
888
+ const environmentPortals = normalizeEnvironmentPortals(
889
+ options.environmentPortals ?? options.portals
890
+ );
891
+ const environmentPortalMode = normalizeEnvironmentPortalMode(
892
+ options.environmentPortalMode ?? options.portalMode,
893
+ environmentPortals.length > 0
894
+ );
895
+ const environmentIntensity = Math.max(
896
+ readFinite(options.environmentIntensity ?? options.intensity, preset.environmentIntensity),
897
+ 1e-4
898
+ );
899
+ const baseConfig = {
900
+ preset: preset.preset,
901
+ scene: preset.scene,
902
+ timeOfDay: preset.timeOfDay,
903
+ profile: typeof options.profile === "string" ? options.profile : defaultLightingProfile,
904
+ environmentMode: Math.max(0, Math.trunc(readFinite(options.environmentMode, preset.environmentMode))),
905
+ environmentIntensity,
906
+ exposure: Math.max(1e-4, readFinite(options.exposure, preset.exposure)),
907
+ horizonColor: readColor(options.horizonColor, preset.horizonColor),
908
+ zenithColor: readColor(options.zenithColor, preset.zenithColor),
909
+ sunDirection: Object.freeze(
910
+ normalizeVector3(options.sunDirection, preset.sunDirection)
911
+ ),
912
+ sunColor: readColor(options.sunColor, preset.sunColor),
913
+ ambientColor: readColor(options.ambientColor, preset.ambientColor),
914
+ environmentPortalMode,
915
+ environmentPortals
916
+ };
917
+ const environmentLightSources = normalizeEnvironmentLightSources(
918
+ options.environmentLightSources ?? options.lightSources,
919
+ preset,
920
+ baseConfig
921
+ );
922
+ const dominantLightSource = findDominantEnvironmentLightSource(
923
+ environmentLightSources
924
+ );
925
+ const config = {
926
+ ...baseConfig,
927
+ environmentLightSources,
928
+ lightSources: environmentLightSources,
929
+ dominantLightSource
930
+ };
931
+ const environmentColor = estimateEnvironmentColor(config);
932
+ const environmentMissLighting = createEnvironmentMissLighting(
933
+ dominantLightSource,
934
+ environmentColor
935
+ );
936
+ return Object.freeze({
937
+ ...config,
938
+ environmentColor,
939
+ environmentMissLighting,
940
+ wavefront: Object.freeze({
941
+ environmentColor,
942
+ ambientColor: config.ambientColor,
943
+ environmentPortalMode: config.environmentPortalMode,
944
+ environmentPortals: config.environmentPortals,
945
+ environmentLightSources: config.environmentLightSources,
946
+ lightSources: config.environmentLightSources,
947
+ dominantLightSource,
948
+ environmentMissLighting,
949
+ environmentLighting: Object.freeze({
950
+ horizonColor: config.horizonColor,
951
+ zenithColor: config.zenithColor,
952
+ sunDirection: Object.freeze([...config.sunDirection]),
953
+ sunColor: config.sunColor,
954
+ intensity: config.environmentIntensity,
955
+ mode: config.environmentMode,
956
+ exposure: config.exposure,
957
+ environmentPortalMode: config.environmentPortalMode,
958
+ environmentPortalCount: config.environmentPortals.length,
959
+ environmentLightSources: config.environmentLightSources,
960
+ environmentLightSourceCount: config.environmentLightSources.length,
961
+ dominantLightSource,
962
+ environmentMissLighting
963
+ })
964
+ })
965
+ });
966
+ }
967
+ function createWavefrontEnvironmentLightingOptions(options = {}) {
968
+ const config = createEnvironmentLightingConfig(options);
969
+ return Object.freeze({
970
+ environmentColor: config.wavefront.environmentColor,
971
+ ambientColor: config.wavefront.ambientColor,
972
+ environmentPortalMode: config.wavefront.environmentPortalMode,
973
+ environmentPortals: config.wavefront.environmentPortals,
974
+ environmentLightSources: config.wavefront.environmentLightSources,
975
+ lightSources: config.wavefront.environmentLightSources,
976
+ dominantLightSource: config.wavefront.dominantLightSource,
977
+ environmentMissLighting: config.wavefront.environmentMissLighting,
978
+ environmentLighting: config.wavefront.environmentLighting,
979
+ lightingEnvironment: config
980
+ });
981
+ }
139
982
  var lightingImportanceLevels = Object.freeze([
140
983
  "low",
141
984
  "medium",
@@ -1252,8 +2095,10 @@ async function loadLightingProfileWorkerPlan(profileName = defaultLightingProfil
1252
2095
  };
1253
2096
  }
1254
2097
  export {
2098
+ createEnvironmentLightingConfig,
1255
2099
  createLightingBandPlan,
1256
2100
  createLightingProfileModeLadder,
2101
+ createWavefrontEnvironmentLightingOptions,
1257
2102
  defaultAdaptiveLightingProfilePolicy,
1258
2103
  defaultLightingProfile,
1259
2104
  defaultLightingTechnique,
@@ -1263,6 +2108,12 @@ export {
1263
2108
  getLightingTechniqueWorkerManifest,
1264
2109
  lightingDebugOwner,
1265
2110
  lightingDistanceBands,
2111
+ lightingEnvironmentLightSourceKinds,
2112
+ lightingEnvironmentPortalModes,
2113
+ lightingEnvironmentPortalShapes,
2114
+ lightingEnvironmentPresetNames,
2115
+ lightingEnvironmentSceneNames,
2116
+ lightingEnvironmentTimeOfDayNames,
1266
2117
  lightingJobLabels,
1267
2118
  lightingJobs,
1268
2119
  lightingPreludeWgslUrl,