fl-web-component 2.0.7 → 2.0.9
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/README.md +2 -0
- package/dist/fl-web-component.common.js +7223 -1277
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -1
- package/packages/components/com-flcanvas/index.vue +0 -2
- package/packages/components/com-graphics/component/context.js +123 -0
- package/packages/components/com-graphics/index.vue +1148 -69
- package/packages/utils/StreamLoader.js +73 -16
- package/src/utils/threejs/editor/command.js +36 -0
- package/src/utils/threejs/editor/commands/add-element-command.js +41 -0
- package/src/utils/threejs/editor/commands/add-group-command.js +10 -0
- package/src/utils/threejs/editor/commands/clone-element-command.js +100 -0
- package/src/utils/threejs/editor/commands/move-element-command.js +57 -0
- package/src/utils/threejs/editor/commands/multi-command.js +29 -0
- package/src/utils/threejs/editor/commands/remove-element-command.js +46 -0
- package/src/utils/threejs/editor/commands/reset-original-model-style-command.js +30 -0
- package/src/utils/threejs/editor/commands/set-geometry-params-command.js +41 -0
- package/src/utils/threejs/editor/commands/set-original-model-style-command.js +56 -0
- package/src/utils/threejs/editor/commands/set-position-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-rotation-command.js +53 -0
- package/src/utils/threejs/editor/commands/set-scale-command.js +54 -0
- package/src/utils/threejs/editor/commands/set-value-command.js +107 -0
- package/src/utils/threejs/editor/constants.js +107 -0
- package/src/utils/threejs/editor/element-factory.js +163 -0
- package/src/utils/threejs/editor/event-bus.js +34 -0
- package/src/utils/threejs/editor/history.js +80 -0
- package/src/utils/threejs/editor/scene-command-service.js +1529 -0
- package/src/utils/threejs/editor/scene-event-bridge.js +32 -0
- package/src/utils/threejs/editor/scene-helpers.js +415 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export default class SceneEventBridge {
|
|
2
|
+
constructor(options = {}) {
|
|
3
|
+
this.eventBus = options.eventBus;
|
|
4
|
+
this.getTreeSnapshot = options.getTreeSnapshot || (() => null);
|
|
5
|
+
this.getOriginalModelStyleChanges = options.getOriginalModelStyleChanges || (() => []);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
emit(eventName, payload) {
|
|
9
|
+
if (!this.eventBus) return;
|
|
10
|
+
this.eventBus.emit(eventName, payload);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
emitSceneGraphChanged(payload = {}) {
|
|
14
|
+
this.emit('sceneGraphChanged', {
|
|
15
|
+
tree: this.getTreeSnapshot(),
|
|
16
|
+
...payload,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
emitOriginalModelChangeListChanged(payload = {}) {
|
|
21
|
+
this.emit('originalModelChangeListChanged', {
|
|
22
|
+
list: this.getOriginalModelStyleChanges(),
|
|
23
|
+
...payload,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
destroy() {
|
|
28
|
+
if (this.eventBus && this.eventBus.clear) {
|
|
29
|
+
this.eventBus.clear();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CUSTOM_ROOT_DISPLAY_NAME,
|
|
3
|
+
CUSTOM_ROOT_NAME,
|
|
4
|
+
ELEMENT_LABELS,
|
|
5
|
+
SCENE_NODE_TYPE,
|
|
6
|
+
} from './constants';
|
|
7
|
+
|
|
8
|
+
export function degreesToRadians(value) {
|
|
9
|
+
return (Number(value) || 0) * (Math.PI / 180);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function radiansToDegrees(value) {
|
|
13
|
+
return (Number(value) || 0) * (180 / Math.PI);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function degreesArrayToRadians(rotation = []) {
|
|
17
|
+
return [
|
|
18
|
+
degreesToRadians(rotation[0]),
|
|
19
|
+
degreesToRadians(rotation[1]),
|
|
20
|
+
degreesToRadians(rotation[2]),
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function radiansArrayToDegrees(rotation = []) {
|
|
25
|
+
return [
|
|
26
|
+
radiansToDegrees(rotation[0]),
|
|
27
|
+
radiansToDegrees(rotation[1]),
|
|
28
|
+
radiansToDegrees(rotation[2]),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getObjectByUuid(scene, uuid) {
|
|
33
|
+
if (!scene || !uuid) return null;
|
|
34
|
+
return scene.getObjectByProperty('uuid', uuid) || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getObjectIndex(object) {
|
|
38
|
+
if (!object || !object.parent) return -1;
|
|
39
|
+
return object.parent.children.indexOf(object);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function clampInsertIndex(parent, index) {
|
|
43
|
+
if (!parent) return 0;
|
|
44
|
+
const length = parent.children.length;
|
|
45
|
+
if (typeof index !== 'number' || Number.isNaN(index)) return length;
|
|
46
|
+
if (index < 0) return 0;
|
|
47
|
+
if (index > length) return length;
|
|
48
|
+
return index;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function insertObjectAt(parent, object, index) {
|
|
52
|
+
if (!parent || !object) return;
|
|
53
|
+
parent.add(object);
|
|
54
|
+
const currentIndex = parent.children.indexOf(object);
|
|
55
|
+
const targetIndex = clampInsertIndex(parent, index);
|
|
56
|
+
if (currentIndex === -1 || currentIndex === targetIndex) return;
|
|
57
|
+
parent.children.splice(currentIndex, 1);
|
|
58
|
+
parent.children.splice(targetIndex, 0, object);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function removeObjectFromParent(object) {
|
|
62
|
+
if (!object || !object.parent) return;
|
|
63
|
+
object.parent.remove(object);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isAncestor(target, possibleDescendant) {
|
|
67
|
+
let current = possibleDescendant;
|
|
68
|
+
while (current) {
|
|
69
|
+
if (current === target) return true;
|
|
70
|
+
current = current.parent;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isCustomRoot(object) {
|
|
76
|
+
if (!object || !object.userData) return false;
|
|
77
|
+
if (object.userData.nodeType) {
|
|
78
|
+
return object.userData.nodeType === 'custom-root';
|
|
79
|
+
}
|
|
80
|
+
return object.userData.rootType === 'custom-root';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isCustomElement(object) {
|
|
84
|
+
return !!(object && object.userData && object.userData.nodeType === 'custom-element');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isCustomGroup(object) {
|
|
88
|
+
return !!(object && object.userData && object.userData.nodeType === 'custom-group');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isSelectableObject(object) {
|
|
92
|
+
return isCustomRoot(object) || isCustomElement(object) || isCustomGroup(object);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isTransformControlHelperObject(object) {
|
|
96
|
+
return !!(object && object.userData && object.userData.transformControlHelper === true);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function isHelperObject(object) {
|
|
100
|
+
if (!object) return false;
|
|
101
|
+
if (isTransformControlHelperObject(object)) return true;
|
|
102
|
+
if (object.type === 'Scene' || object.isCamera || object.isLight) return true;
|
|
103
|
+
if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
|
|
104
|
+
if (
|
|
105
|
+
object.type === 'AxesHelper' ||
|
|
106
|
+
object.type === 'GridHelper' ||
|
|
107
|
+
object.type === 'BoxHelper' ||
|
|
108
|
+
object.type === 'CSS2DObject' ||
|
|
109
|
+
object.type === 'TransformControlsRoot'
|
|
110
|
+
) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getSceneNodeType(object) {
|
|
117
|
+
if (!object) return SCENE_NODE_TYPE.UNKNOWN;
|
|
118
|
+
if (isCustomElement(object)) return SCENE_NODE_TYPE.CUSTOM_ELEMENT;
|
|
119
|
+
if (isCustomGroup(object)) return SCENE_NODE_TYPE.CUSTOM_GROUP;
|
|
120
|
+
if (isCustomRoot(object)) return SCENE_NODE_TYPE.CUSTOM_ROOT;
|
|
121
|
+
if (isHelperObject(object)) return SCENE_NODE_TYPE.HELPER;
|
|
122
|
+
if (object.parent && !isHelperObject(object)) {
|
|
123
|
+
return SCENE_NODE_TYPE.ORIGINAL_MODEL;
|
|
124
|
+
}
|
|
125
|
+
return SCENE_NODE_TYPE.UNKNOWN;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function isOriginalModelObject(object) {
|
|
129
|
+
return getSceneNodeType(object) === SCENE_NODE_TYPE.ORIGINAL_MODEL;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isEditableObject(object) {
|
|
133
|
+
return !!(object && object.userData && object.userData.editable === true);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function isDeletableObject(object) {
|
|
137
|
+
return !!(object && object.userData && object.userData.deletable === true);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function isCloneableObject(object) {
|
|
141
|
+
return !!(object && object.userData && object.userData.cloneable === true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function isObjectVisible(object) {
|
|
145
|
+
let current = object;
|
|
146
|
+
while (current) {
|
|
147
|
+
if (current.visible === false) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
current = current.parent;
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function isTransformAttachableObject(object) {
|
|
156
|
+
return !!(
|
|
157
|
+
object &&
|
|
158
|
+
(isCustomElement(object) || isCustomGroup(object)) &&
|
|
159
|
+
isEditableObject(object) &&
|
|
160
|
+
isObjectVisible(object)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function findSelectableObject(target) {
|
|
165
|
+
let current = target;
|
|
166
|
+
while (current) {
|
|
167
|
+
if (isSelectableObject(current) && isObjectVisible(current)) {
|
|
168
|
+
return current;
|
|
169
|
+
}
|
|
170
|
+
current = current.parent;
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function findSelectableSceneObject(target) {
|
|
176
|
+
let current = target;
|
|
177
|
+
while (current) {
|
|
178
|
+
if (isSelectableObject(current) && isObjectVisible(current)) {
|
|
179
|
+
return current;
|
|
180
|
+
}
|
|
181
|
+
if (isOriginalModelObject(current) && isObjectVisible(current)) {
|
|
182
|
+
return current;
|
|
183
|
+
}
|
|
184
|
+
current = current.parent;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function ensureCustomRoot(scene, THREE) {
|
|
190
|
+
if (!scene || !THREE) return null;
|
|
191
|
+
let root = scene.children.find(child => isCustomRoot(child));
|
|
192
|
+
if (root) return root;
|
|
193
|
+
|
|
194
|
+
root = new THREE.Group();
|
|
195
|
+
root.name = CUSTOM_ROOT_NAME;
|
|
196
|
+
root.userData = {
|
|
197
|
+
nodeType: 'custom-root',
|
|
198
|
+
rootType: 'custom-root',
|
|
199
|
+
// displayName: CUSTOM_ROOT_DISPLAY_NAME,
|
|
200
|
+
editable: false,
|
|
201
|
+
deletable: false,
|
|
202
|
+
cloneable: false,
|
|
203
|
+
selectable: true,
|
|
204
|
+
};
|
|
205
|
+
scene.add(root);
|
|
206
|
+
return root;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getElementBaseLabel(type) {
|
|
210
|
+
return ELEMENT_LABELS[type] || '元素';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function getSiblingNames(parent, ignoreUuid) {
|
|
214
|
+
if (!parent) return [];
|
|
215
|
+
return parent.children
|
|
216
|
+
.filter(child => child && child.uuid !== ignoreUuid)
|
|
217
|
+
.map(child => child.name)
|
|
218
|
+
.filter(Boolean);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function createUniqueName(parent, baseName, options = {}) {
|
|
222
|
+
const siblingNames = getSiblingNames(parent, options.ignoreUuid);
|
|
223
|
+
if (!options.forceSuffix && !siblingNames.includes(baseName)) {
|
|
224
|
+
return baseName;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let index = typeof options.startIndex === 'number' ? options.startIndex : 1;
|
|
228
|
+
while (index < 100000) {
|
|
229
|
+
const suffix =
|
|
230
|
+
options.padding && options.padding > 1
|
|
231
|
+
? String(index).padStart(options.padding, '0')
|
|
232
|
+
: String(index);
|
|
233
|
+
const nextName = `${baseName}_${suffix}`;
|
|
234
|
+
if (!siblingNames.includes(nextName)) {
|
|
235
|
+
return nextName;
|
|
236
|
+
}
|
|
237
|
+
index += 1;
|
|
238
|
+
}
|
|
239
|
+
return `${baseName}_${Date.now()}`;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function createDefaultElementName(parent, type) {
|
|
243
|
+
return createUniqueName(parent, getElementBaseLabel(type), {
|
|
244
|
+
startIndex: 1,
|
|
245
|
+
forceSuffix: true,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function createDefaultGroupName(parent) {
|
|
250
|
+
return createUniqueName(parent, ELEMENT_LABELS.group, {
|
|
251
|
+
startIndex: 1,
|
|
252
|
+
padding: 3,
|
|
253
|
+
forceSuffix: true,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function applyTransform(object, transform = {}) {
|
|
258
|
+
if (!object || !transform) return;
|
|
259
|
+
const { position, rotation, scale } = transform;
|
|
260
|
+
if (Array.isArray(position) && position.length === 3) {
|
|
261
|
+
object.position.set(
|
|
262
|
+
Number(position[0]) || 0,
|
|
263
|
+
Number(position[1]) || 0,
|
|
264
|
+
Number(position[2]) || 0
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (Array.isArray(rotation) && rotation.length === 3) {
|
|
268
|
+
const [x, y, z] = degreesArrayToRadians(rotation);
|
|
269
|
+
object.rotation.set(x, y, z);
|
|
270
|
+
}
|
|
271
|
+
if (Array.isArray(scale) && scale.length === 3) {
|
|
272
|
+
object.scale.set(Number(scale[0]) || 1, Number(scale[1]) || 1, Number(scale[2]) || 1);
|
|
273
|
+
}
|
|
274
|
+
object.updateMatrixWorld(true);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function cloneUserData(userData = {}) {
|
|
278
|
+
return JSON.parse(JSON.stringify(userData));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function updateDisplayName(object) {
|
|
282
|
+
if (!object || !object.userData) return;
|
|
283
|
+
object.userData.displayName = object.name;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function buildTreeSnapshot(object) {
|
|
287
|
+
if (!object) return null;
|
|
288
|
+
return {
|
|
289
|
+
uuid: object.uuid,
|
|
290
|
+
name: object.name,
|
|
291
|
+
// displayName:
|
|
292
|
+
// object.userData && object.userData.displayName ? object.userData.displayName : object.name,
|
|
293
|
+
nodeType: object.userData && object.userData.nodeType ? object.userData.nodeType : '',
|
|
294
|
+
elementType: object.userData && object.userData.elementType ? object.userData.elementType : '',
|
|
295
|
+
visible: object.visible,
|
|
296
|
+
children: object.children
|
|
297
|
+
.filter(child => isCustomElement(child) || isCustomGroup(child))
|
|
298
|
+
.map(child => buildTreeSnapshot(child)),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getMaterialSnapshot(material) {
|
|
303
|
+
if (!material) return null;
|
|
304
|
+
return {
|
|
305
|
+
color: material.color && material.color.isColor ? `#${material.color.getHexString()}` : null,
|
|
306
|
+
opacity: typeof material.opacity === 'number' ? material.opacity : 1,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function flattenCustomSaveNodeMaterial(saveNode = {}) {
|
|
311
|
+
const nextNode = { ...saveNode };
|
|
312
|
+
const material =
|
|
313
|
+
saveNode && saveNode.material && typeof saveNode.material === 'object' ? saveNode.material : null;
|
|
314
|
+
if (material) {
|
|
315
|
+
if (Object.prototype.hasOwnProperty.call(material, 'color')) {
|
|
316
|
+
nextNode.color = material.color;
|
|
317
|
+
}
|
|
318
|
+
if (Object.prototype.hasOwnProperty.call(material, 'opacity')) {
|
|
319
|
+
nextNode.opacity = material.opacity;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (Object.prototype.hasOwnProperty.call(nextNode, 'material')) {
|
|
323
|
+
delete nextNode.material;
|
|
324
|
+
}
|
|
325
|
+
return nextNode;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function buildObjectSnapshot(object) {
|
|
329
|
+
const nodeType = getSceneNodeType(object);
|
|
330
|
+
const primaryMaterial = object
|
|
331
|
+
? Array.isArray(object.material)
|
|
332
|
+
? object.material[0]
|
|
333
|
+
: object.material
|
|
334
|
+
: null;
|
|
335
|
+
if (!object) return null;
|
|
336
|
+
return {
|
|
337
|
+
uuid: object.uuid,
|
|
338
|
+
name: object.name,
|
|
339
|
+
// displayName:
|
|
340
|
+
// object.userData && object.userData.displayName ? object.userData.displayName : object.name,
|
|
341
|
+
nodeType,
|
|
342
|
+
// nodeType: object.userData && object.userData.nodeType ? object.userData.nodeType : '',
|
|
343
|
+
elementType: object.userData && object.userData.elementType ? object.userData.elementType : '',
|
|
344
|
+
visible: object.visible !== false,
|
|
345
|
+
position: [object.position.x, object.position.y, object.position.z],
|
|
346
|
+
rotation: radiansArrayToDegrees([object.rotation.x, object.rotation.y, object.rotation.z]),
|
|
347
|
+
scale: [object.scale.x, object.scale.y, object.scale.z],
|
|
348
|
+
material: getMaterialSnapshot(primaryMaterial),
|
|
349
|
+
geometryParams:
|
|
350
|
+
object.userData && object.userData.geometryParams
|
|
351
|
+
? cloneUserData(object.userData.geometryParams)
|
|
352
|
+
: null,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function buildCustomTree(root) {
|
|
357
|
+
if (!root) return null;
|
|
358
|
+
return {
|
|
359
|
+
uuid: root.uuid,
|
|
360
|
+
name: root.name,
|
|
361
|
+
// displayName: root.userData && root.userData.displayName ? root.userData.displayName : root.name,
|
|
362
|
+
nodeType: root.userData && root.userData.nodeType ? root.userData.nodeType : 'custom-root',
|
|
363
|
+
children: root.children
|
|
364
|
+
.filter(child => isCustomElement(child) || isCustomGroup(child))
|
|
365
|
+
.map(child => buildTreeSnapshot(child)),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function buildCustomSaveTree(root) {
|
|
370
|
+
if (!root) return null;
|
|
371
|
+
const snapshot = buildObjectSnapshot(root);
|
|
372
|
+
const saveNode = {
|
|
373
|
+
...(snapshot || {
|
|
374
|
+
uuid: root.uuid,
|
|
375
|
+
name: root.name,
|
|
376
|
+
displayName:
|
|
377
|
+
root.userData && root.userData.displayName ? root.userData.displayName : root.name,
|
|
378
|
+
nodeType: root.userData && root.userData.nodeType ? root.userData.nodeType : 'custom-root',
|
|
379
|
+
elementType: '',
|
|
380
|
+
visible: root.visible !== false,
|
|
381
|
+
position: [root.position.x, root.position.y, root.position.z],
|
|
382
|
+
rotation: radiansArrayToDegrees([root.rotation.x, root.rotation.y, root.rotation.z]),
|
|
383
|
+
scale: [root.scale.x, root.scale.y, root.scale.z],
|
|
384
|
+
material: null,
|
|
385
|
+
geometryParams: null,
|
|
386
|
+
}),
|
|
387
|
+
children: root.children
|
|
388
|
+
.filter(child => isCustomElement(child) || isCustomGroup(child))
|
|
389
|
+
.map(child => buildCustomSaveTree(child)),
|
|
390
|
+
};
|
|
391
|
+
if (Object.prototype.hasOwnProperty.call(saveNode, 'uuid')) {
|
|
392
|
+
delete saveNode.uuid;
|
|
393
|
+
}
|
|
394
|
+
return flattenCustomSaveNodeMaterial(saveNode);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function disposeObjectResources(object) {
|
|
398
|
+
if (!object) return;
|
|
399
|
+
object.traverse(child => {
|
|
400
|
+
if (child.isMesh) {
|
|
401
|
+
if (child.geometry && child.geometry.dispose) {
|
|
402
|
+
child.geometry.dispose();
|
|
403
|
+
}
|
|
404
|
+
if (Array.isArray(child.material)) {
|
|
405
|
+
child.material.forEach(material => {
|
|
406
|
+
if (material && material.dispose) {
|
|
407
|
+
material.dispose();
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
} else if (child.material && child.material.dispose) {
|
|
411
|
+
child.material.dispose();
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|