react-three-game 0.0.100 → 0.0.101
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/tools/assetviewer/page.js +70 -58
- package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -0
- package/dist/tools/dragdrop/DragDropLoader.js +183 -44
- package/dist/tools/dragdrop/index.d.ts +1 -1
- package/dist/tools/dragdrop/index.js +1 -1
- package/dist/tools/dragdrop/modelLoader.js +2 -0
- package/dist/tools/prefabeditor/EditorUI.js +7 -8
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +3 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +28 -11
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +51 -35
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +20 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +5 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +31 -0
- package/dist/tools/prefabeditor/components/DataComponent.js +1 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +1 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +89 -52
- package/dist/tools/prefabeditor/components/ModelComponent.js +45 -3
- package/dist/tools/prefabeditor/components/SpriteComponent.js +1 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +1 -0
- package/dist/tools/prefabeditor/modelPrefab.d.ts +16 -0
- package/dist/tools/prefabeditor/modelPrefab.js +180 -0
- package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
- package/dist/tools/prefabeditor/prefabStore.js +75 -42
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
-
import { createContext, useContext, useMemo, useRef } from 'react';
|
|
13
|
+
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
|
14
14
|
import { extend } from '@react-three/fiber';
|
|
15
15
|
import { useFrame } from '@react-three/fiber';
|
|
16
16
|
import { assetRef, assetRefs } from './ComponentRegistry';
|
|
@@ -38,26 +38,6 @@ const MAG_FILTER_MAP = {
|
|
|
38
38
|
NearestFilter,
|
|
39
39
|
LinearFilter,
|
|
40
40
|
};
|
|
41
|
-
function cloneConfiguredTexture({ texture, repeat, repeatCount, offset, colorSpace, generateMipmaps, minFilter, magFilter, }) {
|
|
42
|
-
var _a, _b;
|
|
43
|
-
const clonedTexture = texture.clone();
|
|
44
|
-
if (repeat) {
|
|
45
|
-
clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping;
|
|
46
|
-
if (repeatCount)
|
|
47
|
-
clonedTexture.repeat.set(repeatCount[0], repeatCount[1]);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
clonedTexture.wrapS = clonedTexture.wrapT = ClampToEdgeWrapping;
|
|
51
|
-
clonedTexture.repeat.set(1, 1);
|
|
52
|
-
}
|
|
53
|
-
clonedTexture.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
54
|
-
clonedTexture.colorSpace = colorSpace;
|
|
55
|
-
clonedTexture.generateMipmaps = generateMipmaps;
|
|
56
|
-
clonedTexture.minFilter = minFilter;
|
|
57
|
-
clonedTexture.magFilter = magFilter;
|
|
58
|
-
clonedTexture.needsUpdate = true;
|
|
59
|
-
return clonedTexture;
|
|
60
|
-
}
|
|
61
41
|
export function useMaterialOverrides() {
|
|
62
42
|
return useContext(MaterialOverridesContext);
|
|
63
43
|
}
|
|
@@ -80,6 +60,11 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
80
60
|
const isStandardMaterial = materialType === 'standard';
|
|
81
61
|
const isSpriteMaterial = materialType === 'sprite';
|
|
82
62
|
const fields = [
|
|
63
|
+
{
|
|
64
|
+
name: 'attach',
|
|
65
|
+
type: 'string',
|
|
66
|
+
label: 'Attach',
|
|
67
|
+
},
|
|
83
68
|
{
|
|
84
69
|
name: 'materialType',
|
|
85
70
|
type: 'select',
|
|
@@ -208,39 +193,85 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
208
193
|
const normalScaleProp = materialSource.normalScale;
|
|
209
194
|
const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
|
|
210
195
|
// Destructure all material props and separate custom texture handling props
|
|
211
|
-
const { texture: _texture, offset: _offset, repeat: _repeat, repeatCount: _repeatCount, animateOffset: _animateOffset, offsetSpeed: _offsetSpeed, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, rotation, sizeAttenuation, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "offset", "repeat", "repeatCount", "animateOffset", "offsetSpeed", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "rotation", "sizeAttenuation", "side"]);
|
|
196
|
+
const { texture: _texture, offset: _offset, repeat: _repeat, repeatCount: _repeatCount, animateOffset: _animateOffset, offsetSpeed: _offsetSpeed, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, attach, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, rotation, sizeAttenuation, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "offset", "repeat", "repeatCount", "animateOffset", "offsetSpeed", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "attach", "normalMapTexture", "normalScale", "normalMap", "rotation", "sizeAttenuation", "side"]);
|
|
212
197
|
const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
|
|
213
198
|
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
214
199
|
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
215
200
|
const animatedOffsetRef = useRef([(_g = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _g !== void 0 ? _g : 0, (_h = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _h !== void 0 ? _h : 0]);
|
|
216
201
|
const finalTexture = useMemo(() => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
202
|
+
return texture ? texture.clone() : undefined;
|
|
203
|
+
}, [texture]);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
return () => finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.dispose();
|
|
206
|
+
}, [finalTexture]);
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
var _a, _b;
|
|
209
|
+
if (!finalTexture)
|
|
210
|
+
return;
|
|
211
|
+
if (repeat) {
|
|
212
|
+
finalTexture.wrapS = finalTexture.wrapT = RepeatWrapping;
|
|
213
|
+
if (repeatCount) {
|
|
214
|
+
finalTexture.repeat.set(repeatCount[0], repeatCount[1]);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
finalTexture.wrapS = finalTexture.wrapT = ClampToEdgeWrapping;
|
|
219
|
+
finalTexture.repeat.set(1, 1);
|
|
220
|
+
}
|
|
221
|
+
finalTexture.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
222
|
+
finalTexture.colorSpace = SRGBColorSpace;
|
|
223
|
+
finalTexture.generateMipmaps = generateMipmaps;
|
|
224
|
+
finalTexture.minFilter = resolvedMinFilter;
|
|
225
|
+
finalTexture.magFilter = resolvedMagFilter;
|
|
226
|
+
finalTexture.needsUpdate = true;
|
|
227
|
+
}, [
|
|
228
|
+
finalTexture,
|
|
229
|
+
repeat,
|
|
230
|
+
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
|
|
231
|
+
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
|
|
232
|
+
offset === null || offset === void 0 ? void 0 : offset[0],
|
|
233
|
+
offset === null || offset === void 0 ? void 0 : offset[1],
|
|
234
|
+
generateMipmaps,
|
|
235
|
+
resolvedMinFilter,
|
|
236
|
+
resolvedMagFilter
|
|
237
|
+
]);
|
|
230
238
|
const finalNormalMap = useMemo(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
return normalMapTexture ? normalMapTexture.clone() : undefined;
|
|
240
|
+
}, [normalMapTexture]);
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
return () => finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.dispose();
|
|
243
|
+
}, [finalNormalMap]);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
var _a, _b;
|
|
246
|
+
if (!finalNormalMap)
|
|
247
|
+
return;
|
|
248
|
+
if (repeat) {
|
|
249
|
+
finalNormalMap.wrapS = finalNormalMap.wrapT = RepeatWrapping;
|
|
250
|
+
if (repeatCount) {
|
|
251
|
+
finalNormalMap.repeat.set(repeatCount[0], repeatCount[1]);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
finalNormalMap.wrapS = finalNormalMap.wrapT = ClampToEdgeWrapping;
|
|
256
|
+
finalNormalMap.repeat.set(1, 1);
|
|
257
|
+
}
|
|
258
|
+
finalNormalMap.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
259
|
+
finalNormalMap.colorSpace = NoColorSpace;
|
|
260
|
+
finalNormalMap.generateMipmaps = generateMipmaps;
|
|
261
|
+
finalNormalMap.minFilter = resolvedMinFilter;
|
|
262
|
+
finalNormalMap.magFilter = resolvedMagFilter;
|
|
263
|
+
finalNormalMap.needsUpdate = true;
|
|
264
|
+
}, [
|
|
265
|
+
finalNormalMap,
|
|
266
|
+
repeat,
|
|
267
|
+
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
|
|
268
|
+
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
|
|
269
|
+
offset === null || offset === void 0 ? void 0 : offset[0],
|
|
270
|
+
offset === null || offset === void 0 ? void 0 : offset[1],
|
|
271
|
+
generateMipmaps,
|
|
272
|
+
resolvedMinFilter,
|
|
273
|
+
resolvedMagFilter
|
|
274
|
+
]);
|
|
244
275
|
animatedOffsetRef.current = [(_j = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _j !== void 0 ? _j : 0, (_k = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _k !== void 0 ? _k : 0];
|
|
245
276
|
useFrame((_, delta) => {
|
|
246
277
|
var _a, _b;
|
|
@@ -259,22 +290,28 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
259
290
|
const sharedProps = Object.assign(Object.assign({ map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, side: resolvedSide, onUpdate: (material) => {
|
|
260
291
|
material.needsUpdate = true;
|
|
261
292
|
} }, materialProps), overrides);
|
|
293
|
+
const materialKey = [
|
|
294
|
+
materialType,
|
|
295
|
+
textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
|
|
296
|
+
normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
|
|
297
|
+
].join('|');
|
|
262
298
|
if (materialType === 'basic') {
|
|
263
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({ attach:
|
|
299
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
|
|
264
300
|
}
|
|
265
301
|
if (materialType === 'sprite') {
|
|
266
302
|
const spriteTransparent = materialSource.transparent !== false;
|
|
267
|
-
return (_jsx("spriteNodeMaterial", Object.assign({ attach:
|
|
303
|
+
return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (_l = materialSource.color) !== null && _l !== void 0 ? _l : '#ffffff', opacity: (_m = materialSource.opacity) !== null && _m !== void 0 ? _m : 1, transparent: spriteTransparent, alphaTest: (_o = materialSource.alphaTest) !== null && _o !== void 0 ? _o : 0, depthTest: (_p = materialSource.depthTest) !== null && _p !== void 0 ? _p : false, depthWrite: (_q = materialSource.depthWrite) !== null && _q !== void 0 ? _q : false, toneMapped: (_r = materialSource.toneMapped) !== null && _r !== void 0 ? _r : true, onUpdate: material => {
|
|
268
304
|
material.needsUpdate = true;
|
|
269
|
-
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true })));
|
|
305
|
+
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
|
|
270
306
|
}
|
|
271
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach:
|
|
307
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(_s = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _s !== void 0 ? _s : 1, (_t = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _t !== void 0 ? _t : 1] : undefined }), materialKey));
|
|
272
308
|
}
|
|
273
309
|
const MaterialComponent = {
|
|
274
310
|
name: 'Material',
|
|
275
311
|
Editor: MaterialComponentEditor,
|
|
276
312
|
View: MaterialComponentView,
|
|
277
313
|
defaultProperties: {
|
|
314
|
+
attach: 'material',
|
|
278
315
|
materialType: 'standard',
|
|
279
316
|
color: '#ffffff',
|
|
280
317
|
toneMapped: true,
|
|
@@ -5,9 +5,10 @@ import { Mesh } from 'three';
|
|
|
5
5
|
import { assetRef, assetRefs } from './ComponentRegistry';
|
|
6
6
|
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
|
|
7
7
|
import { useAssetRuntime } from '../assetRuntime';
|
|
8
|
-
import { useEditorContext } from '../PrefabEditor';
|
|
8
|
+
import { useEditorContext, useEditorRef } from '../PrefabEditor';
|
|
9
9
|
import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
|
|
10
|
-
import { colors, ui } from '../styles';
|
|
10
|
+
import { base, colors, ui } from '../styles';
|
|
11
|
+
import { decomposeModelToPrefabNodes } from '../modelPrefab';
|
|
11
12
|
const AXIS_OPTIONS = [
|
|
12
13
|
{ value: 'x', label: 'X' },
|
|
13
14
|
{ value: 'y', label: 'Y' },
|
|
@@ -42,8 +43,49 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
|
|
|
42
43
|
}
|
|
43
44
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
44
45
|
const { positionSnap } = useEditorContext();
|
|
46
|
+
const editor = useEditorRef();
|
|
45
47
|
const repeatAxes = getRepeatAxesFromModelProperties(component.properties);
|
|
46
|
-
|
|
48
|
+
const filename = component.properties.filename;
|
|
49
|
+
const canDecompose = Boolean(node && filename);
|
|
50
|
+
const handleDecompose = () => {
|
|
51
|
+
var _a, _b, _c, _d;
|
|
52
|
+
if (!node || !filename)
|
|
53
|
+
return;
|
|
54
|
+
const model = editor.getModel(filename);
|
|
55
|
+
if (!model) {
|
|
56
|
+
console.warn(`Model is not loaded yet: ${filename}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const textureRefs = new Map();
|
|
60
|
+
const decomposed = decomposeModelToPrefabNodes(model, {
|
|
61
|
+
idPrefix: node.id,
|
|
62
|
+
getTexturePath: (texture, usage) => {
|
|
63
|
+
const key = `embedded/${node.id}/${usage}/${texture.uuid}`;
|
|
64
|
+
textureRefs.set(key, texture);
|
|
65
|
+
return key;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
textureRefs.forEach((texture, path) => {
|
|
69
|
+
editor.addTexture(path, texture);
|
|
70
|
+
});
|
|
71
|
+
const preservedComponents = Object.entries((_a = node.components) !== null && _a !== void 0 ? _a : {}).reduce((result, [key, entry]) => {
|
|
72
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.type))
|
|
73
|
+
return result;
|
|
74
|
+
if (entry.type === 'Model' || entry.type === 'Geometry' || entry.type === 'BufferGeometry' || entry.type === 'Material') {
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
result[key] = entry;
|
|
78
|
+
return result;
|
|
79
|
+
}, {});
|
|
80
|
+
const decomposedComponents = Object.entries((_b = decomposed.components) !== null && _b !== void 0 ? _b : {}).reduce((result, [key, entry]) => {
|
|
81
|
+
if (!(entry === null || entry === void 0 ? void 0 : entry.type) || entry.type === 'Transform')
|
|
82
|
+
return result;
|
|
83
|
+
result[key] = entry;
|
|
84
|
+
return result;
|
|
85
|
+
}, {});
|
|
86
|
+
editor.replaceNode(node.id, Object.assign(Object.assign({}, node), { name: (_c = node.name) !== null && _c !== void 0 ? _c : decomposed.name, components: Object.assign(Object.assign({}, preservedComponents), decomposedComponents), children: (_d = decomposed.children) !== null && _d !== void 0 ? _d : [] }));
|
|
87
|
+
};
|
|
88
|
+
return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx("button", { type: "button", style: Object.assign(Object.assign({}, base.btn), { width: '100%' }), onClick: handleDecompose, disabled: !canDecompose, title: canDecompose ? 'Replace this model node with editable geometry and material nodes' : 'Choose a model before decomposing', children: "Decompose Model" }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), !component.properties.instanced ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: component.properties, onChange: onUpdate, placeholder: "node:click" })) : null] })) : null, component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
|
|
47
89
|
}
|
|
48
90
|
// View for Model component
|
|
49
91
|
function ModelComponentView({ properties, children }) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
import type { Texture } from 'three';
|
|
3
|
+
import type { GameObject } from './types';
|
|
4
|
+
export interface DecomposeModelOptions {
|
|
5
|
+
/** Prefix used for generated prefab node ids. Defaults to "model". */
|
|
6
|
+
idPrefix?: string;
|
|
7
|
+
/** Include invisible Three objects in the generated prefab tree. */
|
|
8
|
+
includeInvisible?: boolean;
|
|
9
|
+
/** Return a serializable texture ref for embedded or externally loaded textures. */
|
|
10
|
+
getTexturePath?: (texture: Texture, usage: 'map' | 'normalMap') => string | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Converts a live Three object hierarchy into prefab JSON nodes made from
|
|
14
|
+
* Transform, BufferGeometry, and Material components.
|
|
15
|
+
*/
|
|
16
|
+
export declare function decomposeModelToPrefabNodes(object: Object3D, options?: DecomposeModelOptions): GameObject;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { BackSide, DoubleSide, Mesh, } from 'three';
|
|
2
|
+
function createId(prefix) {
|
|
3
|
+
return `${prefix}-${crypto.randomUUID()}`;
|
|
4
|
+
}
|
|
5
|
+
function toArrayAttribute(attribute, itemSize, vertexIndices) {
|
|
6
|
+
if (!attribute || attribute.itemSize < itemSize)
|
|
7
|
+
return undefined;
|
|
8
|
+
const values = [];
|
|
9
|
+
for (const vertexIndex of vertexIndices) {
|
|
10
|
+
for (let axis = 0; axis < itemSize; axis += 1) {
|
|
11
|
+
values.push(attribute.getComponent(vertexIndex, axis));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return values;
|
|
15
|
+
}
|
|
16
|
+
function getSequentialVertexIndices(count) {
|
|
17
|
+
return Array.from({ length: count }, (_, index) => index);
|
|
18
|
+
}
|
|
19
|
+
function serializeGeometry(geometry) {
|
|
20
|
+
var _a, _b, _c, _d;
|
|
21
|
+
const position = geometry.getAttribute('position');
|
|
22
|
+
const normal = geometry.getAttribute('normal');
|
|
23
|
+
const uv = geometry.getAttribute('uv');
|
|
24
|
+
const vertexIndices = getSequentialVertexIndices((_a = position === null || position === void 0 ? void 0 : position.count) !== null && _a !== void 0 ? _a : 0);
|
|
25
|
+
const index = geometry.index;
|
|
26
|
+
const indices = [];
|
|
27
|
+
if (index) {
|
|
28
|
+
for (let offset = 0; offset < index.count; offset += 1) {
|
|
29
|
+
indices.push(index.getX(offset));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
indices.push(...vertexIndices);
|
|
34
|
+
}
|
|
35
|
+
const positions = (_b = toArrayAttribute(position, 3, vertexIndices)) !== null && _b !== void 0 ? _b : [];
|
|
36
|
+
const normals = (_c = toArrayAttribute(normal, 3, vertexIndices)) !== null && _c !== void 0 ? _c : [];
|
|
37
|
+
const uvs = (_d = toArrayAttribute(uv, 2, vertexIndices)) !== null && _d !== void 0 ? _d : [];
|
|
38
|
+
const groups = geometry.groups.map(group => {
|
|
39
|
+
var _a;
|
|
40
|
+
return ({
|
|
41
|
+
start: group.start,
|
|
42
|
+
count: group.count,
|
|
43
|
+
materialIndex: (_a = group.materialIndex) !== null && _a !== void 0 ? _a : 0,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
positions,
|
|
48
|
+
indices,
|
|
49
|
+
normals,
|
|
50
|
+
uvs,
|
|
51
|
+
groups,
|
|
52
|
+
computeVertexNormals: normals.length === 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function getSideName(side) {
|
|
56
|
+
if (side === BackSide)
|
|
57
|
+
return 'BackSide';
|
|
58
|
+
if (side === DoubleSide)
|
|
59
|
+
return 'DoubleSide';
|
|
60
|
+
return 'FrontSide';
|
|
61
|
+
}
|
|
62
|
+
function getMaterialColor(material) {
|
|
63
|
+
var _a, _b;
|
|
64
|
+
const maybeColor = material;
|
|
65
|
+
return (_b = (_a = maybeColor.color) === null || _a === void 0 ? void 0 : _a.getStyle) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
66
|
+
}
|
|
67
|
+
function getTextureImagePath(texture) {
|
|
68
|
+
var _a;
|
|
69
|
+
const image = texture === null || texture === void 0 ? void 0 : texture.image;
|
|
70
|
+
const path = (_a = image === null || image === void 0 ? void 0 : image.currentSrc) !== null && _a !== void 0 ? _a : image === null || image === void 0 ? void 0 : image.src;
|
|
71
|
+
if (typeof path === 'string' && path.trim() && !path.startsWith('blob:')) {
|
|
72
|
+
return path;
|
|
73
|
+
}
|
|
74
|
+
return getTextureImageDataUrl(image);
|
|
75
|
+
}
|
|
76
|
+
function getTextureImageDataUrl(image) {
|
|
77
|
+
if (!image
|
|
78
|
+
|| typeof document === 'undefined'
|
|
79
|
+
|| typeof image.width !== 'number'
|
|
80
|
+
|| typeof image.height !== 'number'
|
|
81
|
+
|| image.width <= 0
|
|
82
|
+
|| image.height <= 0) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const canvas = document.createElement('canvas');
|
|
87
|
+
canvas.width = image.width;
|
|
88
|
+
canvas.height = image.height;
|
|
89
|
+
const context = canvas.getContext('2d');
|
|
90
|
+
if (!context)
|
|
91
|
+
return undefined;
|
|
92
|
+
context.drawImage(image, 0, 0);
|
|
93
|
+
return canvas.toDataURL('image/png');
|
|
94
|
+
}
|
|
95
|
+
catch (_a) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function getTexturePath(texture, usage, options) {
|
|
100
|
+
var _a, _b;
|
|
101
|
+
if (!texture)
|
|
102
|
+
return undefined;
|
|
103
|
+
return (_b = (_a = getTextureImagePath(texture)) !== null && _a !== void 0 ? _a : options.getTexturePath(texture, usage)) !== null && _b !== void 0 ? _b : undefined;
|
|
104
|
+
}
|
|
105
|
+
function serializeMaterial(material, attach, options) {
|
|
106
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
107
|
+
const source = material;
|
|
108
|
+
const materialType = 'metalness' in source || 'roughness' in source ? 'standard' : 'basic';
|
|
109
|
+
const texture = getTexturePath(source.map, 'map', options);
|
|
110
|
+
const normalMapTexture = getTexturePath(source.normalMap, 'normalMap', options);
|
|
111
|
+
const normalScale = (_b = (_a = source.normalScale) === null || _a === void 0 ? void 0 : _a.toArray) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
112
|
+
return {
|
|
113
|
+
type: 'Material',
|
|
114
|
+
properties: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ attach,
|
|
115
|
+
materialType, color: (_c = getMaterialColor(material)) !== null && _c !== void 0 ? _c : '#ffffff' }, (texture ? { texture } : null)), (normalMapTexture ? { normalMapTexture } : null)), (normalScale ? { normalScale } : null)), { opacity: material.opacity, transparent: material.transparent, side: getSideName(material.side), wireframe: (_d = source.wireframe) !== null && _d !== void 0 ? _d : false, toneMapped: (_e = source.toneMapped) !== null && _e !== void 0 ? _e : true }), (materialType === 'standard' ? {
|
|
116
|
+
metalness: (_f = source.metalness) !== null && _f !== void 0 ? _f : 0,
|
|
117
|
+
roughness: (_g = source.roughness) !== null && _g !== void 0 ? _g : 1,
|
|
118
|
+
} : null)),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function getMeshParts(mesh) {
|
|
122
|
+
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
123
|
+
return materials.map((material, index) => {
|
|
124
|
+
return {
|
|
125
|
+
key: materials.length > 1 ? `material_${index}` : 'material',
|
|
126
|
+
material,
|
|
127
|
+
attach: materials.length > 1 ? `material-${index}` : 'material',
|
|
128
|
+
};
|
|
129
|
+
}).filter(part => part.material);
|
|
130
|
+
}
|
|
131
|
+
function createTransformComponent(object) {
|
|
132
|
+
return {
|
|
133
|
+
type: 'Transform',
|
|
134
|
+
properties: {
|
|
135
|
+
position: object.position.toArray(),
|
|
136
|
+
rotation: [object.rotation.x, object.rotation.y, object.rotation.z],
|
|
137
|
+
scale: object.scale.toArray(),
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function createNode(object, idPrefix, children = [], components = {}) {
|
|
142
|
+
return {
|
|
143
|
+
id: createId(idPrefix),
|
|
144
|
+
name: object.name || object.type,
|
|
145
|
+
hidden: object.visible === false ? true : undefined,
|
|
146
|
+
components: Object.assign({ transform: createTransformComponent(object) }, components),
|
|
147
|
+
children,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function decomposeObject(object, options) {
|
|
151
|
+
if (!options.includeInvisible && !object.visible)
|
|
152
|
+
return null;
|
|
153
|
+
const childNodes = object.children
|
|
154
|
+
.map(child => decomposeObject(child, options))
|
|
155
|
+
.filter((child) => child != null);
|
|
156
|
+
if (!(object instanceof Mesh)) {
|
|
157
|
+
return createNode(object, options.idPrefix, childNodes);
|
|
158
|
+
}
|
|
159
|
+
const parts = getMeshParts(object);
|
|
160
|
+
const materialComponents = parts.reduce((result, part) => {
|
|
161
|
+
result[part.key] = serializeMaterial(part.material, part.attach, options);
|
|
162
|
+
return result;
|
|
163
|
+
}, {});
|
|
164
|
+
return createNode(object, options.idPrefix, childNodes, Object.assign({ geometry: {
|
|
165
|
+
type: 'BufferGeometry',
|
|
166
|
+
properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
|
|
167
|
+
} }, materialComponents));
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Converts a live Three object hierarchy into prefab JSON nodes made from
|
|
171
|
+
* Transform, BufferGeometry, and Material components.
|
|
172
|
+
*/
|
|
173
|
+
export function decomposeModelToPrefabNodes(object, options = {}) {
|
|
174
|
+
var _a, _b, _c, _d, _e;
|
|
175
|
+
return (_d = decomposeObject(object, {
|
|
176
|
+
idPrefix: (_a = options.idPrefix) !== null && _a !== void 0 ? _a : 'model',
|
|
177
|
+
includeInvisible: (_b = options.includeInvisible) !== null && _b !== void 0 ? _b : false,
|
|
178
|
+
getTexturePath: (_c = options.getTexturePath) !== null && _c !== void 0 ? _c : (() => undefined),
|
|
179
|
+
})) !== null && _d !== void 0 ? _d : createNode(object, (_e = options.idPrefix) !== null && _e !== void 0 ? _e : 'model');
|
|
180
|
+
}
|
|
@@ -5,6 +5,7 @@ import { denormalizePrefab, PrefabState, PrefabNodeRecord } from "./prefab";
|
|
|
5
5
|
export interface PrefabStoreState extends PrefabState {
|
|
6
6
|
replacePrefab: (prefab: Prefab) => void;
|
|
7
7
|
updateNode: (id: string, update: (node: PrefabNodeRecord) => PrefabNodeRecord) => void;
|
|
8
|
+
replaceNode: (id: string, node: GameObject) => void;
|
|
8
9
|
addChild: (parentId: string, node: GameObject) => void;
|
|
9
10
|
deleteNode: (id: string) => void;
|
|
10
11
|
duplicateNode: (id: string) => string | null;
|
|
@@ -5,6 +5,45 @@ import { createStore } from "zustand/vanilla";
|
|
|
5
5
|
import { collectAssetRefsForIds, collectSubtreeAssetRefs, collectSubtreeIds, cloneSubtree, createPrefabPatch, denormalizePrefab, insertSubtree, isDescendant, normalizePrefab, updateAssetRefsForNodeChange, } from "./prefab";
|
|
6
6
|
const PrefabStoreContext = createContext(null);
|
|
7
7
|
const EMPTY_CHILD_IDS = [];
|
|
8
|
+
function addAssetRefs(assetRefCounts, refs) {
|
|
9
|
+
refs.forEach(ref => {
|
|
10
|
+
var _a;
|
|
11
|
+
assetRefCounts[ref] = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function removeAssetRefs(assetRefCounts, refs) {
|
|
15
|
+
refs.forEach(ref => {
|
|
16
|
+
var _a;
|
|
17
|
+
const nextCount = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) - 1;
|
|
18
|
+
if (nextCount > 0) {
|
|
19
|
+
assetRefCounts[ref] = nextCount;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
delete assetRefCounts[ref];
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function cloneGraphState(state) {
|
|
26
|
+
return {
|
|
27
|
+
nodesById: Object.assign({}, state.nodesById),
|
|
28
|
+
childIdsById: Object.assign({}, state.childIdsById),
|
|
29
|
+
parentIdById: Object.assign({}, state.parentIdById),
|
|
30
|
+
assetRefCounts: Object.assign({}, state.assetRefCounts),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function removeSubtreeFromGraph(id, state, next) {
|
|
34
|
+
const ids = collectSubtreeIds(id, state.childIdsById);
|
|
35
|
+
removeAssetRefs(next.assetRefCounts, collectAssetRefsForIds(ids, state.nodesById));
|
|
36
|
+
ids.forEach(nodeId => {
|
|
37
|
+
delete next.nodesById[nodeId];
|
|
38
|
+
delete next.childIdsById[nodeId];
|
|
39
|
+
delete next.parentIdById[nodeId];
|
|
40
|
+
});
|
|
41
|
+
return ids;
|
|
42
|
+
}
|
|
43
|
+
function insertSubtreeIntoGraph(node, parentId, next) {
|
|
44
|
+
insertSubtree(node, parentId, next.nodesById, next.childIdsById, next.parentIdById);
|
|
45
|
+
addAssetRefs(next.assetRefCounts, collectSubtreeAssetRefs(node));
|
|
46
|
+
}
|
|
8
47
|
export function PrefabStoreProvider({ store, children, }) {
|
|
9
48
|
return createElement(PrefabStoreContext.Provider, { value: store }, children);
|
|
10
49
|
}
|
|
@@ -42,26 +81,40 @@ export function createPrefabStore(prefab) {
|
|
|
42
81
|
set(createPrefabPatch(state, {
|
|
43
82
|
nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
|
|
44
83
|
}, nextAssetRefCounts));
|
|
84
|
+
}, replaceNode: (id, node) => {
|
|
85
|
+
var _a;
|
|
86
|
+
const state = get();
|
|
87
|
+
if (!state.nodesById[id])
|
|
88
|
+
return;
|
|
89
|
+
const parentId = state.parentIdById[id];
|
|
90
|
+
const next = cloneGraphState(state);
|
|
91
|
+
removeSubtreeFromGraph(id, state, next);
|
|
92
|
+
insertSubtreeIntoGraph(node, parentId, next);
|
|
93
|
+
const patch = {
|
|
94
|
+
nodesById: next.nodesById,
|
|
95
|
+
childIdsById: next.childIdsById,
|
|
96
|
+
parentIdById: next.parentIdById,
|
|
97
|
+
};
|
|
98
|
+
if (id === state.rootId) {
|
|
99
|
+
patch.rootId = node.id;
|
|
100
|
+
}
|
|
101
|
+
else if (parentId) {
|
|
102
|
+
next.childIdsById[parentId] = ((_a = next.childIdsById[parentId]) !== null && _a !== void 0 ? _a : []).map(childId => childId === id ? node.id : childId);
|
|
103
|
+
}
|
|
104
|
+
set(createPrefabPatch(state, patch, next.assetRefCounts));
|
|
45
105
|
}, addChild: (parentId, node) => {
|
|
46
106
|
var _a;
|
|
47
107
|
const state = get();
|
|
48
108
|
if (!state.nodesById[parentId])
|
|
49
109
|
return;
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
54
|
-
insertSubtree(node, parentId, nextNodesById, nextChildIdsById, nextParentIdById);
|
|
55
|
-
nextChildIdsById[parentId] = [...((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []), node.id];
|
|
56
|
-
collectSubtreeAssetRefs(node).forEach(ref => {
|
|
57
|
-
var _a;
|
|
58
|
-
nextAssetRefCounts[ref] = ((_a = nextAssetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
59
|
-
});
|
|
110
|
+
const next = cloneGraphState(state);
|
|
111
|
+
insertSubtreeIntoGraph(node, parentId, next);
|
|
112
|
+
next.childIdsById[parentId] = [...((_a = next.childIdsById[parentId]) !== null && _a !== void 0 ? _a : []), node.id];
|
|
60
113
|
set(createPrefabPatch(state, {
|
|
61
|
-
nodesById:
|
|
62
|
-
childIdsById:
|
|
63
|
-
parentIdById:
|
|
64
|
-
},
|
|
114
|
+
nodesById: next.nodesById,
|
|
115
|
+
childIdsById: next.childIdsById,
|
|
116
|
+
parentIdById: next.parentIdById,
|
|
117
|
+
}, next.assetRefCounts));
|
|
65
118
|
}, deleteNode: (id) => {
|
|
66
119
|
var _a;
|
|
67
120
|
const state = get();
|
|
@@ -70,31 +123,14 @@ export function createPrefabStore(prefab) {
|
|
|
70
123
|
const parentId = state.parentIdById[id];
|
|
71
124
|
if (!parentId)
|
|
72
125
|
return;
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const nextParentIdById = Object.assign({}, state.parentIdById);
|
|
77
|
-
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
78
|
-
collectAssetRefsForIds(idsToDelete, state.nodesById).forEach(ref => {
|
|
79
|
-
var _a;
|
|
80
|
-
const nextCount = ((_a = nextAssetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) - 1;
|
|
81
|
-
if (nextCount > 0) {
|
|
82
|
-
nextAssetRefCounts[ref] = nextCount;
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
delete nextAssetRefCounts[ref];
|
|
86
|
-
});
|
|
87
|
-
idsToDelete.forEach(nodeId => {
|
|
88
|
-
delete nextNodesById[nodeId];
|
|
89
|
-
delete nextChildIdsById[nodeId];
|
|
90
|
-
delete nextParentIdById[nodeId];
|
|
91
|
-
});
|
|
92
|
-
nextChildIdsById[parentId] = ((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []).filter(childId => childId !== id);
|
|
126
|
+
const next = cloneGraphState(state);
|
|
127
|
+
removeSubtreeFromGraph(id, state, next);
|
|
128
|
+
next.childIdsById[parentId] = ((_a = next.childIdsById[parentId]) !== null && _a !== void 0 ? _a : []).filter(childId => childId !== id);
|
|
93
129
|
set(createPrefabPatch(state, {
|
|
94
|
-
nodesById:
|
|
95
|
-
childIdsById:
|
|
96
|
-
parentIdById:
|
|
97
|
-
},
|
|
130
|
+
nodesById: next.nodesById,
|
|
131
|
+
childIdsById: next.childIdsById,
|
|
132
|
+
parentIdById: next.parentIdById,
|
|
133
|
+
}, next.assetRefCounts));
|
|
98
134
|
}, duplicateNode: (id) => {
|
|
99
135
|
var _a;
|
|
100
136
|
const state = get();
|
|
@@ -119,10 +155,7 @@ export function createPrefabStore(prefab) {
|
|
|
119
155
|
}
|
|
120
156
|
nextChildIdsById[parentId] = siblings;
|
|
121
157
|
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
122
|
-
collectAssetRefsForIds(collectSubtreeIds(id, state.childIdsById), state.nodesById)
|
|
123
|
-
var _a;
|
|
124
|
-
nextAssetRefCounts[ref] = ((_a = nextAssetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
125
|
-
});
|
|
158
|
+
addAssetRefs(nextAssetRefCounts, collectAssetRefsForIds(collectSubtreeIds(id, state.childIdsById), state.nodesById));
|
|
126
159
|
set(createPrefabPatch(state, {
|
|
127
160
|
nodesById: nextNodesById,
|
|
128
161
|
childIdsById: nextChildIdsById,
|