@nice2dev/ui-3d 1.3.14 → 1.3.16

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 (108) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,1244 +1,1159 @@
1
- 'use strict';
2
-
3
- var THREE = require('three');
4
-
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
25
+ const THREE = require("three");
5
26
  function _interopNamespaceDefault(e) {
6
- var n = Object.create(null);
7
- if (e) {
8
- Object.keys(e).forEach(function (k) {
9
- if (k !== 'default') {
10
- var d = Object.getOwnPropertyDescriptor(e, k);
11
- Object.defineProperty(n, k, d.get ? d : {
12
- enumerable: true,
13
- get: function () { return e[k]; }
14
- });
15
- }
16
- });
17
- }
18
- n.default = e;
19
- return Object.freeze(n);
27
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
28
+ if (e) {
29
+ for (const k in e) {
30
+ if (k !== "default") {
31
+ const d = Object.getOwnPropertyDescriptor(e, k);
32
+ Object.defineProperty(n, k, d.get ? d : {
33
+ enumerable: true,
34
+ get: () => e[k]
35
+ });
36
+ }
37
+ }
38
+ }
39
+ n.default = e;
40
+ return Object.freeze(n);
20
41
  }
21
-
22
- var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
23
-
24
- /**
25
- * modelEditorUtils.ts — Utility functions for the ModelEditor.
26
- *
27
- * Features:
28
- * - Undo/Redo stack
29
- * - LOD auto-generation
30
- * - PBR material presets
31
- * - Batch import
32
- * - Instancing helpers
33
- */
42
+ const THREE__namespace = /* @__PURE__ */ _interopNamespaceDefault(THREE);
34
43
  class UndoRedoStack {
35
- constructor(maxSize = 50) {
36
- this.undoStack = [];
37
- this.redoStack = [];
38
- this.maxSize = maxSize;
44
+ constructor(maxSize = 50) {
45
+ this.undoStack = [];
46
+ this.redoStack = [];
47
+ this.maxSize = maxSize;
48
+ }
49
+ push(action) {
50
+ this.undoStack.push(action);
51
+ if (this.undoStack.length > this.maxSize) {
52
+ this.undoStack.shift();
39
53
  }
40
- push(action) {
41
- this.undoStack.push(action);
42
- if (this.undoStack.length > this.maxSize) {
43
- this.undoStack.shift();
44
- }
45
- this.redoStack.length = 0;
54
+ this.redoStack.length = 0;
55
+ }
56
+ undo() {
57
+ const action = this.undoStack.pop();
58
+ if (!action) {
59
+ return null;
46
60
  }
47
- undo() {
48
- const action = this.undoStack.pop();
49
- if (!action) {
50
- return null;
51
- }
52
- action.undo();
53
- this.redoStack.push(action);
54
- return action.label;
61
+ action.undo();
62
+ this.redoStack.push(action);
63
+ return action.label;
64
+ }
65
+ redo() {
66
+ const action = this.redoStack.pop();
67
+ if (!action) {
68
+ return null;
55
69
  }
56
- redo() {
57
- const action = this.redoStack.pop();
58
- if (!action) {
59
- return null;
60
- }
61
- action.redo();
62
- this.undoStack.push(action);
63
- return action.label;
70
+ action.redo();
71
+ this.undoStack.push(action);
72
+ return action.label;
73
+ }
74
+ canUndo() {
75
+ return this.undoStack.length > 0;
76
+ }
77
+ canRedo() {
78
+ return this.redoStack.length > 0;
79
+ }
80
+ clear() {
81
+ this.undoStack.length = 0;
82
+ this.redoStack.length = 0;
83
+ }
84
+ get undoLabel() {
85
+ return this.undoStack.length > 0 ? this.undoStack[this.undoStack.length - 1].label : null;
86
+ }
87
+ get redoLabel() {
88
+ return this.redoStack.length > 0 ? this.redoStack[this.redoStack.length - 1].label : null;
89
+ }
90
+ }
91
+ const DEFAULT_LOD_LEVELS = [
92
+ { distance: 0, ratio: 1 },
93
+ { distance: 10, ratio: 0.5 },
94
+ { distance: 25, ratio: 0.25 },
95
+ { distance: 50, ratio: 0.1 }
96
+ ];
97
+ function decimateGeometry(geometry, ratio) {
98
+ if (ratio >= 1) {
99
+ return geometry.clone();
100
+ }
101
+ const posAttr = geometry.getAttribute("position");
102
+ if (!posAttr) {
103
+ return geometry.clone();
104
+ }
105
+ const positions = Array.from(posAttr.array);
106
+ const vertexCount = posAttr.count;
107
+ const targetCount = Math.max(3, Math.floor(vertexCount * ratio));
108
+ const gridSize = Math.cbrt(vertexCount / targetCount) * 0.5;
109
+ const merged = /* @__PURE__ */ new Map();
110
+ const vertexMap = new Int32Array(vertexCount);
111
+ let newIndex = 0;
112
+ for (let i = 0; i < vertexCount; i++) {
113
+ const x = positions[i * 3];
114
+ const y = positions[i * 3 + 1];
115
+ const z = positions[i * 3 + 2];
116
+ const key = `${Math.round(x / gridSize)},${Math.round(y / gridSize)},${Math.round(z / gridSize)}`;
117
+ const existing = merged.get(key);
118
+ if (existing) {
119
+ existing.x += x;
120
+ existing.y += y;
121
+ existing.z += z;
122
+ existing.count++;
123
+ vertexMap[i] = existing.index;
124
+ } else {
125
+ merged.set(key, { x, y, z, count: 1, index: newIndex });
126
+ vertexMap[i] = newIndex;
127
+ newIndex++;
64
128
  }
65
- canUndo() {
66
- return this.undoStack.length > 0;
129
+ }
130
+ const newPositions = new Float32Array(merged.size * 3);
131
+ for (const cell of merged.values()) {
132
+ newPositions[cell.index * 3] = cell.x / cell.count;
133
+ newPositions[cell.index * 3 + 1] = cell.y / cell.count;
134
+ newPositions[cell.index * 3 + 2] = cell.z / cell.count;
135
+ }
136
+ const oldIndex = geometry.index;
137
+ const newIndices = [];
138
+ if (oldIndex) {
139
+ for (let i = 0; i < oldIndex.count; i += 3) {
140
+ const a = vertexMap[oldIndex.array[i]];
141
+ const b = vertexMap[oldIndex.array[i + 1]];
142
+ const c = vertexMap[oldIndex.array[i + 2]];
143
+ if (a !== b && b !== c && a !== c) {
144
+ newIndices.push(a, b, c);
145
+ }
67
146
  }
68
- canRedo() {
69
- return this.redoStack.length > 0;
147
+ } else {
148
+ for (let i = 0; i < vertexCount; i += 3) {
149
+ const a = vertexMap[i];
150
+ const b = vertexMap[i + 1];
151
+ const c = vertexMap[i + 2];
152
+ if (a !== b && b !== c && a !== c) {
153
+ newIndices.push(a, b, c);
154
+ }
70
155
  }
71
- clear() {
72
- this.undoStack.length = 0;
73
- this.redoStack.length = 0;
156
+ }
157
+ const result = new THREE__namespace.BufferGeometry();
158
+ result.setAttribute("position", new THREE__namespace.BufferAttribute(newPositions, 3));
159
+ result.setIndex(newIndices);
160
+ result.computeVertexNormals();
161
+ return result;
162
+ }
163
+ function generateLOD(mesh, levels = DEFAULT_LOD_LEVELS) {
164
+ const lod = new THREE__namespace.LOD();
165
+ lod.name = mesh.name + "_LOD";
166
+ lod.position.copy(mesh.position);
167
+ lod.rotation.copy(mesh.rotation);
168
+ lod.scale.copy(mesh.scale);
169
+ for (const level of levels) {
170
+ const decimated = decimateGeometry(mesh.geometry, level.ratio);
171
+ const mat = Array.isArray(mesh.material) ? mesh.material.map((m) => m.clone()) : mesh.material.clone();
172
+ const lodMesh = new THREE__namespace.Mesh(decimated, mat);
173
+ lodMesh.castShadow = mesh.castShadow;
174
+ lodMesh.receiveShadow = mesh.receiveShadow;
175
+ lod.addLevel(lodMesh, level.distance);
176
+ }
177
+ return lod;
178
+ }
179
+ const PBR_PRESETS = [
180
+ // Metals
181
+ { name: "Gold", category: "Metal", params: { color: 16766720, metalness: 1, roughness: 0.15 } },
182
+ {
183
+ name: "Silver",
184
+ category: "Metal",
185
+ params: { color: 12632256, metalness: 1, roughness: 0.1 }
186
+ },
187
+ {
188
+ name: "Copper",
189
+ category: "Metal",
190
+ params: { color: 12088115, metalness: 1, roughness: 0.25 }
191
+ },
192
+ { name: "Iron", category: "Metal", params: { color: 8421504, metalness: 0.95, roughness: 0.6 } },
193
+ {
194
+ name: "Brushed Steel",
195
+ category: "Metal",
196
+ params: { color: 10526880, metalness: 0.95, roughness: 0.4 }
197
+ },
198
+ {
199
+ name: "Chrome",
200
+ category: "Metal",
201
+ params: { color: 14540253, metalness: 1, roughness: 0.02 }
202
+ },
203
+ {
204
+ name: "Bronze",
205
+ category: "Metal",
206
+ params: { color: 13467442, metalness: 0.9, roughness: 0.35 }
207
+ },
208
+ {
209
+ name: "Aluminum",
210
+ category: "Metal",
211
+ params: { color: 14079702, metalness: 0.85, roughness: 0.2 }
212
+ },
213
+ // Non-metals
214
+ {
215
+ name: "Plastic (White)",
216
+ category: "Plastic",
217
+ params: { color: 15790320, metalness: 0, roughness: 0.3 }
218
+ },
219
+ {
220
+ name: "Plastic (Red)",
221
+ category: "Plastic",
222
+ params: { color: 13378082, metalness: 0, roughness: 0.35 }
223
+ },
224
+ {
225
+ name: "Rubber",
226
+ category: "Plastic",
227
+ params: { color: 3355443, metalness: 0, roughness: 0.9 }
228
+ },
229
+ // Glass
230
+ {
231
+ name: "Clear Glass",
232
+ category: "Glass",
233
+ params: {
234
+ color: 16777215,
235
+ metalness: 0,
236
+ roughness: 0,
237
+ opacity: 0.15,
238
+ transparent: true,
239
+ ior: 1.5,
240
+ transmission: 0.95
74
241
  }
75
- get undoLabel() {
76
- return this.undoStack.length > 0 ? this.undoStack[this.undoStack.length - 1].label : null;
242
+ },
243
+ {
244
+ name: "Frosted Glass",
245
+ category: "Glass",
246
+ params: {
247
+ color: 15658751,
248
+ metalness: 0,
249
+ roughness: 0.5,
250
+ opacity: 0.3,
251
+ transparent: true,
252
+ ior: 1.5,
253
+ transmission: 0.7
77
254
  }
78
- get redoLabel() {
79
- return this.redoStack.length > 0 ? this.redoStack[this.redoStack.length - 1].label : null;
255
+ },
256
+ {
257
+ name: "Tinted Glass",
258
+ category: "Glass",
259
+ params: {
260
+ color: 2245768,
261
+ metalness: 0,
262
+ roughness: 0.05,
263
+ opacity: 0.3,
264
+ transparent: true,
265
+ ior: 1.5,
266
+ transmission: 0.8
80
267
  }
81
- }
82
- const DEFAULT_LOD_LEVELS = [
83
- { distance: 0, ratio: 1.0 },
84
- { distance: 10, ratio: 0.5 },
85
- { distance: 25, ratio: 0.25 },
86
- { distance: 50, ratio: 0.1 },
87
- ];
88
- /**
89
- * Simple vertex decimation by merging nearby vertices.
90
- * Returns a new geometry with reduced vertex count.
91
- */
92
- function decimateGeometry(geometry, ratio) {
93
- if (ratio >= 1.0) {
94
- return geometry.clone();
268
+ },
269
+ // Natural
270
+ {
271
+ name: "Wood (Light)",
272
+ category: "Natural",
273
+ params: { color: 12687979, metalness: 0, roughness: 0.7 }
274
+ },
275
+ {
276
+ name: "Wood (Dark)",
277
+ category: "Natural",
278
+ params: { color: 6042391, metalness: 0, roughness: 0.65 }
279
+ },
280
+ {
281
+ name: "Stone",
282
+ category: "Natural",
283
+ params: { color: 8947848, metalness: 0, roughness: 0.85 }
284
+ },
285
+ {
286
+ name: "Marble",
287
+ category: "Natural",
288
+ params: { color: 15789280, metalness: 0, roughness: 0.15 }
289
+ },
290
+ {
291
+ name: "Clay",
292
+ category: "Natural",
293
+ params: { color: 11887901, metalness: 0, roughness: 0.8 }
294
+ },
295
+ {
296
+ name: "Concrete",
297
+ category: "Natural",
298
+ params: { color: 10066329, metalness: 0, roughness: 0.95 }
299
+ },
300
+ // Fabric
301
+ {
302
+ name: "Cotton (White)",
303
+ category: "Fabric",
304
+ params: {
305
+ color: 16119260,
306
+ metalness: 0,
307
+ roughness: 0.9,
308
+ sheen: 0.5,
309
+ sheenRoughness: 0.8,
310
+ sheenColor: 16777215
95
311
  }
96
- const posAttr = geometry.getAttribute('position');
97
- if (!posAttr) {
98
- return geometry.clone();
312
+ },
313
+ {
314
+ name: "Silk",
315
+ category: "Fabric",
316
+ params: {
317
+ color: 14932681,
318
+ metalness: 0,
319
+ roughness: 0.3,
320
+ sheen: 1,
321
+ sheenRoughness: 0.3,
322
+ sheenColor: 16772829
99
323
  }
100
- const positions = Array.from(posAttr.array);
101
- const vertexCount = posAttr.count;
102
- const targetCount = Math.max(3, Math.floor(vertexCount * ratio));
103
- // Grid-based vertex merging
104
- const gridSize = Math.cbrt(vertexCount / targetCount) * 0.5;
105
- const merged = new Map();
106
- const vertexMap = new Int32Array(vertexCount);
107
- let newIndex = 0;
108
- for (let i = 0; i < vertexCount; i++) {
109
- const x = positions[i * 3];
110
- const y = positions[i * 3 + 1];
111
- const z = positions[i * 3 + 2];
112
- const key = `${Math.round(x / gridSize)},${Math.round(y / gridSize)},${Math.round(z / gridSize)}`;
113
- const existing = merged.get(key);
114
- if (existing) {
115
- existing.x += x;
116
- existing.y += y;
117
- existing.z += z;
118
- existing.count++;
119
- vertexMap[i] = existing.index;
120
- }
121
- else {
122
- merged.set(key, { x, y, z, count: 1, index: newIndex });
123
- vertexMap[i] = newIndex;
124
- newIndex++;
125
- }
324
+ },
325
+ {
326
+ name: "Velvet",
327
+ category: "Fabric",
328
+ params: {
329
+ color: 8388640,
330
+ metalness: 0,
331
+ roughness: 0.95,
332
+ sheen: 0.9,
333
+ sheenRoughness: 0.9,
334
+ sheenColor: 16728160
126
335
  }
127
- // Build new positions
128
- const newPositions = new Float32Array(merged.size * 3);
129
- for (const cell of merged.values()) {
130
- newPositions[cell.index * 3] = cell.x / cell.count;
131
- newPositions[cell.index * 3 + 1] = cell.y / cell.count;
132
- newPositions[cell.index * 3 + 2] = cell.z / cell.count;
336
+ },
337
+ {
338
+ name: "Leather",
339
+ category: "Fabric",
340
+ params: { color: 5583650, metalness: 0, roughness: 0.6 }
341
+ },
342
+ {
343
+ name: "Denim",
344
+ category: "Fabric",
345
+ params: { color: 3889560, metalness: 0, roughness: 0.85 }
346
+ },
347
+ // Skin
348
+ {
349
+ name: "Skin (Light)",
350
+ category: "Skin",
351
+ params: { color: 16767916, metalness: 0, roughness: 0.55 }
352
+ },
353
+ {
354
+ name: "Skin (Medium)",
355
+ category: "Skin",
356
+ params: { color: 13010498, metalness: 0, roughness: 0.5 }
357
+ },
358
+ {
359
+ name: "Skin (Dark)",
360
+ category: "Skin",
361
+ params: { color: 9262372, metalness: 0, roughness: 0.5 }
362
+ },
363
+ // Special
364
+ {
365
+ name: "Car Paint",
366
+ category: "Special",
367
+ params: {
368
+ color: 13369344,
369
+ metalness: 0.5,
370
+ roughness: 0.15,
371
+ clearcoat: 1,
372
+ clearcoatRoughness: 0.05
133
373
  }
134
- // Build new indices
135
- const oldIndex = geometry.index;
136
- const newIndices = [];
137
- if (oldIndex) {
138
- for (let i = 0; i < oldIndex.count; i += 3) {
139
- const a = vertexMap[oldIndex.array[i]];
140
- const b = vertexMap[oldIndex.array[i + 1]];
141
- const c = vertexMap[oldIndex.array[i + 2]];
142
- if (a !== b && b !== c && a !== c) {
143
- newIndices.push(a, b, c);
144
- }
145
- }
374
+ },
375
+ {
376
+ name: "Emissive (Blue)",
377
+ category: "Special",
378
+ params: {
379
+ color: 1118481,
380
+ metalness: 0,
381
+ roughness: 0.5,
382
+ emissive: 35071,
383
+ emissiveIntensity: 2
146
384
  }
147
- else {
148
- for (let i = 0; i < vertexCount; i += 3) {
149
- const a = vertexMap[i];
150
- const b = vertexMap[i + 1];
151
- const c = vertexMap[i + 2];
152
- if (a !== b && b !== c && a !== c) {
153
- newIndices.push(a, b, c);
154
- }
155
- }
385
+ },
386
+ {
387
+ name: "Emissive (Orange)",
388
+ category: "Special",
389
+ params: {
390
+ color: 1118481,
391
+ metalness: 0,
392
+ roughness: 0.5,
393
+ emissive: 16737792,
394
+ emissiveIntensity: 2
156
395
  }
157
- const result = new THREE__namespace.BufferGeometry();
158
- result.setAttribute('position', new THREE__namespace.BufferAttribute(newPositions, 3));
159
- result.setIndex(newIndices);
160
- result.computeVertexNormals();
161
- return result;
162
- }
163
- /**
164
- * Creates a THREE.LOD object from a mesh with automatic level generation.
165
- */
166
- function generateLOD(mesh, levels = DEFAULT_LOD_LEVELS) {
167
- const lod = new THREE__namespace.LOD();
168
- lod.name = mesh.name + '_LOD';
169
- lod.position.copy(mesh.position);
170
- lod.rotation.copy(mesh.rotation);
171
- lod.scale.copy(mesh.scale);
172
- for (const level of levels) {
173
- const decimated = decimateGeometry(mesh.geometry, level.ratio);
174
- const mat = Array.isArray(mesh.material)
175
- ? mesh.material.map((m) => m.clone())
176
- : mesh.material.clone();
177
- const lodMesh = new THREE__namespace.Mesh(decimated, mat);
178
- lodMesh.castShadow = mesh.castShadow;
179
- lodMesh.receiveShadow = mesh.receiveShadow;
180
- lod.addLevel(lodMesh, level.distance);
396
+ },
397
+ {
398
+ name: "Neon",
399
+ category: "Special",
400
+ params: {
401
+ color: 0,
402
+ metalness: 0,
403
+ roughness: 0.1,
404
+ emissive: 65416,
405
+ emissiveIntensity: 5
181
406
  }
182
- return lod;
183
- }
184
- const PBR_PRESETS = [
185
- // Metals
186
- { name: 'Gold', category: 'Metal', params: { color: 0xffd700, metalness: 1.0, roughness: 0.15 } },
187
- {
188
- name: 'Silver',
189
- category: 'Metal',
190
- params: { color: 0xc0c0c0, metalness: 1.0, roughness: 0.1 },
191
- },
192
- {
193
- name: 'Copper',
194
- category: 'Metal',
195
- params: { color: 0xb87333, metalness: 1.0, roughness: 0.25 },
196
- },
197
- { name: 'Iron', category: 'Metal', params: { color: 0x808080, metalness: 0.95, roughness: 0.6 } },
198
- {
199
- name: 'Brushed Steel',
200
- category: 'Metal',
201
- params: { color: 0xa0a0a0, metalness: 0.95, roughness: 0.4 },
202
- },
203
- {
204
- name: 'Chrome',
205
- category: 'Metal',
206
- params: { color: 0xdddddd, metalness: 1.0, roughness: 0.02 },
207
- },
208
- {
209
- name: 'Bronze',
210
- category: 'Metal',
211
- params: { color: 0xcd7f32, metalness: 0.9, roughness: 0.35 },
212
- },
213
- {
214
- name: 'Aluminum',
215
- category: 'Metal',
216
- params: { color: 0xd6d6d6, metalness: 0.85, roughness: 0.2 },
217
- },
218
- // Non-metals
219
- {
220
- name: 'Plastic (White)',
221
- category: 'Plastic',
222
- params: { color: 0xf0f0f0, metalness: 0.0, roughness: 0.3 },
223
- },
224
- {
225
- name: 'Plastic (Red)',
226
- category: 'Plastic',
227
- params: { color: 0xcc2222, metalness: 0.0, roughness: 0.35 },
228
- },
229
- {
230
- name: 'Rubber',
231
- category: 'Plastic',
232
- params: { color: 0x333333, metalness: 0.0, roughness: 0.9 },
233
- },
234
- // Glass
235
- {
236
- name: 'Clear Glass',
237
- category: 'Glass',
238
- params: {
239
- color: 0xffffff,
240
- metalness: 0.0,
241
- roughness: 0.0,
242
- opacity: 0.15,
243
- transparent: true,
244
- ior: 1.5,
245
- transmission: 0.95,
246
- },
247
- },
248
- {
249
- name: 'Frosted Glass',
250
- category: 'Glass',
251
- params: {
252
- color: 0xeeeeff,
253
- metalness: 0.0,
254
- roughness: 0.5,
255
- opacity: 0.3,
256
- transparent: true,
257
- ior: 1.5,
258
- transmission: 0.7,
259
- },
260
- },
261
- {
262
- name: 'Tinted Glass',
263
- category: 'Glass',
264
- params: {
265
- color: 0x224488,
266
- metalness: 0.0,
267
- roughness: 0.05,
268
- opacity: 0.3,
269
- transparent: true,
270
- ior: 1.5,
271
- transmission: 0.8,
272
- },
273
- },
274
- // Natural
275
- {
276
- name: 'Wood (Light)',
277
- category: 'Natural',
278
- params: { color: 0xc19a6b, metalness: 0.0, roughness: 0.7 },
279
- },
280
- {
281
- name: 'Wood (Dark)',
282
- category: 'Natural',
283
- params: { color: 0x5c3317, metalness: 0.0, roughness: 0.65 },
284
- },
285
- {
286
- name: 'Stone',
287
- category: 'Natural',
288
- params: { color: 0x888888, metalness: 0.0, roughness: 0.85 },
289
- },
290
- {
291
- name: 'Marble',
292
- category: 'Natural',
293
- params: { color: 0xf0ece0, metalness: 0.0, roughness: 0.15 },
294
- },
295
- {
296
- name: 'Clay',
297
- category: 'Natural',
298
- params: { color: 0xb5651d, metalness: 0.0, roughness: 0.8 },
299
- },
300
- {
301
- name: 'Concrete',
302
- category: 'Natural',
303
- params: { color: 0x999999, metalness: 0.0, roughness: 0.95 },
304
- },
305
- // Fabric
306
- {
307
- name: 'Cotton (White)',
308
- category: 'Fabric',
309
- params: {
310
- color: 0xf5f5dc,
311
- metalness: 0.0,
312
- roughness: 0.9,
313
- sheen: 0.5,
314
- sheenRoughness: 0.8,
315
- sheenColor: 0xffffff,
316
- },
317
- },
318
- {
319
- name: 'Silk',
320
- category: 'Fabric',
321
- params: {
322
- color: 0xe3dac9,
323
- metalness: 0.0,
324
- roughness: 0.3,
325
- sheen: 1.0,
326
- sheenRoughness: 0.3,
327
- sheenColor: 0xffeedd,
328
- },
329
- },
330
- {
331
- name: 'Velvet',
332
- category: 'Fabric',
333
- params: {
334
- color: 0x800020,
335
- metalness: 0.0,
336
- roughness: 0.95,
337
- sheen: 0.9,
338
- sheenRoughness: 0.9,
339
- sheenColor: 0xff4060,
340
- },
341
- },
342
- {
343
- name: 'Leather',
344
- category: 'Fabric',
345
- params: { color: 0x553322, metalness: 0.0, roughness: 0.6 },
346
- },
347
- {
348
- name: 'Denim',
349
- category: 'Fabric',
350
- params: { color: 0x3b5998, metalness: 0.0, roughness: 0.85 },
351
- },
352
- // Skin
353
- {
354
- name: 'Skin (Light)',
355
- category: 'Skin',
356
- params: { color: 0xffdbac, metalness: 0.0, roughness: 0.55 },
357
- },
358
- {
359
- name: 'Skin (Medium)',
360
- category: 'Skin',
361
- params: { color: 0xc68642, metalness: 0.0, roughness: 0.5 },
362
- },
363
- {
364
- name: 'Skin (Dark)',
365
- category: 'Skin',
366
- params: { color: 0x8d5524, metalness: 0.0, roughness: 0.5 },
367
- },
368
- // Special
369
- {
370
- name: 'Car Paint',
371
- category: 'Special',
372
- params: {
373
- color: 0xcc0000,
374
- metalness: 0.5,
375
- roughness: 0.15,
376
- clearcoat: 1.0,
377
- clearcoatRoughness: 0.05,
378
- },
379
- },
380
- {
381
- name: 'Emissive (Blue)',
382
- category: 'Special',
383
- params: {
384
- color: 0x111111,
385
- metalness: 0.0,
386
- roughness: 0.5,
387
- emissive: 0x0088ff,
388
- emissiveIntensity: 2.0,
389
- },
390
- },
391
- {
392
- name: 'Emissive (Orange)',
393
- category: 'Special',
394
- params: {
395
- color: 0x111111,
396
- metalness: 0.0,
397
- roughness: 0.5,
398
- emissive: 0xff6600,
399
- emissiveIntensity: 2.0,
400
- },
401
- },
402
- {
403
- name: 'Neon',
404
- category: 'Special',
405
- params: {
406
- color: 0x000000,
407
- metalness: 0.0,
408
- roughness: 0.1,
409
- emissive: 0x00ff88,
410
- emissiveIntensity: 5.0,
411
- },
412
- },
407
+ }
413
408
  ];
414
- /**
415
- * Apply a PBR preset to a MeshStandardMaterial or MeshPhysicalMaterial.
416
- */
417
409
  function applyPBRPreset(material, preset) {
418
- var _a, _b;
419
- const p = preset.params;
420
- // Upgrade to MeshPhysicalMaterial if needed for advanced features
421
- const needsPhysical = p.clearcoat != null || p.transmission != null || p.sheen != null || p.ior != null;
422
- if (material instanceof THREE__namespace.MeshPhysicalMaterial) {
423
- material.color.setHex(p.color);
424
- material.metalness = p.metalness;
425
- material.roughness = p.roughness;
426
- if (p.emissive != null) {
427
- material.emissive.setHex(p.emissive);
428
- }
429
- if (p.emissiveIntensity != null) {
430
- material.emissiveIntensity = p.emissiveIntensity;
431
- }
432
- if (p.opacity != null) {
433
- material.opacity = p.opacity;
434
- }
435
- material.transparent = (_a = p.transparent) !== null && _a !== void 0 ? _a : false;
436
- if (p.clearcoat != null) {
437
- material.clearcoat = p.clearcoat;
438
- }
439
- if (p.clearcoatRoughness != null) {
440
- material.clearcoatRoughness = p.clearcoatRoughness;
441
- }
442
- if (p.ior != null) {
443
- material.ior = p.ior;
444
- }
445
- if (p.transmission != null) {
446
- material.transmission = p.transmission;
447
- }
448
- if (p.sheen != null) {
449
- material.sheen = p.sheen;
450
- }
451
- if (p.sheenRoughness != null) {
452
- material.sheenRoughness = p.sheenRoughness;
453
- }
454
- if (p.sheenColor != null) {
455
- material.sheenColor.setHex(p.sheenColor);
456
- }
457
- material.needsUpdate = true;
410
+ const p = preset.params;
411
+ const needsPhysical = p.clearcoat != null || p.transmission != null || p.sheen != null || p.ior != null;
412
+ if (material instanceof THREE__namespace.MeshPhysicalMaterial) {
413
+ material.color.setHex(p.color);
414
+ material.metalness = p.metalness;
415
+ material.roughness = p.roughness;
416
+ if (p.emissive != null) {
417
+ material.emissive.setHex(p.emissive);
458
418
  }
459
- else if (material instanceof THREE__namespace.MeshStandardMaterial && !needsPhysical) {
460
- material.color.setHex(p.color);
461
- material.metalness = p.metalness;
462
- material.roughness = p.roughness;
463
- if (p.emissive != null) {
464
- material.emissive.setHex(p.emissive);
465
- }
466
- if (p.emissiveIntensity != null) {
467
- material.emissiveIntensity = p.emissiveIntensity;
468
- }
469
- if (p.opacity != null) {
470
- material.opacity = p.opacity;
471
- }
472
- material.transparent = (_b = p.transparent) !== null && _b !== void 0 ? _b : false;
473
- material.needsUpdate = true;
419
+ if (p.emissiveIntensity != null) {
420
+ material.emissiveIntensity = p.emissiveIntensity;
421
+ }
422
+ if (p.opacity != null) {
423
+ material.opacity = p.opacity;
424
+ }
425
+ material.transparent = p.transparent ?? false;
426
+ if (p.clearcoat != null) {
427
+ material.clearcoat = p.clearcoat;
474
428
  }
429
+ if (p.clearcoatRoughness != null) {
430
+ material.clearcoatRoughness = p.clearcoatRoughness;
431
+ }
432
+ if (p.ior != null) {
433
+ material.ior = p.ior;
434
+ }
435
+ if (p.transmission != null) {
436
+ material.transmission = p.transmission;
437
+ }
438
+ if (p.sheen != null) {
439
+ material.sheen = p.sheen;
440
+ }
441
+ if (p.sheenRoughness != null) {
442
+ material.sheenRoughness = p.sheenRoughness;
443
+ }
444
+ if (p.sheenColor != null) {
445
+ material.sheenColor.setHex(p.sheenColor);
446
+ }
447
+ material.needsUpdate = true;
448
+ } else if (material instanceof THREE__namespace.MeshStandardMaterial && !needsPhysical) {
449
+ material.color.setHex(p.color);
450
+ material.metalness = p.metalness;
451
+ material.roughness = p.roughness;
452
+ if (p.emissive != null) {
453
+ material.emissive.setHex(p.emissive);
454
+ }
455
+ if (p.emissiveIntensity != null) {
456
+ material.emissiveIntensity = p.emissiveIntensity;
457
+ }
458
+ if (p.opacity != null) {
459
+ material.opacity = p.opacity;
460
+ }
461
+ material.transparent = p.transparent ?? false;
462
+ material.needsUpdate = true;
463
+ }
475
464
  }
476
- /** Create a new MeshPhysicalMaterial from a preset. */
477
465
  function createMaterialFromPreset(preset) {
478
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
479
- const p = preset.params;
480
- return new THREE__namespace.MeshPhysicalMaterial({
481
- color: p.color,
482
- metalness: p.metalness,
483
- roughness: p.roughness,
484
- emissive: (_a = p.emissive) !== null && _a !== void 0 ? _a : 0x000000,
485
- emissiveIntensity: (_b = p.emissiveIntensity) !== null && _b !== void 0 ? _b : 0,
486
- opacity: (_c = p.opacity) !== null && _c !== void 0 ? _c : 1,
487
- transparent: (_d = p.transparent) !== null && _d !== void 0 ? _d : false,
488
- clearcoat: (_e = p.clearcoat) !== null && _e !== void 0 ? _e : 0,
489
- clearcoatRoughness: (_f = p.clearcoatRoughness) !== null && _f !== void 0 ? _f : 0,
490
- ior: (_g = p.ior) !== null && _g !== void 0 ? _g : 1.5,
491
- transmission: (_h = p.transmission) !== null && _h !== void 0 ? _h : 0,
492
- sheen: (_j = p.sheen) !== null && _j !== void 0 ? _j : 0,
493
- sheenRoughness: (_k = p.sheenRoughness) !== null && _k !== void 0 ? _k : 0,
494
- sheenColor: new THREE__namespace.Color((_l = p.sheenColor) !== null && _l !== void 0 ? _l : 0x000000),
495
- });
466
+ const p = preset.params;
467
+ return new THREE__namespace.MeshPhysicalMaterial({
468
+ color: p.color,
469
+ metalness: p.metalness,
470
+ roughness: p.roughness,
471
+ emissive: p.emissive ?? 0,
472
+ emissiveIntensity: p.emissiveIntensity ?? 0,
473
+ opacity: p.opacity ?? 1,
474
+ transparent: p.transparent ?? false,
475
+ clearcoat: p.clearcoat ?? 0,
476
+ clearcoatRoughness: p.clearcoatRoughness ?? 0,
477
+ ior: p.ior ?? 1.5,
478
+ transmission: p.transmission ?? 0,
479
+ sheen: p.sheen ?? 0,
480
+ sheenRoughness: p.sheenRoughness ?? 0,
481
+ sheenColor: new THREE__namespace.Color(p.sheenColor ?? 0)
482
+ });
496
483
  }
497
- /* ═══════════════════════════════════════════
498
- 4. INSTANCING
499
- ═══════════════════════════════════════════ */
500
- /**
501
- * Convert duplicate meshes to InstancedMesh for better performance.
502
- * Groups meshes by geometry UUID + material UUID.
503
- */
504
484
  function convertToInstanced(parent) {
505
- var _a;
506
- const groups = new Map();
507
- parent.traverse((child) => {
508
- if (child instanceof THREE__namespace.Mesh && !child.name.startsWith('__')) {
509
- const geoId = child.geometry.uuid;
510
- const matId = Array.isArray(child.material)
511
- ? child.material.map((m) => m.uuid).join(',')
512
- : child.material.uuid;
513
- const key = `${geoId}|${matId}`;
514
- let list = groups.get(key);
515
- if (!list) {
516
- list = [];
517
- groups.set(key, list);
518
- }
519
- list.push(child);
520
- }
521
- });
522
- let created = 0;
523
- let removed = 0;
524
- for (const [, meshes] of groups) {
525
- if (meshes.length < 2) {
526
- continue;
527
- }
528
- const source = meshes[0];
529
- const instancedMesh = new THREE__namespace.InstancedMesh(source.geometry, source.material, meshes.length);
530
- instancedMesh.name = source.name + '_Instanced';
531
- instancedMesh.castShadow = source.castShadow;
532
- instancedMesh.receiveShadow = source.receiveShadow;
533
- const matrix = new THREE__namespace.Matrix4();
534
- for (let i = 0; i < meshes.length; i++) {
535
- meshes[i].updateWorldMatrix(true, false);
536
- matrix.copy(meshes[i].matrixWorld);
537
- instancedMesh.setMatrixAt(i, matrix);
538
- }
539
- instancedMesh.instanceMatrix.needsUpdate = true;
540
- parent.add(instancedMesh);
541
- created++;
542
- for (const mesh of meshes) {
543
- (_a = mesh.parent) === null || _a === void 0 ? void 0 : _a.remove(mesh);
544
- removed++;
545
- }
485
+ const groups = /* @__PURE__ */ new Map();
486
+ parent.traverse((child) => {
487
+ if (child instanceof THREE__namespace.Mesh && !child.name.startsWith("__")) {
488
+ const geoId = child.geometry.uuid;
489
+ const matId = Array.isArray(child.material) ? child.material.map((m) => m.uuid).join(",") : child.material.uuid;
490
+ const key = `${geoId}|${matId}`;
491
+ let list = groups.get(key);
492
+ if (!list) {
493
+ list = [];
494
+ groups.set(key, list);
495
+ }
496
+ list.push(child);
546
497
  }
547
- return { created, removed };
498
+ });
499
+ let created = 0;
500
+ let removed = 0;
501
+ for (const [, meshes] of groups) {
502
+ if (meshes.length < 2) {
503
+ continue;
504
+ }
505
+ const source = meshes[0];
506
+ const instancedMesh = new THREE__namespace.InstancedMesh(source.geometry, source.material, meshes.length);
507
+ instancedMesh.name = source.name + "_Instanced";
508
+ instancedMesh.castShadow = source.castShadow;
509
+ instancedMesh.receiveShadow = source.receiveShadow;
510
+ const matrix = new THREE__namespace.Matrix4();
511
+ for (let i = 0; i < meshes.length; i++) {
512
+ meshes[i].updateWorldMatrix(true, false);
513
+ matrix.copy(meshes[i].matrixWorld);
514
+ instancedMesh.setMatrixAt(i, matrix);
515
+ }
516
+ instancedMesh.instanceMatrix.needsUpdate = true;
517
+ parent.add(instancedMesh);
518
+ created++;
519
+ for (const mesh of meshes) {
520
+ mesh.parent?.remove(mesh);
521
+ removed++;
522
+ }
523
+ }
524
+ return { created, removed };
548
525
  }
549
- /* ═══════════════════════════════════════════
550
- 5. GEOMETRY HELPERS
551
- ═══════════════════════════════════════════ */
552
- /** Count total triangles in a scene. */
553
526
  function countTriangles(object) {
554
- let count = 0;
555
- object.traverse((child) => {
556
- if (child instanceof THREE__namespace.Mesh) {
557
- const geo = child.geometry;
558
- if (geo.index) {
559
- count += geo.index.count / 3;
560
- }
561
- else {
562
- const pos = geo.getAttribute('position');
563
- if (pos) {
564
- count += pos.count / 3;
565
- }
566
- }
527
+ let count = 0;
528
+ object.traverse((child) => {
529
+ if (child instanceof THREE__namespace.Mesh) {
530
+ const geo = child.geometry;
531
+ if (geo.index) {
532
+ count += geo.index.count / 3;
533
+ } else {
534
+ const pos = geo.getAttribute("position");
535
+ if (pos) {
536
+ count += pos.count / 3;
567
537
  }
568
- });
569
- return Math.round(count);
538
+ }
539
+ }
540
+ });
541
+ return Math.round(count);
570
542
  }
571
- /** Compute bounding box for an object and all its children. */
572
543
  function computeSceneBounds(object) {
573
- const box = new THREE__namespace.Box3();
574
- object.traverse((child) => {
575
- if (child instanceof THREE__namespace.Mesh) {
576
- child.geometry.computeBoundingBox();
577
- if (child.geometry.boundingBox) {
578
- const worldBox = child.geometry.boundingBox.clone();
579
- worldBox.applyMatrix4(child.matrixWorld);
580
- box.union(worldBox);
581
- }
582
- }
583
- });
584
- return box;
544
+ const box = new THREE__namespace.Box3();
545
+ object.traverse((child) => {
546
+ if (child instanceof THREE__namespace.Mesh) {
547
+ child.geometry.computeBoundingBox();
548
+ if (child.geometry.boundingBox) {
549
+ const worldBox = child.geometry.boundingBox.clone();
550
+ worldBox.applyMatrix4(child.matrixWorld);
551
+ box.union(worldBox);
552
+ }
553
+ }
554
+ });
555
+ return box;
585
556
  }
586
- /** Measure distance between two world-space points. */
587
557
  function measureDistance(a, b) {
588
- return a.distanceTo(b);
558
+ return a.distanceTo(b);
589
559
  }
590
- /** Measure angle (in degrees) between three world-space points (vertex at B). */
591
560
  function measureAngle(a, b, c) {
592
- const ba = new THREE__namespace.Vector3().subVectors(a, b).normalize();
593
- const bc = new THREE__namespace.Vector3().subVectors(c, b).normalize();
594
- return THREE__namespace.MathUtils.radToDeg(Math.acos(THREE__namespace.MathUtils.clamp(ba.dot(bc), -1, 1)));
561
+ const ba = new THREE__namespace.Vector3().subVectors(a, b).normalize();
562
+ const bc = new THREE__namespace.Vector3().subVectors(c, b).normalize();
563
+ return THREE__namespace.MathUtils.radToDeg(Math.acos(THREE__namespace.MathUtils.clamp(ba.dot(bc), -1, 1)));
595
564
  }
596
565
  const HDRI_PRESETS = [
597
- // Studio
598
- {
599
- name: 'Studio Soft',
600
- category: 'Studio',
601
- bgColor: 0x1a1a2e,
602
- envIntensity: 1.0,
603
- ambientColor: 0xffffff,
604
- ambientIntensity: 0.6,
605
- dirColor: 0xffffff,
606
- dirIntensity: 1.2,
607
- dirPosition: [5, 10, 7],
608
- exposure: 1.0,
609
- },
610
- {
611
- name: 'Studio High Key',
612
- category: 'Studio',
613
- bgColor: 0xf0f0f0,
614
- envIntensity: 1.2,
615
- ambientColor: 0xffffff,
616
- ambientIntensity: 1.0,
617
- dirColor: 0xffffff,
618
- dirIntensity: 1.5,
619
- dirPosition: [2, 8, 3],
620
- exposure: 1.2,
621
- },
622
- {
623
- name: 'Studio Low Key',
624
- category: 'Studio',
625
- bgColor: 0x0a0a0a,
626
- envIntensity: 0.5,
627
- ambientColor: 0x222244,
628
- ambientIntensity: 0.2,
629
- dirColor: 0xffeedd,
630
- dirIntensity: 2.0,
631
- dirPosition: [3, 5, 2],
632
- exposure: 0.8,
633
- },
634
- {
635
- name: 'Studio Rim',
636
- category: 'Studio',
637
- bgColor: 0x111111,
638
- envIntensity: 0.3,
639
- ambientColor: 0x111122,
640
- ambientIntensity: 0.15,
641
- dirColor: 0xffffff,
642
- dirIntensity: 2.5,
643
- dirPosition: [-5, 3, -2],
644
- exposure: 0.9,
645
- },
646
- // Outdoor
647
- {
648
- name: 'Sunny Day',
649
- category: 'Outdoor',
650
- bgColor: 0x87ceeb,
651
- envIntensity: 1.0,
652
- ambientColor: 0x8ec8f0,
653
- ambientIntensity: 0.4,
654
- dirColor: 0xfff4e0,
655
- dirIntensity: 2.0,
656
- dirPosition: [5, 10, 5],
657
- exposure: 1.0,
658
- },
659
- {
660
- name: 'Overcast',
661
- category: 'Outdoor',
662
- bgColor: 0xb0b0b8,
663
- envIntensity: 0.8,
664
- ambientColor: 0xccccdd,
665
- ambientIntensity: 0.7,
666
- dirColor: 0xddddee,
667
- dirIntensity: 0.5,
668
- dirPosition: [0, 10, 0],
669
- fog: { color: 0xccccdd, near: 20, far: 200 },
670
- exposure: 1.0,
671
- },
672
- {
673
- name: 'Golden Hour',
674
- category: 'Outdoor',
675
- bgColor: 0xff8844,
676
- envIntensity: 1.0,
677
- ambientColor: 0xffaa55,
678
- ambientIntensity: 0.3,
679
- dirColor: 0xff9933,
680
- dirIntensity: 2.5,
681
- dirPosition: [10, 2, 5],
682
- exposure: 1.1,
683
- },
684
- {
685
- name: 'Blue Hour',
686
- category: 'Outdoor',
687
- bgColor: 0x1a2a4a,
688
- envIntensity: 0.6,
689
- ambientColor: 0x334466,
690
- ambientIntensity: 0.3,
691
- dirColor: 0x6688cc,
692
- dirIntensity: 0.8,
693
- dirPosition: [-5, 3, 5],
694
- exposure: 0.8,
695
- },
696
- {
697
- name: 'Night',
698
- category: 'Outdoor',
699
- bgColor: 0x050510,
700
- envIntensity: 0.1,
701
- ambientColor: 0x111133,
702
- ambientIntensity: 0.05,
703
- dirColor: 0xaabbdd,
704
- dirIntensity: 0.3,
705
- dirPosition: [-3, 8, 2],
706
- exposure: 0.5,
707
- },
708
- // Interior
709
- {
710
- name: 'Warm Interior',
711
- category: 'Interior',
712
- bgColor: 0x2a1a10,
713
- envIntensity: 0.7,
714
- ambientColor: 0xffddaa,
715
- ambientIntensity: 0.4,
716
- dirColor: 0xffeebb,
717
- dirIntensity: 1.0,
718
- dirPosition: [2, 5, 3],
719
- exposure: 1.0,
720
- },
721
- {
722
- name: 'Cool Office',
723
- category: 'Interior',
724
- bgColor: 0x1a2230,
725
- envIntensity: 0.8,
726
- ambientColor: 0xddeeff,
727
- ambientIntensity: 0.6,
728
- dirColor: 0xffffff,
729
- dirIntensity: 0.8,
730
- dirPosition: [0, 8, 0],
731
- exposure: 1.0,
732
- },
733
- {
734
- name: 'Gallery',
735
- category: 'Interior',
736
- bgColor: 0xfafafa,
737
- envIntensity: 1.0,
738
- ambientColor: 0xffffff,
739
- ambientIntensity: 0.8,
740
- dirColor: 0xffffff,
741
- dirIntensity: 0.6,
742
- dirPosition: [0, 10, 2],
743
- exposure: 1.1,
744
- },
745
- // Dramatic
746
- {
747
- name: 'Neon City',
748
- category: 'Dramatic',
749
- bgColor: 0x0a0015,
750
- envIntensity: 0.4,
751
- ambientColor: 0xff00ff,
752
- ambientIntensity: 0.2,
753
- dirColor: 0x00ffff,
754
- dirIntensity: 1.5,
755
- dirPosition: [-3, 5, 2],
756
- exposure: 1.0,
757
- },
758
- {
759
- name: 'Cinematic',
760
- category: 'Dramatic',
761
- bgColor: 0x0f0f1a,
762
- envIntensity: 0.5,
763
- ambientColor: 0x223344,
764
- ambientIntensity: 0.15,
765
- dirColor: 0xffeedd,
766
- dirIntensity: 2.0,
767
- dirPosition: [8, 4, 2],
768
- exposure: 0.9,
769
- },
770
- {
771
- name: 'Sunrise',
772
- category: 'Dramatic',
773
- bgColor: 0xff6633,
774
- envIntensity: 0.8,
775
- ambientColor: 0xffaa66,
776
- ambientIntensity: 0.3,
777
- dirColor: 0xff7744,
778
- dirIntensity: 2.0,
779
- dirPosition: [15, 1, 0],
780
- exposure: 1.2,
781
- },
782
- {
783
- name: 'Desert',
784
- category: 'Dramatic',
785
- bgColor: 0xe8c888,
786
- envIntensity: 1.0,
787
- ambientColor: 0xddcc99,
788
- ambientIntensity: 0.5,
789
- dirColor: 0xfff0cc,
790
- dirIntensity: 2.5,
791
- dirPosition: [5, 12, 3],
792
- exposure: 1.3,
793
- },
566
+ // Studio
567
+ {
568
+ name: "Studio Soft",
569
+ category: "Studio",
570
+ bgColor: 1710638,
571
+ envIntensity: 1,
572
+ ambientColor: 16777215,
573
+ ambientIntensity: 0.6,
574
+ dirColor: 16777215,
575
+ dirIntensity: 1.2,
576
+ dirPosition: [5, 10, 7],
577
+ exposure: 1
578
+ },
579
+ {
580
+ name: "Studio High Key",
581
+ category: "Studio",
582
+ bgColor: 15790320,
583
+ envIntensity: 1.2,
584
+ ambientColor: 16777215,
585
+ ambientIntensity: 1,
586
+ dirColor: 16777215,
587
+ dirIntensity: 1.5,
588
+ dirPosition: [2, 8, 3],
589
+ exposure: 1.2
590
+ },
591
+ {
592
+ name: "Studio Low Key",
593
+ category: "Studio",
594
+ bgColor: 657930,
595
+ envIntensity: 0.5,
596
+ ambientColor: 2236996,
597
+ ambientIntensity: 0.2,
598
+ dirColor: 16772829,
599
+ dirIntensity: 2,
600
+ dirPosition: [3, 5, 2],
601
+ exposure: 0.8
602
+ },
603
+ {
604
+ name: "Studio Rim",
605
+ category: "Studio",
606
+ bgColor: 1118481,
607
+ envIntensity: 0.3,
608
+ ambientColor: 1118498,
609
+ ambientIntensity: 0.15,
610
+ dirColor: 16777215,
611
+ dirIntensity: 2.5,
612
+ dirPosition: [-5, 3, -2],
613
+ exposure: 0.9
614
+ },
615
+ // Outdoor
616
+ {
617
+ name: "Sunny Day",
618
+ category: "Outdoor",
619
+ bgColor: 8900331,
620
+ envIntensity: 1,
621
+ ambientColor: 9357552,
622
+ ambientIntensity: 0.4,
623
+ dirColor: 16774368,
624
+ dirIntensity: 2,
625
+ dirPosition: [5, 10, 5],
626
+ exposure: 1
627
+ },
628
+ {
629
+ name: "Overcast",
630
+ category: "Outdoor",
631
+ bgColor: 11579576,
632
+ envIntensity: 0.8,
633
+ ambientColor: 13421789,
634
+ ambientIntensity: 0.7,
635
+ dirColor: 14540270,
636
+ dirIntensity: 0.5,
637
+ dirPosition: [0, 10, 0],
638
+ fog: { color: 13421789, near: 20, far: 200 },
639
+ exposure: 1
640
+ },
641
+ {
642
+ name: "Golden Hour",
643
+ category: "Outdoor",
644
+ bgColor: 16746564,
645
+ envIntensity: 1,
646
+ ambientColor: 16755285,
647
+ ambientIntensity: 0.3,
648
+ dirColor: 16750899,
649
+ dirIntensity: 2.5,
650
+ dirPosition: [10, 2, 5],
651
+ exposure: 1.1
652
+ },
653
+ {
654
+ name: "Blue Hour",
655
+ category: "Outdoor",
656
+ bgColor: 1714762,
657
+ envIntensity: 0.6,
658
+ ambientColor: 3359846,
659
+ ambientIntensity: 0.3,
660
+ dirColor: 6719692,
661
+ dirIntensity: 0.8,
662
+ dirPosition: [-5, 3, 5],
663
+ exposure: 0.8
664
+ },
665
+ {
666
+ name: "Night",
667
+ category: "Outdoor",
668
+ bgColor: 328976,
669
+ envIntensity: 0.1,
670
+ ambientColor: 1118515,
671
+ ambientIntensity: 0.05,
672
+ dirColor: 11189213,
673
+ dirIntensity: 0.3,
674
+ dirPosition: [-3, 8, 2],
675
+ exposure: 0.5
676
+ },
677
+ // Interior
678
+ {
679
+ name: "Warm Interior",
680
+ category: "Interior",
681
+ bgColor: 2759184,
682
+ envIntensity: 0.7,
683
+ ambientColor: 16768426,
684
+ ambientIntensity: 0.4,
685
+ dirColor: 16772795,
686
+ dirIntensity: 1,
687
+ dirPosition: [2, 5, 3],
688
+ exposure: 1
689
+ },
690
+ {
691
+ name: "Cool Office",
692
+ category: "Interior",
693
+ bgColor: 1712688,
694
+ envIntensity: 0.8,
695
+ ambientColor: 14544639,
696
+ ambientIntensity: 0.6,
697
+ dirColor: 16777215,
698
+ dirIntensity: 0.8,
699
+ dirPosition: [0, 8, 0],
700
+ exposure: 1
701
+ },
702
+ {
703
+ name: "Gallery",
704
+ category: "Interior",
705
+ bgColor: 16448250,
706
+ envIntensity: 1,
707
+ ambientColor: 16777215,
708
+ ambientIntensity: 0.8,
709
+ dirColor: 16777215,
710
+ dirIntensity: 0.6,
711
+ dirPosition: [0, 10, 2],
712
+ exposure: 1.1
713
+ },
714
+ // Dramatic
715
+ {
716
+ name: "Neon City",
717
+ category: "Dramatic",
718
+ bgColor: 655381,
719
+ envIntensity: 0.4,
720
+ ambientColor: 16711935,
721
+ ambientIntensity: 0.2,
722
+ dirColor: 65535,
723
+ dirIntensity: 1.5,
724
+ dirPosition: [-3, 5, 2],
725
+ exposure: 1
726
+ },
727
+ {
728
+ name: "Cinematic",
729
+ category: "Dramatic",
730
+ bgColor: 986906,
731
+ envIntensity: 0.5,
732
+ ambientColor: 2241348,
733
+ ambientIntensity: 0.15,
734
+ dirColor: 16772829,
735
+ dirIntensity: 2,
736
+ dirPosition: [8, 4, 2],
737
+ exposure: 0.9
738
+ },
739
+ {
740
+ name: "Sunrise",
741
+ category: "Dramatic",
742
+ bgColor: 16737843,
743
+ envIntensity: 0.8,
744
+ ambientColor: 16755302,
745
+ ambientIntensity: 0.3,
746
+ dirColor: 16742212,
747
+ dirIntensity: 2,
748
+ dirPosition: [15, 1, 0],
749
+ exposure: 1.2
750
+ },
751
+ {
752
+ name: "Desert",
753
+ category: "Dramatic",
754
+ bgColor: 15255688,
755
+ envIntensity: 1,
756
+ ambientColor: 14535833,
757
+ ambientIntensity: 0.5,
758
+ dirColor: 16773324,
759
+ dirIntensity: 2.5,
760
+ dirPosition: [5, 12, 3],
761
+ exposure: 1.3
762
+ }
794
763
  ];
795
- /**
796
- * Apply an HDRI preset to a Three.js scene and renderer.
797
- */
798
764
  function applyHDRIPreset(scene, renderer, preset) {
799
- // Background
800
- scene.background = new THREE__namespace.Color(preset.bgColor);
801
- // Tone mapping exposure
802
- renderer.toneMappingExposure = preset.exposure;
803
- // Update ambient light (find existing or create)
804
- let ambient;
805
- let directional;
806
- scene.traverse((child) => {
807
- if (child instanceof THREE__namespace.AmbientLight && !ambient) {
808
- ambient = child;
809
- }
810
- if (child instanceof THREE__namespace.DirectionalLight && !directional) {
811
- directional = child;
812
- }
813
- });
814
- if (ambient != null) {
815
- ambient.color.setHex(preset.ambientColor);
816
- ambient.intensity = preset.ambientIntensity;
817
- }
818
- if (directional != null) {
819
- directional.color.setHex(preset.dirColor);
820
- directional.intensity = preset.dirIntensity;
821
- directional.position.set(...preset.dirPosition);
765
+ scene.background = new THREE__namespace.Color(preset.bgColor);
766
+ renderer.toneMappingExposure = preset.exposure;
767
+ let ambient;
768
+ let directional;
769
+ scene.traverse((child) => {
770
+ if (child instanceof THREE__namespace.AmbientLight && !ambient) {
771
+ ambient = child;
822
772
  }
823
- // Fog
824
- if (preset.fog) {
825
- scene.fog = new THREE__namespace.Fog(preset.fog.color, preset.fog.near, preset.fog.far);
826
- }
827
- else {
828
- scene.fog = null;
773
+ if (child instanceof THREE__namespace.DirectionalLight && !directional) {
774
+ directional = child;
829
775
  }
776
+ });
777
+ if (ambient != null) {
778
+ ambient.color.setHex(preset.ambientColor);
779
+ ambient.intensity = preset.ambientIntensity;
780
+ }
781
+ if (directional != null) {
782
+ directional.color.setHex(preset.dirColor);
783
+ directional.intensity = preset.dirIntensity;
784
+ directional.position.set(...preset.dirPosition);
785
+ }
786
+ if (preset.fog) {
787
+ scene.fog = new THREE__namespace.Fog(preset.fog.color, preset.fog.near, preset.fog.far);
788
+ } else {
789
+ scene.fog = null;
790
+ }
830
791
  }
831
- /** Snap a point to the nearest grid intersection. */
832
792
  function snapToGrid(point, gridSize) {
833
- return new THREE__namespace.Vector3(Math.round(point.x / gridSize) * gridSize, Math.round(point.y / gridSize) * gridSize, Math.round(point.z / gridSize) * gridSize);
793
+ return new THREE__namespace.Vector3(
794
+ Math.round(point.x / gridSize) * gridSize,
795
+ Math.round(point.y / gridSize) * gridSize,
796
+ Math.round(point.z / gridSize) * gridSize
797
+ );
834
798
  }
835
- /** Find the closest vertex in a mesh to a given world-space point. */
836
799
  function snapToVertex(point, scene, maxDistance = Infinity) {
837
- let closest = null;
838
- let minDist = maxDistance;
839
- const tmpV = new THREE__namespace.Vector3();
840
- scene.traverse((child) => {
841
- if (!(child instanceof THREE__namespace.Mesh)) {
842
- return;
843
- }
844
- const posAttr = child.geometry.getAttribute('position');
845
- if (!posAttr) {
846
- return;
847
- }
848
- child.updateWorldMatrix(true, false);
849
- for (let i = 0; i < posAttr.count; i++) {
850
- tmpV.fromBufferAttribute(posAttr, i);
851
- tmpV.applyMatrix4(child.matrixWorld);
852
- const d = point.distanceTo(tmpV);
853
- if (d < minDist) {
854
- minDist = d;
855
- closest = { point: tmpV.clone(), target: 'vertex', object: child };
856
- }
857
- }
858
- });
859
- return closest;
800
+ let closest = null;
801
+ let minDist = maxDistance;
802
+ const tmpV = new THREE__namespace.Vector3();
803
+ scene.traverse((child) => {
804
+ if (!(child instanceof THREE__namespace.Mesh)) {
805
+ return;
806
+ }
807
+ const posAttr = child.geometry.getAttribute("position");
808
+ if (!posAttr) {
809
+ return;
810
+ }
811
+ child.updateWorldMatrix(true, false);
812
+ for (let i = 0; i < posAttr.count; i++) {
813
+ tmpV.fromBufferAttribute(posAttr, i);
814
+ tmpV.applyMatrix4(child.matrixWorld);
815
+ const d = point.distanceTo(tmpV);
816
+ if (d < minDist) {
817
+ minDist = d;
818
+ closest = { point: tmpV.clone(), target: "vertex", object: child };
819
+ }
820
+ }
821
+ });
822
+ return closest;
860
823
  }
861
- /** Find the closest point on any edge in the scene to a given world-space point. */
862
824
  function snapToEdge(point, scene, maxDistance = Infinity) {
863
- let closest = null;
864
- let minDist = maxDistance;
865
- const a = new THREE__namespace.Vector3();
866
- const b = new THREE__namespace.Vector3();
867
- scene.traverse((child) => {
868
- if (!(child instanceof THREE__namespace.Mesh)) {
869
- return;
870
- }
871
- const posAttr = child.geometry.getAttribute('position');
872
- if (!posAttr) {
873
- return;
874
- }
875
- const index = child.geometry.index;
876
- child.updateWorldMatrix(true, false);
877
- const processEdge = (i0, i1) => {
878
- a.fromBufferAttribute(posAttr, i0).applyMatrix4(child.matrixWorld);
879
- b.fromBufferAttribute(posAttr, i1).applyMatrix4(child.matrixWorld);
880
- const closestOnEdge = closestPointOnSegment(point, a, b);
881
- const d = point.distanceTo(closestOnEdge);
882
- if (d < minDist) {
883
- minDist = d;
884
- closest = { point: closestOnEdge.clone(), target: 'edge', object: child };
885
- }
886
- };
887
- if (index) {
888
- for (let i = 0; i < index.count; i += 3) {
889
- const i0 = index.array[i], i1 = index.array[i + 1], i2 = index.array[i + 2];
890
- processEdge(i0, i1);
891
- processEdge(i1, i2);
892
- processEdge(i2, i0);
893
- }
894
- }
895
- else {
896
- for (let i = 0; i < posAttr.count; i += 3) {
897
- processEdge(i, i + 1);
898
- processEdge(i + 1, i + 2);
899
- processEdge(i + 2, i);
900
- }
901
- }
902
- });
903
- return closest;
825
+ let closest = null;
826
+ let minDist = maxDistance;
827
+ const a = new THREE__namespace.Vector3();
828
+ const b = new THREE__namespace.Vector3();
829
+ scene.traverse((child) => {
830
+ if (!(child instanceof THREE__namespace.Mesh)) {
831
+ return;
832
+ }
833
+ const posAttr = child.geometry.getAttribute("position");
834
+ if (!posAttr) {
835
+ return;
836
+ }
837
+ const index = child.geometry.index;
838
+ child.updateWorldMatrix(true, false);
839
+ const processEdge = (i0, i1) => {
840
+ a.fromBufferAttribute(posAttr, i0).applyMatrix4(child.matrixWorld);
841
+ b.fromBufferAttribute(posAttr, i1).applyMatrix4(child.matrixWorld);
842
+ const closestOnEdge = closestPointOnSegment(point, a, b);
843
+ const d = point.distanceTo(closestOnEdge);
844
+ if (d < minDist) {
845
+ minDist = d;
846
+ closest = { point: closestOnEdge.clone(), target: "edge", object: child };
847
+ }
848
+ };
849
+ if (index) {
850
+ for (let i = 0; i < index.count; i += 3) {
851
+ const i0 = index.array[i], i1 = index.array[i + 1], i2 = index.array[i + 2];
852
+ processEdge(i0, i1);
853
+ processEdge(i1, i2);
854
+ processEdge(i2, i0);
855
+ }
856
+ } else {
857
+ for (let i = 0; i < posAttr.count; i += 3) {
858
+ processEdge(i, i + 1);
859
+ processEdge(i + 1, i + 2);
860
+ processEdge(i + 2, i);
861
+ }
862
+ }
863
+ });
864
+ return closest;
904
865
  }
905
- /** Find closest point on a line segment. */
906
866
  function closestPointOnSegment(p, a, b) {
907
- const ab = new THREE__namespace.Vector3().subVectors(b, a);
908
- const ap = new THREE__namespace.Vector3().subVectors(p, a);
909
- const t = THREE__namespace.MathUtils.clamp(ap.dot(ab) / ab.dot(ab), 0, 1);
910
- return new THREE__namespace.Vector3().addVectors(a, ab.multiplyScalar(t));
867
+ const ab = new THREE__namespace.Vector3().subVectors(b, a);
868
+ const ap = new THREE__namespace.Vector3().subVectors(p, a);
869
+ const t = THREE__namespace.MathUtils.clamp(ap.dot(ab) / ab.dot(ab), 0, 1);
870
+ return new THREE__namespace.Vector3().addVectors(a, ab.multiplyScalar(t));
911
871
  }
912
- /**
913
- * Multi-mode snap: tries vertex/edge/grid in priority order.
914
- * Returns the best snap within maxDistance, or grid snap as fallback.
915
- */
916
- function snapMulti(point, scene, gridSize, modes = ['vertex', 'edge', 'grid'], maxDistance = 0.5) {
917
- for (const mode of modes) {
918
- if (mode === 'vertex') {
919
- const result = snapToVertex(point, scene, maxDistance);
920
- if (result) {
921
- return result;
922
- }
923
- }
924
- else if (mode === 'edge') {
925
- const result = snapToEdge(point, scene, maxDistance);
926
- if (result) {
927
- return result;
928
- }
929
- }
930
- else if (mode === 'grid') {
931
- return { point: snapToGrid(point, gridSize), target: 'grid' };
932
- }
872
+ function snapMulti(point, scene, gridSize, modes = ["vertex", "edge", "grid"], maxDistance = 0.5) {
873
+ for (const mode of modes) {
874
+ if (mode === "vertex") {
875
+ const result = snapToVertex(point, scene, maxDistance);
876
+ if (result) {
877
+ return result;
878
+ }
879
+ } else if (mode === "edge") {
880
+ const result = snapToEdge(point, scene, maxDistance);
881
+ if (result) {
882
+ return result;
883
+ }
884
+ } else if (mode === "grid") {
885
+ return { point: snapToGrid(point, gridSize), target: "grid" };
933
886
  }
934
- return { point: snapToGrid(point, gridSize), target: 'grid' };
887
+ }
888
+ return { point: snapToGrid(point, gridSize), target: "grid" };
935
889
  }
936
- /** Evaluate a cubic bezier segment at parameter t (0-1). */
937
890
  function cubicBezier(p0, p1, p2, p3, t) {
938
- const t2 = t * t;
939
- const t3 = t2 * t;
940
- const mt = 1 - t;
941
- const mt2 = mt * mt;
942
- const mt3 = mt2 * mt;
943
- return mt3 * p0 + 3 * mt2 * t * p1 + 3 * mt * t2 * p2 + t3 * p3;
891
+ const t2 = t * t;
892
+ const t3 = t2 * t;
893
+ const mt = 1 - t;
894
+ const mt2 = mt * mt;
895
+ const mt3 = mt2 * mt;
896
+ return mt3 * p0 + 3 * mt2 * t * p1 + 3 * mt * t2 * p2 + t3 * p3;
944
897
  }
945
- /** Evaluate a keyframe track at a given time. */
946
898
  function evaluateTrack(track, time) {
947
- var _a, _b;
948
- const { keyframes } = track;
949
- if (keyframes.length === 0) {
950
- return 0;
899
+ const { keyframes } = track;
900
+ if (keyframes.length === 0) {
901
+ return 0;
902
+ }
903
+ if (keyframes.length === 1) {
904
+ return keyframes[0].value;
905
+ }
906
+ if (time <= keyframes[0].time) {
907
+ return keyframes[0].value;
908
+ }
909
+ if (time >= keyframes[keyframes.length - 1].time) {
910
+ return keyframes[keyframes.length - 1].value;
911
+ }
912
+ let left = 0;
913
+ for (let i = 1; i < keyframes.length; i++) {
914
+ if (keyframes[i].time >= time) {
915
+ left = i - 1;
916
+ break;
951
917
  }
952
- if (keyframes.length === 1) {
953
- return keyframes[0].value;
954
- }
955
- // Clamp to range
956
- if (time <= keyframes[0].time) {
957
- return keyframes[0].value;
958
- }
959
- if (time >= keyframes[keyframes.length - 1].time) {
960
- return keyframes[keyframes.length - 1].value;
961
- }
962
- // Find surrounding keyframes
963
- let left = 0;
964
- for (let i = 1; i < keyframes.length; i++) {
965
- if (keyframes[i].time >= time) {
966
- left = i - 1;
967
- break;
968
- }
969
- }
970
- const right = left + 1;
971
- const kfL = keyframes[left];
972
- const kfR = keyframes[right];
973
- const dt = kfR.time - kfL.time;
974
- if (dt === 0) {
975
- return kfL.value;
976
- }
977
- const t = (time - kfL.time) / dt;
978
- switch (kfL.interpolation) {
979
- case 'constant':
980
- return kfL.value;
981
- case 'linear':
982
- return kfL.value + (kfR.value - kfL.value) * t;
983
- case 'ease-in':
984
- return kfL.value + (kfR.value - kfL.value) * (t * t);
985
- case 'ease-out':
986
- return kfL.value + (kfR.value - kfL.value) * (1 - (1 - t) * (1 - t));
987
- case 'ease-in-out':
988
- return (kfL.value + (kfR.value - kfL.value) * (t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t)));
989
- case 'bezier': {
990
- const handleR = (_a = kfL.handleRight) !== null && _a !== void 0 ? _a : { y: 0 };
991
- const handleL = (_b = kfR.handleLeft) !== null && _b !== void 0 ? _b : { y: 0 };
992
- return cubicBezier(kfL.value, kfL.value + handleR.y, kfR.value + handleL.y, kfR.value, t);
993
- }
994
- default:
995
- return kfL.value + (kfR.value - kfL.value) * t;
918
+ }
919
+ const right = left + 1;
920
+ const kfL = keyframes[left];
921
+ const kfR = keyframes[right];
922
+ const dt = kfR.time - kfL.time;
923
+ if (dt === 0) {
924
+ return kfL.value;
925
+ }
926
+ const t = (time - kfL.time) / dt;
927
+ switch (kfL.interpolation) {
928
+ case "constant":
929
+ return kfL.value;
930
+ case "linear":
931
+ return kfL.value + (kfR.value - kfL.value) * t;
932
+ case "ease-in":
933
+ return kfL.value + (kfR.value - kfL.value) * (t * t);
934
+ case "ease-out":
935
+ return kfL.value + (kfR.value - kfL.value) * (1 - (1 - t) * (1 - t));
936
+ case "ease-in-out":
937
+ return kfL.value + (kfR.value - kfL.value) * (t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t));
938
+ case "bezier": {
939
+ const handleR = kfL.handleRight ?? { y: 0 };
940
+ const handleL = kfR.handleLeft ?? { y: 0 };
941
+ return cubicBezier(kfL.value, kfL.value + handleR.y, kfR.value + handleL.y, kfR.value, t);
996
942
  }
943
+ default:
944
+ return kfL.value + (kfR.value - kfL.value) * t;
945
+ }
997
946
  }
998
- /** Insert or update a keyframe at a given time. */
999
- function setKeyframe(track, time, value, interpolation = 'bezier') {
1000
- // Find existing keyframe at same time (within epsilon)
1001
- const epsilon = 0.001;
1002
- const existingIdx = track.keyframes.findIndex((kf) => Math.abs(kf.time - time) < epsilon);
1003
- const kf = { time, value, interpolation };
1004
- if (existingIdx >= 0) {
1005
- track.keyframes[existingIdx] = kf;
1006
- }
1007
- else {
1008
- track.keyframes.push(kf);
1009
- track.keyframes.sort((a, b) => a.time - b.time);
1010
- }
1011
- return kf;
947
+ function setKeyframe(track, time, value, interpolation = "bezier") {
948
+ const epsilon = 1e-3;
949
+ const existingIdx = track.keyframes.findIndex((kf2) => Math.abs(kf2.time - time) < epsilon);
950
+ const kf = { time, value, interpolation };
951
+ if (existingIdx >= 0) {
952
+ track.keyframes[existingIdx] = kf;
953
+ } else {
954
+ track.keyframes.push(kf);
955
+ track.keyframes.sort((a, b) => a.time - b.time);
956
+ }
957
+ return kf;
1012
958
  }
1013
- /** Remove a keyframe at a given time. */
1014
959
  function removeKeyframe(track, time) {
1015
- const epsilon = 0.001;
1016
- const idx = track.keyframes.findIndex((kf) => Math.abs(kf.time - time) < epsilon);
1017
- if (idx >= 0) {
1018
- track.keyframes.splice(idx, 1);
1019
- return true;
1020
- }
1021
- return false;
960
+ const epsilon = 1e-3;
961
+ const idx = track.keyframes.findIndex((kf) => Math.abs(kf.time - time) < epsilon);
962
+ if (idx >= 0) {
963
+ track.keyframes.splice(idx, 1);
964
+ return true;
965
+ }
966
+ return false;
1022
967
  }
1023
- /** Convert KeyframeTrack to THREE.KeyframeTrack for use in AnimationClip. */
1024
968
  function toThreeKeyframeTrack(track, sampleRate = 30) {
1025
- if (track.keyframes.length === 0) {
1026
- return new THREE__namespace.NumberKeyframeTrack(track.property, [0], [0]);
1027
- }
1028
- const startTime = track.keyframes[0].time;
1029
- const endTime = track.keyframes[track.keyframes.length - 1].time;
1030
- const duration = endTime - startTime;
1031
- const numSamples = Math.max(2, Math.ceil(duration * sampleRate));
1032
- const times = [];
1033
- const values = [];
1034
- for (let i = 0; i < numSamples; i++) {
1035
- const t = startTime + (duration * i) / (numSamples - 1);
1036
- times.push(t);
1037
- values.push(evaluateTrack(track, t));
1038
- }
1039
- return new THREE__namespace.NumberKeyframeTrack(track.property, times, values);
969
+ if (track.keyframes.length === 0) {
970
+ return new THREE__namespace.NumberKeyframeTrack(track.property, [0], [0]);
971
+ }
972
+ const startTime = track.keyframes[0].time;
973
+ const endTime = track.keyframes[track.keyframes.length - 1].time;
974
+ const duration = endTime - startTime;
975
+ const numSamples = Math.max(2, Math.ceil(duration * sampleRate));
976
+ const times = [];
977
+ const values = [];
978
+ for (let i = 0; i < numSamples; i++) {
979
+ const t = startTime + duration * i / (numSamples - 1);
980
+ times.push(t);
981
+ values.push(evaluateTrack(track, t));
982
+ }
983
+ return new THREE__namespace.NumberKeyframeTrack(track.property, times, values);
1040
984
  }
1041
- /** Create an AnimationClip from multiple KeyframeTracks. */
1042
985
  function createAnimationClip(name, tracks, sampleRate = 30) {
1043
- const threeTracks = tracks.map((t) => toThreeKeyframeTrack(t, sampleRate));
1044
- return new THREE__namespace.AnimationClip(name, -1, threeTracks);
986
+ const threeTracks = tracks.map((t) => toThreeKeyframeTrack(t, sampleRate));
987
+ return new THREE__namespace.AnimationClip(name, -1, threeTracks);
1045
988
  }
1046
- /** Trigger a file download in the browser. */
1047
989
  function downloadBlob(blob, filename) {
1048
- const url = URL.createObjectURL(blob);
1049
- const a = document.createElement('a');
1050
- a.href = url;
1051
- a.download = filename;
1052
- document.body.appendChild(a);
1053
- a.click();
1054
- document.body.removeChild(a);
1055
- URL.revokeObjectURL(url);
990
+ const url = URL.createObjectURL(blob);
991
+ const a = document.createElement("a");
992
+ a.href = url;
993
+ a.download = filename;
994
+ document.body.appendChild(a);
995
+ a.click();
996
+ document.body.removeChild(a);
997
+ URL.revokeObjectURL(url);
1056
998
  }
1057
- /** Export scene to ArrayBuffer as GLTF/GLB with optional Draco. */
1058
999
  async function exportSceneGLTF(scene, options = {}) {
1059
- const { GLTFExporter } = await import('three/examples/jsm/exporters/GLTFExporter.js');
1060
- const exporter = new GLTFExporter();
1061
- return new Promise((resolve, reject) => {
1062
- var _a, _b;
1063
- exporter.parse(scene, (result) => resolve(result), (error) => reject(error), {
1064
- binary: (_a = options.binary) !== null && _a !== void 0 ? _a : true,
1065
- animations: (_b = options.animations) !== null && _b !== void 0 ? _b : [],
1066
- });
1067
- });
1000
+ const { GLTFExporter } = await import("three/examples/jsm/exporters/GLTFExporter.js");
1001
+ const exporter = new GLTFExporter();
1002
+ return new Promise((resolve, reject) => {
1003
+ exporter.parse(
1004
+ scene,
1005
+ (result) => resolve(result),
1006
+ (error) => reject(error),
1007
+ {
1008
+ binary: options.binary ?? true,
1009
+ animations: options.animations ?? []
1010
+ }
1011
+ );
1012
+ });
1068
1013
  }
1069
- /** Export scene to USDZ format (Apple AR). */
1070
1014
  async function exportSceneUSDZ(scene) {
1071
- const { USDZExporter } = await import('three/examples/jsm/exporters/USDZExporter.js');
1072
- const exporter = new USDZExporter();
1073
- const arraybuffer = await new Promise((resolve, reject) => {
1074
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- USDZExporter.parse callback signature varies between three.js minor versions
1075
- exporter.parse(scene, resolve, reject);
1076
- });
1077
- return new Blob([arraybuffer], { type: 'model/vnd.usdz+zip' });
1015
+ const { USDZExporter } = await import("three/examples/jsm/exporters/USDZExporter.js");
1016
+ const exporter = new USDZExporter();
1017
+ const arraybuffer = await new Promise((resolve, reject) => {
1018
+ exporter.parse(scene, resolve, reject);
1019
+ });
1020
+ return new Blob([arraybuffer], { type: "model/vnd.usdz+zip" });
1078
1021
  }
1079
- /**
1080
- * Setup crossfade between two animation actions.
1081
- * Smoothly transitions from current to target over duration.
1082
- */
1083
1022
  function crossfadeAnimations(mixer, fromAction, toAction, duration = 0.5) {
1084
- toAction.reset();
1085
- toAction.setEffectiveTimeScale(1);
1086
- toAction.setEffectiveWeight(1);
1087
- toAction.play();
1088
- fromAction.crossFadeTo(toAction, duration, true);
1023
+ toAction.reset();
1024
+ toAction.setEffectiveTimeScale(1);
1025
+ toAction.setEffectiveWeight(1);
1026
+ toAction.play();
1027
+ fromAction.crossFadeTo(toAction, duration, true);
1089
1028
  }
1090
- /**
1091
- * Setup additive blending for an animation action.
1092
- * Additive animations can layer on top of other animations.
1093
- */
1094
1029
  function makeAdditiveAction(mixer, clip, referenceClip) {
1095
- const action = mixer.clipAction(clip);
1096
- if (referenceClip) {
1097
- THREE__namespace.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);
1098
- }
1099
- action.blendMode = THREE__namespace.AdditiveAnimationBlendMode;
1100
- return action;
1030
+ const action = mixer.clipAction(clip);
1031
+ if (referenceClip) {
1032
+ THREE__namespace.AnimationUtils.makeClipAdditive(clip, 0, referenceClip);
1033
+ }
1034
+ action.blendMode = THREE__namespace.AdditiveAnimationBlendMode;
1035
+ return action;
1101
1036
  }
1102
- /**
1103
- * Apply weighted blend to multiple animation actions.
1104
- * Normalizes weights so they sum to 1.0.
1105
- */
1106
1037
  function blendAnimations(actions) {
1107
- const totalWeight = actions.reduce((sum, a) => sum + a.weight, 0);
1108
- if (totalWeight === 0) {
1109
- return;
1110
- }
1111
- for (const { action, weight } of actions) {
1112
- const normalizedWeight = weight / totalWeight;
1113
- action.setEffectiveWeight(normalizedWeight);
1114
- if (!action.isRunning()) {
1115
- action.play();
1116
- }
1038
+ const totalWeight = actions.reduce((sum, a) => sum + a.weight, 0);
1039
+ if (totalWeight === 0) {
1040
+ return;
1041
+ }
1042
+ for (const { action, weight } of actions) {
1043
+ const normalizedWeight = weight / totalWeight;
1044
+ action.setEffectiveWeight(normalizedWeight);
1045
+ if (!action.isRunning()) {
1046
+ action.play();
1117
1047
  }
1048
+ }
1118
1049
  }
1119
- /**
1120
- * Create a sub-clip from an existing animation clip (trim).
1121
- */
1122
1050
  function subclip(clip, name, startFrame, endFrame, fps = 30) {
1123
- return THREE__namespace.AnimationUtils.subclip(clip, name, startFrame, endFrame, fps);
1051
+ return THREE__namespace.AnimationUtils.subclip(clip, name, startFrame, endFrame, fps);
1124
1052
  }
1125
- /**
1126
- * Simple CCD (Cyclic Coordinate Descent) IK solver.
1127
- * Iteratively rotates each bone in the chain to reach the target.
1128
- */
1129
1053
  function solveIKChain(skeleton, chain) {
1130
- const { bones: boneNames, target, iterations = 10, tolerance = 0.001 } = chain;
1131
- const bones = [];
1132
- for (const name of boneNames) {
1133
- const bone = skeleton.bones.find((b) => b.name === name);
1134
- if (!bone) {
1135
- return false;
1136
- }
1137
- bones.push(bone);
1138
- }
1139
- if (bones.length < 2) {
1140
- return false;
1141
- }
1142
- const endEffector = bones[bones.length - 1];
1143
- const endPos = new THREE__namespace.Vector3();
1144
- const toEnd = new THREE__namespace.Vector3();
1145
- const toTarget = new THREE__namespace.Vector3();
1146
- const axis = new THREE__namespace.Vector3();
1147
- const quat = new THREE__namespace.Quaternion();
1148
- for (let iter = 0; iter < iterations; iter++) {
1149
- // Get end-effector world position
1150
- endEffector.updateWorldMatrix(true, false);
1151
- endPos.setFromMatrixPosition(endEffector.matrixWorld);
1152
- if (endPos.distanceTo(target) < tolerance) {
1153
- return true;
1154
- }
1155
- // Iterate bones from end to root (skip end-effector)
1156
- for (let i = bones.length - 2; i >= 0; i--) {
1157
- const bone = bones[i];
1158
- bone.updateWorldMatrix(true, false);
1159
- const boneWorldPos = new THREE__namespace.Vector3().setFromMatrixPosition(bone.matrixWorld);
1160
- // Direction from bone to end-effector
1161
- endEffector.updateWorldMatrix(true, false);
1162
- endPos.setFromMatrixPosition(endEffector.matrixWorld);
1163
- toEnd.subVectors(endPos, boneWorldPos).normalize();
1164
- // Direction from bone to target
1165
- toTarget.subVectors(target, boneWorldPos).normalize();
1166
- // Calculate rotation axis and angle
1167
- const dot = THREE__namespace.MathUtils.clamp(toEnd.dot(toTarget), -1, 1);
1168
- const angle = Math.acos(dot);
1169
- if (angle > 0.0001) {
1170
- axis.crossVectors(toEnd, toTarget).normalize();
1171
- // Convert to bone local space
1172
- const boneWorldQuat = new THREE__namespace.Quaternion();
1173
- bone.getWorldQuaternion(boneWorldQuat);
1174
- const invWorldQuat = boneWorldQuat.clone().invert();
1175
- const localAxis = axis.clone().applyQuaternion(invWorldQuat);
1176
- // Apply limited rotation
1177
- const maxAngle = Math.PI / 6; // 30° per iteration
1178
- const clampedAngle = Math.min(angle, maxAngle);
1179
- quat.setFromAxisAngle(localAxis, clampedAngle);
1180
- bone.quaternion.premultiply(quat);
1181
- bone.updateWorldMatrix(true, true);
1182
- }
1183
- }
1054
+ const { bones: boneNames, target, iterations = 10, tolerance = 1e-3 } = chain;
1055
+ const bones = [];
1056
+ for (const name of boneNames) {
1057
+ const bone = skeleton.bones.find((b) => b.name === name);
1058
+ if (!bone) {
1059
+ return false;
1184
1060
  }
1061
+ bones.push(bone);
1062
+ }
1063
+ if (bones.length < 2) {
1064
+ return false;
1065
+ }
1066
+ const endEffector = bones[bones.length - 1];
1067
+ const endPos = new THREE__namespace.Vector3();
1068
+ const toEnd = new THREE__namespace.Vector3();
1069
+ const toTarget = new THREE__namespace.Vector3();
1070
+ const axis = new THREE__namespace.Vector3();
1071
+ const quat = new THREE__namespace.Quaternion();
1072
+ for (let iter = 0; iter < iterations; iter++) {
1185
1073
  endEffector.updateWorldMatrix(true, false);
1186
1074
  endPos.setFromMatrixPosition(endEffector.matrixWorld);
1187
- return endPos.distanceTo(target) < tolerance;
1075
+ if (endPos.distanceTo(target) < tolerance) {
1076
+ return true;
1077
+ }
1078
+ for (let i = bones.length - 2; i >= 0; i--) {
1079
+ const bone = bones[i];
1080
+ bone.updateWorldMatrix(true, false);
1081
+ const boneWorldPos = new THREE__namespace.Vector3().setFromMatrixPosition(bone.matrixWorld);
1082
+ endEffector.updateWorldMatrix(true, false);
1083
+ endPos.setFromMatrixPosition(endEffector.matrixWorld);
1084
+ toEnd.subVectors(endPos, boneWorldPos).normalize();
1085
+ toTarget.subVectors(target, boneWorldPos).normalize();
1086
+ const dot = THREE__namespace.MathUtils.clamp(toEnd.dot(toTarget), -1, 1);
1087
+ const angle = Math.acos(dot);
1088
+ if (angle > 1e-4) {
1089
+ axis.crossVectors(toEnd, toTarget).normalize();
1090
+ const boneWorldQuat = new THREE__namespace.Quaternion();
1091
+ bone.getWorldQuaternion(boneWorldQuat);
1092
+ const invWorldQuat = boneWorldQuat.clone().invert();
1093
+ const localAxis = axis.clone().applyQuaternion(invWorldQuat);
1094
+ const maxAngle = Math.PI / 6;
1095
+ const clampedAngle = Math.min(angle, maxAngle);
1096
+ quat.setFromAxisAngle(localAxis, clampedAngle);
1097
+ bone.quaternion.premultiply(quat);
1098
+ bone.updateWorldMatrix(true, true);
1099
+ }
1100
+ }
1101
+ }
1102
+ endEffector.updateWorldMatrix(true, false);
1103
+ endPos.setFromMatrixPosition(endEffector.matrixWorld);
1104
+ return endPos.distanceTo(target) < tolerance;
1188
1105
  }
1189
- /** Standard quad viewport layout. */
1190
1106
  const QUAD_VIEWPORT = [
1191
- { preset: 'perspective', x: 0.5, y: 0.5, width: 0.5, height: 0.5 },
1192
- { preset: 'top', x: 0, y: 0.5, width: 0.5, height: 0.5 },
1193
- { preset: 'front', x: 0, y: 0, width: 0.5, height: 0.5 },
1194
- { preset: 'right', x: 0.5, y: 0, width: 0.5, height: 0.5 },
1107
+ { preset: "perspective", x: 0.5, y: 0.5, width: 0.5, height: 0.5 },
1108
+ { preset: "top", x: 0, y: 0.5, width: 0.5, height: 0.5 },
1109
+ { preset: "front", x: 0, y: 0, width: 0.5, height: 0.5 },
1110
+ { preset: "right", x: 0.5, y: 0, width: 0.5, height: 0.5 }
1195
1111
  ];
1196
- /** Camera position/rotation for orthographic viewport presets. */
1197
1112
  const VIEWPORT_CAMERAS = {
1198
- perspective: { position: [5, 5, 5], up: [0, 1, 0], ortho: false },
1199
- top: { position: [0, 10, 0], up: [0, 0, -1], ortho: true },
1200
- front: { position: [0, 0, 10], up: [0, 1, 0], ortho: true },
1201
- right: { position: [10, 0, 0], up: [0, 1, 0], ortho: true },
1202
- left: { position: [-10, 0, 0], up: [0, 1, 0], ortho: true },
1203
- back: { position: [0, 0, -10], up: [0, 1, 0], ortho: true },
1204
- bottom: { position: [0, -10, 0], up: [0, 0, 1], ortho: true },
1113
+ perspective: { position: [5, 5, 5], up: [0, 1, 0], ortho: false },
1114
+ top: { position: [0, 10, 0], up: [0, 0, -1], ortho: true },
1115
+ front: { position: [0, 0, 10], up: [0, 1, 0], ortho: true },
1116
+ right: { position: [10, 0, 0], up: [0, 1, 0], ortho: true },
1117
+ left: { position: [-10, 0, 0], up: [0, 1, 0], ortho: true },
1118
+ back: { position: [0, 0, -10], up: [0, 1, 0], ortho: true },
1119
+ bottom: { position: [0, -10, 0], up: [0, 0, 1], ortho: true }
1205
1120
  };
1206
- /**
1207
- * Create a camera for a viewport preset.
1208
- */
1209
1121
  function createViewportCamera(preset, aspect = 1, frustumSize = 10) {
1210
- const config = VIEWPORT_CAMERAS[preset];
1211
- if (config.ortho) {
1212
- const camera = new THREE__namespace.OrthographicCamera((-frustumSize * aspect) / 2, (frustumSize * aspect) / 2, frustumSize / 2, -frustumSize / 2, 0.01, 10000);
1213
- camera.position.set(...config.position);
1214
- camera.up.set(...config.up);
1215
- camera.lookAt(0, 0, 0);
1216
- return camera;
1217
- }
1218
- const camera = new THREE__namespace.PerspectiveCamera(50, aspect, 0.01, 10000);
1219
- camera.position.set(...config.position);
1220
- camera.up.set(...config.up);
1221
- camera.lookAt(0, 0, 0);
1222
- return camera;
1122
+ const config = VIEWPORT_CAMERAS[preset];
1123
+ if (config.ortho) {
1124
+ const camera2 = new THREE__namespace.OrthographicCamera(
1125
+ -frustumSize * aspect / 2,
1126
+ frustumSize * aspect / 2,
1127
+ frustumSize / 2,
1128
+ -frustumSize / 2,
1129
+ 0.01,
1130
+ 1e4
1131
+ );
1132
+ camera2.position.set(...config.position);
1133
+ camera2.up.set(...config.up);
1134
+ camera2.lookAt(0, 0, 0);
1135
+ return camera2;
1136
+ }
1137
+ const camera = new THREE__namespace.PerspectiveCamera(50, aspect, 0.01, 1e4);
1138
+ camera.position.set(...config.position);
1139
+ camera.up.set(...config.up);
1140
+ camera.lookAt(0, 0, 0);
1141
+ return camera;
1223
1142
  }
1224
- /**
1225
- * Render a multi-viewport layout to a single renderer.
1226
- */
1227
1143
  function renderMultiViewport(renderer, scene, viewports) {
1228
- const { width, height } = renderer.getSize(new THREE__namespace.Vector2());
1229
- renderer.setScissorTest(true);
1230
- for (const { camera, config } of viewports) {
1231
- const left = Math.floor(config.x * width);
1232
- const bottom = Math.floor(config.y * height);
1233
- const vpWidth = Math.floor(config.width * width);
1234
- const vpHeight = Math.floor(config.height * height);
1235
- renderer.setViewport(left, bottom, vpWidth, vpHeight);
1236
- renderer.setScissor(left, bottom, vpWidth, vpHeight);
1237
- renderer.render(scene, camera);
1238
- }
1239
- renderer.setScissorTest(false);
1144
+ const { width, height } = renderer.getSize(new THREE__namespace.Vector2());
1145
+ renderer.setScissorTest(true);
1146
+ for (const { camera, config } of viewports) {
1147
+ const left = Math.floor(config.x * width);
1148
+ const bottom = Math.floor(config.y * height);
1149
+ const vpWidth = Math.floor(config.width * width);
1150
+ const vpHeight = Math.floor(config.height * height);
1151
+ renderer.setViewport(left, bottom, vpWidth, vpHeight);
1152
+ renderer.setScissor(left, bottom, vpWidth, vpHeight);
1153
+ renderer.render(scene, camera);
1154
+ }
1155
+ renderer.setScissorTest(false);
1240
1156
  }
1241
-
1242
1157
  exports.HDRI_PRESETS = HDRI_PRESETS;
1243
1158
  exports.PBR_PRESETS = PBR_PRESETS;
1244
1159
  exports.QUAD_VIEWPORT = QUAD_VIEWPORT;