@3driseai/3drise-core 1.0.9 → 1.0.11
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/data/effectsDefaults.d.ts.map +1 -1
- package/dist/data/effectsDefaults.js +32 -8
- package/dist/shaders/ChladniGridMaterial.d.ts +77 -0
- package/dist/shaders/ChladniGridMaterial.d.ts.map +1 -0
- package/dist/shaders/ChladniGridMaterial.js +301 -0
- package/dist/shaders/FlowNetGridMaterial.d.ts +74 -0
- package/dist/shaders/FlowNetGridMaterial.d.ts.map +1 -0
- package/dist/shaders/FlowNetGridMaterial.js +269 -0
- package/dist/shaders/GirihGridMaterial.d.ts +77 -0
- package/dist/shaders/GirihGridMaterial.d.ts.map +1 -0
- package/dist/shaders/GirihGridMaterial.js +464 -0
- package/dist/shaders/GravWaveGridMaterial.d.ts +77 -0
- package/dist/shaders/GravWaveGridMaterial.d.ts.map +1 -0
- package/dist/shaders/GravWaveGridMaterial.js +479 -0
- package/dist/shaders/HyperbolicGridMaterial.d.ts +74 -0
- package/dist/shaders/HyperbolicGridMaterial.d.ts.map +1 -0
- package/dist/shaders/HyperbolicGridMaterial.js +298 -0
- package/dist/shaders/ReactionDiffusionGridMaterial.d.ts +80 -0
- package/dist/shaders/ReactionDiffusionGridMaterial.d.ts.map +1 -0
- package/dist/shaders/ReactionDiffusionGridMaterial.js +247 -0
- package/dist/shaders/WallpaperGridMaterial.d.ts +134 -0
- package/dist/shaders/WallpaperGridMaterial.d.ts.map +1 -0
- package/dist/shaders/WallpaperGridMaterial.js +433 -0
- package/dist/shaders/index.d.ts +7 -0
- package/dist/shaders/index.d.ts.map +1 -1
- package/dist/shaders/index.js +7 -0
- package/dist/types/gallery.d.ts +21 -0
- package/dist/types/gallery.d.ts.map +1 -1
- package/dist/types/generativeEffects.d.ts +270 -2
- package/dist/types/generativeEffects.d.ts.map +1 -1
- package/dist/types/mesh.d.ts +3 -0
- package/dist/types/mesh.d.ts.map +1 -1
- package/dist/utils/GalleryCreator.d.ts +4 -75
- package/dist/utils/GalleryCreator.d.ts.map +1 -1
- package/dist/utils/GalleryCreator.js +12 -461
- package/dist/utils/LayoutCreator.d.ts +125 -0
- package/dist/utils/LayoutCreator.d.ts.map +1 -0
- package/dist/utils/LayoutCreator.js +521 -0
- package/dist/utils/galleriesUtils.d.ts.map +1 -1
- package/dist/utils/galleriesUtils.js +3 -2
- package/dist/utils/handleTransformEffects.d.ts.map +1 -1
- package/dist/utils/handleTransformEffects.js +5 -3
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/modelCache.d.ts +78 -0
- package/dist/utils/modelCache.d.ts.map +1 -0
- package/dist/utils/modelCache.js +164 -0
- package/dist/utils/utils.d.ts +8 -0
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +17 -3
- package/package.json +1 -1
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { modeGroupOf } from "./surfaceUtils.js";
|
|
2
|
+
export class LayoutCreator {
|
|
3
|
+
/**
|
|
4
|
+
* Creates a scattered gallery layout with concentric circles
|
|
5
|
+
* Center image + rings of images around it, with side images rotated inward
|
|
6
|
+
*/
|
|
7
|
+
static scattered(count, spacing = 3.5) {
|
|
8
|
+
const numImages = count;
|
|
9
|
+
const frames = [];
|
|
10
|
+
const actualCount = numImages;
|
|
11
|
+
if (actualCount === 0)
|
|
12
|
+
return frames;
|
|
13
|
+
// First image at center
|
|
14
|
+
frames.push({
|
|
15
|
+
position: [0, 0, 0],
|
|
16
|
+
rotation: [0, 0, 0],
|
|
17
|
+
});
|
|
18
|
+
let imageIndex = 1;
|
|
19
|
+
let ringNumber = 1;
|
|
20
|
+
// Distribute remaining images across rings
|
|
21
|
+
while (imageIndex < actualCount) {
|
|
22
|
+
const radius = ringNumber * spacing;
|
|
23
|
+
// Each ring can hold more images as radius increases
|
|
24
|
+
const imagesInRing = Math.min(Math.ceil(6 + ringNumber * 2), // 8, 10, 12, 14... images per ring
|
|
25
|
+
actualCount - imageIndex);
|
|
26
|
+
for (let i = 0; i < imagesInRing && imageIndex < actualCount; i++) {
|
|
27
|
+
const angle = (i / imagesInRing) * Math.PI * 2;
|
|
28
|
+
const x = Math.cos(angle) * radius;
|
|
29
|
+
const z = Math.sin(angle) * radius;
|
|
30
|
+
frames.push({
|
|
31
|
+
position: [x, 0, z],
|
|
32
|
+
rotation: [0, 0, 0], // All images face forward for now
|
|
33
|
+
});
|
|
34
|
+
imageIndex++;
|
|
35
|
+
}
|
|
36
|
+
ringNumber++;
|
|
37
|
+
}
|
|
38
|
+
return frames;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Creates a grid/tile layout with frames arranged in rows and columns
|
|
42
|
+
*/
|
|
43
|
+
static tiles(count, columns = 3, spacing = 2.5) {
|
|
44
|
+
const frames = [];
|
|
45
|
+
const totalImages = count;
|
|
46
|
+
const rows = Math.ceil(totalImages / columns);
|
|
47
|
+
// Calculate centering offset to position gallery center at [0, 0, 0]
|
|
48
|
+
const totalWidth = (columns - 1) * spacing;
|
|
49
|
+
const totalHeight = (rows - 1) * spacing;
|
|
50
|
+
const offsetX = -totalWidth / 2;
|
|
51
|
+
const offsetY = -totalHeight / 2;
|
|
52
|
+
for (let i = 0; i < totalImages; i++) {
|
|
53
|
+
const row = Math.floor(i / columns);
|
|
54
|
+
const col = i % columns;
|
|
55
|
+
frames.push({
|
|
56
|
+
position: [
|
|
57
|
+
offsetX + col * spacing,
|
|
58
|
+
offsetY + row * spacing,
|
|
59
|
+
0, // Centered at origin
|
|
60
|
+
],
|
|
61
|
+
rotation: [0, 0, 0],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return frames;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Creates a horizontal row layout
|
|
68
|
+
*/
|
|
69
|
+
static horizontal(count, spacing = 2.5, startX = 0) {
|
|
70
|
+
const frames = [];
|
|
71
|
+
const totalImages = count;
|
|
72
|
+
// Calculate centering offset to position gallery center at [0, 0, 0]
|
|
73
|
+
const totalWidth = (totalImages - 1) * spacing;
|
|
74
|
+
const offsetX = startX - totalWidth / 2;
|
|
75
|
+
for (let i = 0; i < totalImages; i++) {
|
|
76
|
+
frames.push({
|
|
77
|
+
position: [offsetX + i * spacing, 0, 0],
|
|
78
|
+
rotation: [0, 0, 0],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return frames;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Creates a vertical column layout
|
|
85
|
+
*/
|
|
86
|
+
static vertical(count, spacing = 2.0, startY = 0) {
|
|
87
|
+
const frames = [];
|
|
88
|
+
const totalImages = count;
|
|
89
|
+
// Calculate centering offset to position gallery center at [0, 0, 0]
|
|
90
|
+
const totalHeight = (totalImages - 1) * spacing;
|
|
91
|
+
const offsetY = startY - totalHeight / 2;
|
|
92
|
+
for (let i = 0; i < totalImages; i++) {
|
|
93
|
+
frames.push({
|
|
94
|
+
position: [0, offsetY + i * spacing, 0],
|
|
95
|
+
rotation: [0, 0, 0],
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return frames;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Creates a circular layout with images arranged in a ring
|
|
102
|
+
*/
|
|
103
|
+
static circular(count, radius = 5) {
|
|
104
|
+
const frames = [];
|
|
105
|
+
const totalImages = count;
|
|
106
|
+
for (let i = 0; i < totalImages; i++) {
|
|
107
|
+
const angle = (i / totalImages) * Math.PI * 2;
|
|
108
|
+
const x = Math.cos(angle) * radius;
|
|
109
|
+
const z = Math.sin(angle) * radius;
|
|
110
|
+
frames.push({
|
|
111
|
+
position: [x, 0, z],
|
|
112
|
+
rotation: [0, -angle, 0], // Face inward
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return frames;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Creates a spiral layout ascending or expanding outward
|
|
119
|
+
*/
|
|
120
|
+
static spiral(count, radius = 5, rotations = 2, heightIncrement = 0.5) {
|
|
121
|
+
const frames = [];
|
|
122
|
+
const totalImages = count;
|
|
123
|
+
for (let i = 0; i < totalImages; i++) {
|
|
124
|
+
const progress = i / totalImages;
|
|
125
|
+
const angle = progress * rotations * Math.PI * 2;
|
|
126
|
+
const currentRadius = radius * (1 - progress * 0.3); // Spiral inward slightly
|
|
127
|
+
const x = Math.cos(angle) * currentRadius;
|
|
128
|
+
const z = Math.sin(angle) * currentRadius;
|
|
129
|
+
const y = progress * totalImages * heightIncrement - (totalImages * heightIncrement) / 2;
|
|
130
|
+
frames.push({
|
|
131
|
+
position: [x, y, z],
|
|
132
|
+
rotation: [0, -angle, 0],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return frames;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Creates an arc/curved layout
|
|
139
|
+
*/
|
|
140
|
+
static arc(count, radius = 5, arcAngle = Math.PI) {
|
|
141
|
+
const frames = [];
|
|
142
|
+
const totalImages = count;
|
|
143
|
+
for (let i = 0; i < totalImages; i++) {
|
|
144
|
+
const progress = i / (totalImages - 1 || 1);
|
|
145
|
+
const angle = -arcAngle / 2 + progress * arcAngle;
|
|
146
|
+
const x = Math.sin(angle) * radius;
|
|
147
|
+
const z = Math.cos(angle) * radius;
|
|
148
|
+
frames.push({
|
|
149
|
+
position: [x, 0, z],
|
|
150
|
+
rotation: [0, -angle, 0],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return frames;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Creates a pyramid stack layout
|
|
157
|
+
*/
|
|
158
|
+
static pyramid(count, layers = 3, spacing = 2.5) {
|
|
159
|
+
const frames = [];
|
|
160
|
+
let imageIndex = 0;
|
|
161
|
+
for (let layer = layers; layer > 0; layer--) {
|
|
162
|
+
const itemsInLayer = layer;
|
|
163
|
+
const layerY = (layers - layer) * spacing - (layers * spacing) / 2;
|
|
164
|
+
const layerWidth = (itemsInLayer - 1) * spacing;
|
|
165
|
+
const offsetX = -layerWidth / 2;
|
|
166
|
+
for (let i = 0; i < itemsInLayer && imageIndex < count; i++) {
|
|
167
|
+
frames.push({
|
|
168
|
+
position: [offsetX + i * spacing, layerY, 0],
|
|
169
|
+
rotation: [0, 0, 0],
|
|
170
|
+
});
|
|
171
|
+
imageIndex++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return frames;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Creates a helix (DNA-style spiral)
|
|
178
|
+
*/
|
|
179
|
+
static helix(count, radius = 3, rotations = 3, heightIncrement = 0.4) {
|
|
180
|
+
const frames = [];
|
|
181
|
+
const totalImages = count;
|
|
182
|
+
for (let i = 0; i < totalImages; i++) {
|
|
183
|
+
const progress = i / totalImages;
|
|
184
|
+
const angle = progress * rotations * Math.PI * 2;
|
|
185
|
+
const x = Math.cos(angle) * radius;
|
|
186
|
+
const z = Math.sin(angle) * radius;
|
|
187
|
+
const y = progress * totalImages * heightIncrement - (totalImages * heightIncrement) / 2;
|
|
188
|
+
frames.push({
|
|
189
|
+
position: [x, y, z],
|
|
190
|
+
rotation: [0, -angle - Math.PI / 2, 0], // Tangent to helix
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return frames;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Creates a 3D box layout with images on all 6 faces
|
|
197
|
+
*/
|
|
198
|
+
static box(count, size = 5) {
|
|
199
|
+
const frames = [];
|
|
200
|
+
const imagesPerFace = Math.ceil(count / 6);
|
|
201
|
+
let imageIndex = 0;
|
|
202
|
+
// Define the 6 faces: Front, Back, Right, Left, Top, Bottom
|
|
203
|
+
const faceConfigs = [
|
|
204
|
+
{ name: 'Front', basePos: [0, 0, size], baseRot: [0, 0, 0] },
|
|
205
|
+
{ name: 'Back', basePos: [0, 0, -size], baseRot: [0, Math.PI, 0] },
|
|
206
|
+
{ name: 'Right', basePos: [size, 0, 0], baseRot: [0, Math.PI / 2, 0] },
|
|
207
|
+
{ name: 'Left', basePos: [-size, 0, 0], baseRot: [0, -Math.PI / 2, 0] },
|
|
208
|
+
{ name: 'Top', basePos: [0, size, 0], baseRot: [-Math.PI / 2, 0, 0] },
|
|
209
|
+
{ name: 'Bottom', basePos: [0, -size, 0], baseRot: [Math.PI / 2, 0, 0] }
|
|
210
|
+
];
|
|
211
|
+
for (let faceIndex = 0; faceIndex < 6 && imageIndex < count; faceIndex++) {
|
|
212
|
+
const config = faceConfigs[faceIndex];
|
|
213
|
+
const itemsOnFace = Math.min(imagesPerFace, count - imageIndex);
|
|
214
|
+
const cols = Math.ceil(Math.sqrt(itemsOnFace));
|
|
215
|
+
const rows = Math.ceil(itemsOnFace / cols);
|
|
216
|
+
const spacing = (size * 1.8) / Math.max(cols, rows);
|
|
217
|
+
for (let i = 0; i < itemsOnFace; i++) {
|
|
218
|
+
const row = Math.floor(i / cols);
|
|
219
|
+
const col = i % cols;
|
|
220
|
+
const offsetX = (col - (cols - 1) / 2) * spacing;
|
|
221
|
+
const offsetY = (row - (rows - 1) / 2) * spacing;
|
|
222
|
+
// Calculate position based on face orientation
|
|
223
|
+
let position;
|
|
224
|
+
if (faceIndex === 0)
|
|
225
|
+
position = [offsetX, offsetY, size]; // Front
|
|
226
|
+
else if (faceIndex === 1)
|
|
227
|
+
position = [-offsetX, offsetY, -size]; // Back (mirror X)
|
|
228
|
+
else if (faceIndex === 2)
|
|
229
|
+
position = [size, offsetY, -offsetX]; // Right (X fixed, offset in -Z)
|
|
230
|
+
else if (faceIndex === 3)
|
|
231
|
+
position = [-size, offsetY, offsetX]; // Left (X fixed, offset in +Z)
|
|
232
|
+
else if (faceIndex === 4)
|
|
233
|
+
position = [offsetX, size, -offsetY]; // Top (Y fixed, offset in -Z)
|
|
234
|
+
else
|
|
235
|
+
position = [offsetX, -size, offsetY]; // Bottom (Y fixed, offset in +Z)
|
|
236
|
+
frames.push({
|
|
237
|
+
position,
|
|
238
|
+
rotation: config.baseRot,
|
|
239
|
+
});
|
|
240
|
+
imageIndex++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return frames;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Creates a sphere layout with images distributed evenly on a sphere surface
|
|
247
|
+
* Uses Fibonacci sphere algorithm for even distribution
|
|
248
|
+
*/
|
|
249
|
+
static sphere(count, radius = 5) {
|
|
250
|
+
const frames = [];
|
|
251
|
+
const totalImages = count;
|
|
252
|
+
const goldenRatio = (1 + Math.sqrt(5)) / 2;
|
|
253
|
+
const angleIncrement = Math.PI * 2 * goldenRatio;
|
|
254
|
+
for (let i = 0; i < totalImages; i++) {
|
|
255
|
+
// Fibonacci sphere distribution
|
|
256
|
+
const t = i / totalImages;
|
|
257
|
+
const inclination = Math.acos(1 - 2 * t); // 0 to PI (theta - angle from north pole)
|
|
258
|
+
const azimuth = angleIncrement * i; // phi - angle around equator
|
|
259
|
+
// Convert spherical to Cartesian coordinates
|
|
260
|
+
const x = radius * Math.sin(inclination) * Math.cos(azimuth);
|
|
261
|
+
const y = radius * Math.cos(inclination);
|
|
262
|
+
const z = radius * Math.sin(inclination) * Math.sin(azimuth);
|
|
263
|
+
frames.push({
|
|
264
|
+
position: [x, y, z],
|
|
265
|
+
rotation: [0, 0, 0],
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return frames;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Creates a fan/radial layout
|
|
272
|
+
*/
|
|
273
|
+
static fan(count, radius = 5, arcAngle = Math.PI * 1.5) {
|
|
274
|
+
const frames = [];
|
|
275
|
+
const totalImages = count;
|
|
276
|
+
for (let i = 0; i < totalImages; i++) {
|
|
277
|
+
const progress = i / (totalImages - 1 || 1);
|
|
278
|
+
const angle = -arcAngle / 2 + progress * arcAngle;
|
|
279
|
+
const x = Math.sin(angle) * radius;
|
|
280
|
+
const z = Math.cos(angle) * radius;
|
|
281
|
+
frames.push({
|
|
282
|
+
position: [x, 0, z],
|
|
283
|
+
rotation: [0, -angle, 0], // All face center
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return frames;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Creates a wave pattern layout
|
|
290
|
+
*/
|
|
291
|
+
static wave(count, amplitude = 2, frequency = 2, spacing = 1.5) {
|
|
292
|
+
const frames = [];
|
|
293
|
+
const totalImages = count;
|
|
294
|
+
const totalWidth = (totalImages - 1) * spacing;
|
|
295
|
+
const offsetX = -totalWidth / 2;
|
|
296
|
+
for (let i = 0; i < totalImages; i++) {
|
|
297
|
+
const x = offsetX + i * spacing;
|
|
298
|
+
const progress = i / totalImages;
|
|
299
|
+
const y = Math.sin(progress * frequency * Math.PI * 2) * amplitude;
|
|
300
|
+
frames.push({
|
|
301
|
+
position: [x, y, 0],
|
|
302
|
+
rotation: [0, 0, 0],
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return frames;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Creates a tunnel/perspective layout
|
|
309
|
+
*/
|
|
310
|
+
static tunnel(count, depth = 20, scaleFactor = 0.8) {
|
|
311
|
+
const frames = [];
|
|
312
|
+
const totalImages = count;
|
|
313
|
+
for (let i = 0; i < totalImages; i++) {
|
|
314
|
+
const progress = i / totalImages;
|
|
315
|
+
const z = -progress * depth;
|
|
316
|
+
const scale = Math.pow(scaleFactor, i);
|
|
317
|
+
frames.push({
|
|
318
|
+
position: [0, 0, z],
|
|
319
|
+
rotation: [0, 0, 0],
|
|
320
|
+
scale: [scale, scale, scale],
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return frames;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Creates a tunnel-pyramid layout combining depth perspective with pyramid stacking
|
|
327
|
+
* Starts with 1 frame at front, expands as it recedes. All at y=0
|
|
328
|
+
*/
|
|
329
|
+
static tunnelPyramid(count, depth = 20, layers = 6, spacing = 2.5) {
|
|
330
|
+
const frames = [];
|
|
331
|
+
let imageIndex = 0;
|
|
332
|
+
let currentLayer = 1;
|
|
333
|
+
// Continue adding layers until all images are placed
|
|
334
|
+
while (imageIndex < count) {
|
|
335
|
+
const itemsInLayer = currentLayer;
|
|
336
|
+
const layerProgress = (currentLayer - 1) / (layers - 1 || 1);
|
|
337
|
+
const z = -layerProgress * depth;
|
|
338
|
+
const layerWidth = (itemsInLayer - 1) * spacing;
|
|
339
|
+
const offsetX = -layerWidth / 2;
|
|
340
|
+
for (let i = 0; i < itemsInLayer && imageIndex < count; i++) {
|
|
341
|
+
frames.push({
|
|
342
|
+
position: [offsetX + i * spacing, 0, z], // Y always 0
|
|
343
|
+
rotation: [0, 0, 0],
|
|
344
|
+
});
|
|
345
|
+
imageIndex++;
|
|
346
|
+
}
|
|
347
|
+
currentLayer++;
|
|
348
|
+
}
|
|
349
|
+
return frames;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Creates a tunnel-wave layout combining depth perspective with wave pattern
|
|
353
|
+
* Wave pattern in X axis, all at y=0, receding in Z
|
|
354
|
+
*/
|
|
355
|
+
static tunnelWave(count, depth = 20, amplitude = 2, frequency = 2, spacing = 1.5) {
|
|
356
|
+
const frames = [];
|
|
357
|
+
const totalImages = count;
|
|
358
|
+
for (let i = 0; i < totalImages; i++) {
|
|
359
|
+
const progress = i / totalImages;
|
|
360
|
+
const x = Math.sin(progress * frequency * Math.PI * 2) * amplitude;
|
|
361
|
+
const z = -progress * depth;
|
|
362
|
+
frames.push({
|
|
363
|
+
position: [x, 0, z], // Y always 0, wave in X
|
|
364
|
+
rotation: [0, 0, 0],
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return frames;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Creates a random/organic layout
|
|
371
|
+
*/
|
|
372
|
+
static random(count, bounds = 8, seed = 1) {
|
|
373
|
+
const frames = [];
|
|
374
|
+
const totalImages = count;
|
|
375
|
+
const rand = seededRandom(seed);
|
|
376
|
+
for (let i = 0; i < totalImages; i++) {
|
|
377
|
+
const x = (rand() - 0.5) * bounds * 2;
|
|
378
|
+
const y = (rand() - 0.5) * bounds;
|
|
379
|
+
const z = (rand() - 0.5) * bounds * 2;
|
|
380
|
+
const rotY = rand() * Math.PI * 2;
|
|
381
|
+
frames.push({
|
|
382
|
+
position: [x, y, z],
|
|
383
|
+
rotation: [0, rotY, 0],
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return frames;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Slots for `count` copies in the given layout. Reads only the fields the layout uses
|
|
390
|
+
* (see GALLERY_LAYOUT_CONFIGS); anything missing falls back to that layout's default.
|
|
391
|
+
*/
|
|
392
|
+
static generate(count, settings) {
|
|
393
|
+
const n = Math.max(0, Math.floor(count || 0));
|
|
394
|
+
switch (settings.layout) {
|
|
395
|
+
case 'scattered':
|
|
396
|
+
return this.scattered(n, settings.spacing || 3.5);
|
|
397
|
+
case 'tiles':
|
|
398
|
+
return this.tiles(n, settings.columns || 3, settings.spacing || 2.5);
|
|
399
|
+
case 'horizontal':
|
|
400
|
+
return this.horizontal(n, settings.spacing || 2.5, settings.startX || 0);
|
|
401
|
+
case 'vertical':
|
|
402
|
+
return this.vertical(n, settings.spacing || 2.0, settings.startY || 0);
|
|
403
|
+
case 'circular':
|
|
404
|
+
return this.circular(n, settings.radius || 5);
|
|
405
|
+
case 'spiral':
|
|
406
|
+
return this.spiral(n, settings.radius || 5, settings.rotations || 2, settings.heightIncrement || 0.5);
|
|
407
|
+
case 'arc':
|
|
408
|
+
return this.arc(n, settings.radius || 5, settings.arcAngle || Math.PI);
|
|
409
|
+
case 'pyramid':
|
|
410
|
+
return this.pyramid(n, settings.layers || 3, settings.spacing || 2.5);
|
|
411
|
+
case 'helix':
|
|
412
|
+
return this.helix(n, settings.radius || 3, settings.rotations || 3, settings.heightIncrement || 0.4);
|
|
413
|
+
case 'box':
|
|
414
|
+
return this.box(n, settings.size || 5);
|
|
415
|
+
case 'sphere':
|
|
416
|
+
return this.sphere(n, settings.radius || 5);
|
|
417
|
+
case 'fan':
|
|
418
|
+
return this.fan(n, settings.radius || 5, settings.arcAngle || Math.PI * 1.5);
|
|
419
|
+
case 'wave':
|
|
420
|
+
return this.wave(n, settings.amplitude || 2, settings.frequency || 2, settings.spacing || 1.5);
|
|
421
|
+
case 'tunnel':
|
|
422
|
+
return this.tunnel(n, settings.depth || 20, settings.scaleFactor || 0.8);
|
|
423
|
+
case 'tunnelPyramid':
|
|
424
|
+
return this.tunnelPyramid(n, settings.depth || 20, settings.layers || 3, settings.spacing || 2.5);
|
|
425
|
+
case 'tunnelWave':
|
|
426
|
+
return this.tunnelWave(n, settings.depth || 20, settings.amplitude || 2, settings.frequency || 2, settings.spacing || 1.5);
|
|
427
|
+
case 'random':
|
|
428
|
+
return this.random(n, settings.bounds || 8, settings.seed ?? 1);
|
|
429
|
+
default:
|
|
430
|
+
return this.scattered(n);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
export const generateLayoutSlots = (count, settings) => LayoutCreator.generate(count, settings);
|
|
435
|
+
// =============================================================================
|
|
436
|
+
// Object layout (meshSettings.layout) — repeat a scene object in a gallery layout
|
|
437
|
+
// =============================================================================
|
|
438
|
+
/** Object types whose renderer output ObjectNode can repeat across layout slots. */
|
|
439
|
+
export const LAYOUT_CAPABLE_TYPES = new Set(['mesh', 'model', 'effect', 'light']);
|
|
440
|
+
export const DEFAULT_OBJECT_LAYOUT = {
|
|
441
|
+
enabled: false,
|
|
442
|
+
layout: 'circular',
|
|
443
|
+
count: 6,
|
|
444
|
+
seed: 1,
|
|
445
|
+
columns: 3,
|
|
446
|
+
spacing: 3.5,
|
|
447
|
+
startX: 0,
|
|
448
|
+
startY: 0,
|
|
449
|
+
radius: 5,
|
|
450
|
+
size: 5,
|
|
451
|
+
rotations: 2,
|
|
452
|
+
heightIncrement: 0.5,
|
|
453
|
+
arcAngle: Math.PI,
|
|
454
|
+
amplitude: 2,
|
|
455
|
+
frequency: 2,
|
|
456
|
+
layers: 3,
|
|
457
|
+
depth: 20,
|
|
458
|
+
scaleFactor: 0.8,
|
|
459
|
+
bounds: 8,
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* Saved objects predate this field. Absent means no layout at all — never the default —
|
|
463
|
+
* and a partial block is merged over the defaults so an added knob fills in.
|
|
464
|
+
*/
|
|
465
|
+
export const normalizeObjectLayout = (layout) => {
|
|
466
|
+
if (!layout || typeof layout !== 'object')
|
|
467
|
+
return null;
|
|
468
|
+
return { ...DEFAULT_OBJECT_LAYOUT, ...layout };
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Per-type ceilings on layout copies. Every copy is a full render of the object, so these
|
|
472
|
+
* track what each copy costs:
|
|
473
|
+
* - light: each one adds per-fragment cost to every lit material, and changing the light
|
|
474
|
+
* count recompiles every material (three.js keys programs on light counts).
|
|
475
|
+
* - effect: each copy is a whole particle / shader effect.
|
|
476
|
+
* - mesh in a points mode: each copy resamples its points (volumeFill raycasts in render).
|
|
477
|
+
* - model: geometry is shared with the cache master, but every part of every copy is its
|
|
478
|
+
* own draw call with its own material clone.
|
|
479
|
+
*/
|
|
480
|
+
export const LAYOUT_COUNT_LIMITS = {
|
|
481
|
+
mesh: 100,
|
|
482
|
+
meshPoints: 12,
|
|
483
|
+
model: 30,
|
|
484
|
+
effect: 24,
|
|
485
|
+
light: 8,
|
|
486
|
+
};
|
|
487
|
+
/** Light kinds a layout makes sense for: N directional copies are one brighter light, and
|
|
488
|
+
* an ambient light has no position. */
|
|
489
|
+
export const LAYOUT_LIGHT_KINDS = new Set(['point', 'spot']);
|
|
490
|
+
/** Whether this object can be repeated, and how many times. One answer for the viewer and the panel. */
|
|
491
|
+
export const getObjectLayoutSupport = (obj) => {
|
|
492
|
+
switch (obj.type) {
|
|
493
|
+
case 'mesh':
|
|
494
|
+
return modeGroupOf(obj.modeConfig?.mode) === 'points'
|
|
495
|
+
? { allowed: true, maxCount: LAYOUT_COUNT_LIMITS.meshPoints }
|
|
496
|
+
: { allowed: true, maxCount: LAYOUT_COUNT_LIMITS.mesh };
|
|
497
|
+
case 'model':
|
|
498
|
+
return { allowed: true, maxCount: LAYOUT_COUNT_LIMITS.model };
|
|
499
|
+
case 'effect':
|
|
500
|
+
return { allowed: true, maxCount: LAYOUT_COUNT_LIMITS.effect };
|
|
501
|
+
case 'light': {
|
|
502
|
+
const kind = obj.config?.type;
|
|
503
|
+
return kind && LAYOUT_LIGHT_KINDS.has(kind)
|
|
504
|
+
? { allowed: true, maxCount: LAYOUT_COUNT_LIMITS.light }
|
|
505
|
+
: { allowed: false, maxCount: 0, reason: 'Layouts work with point and spot lights only.' };
|
|
506
|
+
}
|
|
507
|
+
default:
|
|
508
|
+
return { allowed: false, maxCount: 0, reason: 'This object type cannot be repeated in a layout.' };
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
/** mulberry32 — deterministic, so a random layout keeps its shape across re-renders. */
|
|
512
|
+
function seededRandom(seed) {
|
|
513
|
+
let a = (Math.floor(seed) || 1) >>> 0;
|
|
514
|
+
return () => {
|
|
515
|
+
a = (a + 0x6D2B79F5) >>> 0;
|
|
516
|
+
let t = a;
|
|
517
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
518
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
519
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
520
|
+
};
|
|
521
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"galleriesUtils.d.ts","sourceRoot":"","sources":["../../src/utils/galleriesUtils.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtH,eAAO,MAAM,gBAAgB,EAAE,MAAM,EAqBpC,CAAC;AACF,eAAO,MAAM,2BAA2B,GAAI,KAAK,MAAM,KAAG,MAAM,EAM/D,CAAC;AAIF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,aAAa,KAAG,
|
|
1
|
+
{"version":3,"file":"galleriesUtils.d.ts","sourceRoot":"","sources":["../../src/utils/galleriesUtils.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAKtH,eAAO,MAAM,gBAAgB,EAAE,MAAM,EAqBpC,CAAC;AACF,eAAO,MAAM,2BAA2B,GAAI,KAAK,MAAM,KAAG,MAAM,EAM/D,CAAC;AAIF,eAAO,MAAM,wBAAwB,GAAI,QAAQ,aAAa,KAAG,qBAwBhE,CAAC;AACF,eAAO,MAAM,2BAA2B,GAAI,iBAAiB,qBAAqB,EAAE,cAAc,OAAO,CAAC,qBAAqB,CAAC,KAAG;IAC3H,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,cAAc,EAAE,qBAAqB,CAAC;CA8B7C,CAAC;AAIF,eAAO,MAAM,mBAAmB,EAAE,aAgEjC,CAAC"}
|
|
@@ -48,6 +48,7 @@ export const getInitialLayoutSettings = (layout) => {
|
|
|
48
48
|
startX: 0,
|
|
49
49
|
startY: 0,
|
|
50
50
|
radius: 5,
|
|
51
|
+
size: 5,
|
|
51
52
|
rotations: 2,
|
|
52
53
|
heightIncrement: 0.5,
|
|
53
54
|
arcAngle: Math.PI,
|
|
@@ -63,7 +64,6 @@ export const getInitialLayoutSettings = (layout) => {
|
|
|
63
64
|
return getLayoutSettings(layout, galleryState);
|
|
64
65
|
};
|
|
65
66
|
export const updateCurrentLayoutSettings = (currentSettings, newSettings) => {
|
|
66
|
-
console.log('Updating layout settings for gallery:', currentSettings);
|
|
67
67
|
const galleryState = {
|
|
68
68
|
geometryType: newSettings?.geometryType ?? currentSettings.geometryType,
|
|
69
69
|
columns: newSettings?.columns ?? currentSettings.columns ?? 3,
|
|
@@ -73,6 +73,8 @@ export const updateCurrentLayoutSettings = (currentSettings, newSettings) => {
|
|
|
73
73
|
startX: newSettings?.startX ?? currentSettings.startX ?? 0,
|
|
74
74
|
startY: newSettings?.startY ?? currentSettings.startY ?? 0,
|
|
75
75
|
radius: newSettings?.radius ?? currentSettings.radius ?? 5,
|
|
76
|
+
size: newSettings?.size ?? currentSettings.size ?? 5,
|
|
77
|
+
seed: newSettings?.seed ?? currentSettings.seed ?? 1,
|
|
76
78
|
rotations: newSettings?.rotations ?? currentSettings.rotations ?? 2,
|
|
77
79
|
heightIncrement: newSettings?.heightIncrement ?? currentSettings.heightIncrement ?? 0.5,
|
|
78
80
|
arcAngle: newSettings?.arcAngle ?? currentSettings.arcAngle ?? Math.PI,
|
|
@@ -89,7 +91,6 @@ export const updateCurrentLayoutSettings = (currentSettings, newSettings) => {
|
|
|
89
91
|
const frames = GalleryCreator.generateGallery(currentSettings.imageUrls && currentSettings.imageUrls.length > 0
|
|
90
92
|
? currentSettings.imageUrls
|
|
91
93
|
: createNumberOfDefaultImages(galleryState.numImages ?? 20), galleryState);
|
|
92
|
-
const layoutSettings = getLayoutSettings(currentSettings.layout, galleryState);
|
|
93
94
|
return { frames, layoutSettings: galleryState };
|
|
94
95
|
};
|
|
95
96
|
// =============================================================================
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handleTransformEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleTransformEffects.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAKtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,MAAM,WAAW,yBAAyB;IACtC,MAAM,EAAE,QAAQ,CAAC;IACjB,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACzB;AAMD,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAS7C;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAmBxN;AAID;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"handleTransformEffects.d.ts","sourceRoot":"","sources":["../../src/utils/handleTransformEffects.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAKtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,MAAM,WAAW,yBAAyB;IACtC,MAAM,EAAE,QAAQ,CAAC;IACjB,YAAY,EAAE,GAAG,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;CACzB;AAMD,eAAO,MAAM,uBAAuB,QAAQ,CAAC;AAS7C;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAmBxN;AAID;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,yBAAyB,EAAE,UAAU,EAAE,gBAAgB,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAsDrJ;AAID;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE;IACtJ,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC/B,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAqBjC"}
|
|
@@ -83,9 +83,11 @@ export function handleTransformAnimations(ctx, animations, currentTime, hovered)
|
|
|
83
83
|
const targetsToAnimate = [];
|
|
84
84
|
if (objectIds && objectIds.length > 0) {
|
|
85
85
|
// Animate specific children by name
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
// Every match, not getObjectByName's first: a model repeated by
|
|
87
|
+
// meshSettings.layout has the same part name once per copy.
|
|
88
|
+
const names = new Set(objectIds);
|
|
89
|
+
object.traverse((child) => {
|
|
90
|
+
if (child.name && names.has(child.name)) {
|
|
89
91
|
targetsToAnimate.push(child);
|
|
90
92
|
}
|
|
91
93
|
});
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -4,10 +4,12 @@ export * from './frameAnimations';
|
|
|
4
4
|
export * from './particleAnimations';
|
|
5
5
|
export * from './galleriesUtils';
|
|
6
6
|
export * from './GalleryCreator';
|
|
7
|
+
export * from './LayoutCreator';
|
|
7
8
|
export * from './GalleryLayouts';
|
|
8
9
|
export * from './gridUtils';
|
|
9
10
|
export * from './idUtils';
|
|
10
11
|
export * from './loadingManager';
|
|
12
|
+
export * from './modelCache';
|
|
11
13
|
export * from './materialApplicationUtils';
|
|
12
14
|
export * from './materialUtils';
|
|
13
15
|
export * from './meshUtils';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,wBAAwB,CAAC;AACvC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
package/dist/utils/index.js
CHANGED
|
@@ -4,10 +4,12 @@ export * from './frameAnimations.js';
|
|
|
4
4
|
export * from './particleAnimations.js';
|
|
5
5
|
export * from './galleriesUtils.js';
|
|
6
6
|
export * from './GalleryCreator.js';
|
|
7
|
+
export * from './LayoutCreator.js';
|
|
7
8
|
export * from './GalleryLayouts.js';
|
|
8
9
|
export * from './gridUtils.js';
|
|
9
10
|
export * from './idUtils.js';
|
|
10
11
|
export * from './loadingManager.js';
|
|
12
|
+
export * from './modelCache.js';
|
|
11
13
|
export * from './materialApplicationUtils.js';
|
|
12
14
|
export * from './materialUtils.js';
|
|
13
15
|
export * from './meshUtils.js';
|