@nice2dev/ui-3d 1.3.15 → 1.3.17
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/CHANGELOG.md +13 -0
- package/dist/cjs/collaborative/collaborativeScene.js +160 -206
- package/dist/cjs/core/editorAsk.js +51 -41
- package/dist/cjs/core/i18n.js +13 -14
- package/dist/cjs/core/threeCompat.js +29 -42
- package/dist/cjs/dance/DanceBridge.js +148 -177
- package/dist/cjs/dance/DanceScoreEngine.js +209 -221
- package/dist/cjs/dance/PoseDetector.js +302 -275
- package/dist/cjs/index.js +110 -113
- package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
- package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
- package/dist/cjs/material/materialEditorTypes.js +35 -49
- package/dist/cjs/material/materialEditorUtils.js +684 -747
- package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
- package/dist/cjs/model/ModelEditor.js +90 -40
- package/dist/cjs/model/ModelEditor.module.css.js +257 -5
- package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
- package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
- package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
- package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
- package/dist/cjs/model/ModelEditorTimeline.js +232 -34
- package/dist/cjs/model/ModelEditorToolbar.js +170 -14
- package/dist/cjs/model/ModelEditorViewport.js +231 -39
- package/dist/cjs/model/ModelViewer.js +388 -63
- package/dist/cjs/model/ModelViewer.module.css.js +77 -5
- package/dist/cjs/model/NiceArmatureEditor.js +473 -274
- package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
- package/dist/cjs/model/NiceOctree.js +372 -320
- package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
- package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
- package/dist/cjs/model/NiceTerrainEditor.js +595 -385
- package/dist/cjs/model/NiceWeightPainter.js +384 -261
- package/dist/cjs/model/NiceXRPreview.js +385 -308
- package/dist/cjs/model/cadModeUtils.js +103 -106
- package/dist/cjs/model/editorShortcuts.js +171 -194
- package/dist/cjs/model/modelEditorTypes.js +100 -118
- package/dist/cjs/model/modelEditorUtils.js +1061 -1146
- package/dist/cjs/model/simsModeUtils.js +1023 -1053
- package/dist/cjs/model/useModelEditor.js +1977 -1933
- package/dist/cjs/model/useModelViewer.js +633 -665
- package/dist/cjs/nice2dev-ui-3d.css +1 -1
- package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
- package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
- package/dist/cjs/particle/particleEditorTypes.js +62 -70
- package/dist/cjs/particle/particleEditorUtils.js +998 -1013
- package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
- package/dist/cjs/rendering/NiceRenderExport.js +424 -337
- package/dist/cjs/rendering/NiceSSAO.js +454 -337
- package/dist/cjs/rendering/NiceSSR.js +387 -261
- package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
- package/dist/cjs/uv/NiceUVEditor.js +787 -550
- package/dist/cjs/uv/UVEditor.module.css.js +53 -5
- package/dist/cjs/uv/uvEditorTypes.js +59 -76
- package/dist/cjs/uv/uvEditorUtils.js +501 -575
- package/dist/esm/collaborative/collaborativeScene.js +163 -205
- package/dist/esm/core/editorAsk.js +53 -40
- package/dist/esm/core/i18n.js +15 -13
- package/dist/esm/core/threeCompat.js +18 -26
- package/dist/esm/dance/DanceBridge.js +151 -176
- package/dist/esm/dance/DanceScoreEngine.js +211 -220
- package/dist/esm/dance/PoseDetector.js +304 -274
- package/dist/esm/index.js +258 -36
- package/dist/esm/material/MaterialEditor.module.css.js +242 -3
- package/dist/esm/material/NiceMaterialEditor.js +1215 -756
- package/dist/esm/material/materialEditorTypes.js +45 -48
- package/dist/esm/material/materialEditorUtils.js +713 -746
- package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
- package/dist/esm/model/ModelEditor.js +91 -39
- package/dist/esm/model/ModelEditor.module.css.js +257 -3
- package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
- package/dist/esm/model/ModelEditorMenuBar.js +359 -30
- package/dist/esm/model/ModelEditorRightPanel.js +634 -114
- package/dist/esm/model/ModelEditorSubComponents.js +265 -94
- package/dist/esm/model/ModelEditorTimeline.js +232 -32
- package/dist/esm/model/ModelEditorToolbar.js +170 -12
- package/dist/esm/model/ModelEditorViewport.js +231 -37
- package/dist/esm/model/ModelViewer.js +389 -62
- package/dist/esm/model/ModelViewer.module.css.js +77 -3
- package/dist/esm/model/NiceArmatureEditor.js +462 -258
- package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
- package/dist/esm/model/NiceOctree.js +361 -304
- package/dist/esm/model/NicePhysicsSimulation.js +517 -282
- package/dist/esm/model/NiceProceduralGeometry.js +667 -284
- package/dist/esm/model/NiceTerrainEditor.js +584 -369
- package/dist/esm/model/NiceWeightPainter.js +373 -245
- package/dist/esm/model/NiceXRPreview.js +374 -292
- package/dist/esm/model/cadModeUtils.js +95 -88
- package/dist/esm/model/editorShortcuts.js +172 -193
- package/dist/esm/model/modelEditorTypes.js +90 -100
- package/dist/esm/model/modelEditorUtils.js +1056 -1128
- package/dist/esm/model/simsModeUtils.js +1021 -1035
- package/dist/esm/model/useModelEditor.js +1946 -1920
- package/dist/esm/model/useModelViewer.js +619 -647
- package/dist/esm/nice2dev-ui-3d.css +1 -1
- package/dist/esm/particle/NiceParticleEditor.js +1188 -570
- package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
- package/dist/esm/particle/particleEditorTypes.js +69 -69
- package/dist/esm/particle/particleEditorUtils.js +1027 -1012
- package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
- package/dist/esm/rendering/NiceRenderExport.js +413 -321
- package/dist/esm/rendering/NiceSSAO.js +450 -328
- package/dist/esm/rendering/NiceSSR.js +383 -252
- package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
- package/dist/esm/uv/NiceUVEditor.js +788 -549
- package/dist/esm/uv/UVEditor.module.css.js +53 -3
- package/dist/esm/uv/uvEditorTypes.js +68 -75
- package/dist/esm/uv/uvEditorUtils.js +515 -557
- package/package.json +7 -12
|
@@ -1,1244 +1,1159 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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
|
-
|
|
824
|
-
|
|
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
|
-
|
|
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
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
|
|
1044
|
-
|
|
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
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
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
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
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;
|