@headless-three/renderer 0.1.7 → 0.1.9

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 (58) hide show
  1. package/README.md +227 -52
  2. package/dist/attributes.d.ts +7 -5
  3. package/dist/attributes.d.ts.map +1 -1
  4. package/dist/attributes.js +112 -36
  5. package/dist/attributes.js.map +1 -1
  6. package/dist/camera.d.ts +1 -0
  7. package/dist/camera.d.ts.map +1 -1
  8. package/dist/camera.js +52 -13
  9. package/dist/camera.js.map +1 -1
  10. package/dist/clipping.d.ts +6 -0
  11. package/dist/clipping.d.ts.map +1 -0
  12. package/dist/clipping.js +84 -0
  13. package/dist/clipping.js.map +1 -0
  14. package/dist/color.d.ts +8 -3
  15. package/dist/color.d.ts.map +1 -1
  16. package/dist/color.js +136 -11
  17. package/dist/color.js.map +1 -1
  18. package/dist/index.d.ts +770 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +6213 -46
  21. package/dist/index.js.map +1 -1
  22. package/dist/layers.d.ts +3 -0
  23. package/dist/layers.d.ts.map +1 -0
  24. package/dist/layers.js +33 -0
  25. package/dist/layers.js.map +1 -0
  26. package/dist/lights.d.ts +5 -4
  27. package/dist/lights.d.ts.map +1 -1
  28. package/dist/lights.js +473 -77
  29. package/dist/lights.js.map +1 -1
  30. package/dist/loaders.d.ts +92 -0
  31. package/dist/loaders.d.ts.map +1 -0
  32. package/dist/loaders.js +858 -0
  33. package/dist/loaders.js.map +1 -0
  34. package/dist/materials.d.ts +38 -6
  35. package/dist/materials.d.ts.map +1 -1
  36. package/dist/materials.js +4236 -162
  37. package/dist/materials.js.map +1 -1
  38. package/dist/math.js +2 -2
  39. package/dist/math.js.map +1 -1
  40. package/dist/morphs.d.ts.map +1 -1
  41. package/dist/morphs.js +50 -15
  42. package/dist/morphs.js.map +1 -1
  43. package/dist/objects.d.ts +4 -0
  44. package/dist/objects.d.ts.map +1 -0
  45. package/dist/objects.js +25 -0
  46. package/dist/objects.js.map +1 -0
  47. package/dist/scene.d.ts +279 -2
  48. package/dist/scene.d.ts.map +1 -1
  49. package/dist/scene.js +3818 -104
  50. package/dist/scene.js.map +1 -1
  51. package/dist/skinning.d.ts.map +1 -1
  52. package/dist/skinning.js +37 -18
  53. package/dist/skinning.js.map +1 -1
  54. package/dist/types.d.ts +1264 -12
  55. package/dist/types.d.ts.map +1 -1
  56. package/native.d.ts +767 -3
  57. package/native.js +54 -52
  58. package/package.json +20 -9
package/dist/lights.js CHANGED
@@ -3,39 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractLights = extractLights;
4
4
  exports.extractAmbientLight = extractAmbientLight;
5
5
  exports.extractAmbientIntensity = extractAmbientIntensity;
6
+ exports.extractLightProbe = extractLightProbe;
6
7
  const color_1 = require("./color");
