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/BiFovCamera.d.ts +71 -0
- package/dist/Bounds.d.ts +21 -1
- package/dist/{GeometryComparator.d.ts → GeometryHasher.d.ts} +2 -2
- package/dist/InstanceAssembler.d.ts +2 -1
- package/dist/SceneProcessor.d.ts +1 -1
- package/dist/{Enumerator.d.ts → SceneTraversal.d.ts} +2 -2
- package/dist/SkinnedMeshBaker.d.ts +2 -1
- package/dist/Sun.d.ts +61 -11
- package/dist/index.d.ts +2 -2
- package/dist/index.js +297 -201
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +2 -0
- package/dist/index.min.js.map +1 -0
- package/package.json +12 -4
- package/dist/DoubleFOVCamera.d.ts +0 -7
package/dist/index.js
CHANGED
|
@@ -1,51 +1,254 @@
|
|
|
1
|
-
import {
|
|
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.
|
|
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.
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
625
|
+
export { BiFovCamera, Bounds, InstanceAssembler, SceneProcessor, SceneTraversal, SkinnedMeshBaker, Sun };
|
|
530
626
|
//# sourceMappingURL=index.js.map
|