three-zoo 0.4.1 → 0.4.2

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
@@ -1,51 +1,254 @@
1
- import { Box3, Vector3, PerspectiveCamera, MathUtils, Mesh, InstancedMesh, FrontSide, BufferAttribute, AnimationMixer, DirectionalLight, Spherical, RGBAFormat } from 'three';
1
+ import { PerspectiveCamera, MathUtils, Box3, Vector3, Mesh, InstancedMesh, FrontSide, BufferAttribute, AnimationMixer, DirectionalLight, Spherical, RGBAFormat } from 'three';
2
+
3
+ /**
4
+ * Default camera settings
5
+ */
6
+ const DEFAULT_HORIZONTAL_FOV = 90;
7
+ const DEFAULT_VERTICAL_FOV = 90;
8
+ const DEFAULT_ASPECT = 1;
9
+ const DEFAULT_NEAR = 1;
10
+ const DEFAULT_FAR = 1000;
11
+ /**
12
+ * BiFovCamera - A specialized PerspectiveCamera that supports independent horizontal and vertical FOV settings
13
+ *
14
+ * This camera extends Three.js PerspectiveCamera to provide better control over the field of view,
15
+ * allowing separate horizontal and vertical FOV values. The camera automatically adjusts its projection
16
+ * matrix based on the aspect ratio to maintain proper perspective.
17
+ *
18
+ * @extends PerspectiveCamera
19
+ */
20
+ class BiFovCamera extends PerspectiveCamera {
21
+ /**
22
+ * Creates a new BiFovCamera instance
23
+ *
24
+ * @param horizontalFov - Horizontal field of view in degrees (default: 90)
25
+ * @param verticalFov - Vertical field of view in degrees (default: 90)
26
+ * @param aspect - Aspect ratio (width/height) of the viewport (default: 1)
27
+ * @param near - Near clipping plane distance (default: 1)
28
+ * @param far - Far clipping plane distance (default: 1000)
29
+ */
30
+ constructor(horizontalFov = DEFAULT_HORIZONTAL_FOV, verticalFov = DEFAULT_VERTICAL_FOV, aspect = DEFAULT_ASPECT, near = DEFAULT_NEAR, far = DEFAULT_FAR) {
31
+ super(verticalFov, aspect, near, far);
32
+ this.horizontalFovInternal = horizontalFov;
33
+ this.verticalFovInternal = verticalFov;
34
+ this.updateProjectionMatrix();
35
+ }
36
+ /**
37
+ * Gets the horizontal field of view in degrees
38
+ */
39
+ get horizontalFov() {
40
+ return this.horizontalFovInternal;
41
+ }
42
+ /**
43
+ * Gets the vertical field of view in degrees
44
+ */
45
+ get verticalFov() {
46
+ return this.verticalFovInternal;
47
+ }
48
+ /**
49
+ * Sets the horizontal field of view in degrees
50
+ * @param value - The new horizontal FOV value
51
+ */
52
+ set horizontalFov(value) {
53
+ this.horizontalFovInternal = MathUtils.clamp(value, 1, 179);
54
+ this.updateProjectionMatrix();
55
+ }
56
+ /**
57
+ * Sets the vertical field of view in degrees
58
+ * @param value - The new vertical FOV value
59
+ */
60
+ set verticalFov(value) {
61
+ this.verticalFovInternal = MathUtils.clamp(value, 1, 179);
62
+ this.updateProjectionMatrix();
63
+ }
64
+ /**
65
+ * Updates both horizontal and vertical FOV simultaneously
66
+ * @param horizontal - New horizontal FOV in degrees
67
+ * @param vertical - New vertical FOV in degrees
68
+ */
69
+ setFov(horizontal, vertical) {
70
+ this.horizontalFovInternal = MathUtils.clamp(horizontal, 1, 179);
71
+ this.verticalFovInternal = MathUtils.clamp(vertical, 1, 179);
72
+ this.updateProjectionMatrix();
73
+ }
74
+ /**
75
+ * Copies FOV settings from another BiFovCamera
76
+ * @param source - The camera to copy settings from
77
+ */
78
+ copyFovSettings(source) {
79
+ this.horizontalFovInternal = source.horizontalFov;
80
+ this.verticalFovInternal = source.verticalFov;
81
+ this.updateProjectionMatrix();
82
+ }
83
+ /**
84
+ * Updates the projection matrix based on current FOV settings and aspect ratio
85
+ * For aspect ratios >= 1 (landscape), horizontal FOV is preserved
86
+ * For aspect ratios < 1 (portrait), vertical FOV is preserved
87
+ */
88
+ updateProjectionMatrix() {
89
+ if (this.aspect >= 1) {
90
+ // Landscape orientation: preserve horizontal FOV
91
+ const radians = MathUtils.degToRad(this.horizontalFovInternal);
92
+ this.fov = MathUtils.radToDeg(Math.atan(Math.tan(radians / 2) / this.aspect) * 2);
93
+ }
94
+ else {
95
+ // Portrait orientation: preserve vertical FOV
96
+ this.fov = this.verticalFovInternal;
97
+ }
98
+ super.updateProjectionMatrix();
99
+ }
100
+ /**
101
+ * Returns the actual horizontal FOV after aspect ratio adjustments
102
+ */
103
+ getEffectiveHorizontalFov() {
104
+ if (this.aspect >= 1) {
105
+ return this.horizontalFovInternal;
106
+ }
107
+ const verticalRadians = MathUtils.degToRad(this.verticalFovInternal);
108
+ return MathUtils.radToDeg(Math.atan(Math.tan(verticalRadians / 2) * this.aspect) * 2);
109
+ }
110
+ /**
111
+ * Returns the actual vertical FOV after aspect ratio adjustments
112
+ */
113
+ getEffectiveVerticalFov() {
114
+ if (this.aspect < 1) {
115
+ return this.verticalFovInternal;
116
+ }
117
+ const horizontalRadians = MathUtils.degToRad(this.horizontalFovInternal);
118
+ return MathUtils.radToDeg(Math.atan(Math.tan(horizontalRadians / 2) / this.aspect) * 2);
119
+ }
120
+ /**
121
+ * Creates a clone of this camera with the same properties
122
+ */
123
+ clone() {
124
+ const camera = new BiFovCamera(this.horizontalFovInternal, this.verticalFovInternal, this.aspect, this.near, this.far);
125
+ camera.copy(this, true);
126
+ return camera;
127
+ }
128
+ }
2
129
 