7
- function extractLights(scene) {
8
+ const layers_1 = require("./layers");
9
+ const math_1 = require("./math");
10
+ const objects_1 = require("./objects");
11
+ const MAX_NATIVE_LIGHTS = 64;
12
+ const MAX_SHADOW_CASCADES = 4;
13
+ function extractLights(scene, camera) {
8
14
  const lights = [];
9
- visitLights(scene, lights);
15
+ visitLights(scene, camera, lights);
16
+ assertSupportedLightCount(lights);
10
17
  return lights.length > 0 ? lights : undefined;
11
18
  }
12
- function visitLights(object, lights) {
19
+ function visitLights(object, camera, lights) {
13
20
  if (!object)
14
21
  return;
15
- if (object.visible === false)
22
+ if (optionalBoolean(object.visible, 'object.visible') === false)
16
23
  return;
17
- if (object.isLight === true) {
24
+ if (object.isLight === true && (0, layers_1.objectLayersMatchCamera)(object, camera)) {
18
25
  const light = extractLight(object);
19
26
  if (light)
20
27
  lights.push(light);
21
28
  }
22
- const children = Array.isArray(object.children) ? object.children : [];
23
- for (const child of children) {
24
- visitLights(child, lights);
29
+ for (const child of (0, objects_1.objectChildren)(object)) {
30
+ visitLights(child, camera, lights);
25
31
  }
26
32
  }
27
33
  function extractLight(light) {
28
- const color = (0, color_1.colorLikeToArray)(light.color) ?? [1, 1, 1, 1];
29
- const intensity = Number.isFinite(light.intensity) ? light.intensity : 1;
34
+ const color = (0, color_1.validatedColorLikeToArray)(light.color, 'light.color') ?? [1, 1, 1, 1];
35
+ const intensity = finiteNumberOrDefault(light.intensity, 'light.intensity', 1);
30
36
  if (light.isDirectionalLight === true) {
31
- const pos = light.matrixWorld
32
- ? [light.matrixWorld.elements[12], light.matrixWorld.elements[13], light.matrixWorld.elements[14]]
33
- : [0, 10, 0];
34
- let targetPos = [0, 0, 0];
35
- if (light.target?.matrixWorld) {
36
- const te = light.target.matrixWorld.elements;
37
- targetPos = [te[12], te[13], te[14]];
38
- }
37
+ const pos = positionFromMatrix(light.matrixWorld, 'DirectionalLight.matrixWorld', [0, 10, 0]);
38
+ const targetPos = positionFromMatrix(lightTargetMatrix(light, 'DirectionalLight.target'), 'DirectionalLight.target.matrixWorld', [0, 0, 0]);
39
39
  const direction = [
40
40
  targetPos[0] - pos[0],
41
41
  targetPos[1] - pos[1],
@@ -54,56 +54,31 @@ function extractLight(light) {
54
54
  position: pos,
55
55
  direction,
56
56
  };
57
- if (light.castShadow === true) {
58
- const shadow = light.shadow;
59
- const mapSize = shadow?.mapSize;
60
- const size = Math.max(32, Math.floor(mapSize?.x ?? mapSize?.width ?? 512));
61
- const cam = shadow?.camera;
57
+ if (optionalBoolean(light.castShadow, 'light.castShadow') === true) {
62
58
  out.castShadow = true;
63
- out.shadowMapSize = size;
64
- if (Number.isFinite(shadow?.bias))
65
- out.shadowBias = shadow.bias;
66
- if (Number.isFinite(shadow?.normalBias))
67
- out.shadowNormalBias = shadow.normalBias;
68
- if (cam) {
69
- if (Number.isFinite(cam.left))
70
- out.shadowCameraLeft = cam.left;
71
- if (Number.isFinite(cam.right))
72
- out.shadowCameraRight = cam.right;
73
- if (Number.isFinite(cam.top))
74
- out.shadowCameraTop = cam.top;
75
- if (Number.isFinite(cam.bottom))
76
- out.shadowCameraBottom = cam.bottom;
77
- if (Number.isFinite(cam.near))
78
- out.shadowCameraNear = cam.near;
79
- if (Number.isFinite(cam.far))
80
- out.shadowCameraFar = cam.far;
81
- }
59
+ applyShadowOptions(out, light);
82
60
  }
83
61
  return out;
84
62
  }
85
63
  if (light.isPointLight === true) {
86
- const pos = light.matrixWorld
87
- ? [light.matrixWorld.elements[12], light.matrixWorld.elements[13], light.matrixWorld.elements[14]]
88
- : [0, 0, 0];
89
- return {
64
+ const pos = positionFromMatrix(light.matrixWorld, 'PointLight.matrixWorld', [0, 0, 0]);
65
+ const out = {
90
66
  lightType: 'point',
91
67
  color: [color[0], color[1], color[2]],
92
68
  intensity,
93
69
  position: pos,
94
- distance: Number.isFinite(light.distance) ? light.distance : 0,
95
- decay: Number.isFinite(light.decay) ? light.decay : 2,
70
+ distance: nonNegativeFiniteNumberOrDefault(light.distance, 'PointLight.distance', 0),
71
+ decay: nonNegativeFiniteNumberOrDefault(light.decay, 'PointLight.decay', 2),
96
72
  };
73
+ if (optionalBoolean(light.castShadow, 'light.castShadow') === true) {
74
+ out.castShadow = true;
75
+ applyShadowOptions(out, light);
76
+ }
77
+ return out;
97
78
  }
98
79
  if (light.isSpotLight === true) {
99
- const pos = light.matrixWorld
100
- ? [light.matrixWorld.elements[12], light.matrixWorld.elements[13], light.matrixWorld.elements[14]]
101
- : [0, 0, 0];
102
- let targetPos = [0, 0, 0];
103
- if (light.target?.matrixWorld) {
104
- const te = light.target.matrixWorld.elements;
105
- targetPos = [te[12], te[13], te[14]];
106
- }
80
+ const pos = positionFromMatrix(light.matrixWorld, 'SpotLight.matrixWorld', [0, 0, 0]);
81
+ const targetPos = positionFromMatrix(lightTargetMatrix(light, 'SpotLight.target'), 'SpotLight.target.matrixWorld', [0, 0, 0]);
107
82
  const direction = [
108
83
  targetPos[0] - pos[0],
109
84
  targetPos[1] - pos[1],
@@ -115,24 +90,52 @@ function extractLight(light) {
115
90
  direction[1] /= len;
116
91
  direction[2] /= len;
117
92
  }
118
- return {
93
+ const out = {
119
94
  lightType: 'spot',
120
95
  color: [color[0], color[1], color[2]],
121
96
  intensity,
122
97
  position: pos,
123
98
  direction,
124
- distance: Number.isFinite(light.distance) ? light.distance : 0,
125
- decay: Number.isFinite(light.decay) ? light.decay : 2,
126
- angle: Number.isFinite(light.angle) ? light.angle : Math.PI / 3,
127
- penumbra: Number.isFinite(light.penumbra) ? light.penumbra : 0,
99
+ distance: nonNegativeFiniteNumberOrDefault(light.distance, 'SpotLight.distance', 0),
100
+ decay: nonNegativeFiniteNumberOrDefault(light.decay, 'SpotLight.decay', 2),
101
+ angle: spotAngleOrDefault(light.angle),
102
+ penumbra: normalizedFiniteNumberOrDefault(light.penumbra, 'SpotLight.penumbra', 0),
103
+ };
104
+ if (optionalBoolean(light.castShadow, 'light.castShadow') === true) {
105
+ out.castShadow = true;
106
+ applyShadowOptions(out, light);
107
+ }
108
+ return out;
109
+ }
110
+ if (light.isRectAreaLight === true) {
111
+ const matrix = matrixElementsOrUndefined(light.matrixWorld, 'RectAreaLight.matrixWorld');
112
+ const pos = matrix ? [matrix[12], matrix[13], matrix[14]] : [0, 0, 0];
113
+ let direction = [0, 0, -1];
114
+ if (matrix) {
115
+ direction = [-matrix[8], -matrix[9], -matrix[10]];
116
+ }
117
+ const len = Math.sqrt(direction[0] ** 2 + direction[1] ** 2 + direction[2] ** 2);
118
+ if (len > 0) {
119
+ direction[0] /= len;
120
+ direction[1] /= len;
121
+ direction[2] /= len;
122
+ }
123
+ return {
124
+ lightType: 'rectArea',
125
+ color: [color[0], color[1], color[2]],
126
+ intensity,
127
+ position: pos,
128
+ direction,
129
+ width: positiveFiniteNumberOrDefault(light.width, 'RectAreaLight.width', 10),
130
+ height: positiveFiniteNumberOrDefault(light.height, 'RectAreaLight.height', 10),
128
131
  };
129
132
  }
130
133
  if (light.isHemisphereLight === true) {
131
- const groundColor = (0, color_1.colorLikeToArray)(light.groundColor) ?? [0.04, 0.02, 0.0, 1];
134
+ const groundColor = (0, color_1.validatedColorLikeToArray)(light.groundColor, 'HemisphereLight.groundColor') ?? [0.04, 0.02, 0.0, 1];
132
135
  let direction = [0, 1, 0];
133
- if (light.matrixWorld) {
134
- const e = light.matrixWorld.elements;
135
- const ux = e[4], uy = e[5], uz = e[6];
136
+ const matrix = matrixElementsOrUndefined(light.matrixWorld, 'HemisphereLight.matrixWorld');
137
+ if (matrix) {
138
+ const ux = matrix[4], uy = matrix[5], uz = matrix[6];
136
139
  const ulen = Math.sqrt(ux * ux + uy * uy + uz * uz);
137
140
  if (ulen > 0) {
138
141
  direction = [ux / ulen, uy / ulen, uz / ulen];
@@ -149,10 +152,332 @@ function extractLight(light) {
149
152
  // AmbientLight is handled separately
150
153
  return null;
151
154
  }
152
- function extractAmbientLight(scene) {
155
+ function positionFromMatrix(matrix, label, fallback) {
156
+ const elements = matrixElementsOrUndefined(matrix, label);
157
+ return elements ? [elements[12], elements[13], elements[14]] : fallback;
158
+ }
159
+ function matrixElementsOrUndefined(matrix, label) {
160
+ return matrix ? (0, math_1.matrixElements)(matrix, label) : undefined;
161
+ }
162
+ function lightTargetMatrix(light, label) {
163
+ const target = light.target;
164
+ if (target == null)
165
+ return undefined;
166
+ if (typeof target !== 'object' || Array.isArray(target)) {
167
+ throw new TypeError(`${label} must be an object.`);
168
+ }
169
+ return target.matrixWorld;
170
+ }
171
+ function applyShadowOptions(out, light) {
172
+ const shadow = light.shadow;
173
+ assertShadowContainerLike(shadow);
174
+ const mapSize = shadowMapSizeOrDefault(shadow?.mapSize, light);
175
+ out.shadowMapSize = Math.max(mapSize.width, mapSize.height);
176
+ out.shadowMapWidth = mapSize.width;
177
+ out.shadowMapHeight = mapSize.height;
178
+ optionalBoolean(shadow?.autoUpdate, 'light.shadow.autoUpdate');
179
+ optionalBoolean(shadow?.needsUpdate, 'light.shadow.needsUpdate');
180
+ validateShadowObjectState(shadow?.map, 'light.shadow.map');
181
+ validateShadowObjectState(shadow?.mapPass, 'light.shadow.mapPass');
182
+ if (shadow?.matrix != null)
183
+ (0, math_1.matrixElements)(shadow.matrix, 'light.shadow.matrix');
184
+ const bias = optionalFiniteNumber(shadow?.bias, 'light.shadow.bias');
185
+ const normalBias = optionalFiniteNumber(shadow?.normalBias, 'light.shadow.normalBias');
186
+ const radius = optionalNonNegativeFiniteNumber(shadow?.radius, 'light.shadow.radius');
187
+ const intensity = optionalNonNegativeFiniteNumber(shadow?.intensity, 'light.shadow.intensity');
188
+ const blurSamples = optionalNonNegativeFiniteNumber(shadow?.blurSamples, 'light.shadow.blurSamples');
189
+ if (bias !== undefined)
190
+ out.shadowBias = bias;
191
+ if (normalBias !== undefined)
192
+ out.shadowNormalBias = normalBias;
193
+ if (radius !== undefined)
194
+ out.shadowRadius = radius;
195
+ if (intensity !== undefined)
196
+ out.shadowIntensity = intensity;
197
+ if (blurSamples !== undefined)
198
+ out.shadowBlurSamples = blurSamples;
199
+ const cam = shadow?.camera;
200
+ if (cam != null) {
201
+ assertPlainObject(cam, 'light.shadow.camera');
202
+ const bounds = shadowCameraOrthoBounds(cam, out);
203
+ const clipDistances = shadowCameraClipDistances(cam, out);
204
+ if (bounds.shadowCameraLeft !== undefined)
205
+ out.shadowCameraLeft = bounds.shadowCameraLeft;
206
+ if (bounds.shadowCameraRight !== undefined)
207
+ out.shadowCameraRight = bounds.shadowCameraRight;
208
+ if (bounds.shadowCameraTop !== undefined)
209
+ out.shadowCameraTop = bounds.shadowCameraTop;
210
+ if (bounds.shadowCameraBottom !== undefined)
211
+ out.shadowCameraBottom = bounds.shadowCameraBottom;
212
+ if (clipDistances.shadowCameraNear !== undefined)
213
+ out.shadowCameraNear = clipDistances.shadowCameraNear;
214
+ if (clipDistances.shadowCameraFar !== undefined)
215
+ out.shadowCameraFar = clipDistances.shadowCameraFar;
216
+ }
217
+ applyShadowCascadeOptions(out, light);
218
+ }
219
+ function shadowCameraClipDistances(camera, light) {
220
+ const near = optionalFiniteNumber(camera.near, 'light.shadow.camera.near');
221
+ const far = optionalFiniteNumber(camera.far, 'light.shadow.camera.far');
222
+ if (near !== undefined && near < 0) {
223
+ throw new TypeError('light.shadow.camera.near must be non-negative.');
224
+ }
225
+ if (near === 0 && (light.lightType === 'point' || light.lightType === 'spot')) {
226
+ throw new TypeError('light.shadow.camera.near must be positive for point and spot shadows.');
227
+ }
228
+ if (far !== undefined && far <= 0) {
229
+ throw new TypeError('light.shadow.camera.far must be positive.');
230
+ }
231
+ const effectiveNear = near ?? 0.5;
232
+ const effectiveFar = far ?? defaultShadowCameraFar(light);
233
+ if (effectiveFar <= effectiveNear) {
234
+ if (far !== undefined) {
235
+ throw new TypeError('light.shadow.camera.far must be greater than light.shadow.camera.near.');
236
+ }
237
+ throw new TypeError('light.shadow.camera.near must be less than the effective light.shadow.camera.far.');
238
+ }
239
+ return { shadowCameraNear: near, shadowCameraFar: far };
240
+ }
241
+ function defaultShadowCameraFar(light) {
242
+ return light.lightType === 'point' && light.distance !== undefined && light.distance > 0
243
+ ? light.distance
244
+ : 500;
245
+ }
246
+ function shadowCameraOrthoBounds(camera, light) {
247
+ const left = optionalFiniteNumber(camera.left, 'light.shadow.camera.left');
248
+ const right = optionalFiniteNumber(camera.right, 'light.shadow.camera.right');
249
+ const top = optionalFiniteNumber(camera.top, 'light.shadow.camera.top');
250
+ const bottom = optionalFiniteNumber(camera.bottom, 'light.shadow.camera.bottom');
251
+ if (light.lightType === 'directional') {
252
+ const effectiveLeft = left ?? -5;
253
+ const effectiveRight = right ?? 5;
254
+ const effectiveTop = top ?? 5;
255
+ const effectiveBottom = bottom ?? -5;
256
+ if (effectiveRight <= effectiveLeft) {
257
+ if (right !== undefined) {
258
+ throw new TypeError('light.shadow.camera.right must be greater than light.shadow.camera.left.');
259
+ }
260
+ throw new TypeError('light.shadow.camera.left must be less than the effective light.shadow.camera.right.');
261
+ }
262
+ if (effectiveTop <= effectiveBottom) {
263
+ if (top !== undefined) {
264
+ throw new TypeError('light.shadow.camera.top must be greater than light.shadow.camera.bottom.');
265
+ }
266
+ throw new TypeError('light.shadow.camera.bottom must be less than the effective light.shadow.camera.top.');
267
+ }
268
+ }
269
+ return {
270
+ shadowCameraLeft: left,
271
+ shadowCameraRight: right,
272
+ shadowCameraTop: top,
273
+ shadowCameraBottom: bottom,
274
+ };
275
+ }
276
+ function shadowMapSizeOrDefault(mapSize, light) {
277
+ if (mapSize != null) {
278
+ assertPlainObject(mapSize, 'light.shadow.mapSize');
279
+ }
280
+ const width = shadowMapSizeComponent(mapSize, 'x', 'width');
281
+ const height = shadowMapSizeComponent(mapSize, 'y', 'height');
282
+ const resolvedWidth = Math.max(32, Math.floor(width ?? height ?? 512));
283
+ const resolvedHeight = Math.max(32, Math.floor(height ?? width ?? 512));
284
+ if (light.isPointLight === true && resolvedWidth !== resolvedHeight) {
285
+ throw new Error(`Non-square PointLight shadow map sizes are not supported by @headless-three/renderer yet (${resolvedWidth}x${resolvedHeight}). Use square point-light shadow maps until rectangular cube-face shadows are supported.`);
286
+ }
287
+ return { width: resolvedWidth, height: resolvedHeight };
288
+ }
289
+ function assertShadowContainerLike(shadow) {
290
+ if (shadow == null)
291
+ return;
292
+ assertPlainObject(shadow, 'light.shadow');
293
+ }
294
+ function validateShadowObjectState(value, label) {
295
+ if (value == null)
296
+ return;
297
+ assertPlainObject(value, label);
298
+ }
299
+ function assertPlainObject(value, label) {
300
+ if (value !== null && typeof value === 'object' && !Array.isArray(value))
301
+ return;
302
+ throw new TypeError(`${label} must be an object.`);
303
+ }
304
+ function shadowMapSizeComponent(mapSize, vectorKey, dimensionKey) {
305
+ if (!mapSize)
306
+ return undefined;
307
+ const vectorValue = mapSize[vectorKey];
308
+ if (vectorValue != null)
309
+ return optionalPositiveFiniteNumber(vectorValue, `light.shadow.mapSize.${vectorKey}`);
310
+ return optionalPositiveFiniteNumber(mapSize[dimensionKey], `light.shadow.mapSize.${dimensionKey}`);
311
+ }
312
+ function applyShadowCascadeOptions(out, light) {
313
+ const cascadeHints = shadowCascadeHints(light);
314
+ if (!cascadeHints)
315
+ return;
316
+ const { value: cascades, label } = cascadeHints;
317
+ if (!Array.isArray(cascades)) {
318
+ throw new TypeError(`${label} must be an array of shadow cascade hint objects.`);
319
+ }
320
+ if (cascades.length < 2)
321
+ return;
322
+ const splits = [];
323
+ const bounds = [];
324
+ for (let i = 0; i < cascades.length; i += 1) {
325
+ const rawCascade = cascades[i];
326
+ if (!rawCascade || typeof rawCascade !== 'object') {
327
+ throw new TypeError(`shadowCascades[${i}] must be an object with finite left, right, top, bottom, near, and far values.`);
328
+ }
329
+ const cascade = rawCascade;
330
+ const label = `shadowCascades[${i}]`;
331
+ const left = requiredFiniteNumber(cascade.left, `${label}.left`);
332
+ const right = requiredFiniteNumber(cascade.right, `${label}.right`);
333
+ const top = requiredFiniteNumber(cascade.top, `${label}.top`);
334
+ const bottom = requiredFiniteNumber(cascade.bottom, `${label}.bottom`);
335
+ const near = requiredFiniteNumber(cascade.near, `${label}.near`);
336
+ const far = requiredFiniteNumber(cascade.far, `${label}.far`);
337
+ if (bounds.length / 6 >= MAX_SHADOW_CASCADES) {
338
+ throw new Error(`Directional shadow cascade hints support at most ${MAX_SHADOW_CASCADES} valid cascades in @headless-three/renderer. Reduce light.userData.headlessThreeRenderer.shadowCascades or render separate shadow passes.`);
339
+ }
340
+ bounds.push(left, right, top, bottom, near, far);
341
+ const split = shadowCascadeSplit(cascade, label);
342
+ if (split !== undefined)
343
+ splits.push(split);
344
+ }
345
+ const count = bounds.length / 6;
346
+ if (count >= 2) {
347
+ out.shadowCascadeBounds = bounds;
348
+ out.shadowCascadeSplits = splits.slice(0, count - 1);
349
+ }
350
+ }
351
+ function shadowCascadeHints(light) {
352
+ const userData = light.userData;
353
+ if (userData != null && (typeof userData !== 'object' || Array.isArray(userData))) {
354
+ throw new TypeError('light.userData must be an object.');
355
+ }
356
+ const modernHints = userData?.headlessThreeRenderer;
357
+ if (modernHints != null) {
358
+ assertPlainObject(modernHints, 'light.userData.headlessThreeRenderer');
359
+ if (modernHints.shadowCascades != null) {
360
+ return {
361
+ value: modernHints.shadowCascades,
362
+ label: 'light.userData.headlessThreeRenderer.shadowCascades',
363
+ };
364
+ }
365
+ if (modernHints.cascades != null) {
366
+ return {
367
+ value: modernHints.cascades,
368
+ label: 'light.userData.headlessThreeRenderer.cascades',
369
+ };
370
+ }
371
+ }
372
+ const legacyHints = userData?.headlessRenderer;
373
+ if (legacyHints != null) {
374
+ assertPlainObject(legacyHints, 'light.userData.headlessRenderer');
375
+ if (legacyHints.shadowCascades != null) {
376
+ return {
377
+ value: legacyHints.shadowCascades,
378
+ label: 'light.userData.headlessRenderer.shadowCascades',
379
+ };
380
+ }
381
+ if (legacyHints.cascades != null) {
382
+ return {
383
+ value: legacyHints.cascades,
384
+ label: 'light.userData.headlessRenderer.cascades',
385
+ };
386
+ }
387
+ }
388
+ const shadowCascades = light.shadow?.cascades;
389
+ return shadowCascades != null
390
+ ? { value: shadowCascades, label: 'light.shadow.cascades' }
391
+ : null;
392
+ }
393
+ function shadowCascadeSplit(cascade, label) {
394
+ if (cascade.split != null)
395
+ return optionalFiniteNumber(cascade.split, `${label}.split`);
396
+ if (cascade.distance != null)
397
+ return optionalFiniteNumber(cascade.distance, `${label}.distance`);
398
+ return optionalFiniteNumber(cascade.farDistance, `${label}.farDistance`);
399
+ }
400
+ function finiteNumberOrDefault(value, label, fallback) {
401
+ if (value == null)
402
+ return fallback;
403
+ if (typeof value === 'number' && Number.isFinite(value))
404
+ return value;
405
+ throw new TypeError(`${label} must be a finite number.`);
406
+ }
407
+ function nonNegativeFiniteNumberOrDefault(value, label, fallback) {
408
+ const number = finiteNumberOrDefault(value, label, fallback);
409
+ if (number < 0) {
410
+ throw new TypeError(`${label} must be non-negative.`);
411
+ }
412
+ return number;
413
+ }
414
+ function positiveFiniteNumberOrDefault(value, label, fallback) {
415
+ const number = finiteNumberOrDefault(value, label, fallback);
416
+ if (number <= 0) {
417
+ throw new TypeError(`${label} must be positive.`);
418
+ }
419
+ return number;
420
+ }
421
+ function normalizedFiniteNumberOrDefault(value, label, fallback) {
422
+ const number = finiteNumberOrDefault(value, label, fallback);
423
+ if (number < 0 || number > 1) {
424
+ throw new TypeError(`${label} must be between 0 and 1.`);
425
+ }
426
+ return number;
427
+ }
428
+ function spotAngleOrDefault(value) {
429
+ const angle = finiteNumberOrDefault(value, 'SpotLight.angle', Math.PI / 3);
430
+ if (angle < 0 || angle > Math.PI / 2) {
431
+ throw new TypeError('SpotLight.angle must be between 0 and Math.PI / 2.');
432
+ }
433
+ return angle;
434
+ }
435
+ function optionalFiniteNumber(value, label) {
436
+ if (value == null)
437
+ return undefined;
438
+ if (typeof value === 'number' && Number.isFinite(value))
439
+ return value;
440
+ throw new TypeError(`${label} must be a finite number.`);
441
+ }
442
+ function optionalNonNegativeFiniteNumber(value, label) {
443
+ const number = optionalFiniteNumber(value, label);
444
+ if (number === undefined)
445
+ return undefined;
446
+ if (number < 0) {
447
+ throw new TypeError(`${label} must be non-negative.`);
448
+ }
449
+ return number;
450
+ }
451
+ function optionalBoolean(value, label) {
452
+ if (value == null)
453
+ return undefined;
454
+ if (typeof value === 'boolean')
455
+ return value;
456
+ throw new TypeError(`${label} must be a boolean.`);
457
+ }
458
+ function optionalPositiveFiniteNumber(value, label) {
459
+ const number = optionalFiniteNumber(value, label);
460
+ if (number === undefined)
461
+ return undefined;
462
+ if (number <= 0) {
463
+ throw new TypeError(`${label} must be positive.`);
464
+ }
465
+ return number;
466
+ }
467
+ function requiredFiniteNumber(value, label) {
468
+ if (typeof value === 'number' && Number.isFinite(value))
469
+ return value;
470
+ throw new TypeError(`${label} must be a finite number.`);
471
+ }
472
+ function assertSupportedLightCount(lights) {
473
+ if (lights.length > MAX_NATIVE_LIGHTS) {
474
+ throw new Error(`More than ${MAX_NATIVE_LIGHTS} visible non-ambient lights are not supported by @headless-three/renderer yet (${lights.length} found). Keep the closest/brightest ${MAX_NATIVE_LIGHTS} lights, bake lighting, or render separate passes until native light arrays are expanded.`);
475
+ }
476
+ }
477
+ function extractAmbientLight(scene, camera) {
153
478
  let color = null;
154
- visitForAmbient(scene, (light) => {
155
- const c = (0, color_1.colorLikeToArray)(light.color) ?? [1, 1, 1, 1];
479
+ visitForAmbient(scene, camera, (light) => {
480
+ const c = (0, color_1.validatedColorLikeToArray)(light.color, 'AmbientLight.color') ?? [1, 1, 1, 1];
156
481
  if (!color) {
157
482
  color = [c[0], c[1], c[2]];
158
483
  }
@@ -164,23 +489,94 @@ function extractAmbientLight(scene) {
164
489
  });
165
490
  return color;
166
491
  }
167
- function extractAmbientIntensity(scene) {
492
+ function extractAmbientIntensity(scene, camera) {
168
493
  let intensity = 0;
169
- visitForAmbient(scene, (light) => {
170
- intensity += Number.isFinite(light.intensity) ? light.intensity : 1;
494
+ visitForAmbient(scene, camera, (light) => {
495
+ intensity += finiteNumberOrDefault(light.intensity, 'AmbientLight.intensity', 1);
171
496
  });
172
497
  return intensity > 0 ? intensity : undefined;
173
498
  }
174
- function visitForAmbient(object, callback) {
499
+ function extractLightProbe(scene, camera) {
500
+ const coefficients = new Array(27).fill(0);
501
+ let found = false;
502
+ visitForLightProbe(scene, camera, (light) => {
503
+ const source = light.sh?.coefficients;
504
+ if (source == null)
505
+ return;
506
+ if (!Array.isArray(source)) {
507
+ throw new TypeError('LightProbe.sh.coefficients must be an array of 9 coefficients.');
508
+ }
509
+ if (source.length < 9) {
510
+ throw new TypeError('LightProbe.sh.coefficients must contain 9 coefficients.');
511
+ }
512
+ const intensity = finiteNumberOrDefault(light.intensity, 'LightProbe.intensity', 1);
513
+ for (let i = 0; i < 9; i += 1) {
514
+ const coefficient = coefficientToRgb(source[i], `LightProbe.sh.coefficients[${i}]`);
515
+ if (!coefficient)
516
+ continue;
517
+ coefficients[i * 3] += coefficient[0] * intensity;
518
+ coefficients[i * 3 + 1] += coefficient[1] * intensity;
519
+ coefficients[i * 3 + 2] += coefficient[2] * intensity;
520
+ }
521
+ found = true;
522
+ });
523
+ return found ? coefficients : null;
524
+ }
525
+ function visitForLightProbe(object, camera, callback) {
526
+ if (!object)
527
+ return;
528
+ if (optionalBoolean(object.visible, 'object.visible') === false)
529
+ return;
530
+ if (object.isLightProbe === true && (0, layers_1.objectLayersMatchCamera)(object, camera))
531
+ callback(object);
532
+ for (const child of (0, objects_1.objectChildren)(object)) {
533
+ visitForLightProbe(child, camera, callback);
534
+ }
535
+ }
536
+ function coefficientToRgb(value, label) {
537
+ if (!value)
538
+ return null;
539
+ const maybeArrayLike = value;
540
+ if (Array.isArray(value) || (ArrayBuffer.isView(value) && typeof maybeArrayLike.length === 'number')) {
541
+ const array = value;
542
+ return [
543
+ requiredFiniteRgbComponent(array[0], `${label}[0]`),
544
+ requiredFiniteRgbComponent(array[1], `${label}[1]`),
545
+ requiredFiniteRgbComponent(array[2], `${label}[2]`),
546
+ ];
547
+ }
548
+ const v = value;
549
+ if ('r' in v || 'g' in v || 'b' in v) {
550
+ return [
551
+ requiredFiniteRgbComponent(v.r, `${label}.r`),
552
+ requiredFiniteRgbComponent(v.g, `${label}.g`),
553
+ requiredFiniteRgbComponent(v.b, `${label}.b`),
554
+ ];
555
+ }
556
+ if ('x' in v || 'y' in v || 'z' in v) {
557
+ return [
558
+ requiredFiniteRgbComponent(v.x, `${label}.x`),
559
+ requiredFiniteRgbComponent(v.y, `${label}.y`),
560
+ requiredFiniteRgbComponent(v.z, `${label}.z`),
561
+ ];
562
+ }
563
+ return null;
564
+ }
565
+ function requiredFiniteRgbComponent(value, label) {
566
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
567
+ throw new TypeError(`${label} must be a finite number.`);
568
+ }
569
+ return value;
570
+ }
571
+ function visitForAmbient(object, camera, callback) {
175
572
  if (!object)
176
573
  return;
177
- if (object.visible === false)
574
+ if (optionalBoolean(object.visible, 'object.visible') === false)
178
575
  return;
179
- if (object.isAmbientLight === true)
576
+ if (object.isAmbientLight === true && (0, layers_1.objectLayersMatchCamera)(object, camera))
180
577
  callback(object);
181
- const children = Array.isArray(object.children) ? object.children : [];
182
- for (const child of children) {
183
- visitForAmbient(child, callback);
578
+ for (const child of (0, objects_1.objectChildren)(object)) {
579
+ visitForAmbient(child, camera, callback);
184
580
  }
185
581
  }
186
582
  //# sourceMappingURL=lights.js.map