3
130
  class Bounds extends Box3 {
4
131
  constructor() {
5
132
  super(...arguments);
6
- this.tempVector3 = new Vector3();
133
+ this.tempVector3A = new Vector3();
7
134
  }
135
+ /**
136
+ * Gets the width (x-axis length) of the bounding box
137
+ */
8
138
  get width() {
9
139
  return this.max.x - this.min.x;
10
140
  }
141
+ /**
142
+ * Gets the height (y-axis length) of the bounding box
143
+ */
11
144
  get height() {
12
145
  return this.max.y - this.min.y;
13
146
  }
147
+ /**
148
+ * Gets the depth (z-axis length) of the bounding box
149
+ */
14
150
  get depth() {
15
151
  return this.max.z - this.min.z;
16
152
  }
153
+ /**
154
+ * Gets the length of the box's diagonal
155
+ */
17
156
  get diagonal() {
18
- this.tempVector3.subVectors(this.max, this.min);
19
- return this.tempVector3.length();
157
+ return this.tempVector3A.subVectors(this.max, this.min).length();
158
+ }
159
+ /**
160
+ * Gets the volume of the bounding box
161
+ */
162
+ getVolume() {
163
+ return this.width * this.height * this.depth;
164
+ }
165
+ /**
166
+ * Gets the surface area of the bounding box
167
+ */
168
+ getSurfaceArea() {
169
+ const w = this.width;
170
+ const h = this.height;
171
+ const d = this.depth;
172
+ return 2 * (w * h + h * d + d * w);
20
173
  }
21
174
  }
22
175
 
23
- class DoubleFOVCamera extends PerspectiveCamera {
24
- constructor(horizontalFov = 90, verticalFov = 90, aspect = 1, near = 1, far = 1000) {
25
- super(verticalFov, aspect, near, far);
26
- this.horizontalFov = horizontalFov;
27
- this.verticalFov = verticalFov;
176
+ /**
177
+ * Utility class for comparing and hashing BufferGeometry instances with tolerance support.
178
+ */
179
+ class GeometryHasher {
180
+ /**
181
+ * Generates a consistent hash for a BufferGeometry based on its contents and tolerance.
182
+ *
183
+ * @param geometry - The geometry to hash
184
+ * @param tolerance - Precision level for number comparison (values within tolerance are considered equal)
185
+ * @returns A string hash that will be identical for geometrically equivalent geometries
186
+ */
187
+ static getGeometryHash(geometry, tolerance = 1e-6) {
188
+ const hashParts = [];
189
+ // Process attributes
190
+ const attributes = geometry.attributes;
191
+ const attributeNames = Object.keys(attributes).sort(); // Sort for consistent order
192
+ for (const name of attributeNames) {
193
+ const attribute = attributes[name];
194
+ hashParts.push(`${name}:${attribute.itemSize}:${this.getAttributeHash(attribute, tolerance)}`);
195
+ }
196
+ // Process index if present
197
+ if (geometry.index) {
198
+ hashParts.push(`index:${this.getAttributeHash(geometry.index, tolerance)}`);
199
+ }
200
+ return hashParts.join("|");
28
201
  }
29
- updateProjectionMatrix() {
30
- if (this.aspect >= 1) {
31
- const radians = MathUtils.degToRad(this.horizontalFov);
32
- this.fov = MathUtils.radToDeg(Math.atan(Math.tan(radians / 2) / this.aspect) * 2);
202
+ /**
203
+ * Compares two BufferGeometry instances for approximate equality.
204
+ * Early exit if UUIDs match (same object or cloned geometry).
205
+ */
206
+ static compare(firstGeometry, secondGeometry, tolerance = 1e-6) {
207
+ if (firstGeometry.uuid === secondGeometry.uuid) {
208
+ return true;
33
209
  }
34
- else {
35
- this.fov = this.verticalFov;
210
+ // Use hash comparison for consistent results
211
+ return (this.getGeometryHash(firstGeometry, tolerance) ===
212
+ this.getGeometryHash(secondGeometry, tolerance));
213
+ }
214
+ /**
215
+ * Generates a hash for a buffer attribute with tolerance.
216
+ */
217
+ static getAttributeHash(attribute, tolerance) {
218
+ const array = attribute.array;
219
+ const itemSize = "itemSize" in attribute ? attribute.itemSize : 1;
220
+ const hashParts = [];
221
+ // Group values by their "tolerance buckets"
222
+ for (let i = 0; i < array.length; i += itemSize) {
223
+ const itemValues = [];
224
+ for (let j = 0; j < itemSize; j++) {
225
+ const val = array[i + j];
226
+ // Round to nearest tolerance multiple to group similar values
227
+ itemValues.push(Math.round(val / tolerance) * tolerance);
228
+ }
229
+ hashParts.push(itemValues.join(","));
36
230
  }
37
- super.updateProjectionMatrix();
231
+ return hashParts.join(";");
232
+ }
233
+ /**
234
+ * Compares two buffer attributes with tolerance.
235
+ */
236
+ static compareBufferAttributes(firstAttribute, secondAttribute, tolerance) {
237
+ return (this.getAttributeHash(firstAttribute, tolerance) ===
238
+ this.getAttributeHash(secondAttribute, tolerance));
38
239
  }
39
240
  }
40
241
 
41
- class Enumerator {
242
+ class SceneTraversal {
42
243
  static getObjectByName(object, name) {
43
- if (object.name === name)
244
+ if (object.name === name) {
44
245
  return object;
246
+ }
45
247
  for (const child of object.children) {
46
- const result = Enumerator.getObjectByName(child, name);
47
- if (result)
248
+ const result = SceneTraversal.getObjectByName(child, name);
249
+ if (result) {
48
250
  return result;
251
+ }
49
252
  }
50
253
  return null;
51
254
  }
@@ -53,8 +256,9 @@ class Enumerator {
53
256
  if (object instanceof Mesh) {
54
257
  if (Array.isArray(object.material)) {
55
258
  for (const material of object.material) {
56
- if (material.name === name)
259
+ if (material.name === name) {
57
260
  return material;
261
+ }
58
262
  }
59
263
  }
60
264
  else if (object.material.name === name) {
@@ -62,9 +266,10 @@ class Enumerator {
62
266
  }
63
267
  }
64
268
  for (const child of object.children) {
65
- const material = Enumerator.getMaterialByName(child, name);
66
- if (material)
269
+ const material = SceneTraversal.getMaterialByName(child, name);
270
+ if (material) {
67
271
  return material;
272
+ }
68
273
  }
69
274
  return null;
70
275
  }
@@ -73,7 +278,7 @@ class Enumerator {
73
278
  callback(object);
74
279
  }
75
280
  for (const child of object.children) {
76
- Enumerator.enumerateObjectsByType(child, type, callback);
281
+ SceneTraversal.enumerateObjectsByType(child, type, callback);
77
282
  }
78
283
  }
79
284
  static enumerateMaterials(object, callback) {
@@ -88,7 +293,7 @@ class Enumerator {
88
293
  }
89
294
  }
90
295
  for (const child of object.children) {
91
- Enumerator.enumerateMaterials(child, callback);
296
+ SceneTraversal.enumerateMaterials(child, callback);
92
297
  }
93
298
  }
94
299
  static filterObjects(object, name) {
@@ -97,7 +302,7 @@ class Enumerator {
97
302
  result.push(object);
98
303
  }
99
304
  for (const child of object.children) {
100
- result = result.concat(Enumerator.filterObjects(child, name));
305
+ result = result.concat(SceneTraversal.filterObjects(child, name));
101
306
  }
102
307
  return result;
103
308
  }
@@ -118,7 +323,7 @@ class Enumerator {
118
323
  }
119
324
  }
120
325
  for (const child of object.children) {
121
- result = result.concat(Enumerator.filterMaterials(child, name));
326
+ result = result.concat(SceneTraversal.filterMaterials(child, name));
122
327
  }
123
328
  return result;
124
329
  }
@@ -128,176 +333,11 @@ class Enumerator {
128
333
  object.receiveShadow = receiveShadow;
129
334
  }
130
335
  for (const child of object.children) {
131
- Enumerator.setShadowRecursive(child, castShadow, receiveShadow);
336
+ SceneTraversal.setShadowRecursive(child, castShadow, receiveShadow);
132
337
  }
133
338
  }
134
339
  }
135
340
 
136
- /**
137
- * Utility class for comparing and hashing BufferGeometry instances with tolerance support.
138
- */
139
- class GeometryComparator {
140
- /**
141
- * Generates a consistent hash for a BufferGeometry based on its contents and tolerance.
142
- *
143
- * @param geometry - The geometry to hash
144
- * @param tolerance - Precision level for number comparison (values within tolerance are considered equal)
145
- * @returns A string hash that will be identical for geometrically equivalent geometries
146
- */
147
- static getGeometryHash(geometry, tolerance = 1e-6) {
148
- const hashParts = [];
149
- // Process attributes
150
- const attributes = geometry.attributes;
151
- const attributeNames = Object.keys(attributes).sort(); // Sort for consistent order
152
- for (const name of attributeNames) {
153
- const attribute = attributes[name];
154
- hashParts.push(`${name}:${attribute.itemSize}:${this.getAttributeHash(attribute, tolerance)}`);
155
- }
156
- // Process index if present
157
- if (geometry.index) {
158
- hashParts.push(`index:${this.getAttributeHash(geometry.index, tolerance)}`);
159
- }
160
- return hashParts.join("|");
161
- }
162
- /**
163
- * Compares two BufferGeometry instances for approximate equality.
164
- * Early exit if UUIDs match (same object or cloned geometry).
165
- */
166
- static compare(firstGeometry, secondGeometry, tolerance = 1e-6) {
167
- if (firstGeometry.uuid === secondGeometry.uuid) {
168
- return true;
169
- }
170
- // Use hash comparison for consistent results
171
- return (this.getGeometryHash(firstGeometry, tolerance) ===
172
- this.getGeometryHash(secondGeometry, tolerance));
173
- }
174
- /**
175
- * Generates a hash for a buffer attribute with tolerance.
176
- */
177
- static getAttributeHash(attribute, tolerance) {
178
- const array = attribute.array;
179
- const itemSize = "itemSize" in attribute ? attribute.itemSize : 1;
180
- const hashParts = [];
181
- // Group values by their "tolerance buckets"
182
- for (let i = 0; i < array.length; i += itemSize) {
183
- const itemValues = [];
184
- for (let j = 0; j < itemSize; j++) {
185
- const val = array[i + j];
186
- // Round to nearest tolerance multiple to group similar values
187
- itemValues.push(Math.round(val / tolerance) * tolerance);
188
- }
189
- hashParts.push(itemValues.join(","));
190
- }
191
- return hashParts.join(";");
192
- }
193
- /**
194
- * Compares two buffer attributes with tolerance.
195
- */
196
- static compareBufferAttributes(firstAttribute, secondAttribute, tolerance) {
197
- return (this.getAttributeHash(firstAttribute, tolerance) ===
198
- this.getAttributeHash(secondAttribute, tolerance));
199
- }
200
- }
201
- // import {
202
- // BufferAttribute,
203
- // BufferGeometry,
204
- // InterleavedBufferAttribute,
205
- // } from "three";
206
- // type AnySuitableAttribute = BufferAttribute | InterleavedBufferAttribute;
207
- // /**
208
- // * Utility class for comparing two BufferGeometry instances with tolerance support.
209
- // * Checks geometry attributes (positions, normals, UVs, etc.) and indices (if present).
210
- // */
211
- // export class GeometryComparator {
212
- // /**
213
- // * Compares two BufferGeometry instances for approximate equality.
214
- // * Early exit if UUIDs match (same object or cloned geometry).
215
- // *
216
- // * @param firstGeometry - The first geometry to compare.
217
- // * @param secondGeometry - The second geometry to compare.
218
- // * @param tolerance - Maximum allowed difference between numeric values (default: 1e-6).
219
- // * @returns `true` if geometries are equivalent within tolerance, otherwise `false`.
220
- // */
221
- // public static compare(
222
- // firstGeometry: BufferGeometry,
223
- // secondGeometry: BufferGeometry,
224
- // tolerance = 1e-6,
225
- // ): boolean {
226
- // if (firstGeometry.uuid === secondGeometry.uuid) {
227
- // return true;
228
- // }
229
- // const firstAttributes = firstGeometry.attributes;
230
- // const secondAttributes = secondGeometry.attributes;
231
- // const firstAttributeNames = Object.keys(firstAttributes);
232
- // const secondAttributeNames = Object.keys(secondAttributes);
233
- // if (firstAttributeNames.length !== secondAttributeNames.length) {
234
- // return false;
235
- // }
236
- // for (const attributeName of firstAttributeNames) {
237
- // if (!secondAttributes[attributeName]) {
238
- // return false;
239
- // }
240
- // const firstAttribute = firstAttributes[
241
- // attributeName
242
- // ] as AnySuitableAttribute;
243
- // const secondAttribute = secondAttributes[
244
- // attributeName
245
- // ] as AnySuitableAttribute;
246
- // if (
247
- // firstAttribute.count !== secondAttribute.count ||
248
- // firstAttribute.itemSize !== secondAttribute.itemSize ||
249
- // !this.compareBufferAttributes(
250
- // firstAttribute,
251
- // secondAttribute,
252
- // tolerance,
253
- // )
254
- // ) {
255
- // return false;
256
- // }
257
- // }
258
- // if (firstGeometry.index || secondGeometry.index) {
259
- // if (!firstGeometry.index || !secondGeometry.index) {
260
- // return false;
261
- // }
262
- // if (
263
- // !this.compareBufferAttributes(
264
- // firstGeometry.index,
265
- // secondGeometry.index,
266
- // tolerance,
267
- // )
268
- // ) {
269
- // return false;
270
- // }
271
- // }
272
- // return true;
273
- // }
274
- // /**
275
- // * Compares two buffer attributes (or index buffers) with tolerance.
276
- // *
277
- // * @param firstAttribute - First attribute/indices to compare.
278
- // * @param secondAttribute - Second attribute/indices to compare.
279
- // * @param tolerance - Maximum allowed difference between array elements.
280
- // * @returns `true` if arrays are equal within tolerance, otherwise `false`.
281
- // */
282
- // private static compareBufferAttributes(
283
- // firstAttribute: AnySuitableAttribute,
284
- // secondAttribute: AnySuitableAttribute,
285
- // tolerance: number,
286
- // ): boolean {
287
- // const firstArray = firstAttribute.array;
288
- // const secondArray = secondAttribute.array;
289
- // if (firstArray.length !== secondArray.length) {
290
- // return false;
291
- // }
292
- // for (let index = 0; index < firstArray.length; index++) {
293
- // if (Math.abs(firstArray[index] - secondArray[index]) > tolerance) {
294
- // return false;
295
- // }
296
- // }
297
- // return true;
298
- // }
299
- // }
300
-
301
341
  class InstanceAssembler {
302
342
  static assemble(options) {
303
343
  var _a, _b;
@@ -305,7 +345,7 @@ class InstanceAssembler {
305
345
  const instancedMeshes = [];
306
346
  const tolerance = (_a = options.geometryTolerance) !== null && _a !== void 0 ? _a : 1e-6;
307
347
  const geometryHashCache = new Map();
308
- Enumerator.enumerateObjectsByType(options.container, Mesh, (child) => {
348
+ SceneTraversal.enumerateObjectsByType(options.container, Mesh, (child) => {
309
349
  var _a;
310
350
  if (child.children.length === 0 &&
311
351
  (!options.filter || options.filter(child))) {
@@ -314,7 +354,7 @@ class InstanceAssembler {
314
354
  : [child.material];
315
355
  let geometryHash = geometryHashCache.get(child.geometry.uuid);
316
356
  if (!geometryHash) {
317
- geometryHash = GeometryComparator.getGeometryHash(child.geometry, tolerance);
357
+ geometryHash = GeometryHasher.getGeometryHash(child.geometry, tolerance);
318
358
  geometryHashCache.set(child.geometry.uuid, geometryHash);
319
359
  }
320
360
  const materialKey = materials.map((m) => m.uuid).join(",");
@@ -325,17 +365,20 @@ class InstanceAssembler {
325
365
  castShadow: false,
326
366
  receiveShadow: false,
327
367
  };
328
- if (child.castShadow)
368
+ if (child.castShadow) {
329
369
  entry.castShadow = true;
330
- if (child.receiveShadow)
370
+ }
371
+ if (child.receiveShadow) {
331
372
  entry.receiveShadow = true;
373
+ }
332
374
  entry.meshes.push(child);
333
375
  dictionary.set(compositeKey, entry);
334
376
  }
335
377
  });
336
378
  for (const descriptor of dictionary.values()) {
337
- if (descriptor.meshes.length < 2)
379
+ if (descriptor.meshes.length < 2) {
338
380
  continue;
381
+ }
339
382
  const { meshes, materials, castShadow, receiveShadow } = descriptor;
340
383
  const sortedMeshes = meshes.sort((a, b) => a.name.localeCompare(b.name));
341
384
  const defaultMesh = sortedMeshes[0];
@@ -365,14 +408,14 @@ class SceneProcessor {
365
408
  static process(options) {
366
409
  const container = options.asset.clone();
367
410
  InstanceAssembler.assemble({ container: container });
368
- Enumerator.enumerateMaterials(container, (material) => {
411
+ SceneTraversal.enumerateMaterials(container, (material) => {
369
412
  material.transparent = SceneProcessor.matchesAny(material.name, options.transparentMaterialNames);
370
413
  material.depthWrite = !SceneProcessor.matchesAny(material.name, options.noDepthWriteMaterialNames);
371
414
  material.side = FrontSide;
372
415
  material.forceSinglePass = true;
373
416
  material.depthTest = true;
374
417
  });
375
- Enumerator.enumerateObjectsByType(container, Mesh, (child) => {
418
+ SceneTraversal.enumerateObjectsByType(container, Mesh, (child) => {
376
419
  child.castShadow = SceneProcessor.matchesAny(child.name, options.castShadowMeshNames);
377
420
  child.receiveShadow = SceneProcessor.matchesAny(child.name, options.receiveShadowMeshNames);
378
421
  });
@@ -434,9 +477,22 @@ class SkinnedMeshBaker {
434
477
  }
435
478
  }
436
479
 
480
+ /**
481
+ * Sun extends Three.js DirectionalLight to provide a specialized light source that simulates
482
+ * sunlight with advanced positioning and shadow controls.
483
+ *
484
+ * Features:
485
+ * - Spherical coordinate control (distance, elevation, azimuth)
486
+ * - Automatic shadow map configuration based on bounding boxes
487
+ * - HDR environment map-based positioning
488
+ * - Efficient temporary vector management for calculations
489
+ *
490
+ * @extends DirectionalLight
491
+ */
437
492
  class Sun extends DirectionalLight {
438
493
  constructor() {
439
494
  super(...arguments);
495
+ // Temporary vectors for calculations to avoid garbage collection
440
496
  this.tempVector3D0 = new Vector3();
441
497
  this.tempVector3D1 = new Vector3();
442
498
  this.tempVector3D2 = new Vector3();
@@ -448,27 +504,57 @@ class Sun extends DirectionalLight {
448
504
  this.tempBox3 = new Box3();
449
505
  this.tempSpherical = new Spherical();
450
506
  }
507
+ /**
508
+ * Gets the distance of the sun from its target (radius in spherical coordinates)
509
+ * @returns The distance in world units
510
+ */
451
511
  get distance() {
452
512
  return this.position.length();
453
513
  }
514
+ /**
515
+ * Gets the elevation angle of the sun (phi in spherical coordinates)
516
+ * @returns The elevation in radians
517
+ */
454
518
  get elevation() {
455
519
  return this.tempSpherical.setFromVector3(this.position).phi;
456
520
  }
521
+ /**
522
+ * Gets the azimuth angle of the sun (theta in spherical coordinates)
523
+ * @returns The azimuth in radians
524
+ */
457
525
  get azimuth() {
458
526
  return this.tempSpherical.setFromVector3(this.position).theta;
459
527
  }
528
+ /**
529
+ * Sets the distance of the sun from its target while maintaining current angles
530
+ * @param value - The new distance in world units
531
+ */
460
532
  set distance(value) {
461
533
  this.tempSpherical.setFromVector3(this.position);
462
534
  this.position.setFromSphericalCoords(value, this.tempSpherical.phi, this.tempSpherical.theta);
463
535
  }
536
+ /**
537
+ * Sets the elevation angle of the sun while maintaining current distance and azimuth
538
+ * @param value - The new elevation in radians
539
+ */
464
540
  set elevation(value) {
465
541
  this.tempSpherical.setFromVector3(this.position);
466
542
  this.position.setFromSphericalCoords(this.tempSpherical.radius, value, this.tempSpherical.theta);
467
543
  }
544
+ /**
545
+ * Sets the azimuth angle of the sun while maintaining current distance and elevation
546
+ * @param value - The new azimuth in radians
547
+ */
468
548
  set azimuth(value) {
469
549
  this.tempSpherical.setFromVector3(this.position);
470
550
  this.position.setFromSphericalCoords(this.tempSpherical.radius, this.tempSpherical.phi, value);
471
551
  }
552
+ /**
553
+ * Configures the shadow camera's frustum to encompass the given bounding box
554
+ * This ensures that shadows are cast correctly for objects within the box
555
+ *
556
+ * @param box3 - The bounding box to configure shadows for
557
+ */
472
558
  setShadowMapFromBox3(box3) {
473
559
  const camera = this.shadow.camera;
474
560
  this.target.updateWorldMatrix(true, false);
@@ -498,23 +584,33 @@ class Sun extends DirectionalLight {
498
584
  camera.updateWorldMatrix(true, false);
499
585
  camera.updateProjectionMatrix();
500
586
  }
587
+ /**
588
+ * Sets the sun's direction based on the brightest point in an HDR texture
589
+ * This is useful for matching the sun's position to an environment map
590
+ *
591
+ * @param texture - The HDR texture to analyze (must be loaded and have valid image data)
592
+ * @param distance - Optional distance to position the sun from its target (default: 1)
593
+ */
501
594
  setDirectionFromHDR(texture, distance = 1) {
502
595
  const data = texture.image.data;
503
596
  const width = texture.image.width;
504
597
  const height = texture.image.height;
505
598
  let maxLuminance = 0;
506
599
  let maxIndex = 0;
600
+ // Find the brightest pixel in the HDR texture
507
601
  const step = texture.format === RGBAFormat ? 4 : 3;
508
602
  for (let i = 0; i < data.length; i += step) {
509
603
  const r = data[i];
510
604
  const g = data[i + 1];
511
605
  const b = data[i + 2];
606
+ // Calculate luminance using the Rec. 709 coefficients
512
607
  const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
513
608
  if (luminance > maxLuminance) {
514
609
  maxLuminance = luminance;
515
610
  maxIndex = i;
516
611
  }
517
612
  }
613
+ // Convert pixel coordinates to spherical coordinates
518
614
  const pixelIndex = maxIndex / step;
519
615
  const x = pixelIndex % width;
520
616
  const y = Math.floor(pixelIndex / width);
@@ -526,5 +622,5 @@ class Sun extends DirectionalLight {
526
622
  }
527
623
  }
528
624
 
529
- export { Bounds, DoubleFOVCamera, Enumerator, InstanceAssembler, SceneProcessor, SkinnedMeshBaker, Sun };
625
+ export { BiFovCamera, Bounds, InstanceAssembler, SceneProcessor, SceneTraversal, SkinnedMeshBaker, Sun };
530
626
  //# sourceMappingURL=index.js.map