react-three-game 0.0.100 → 0.0.102
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 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.d.ts +3 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +352 -0
- package/dist/plugins/crashcat/CrashcatRuntime.d.ts +27 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +154 -0
- package/dist/plugins/crashcat/index.d.ts +2 -0
- package/dist/plugins/crashcat/index.js +2 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/tools/assetviewer/page.js +95 -46
- 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 +85 -57
- 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 +27 -2
|
@@ -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, useLayoutEffect, 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,25 +38,53 @@ const MAG_FILTER_MAP = {
|
|
|
38
38
|
NearestFilter,
|
|
39
39
|
LinearFilter,
|
|
40
40
|
};
|
|
41
|
-
function
|
|
42
|
-
var _a, _b;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
function configureTexture(texture, options) {
|
|
42
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
43
|
+
if (!texture)
|
|
44
|
+
return;
|
|
45
|
+
if (options.repeat) {
|
|
46
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
47
|
+
texture.repeat.set((_b = (_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, (_d = (_c = options.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1);
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
texture.wrapS = texture.wrapT = ClampToEdgeWrapping;
|
|
51
|
+
texture.repeat.set(1, 1);
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
texture.offset.set((_f = (_e = options.offset) === null || _e === void 0 ? void 0 : _e[0]) !== null && _f !== void 0 ? _f : 0, (_h = (_g = options.offset) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : 0);
|
|
54
|
+
texture.colorSpace = options.colorSpace;
|
|
55
|
+
texture.generateMipmaps = options.generateMipmaps;
|
|
56
|
+
texture.minFilter = options.minFilter;
|
|
57
|
+
texture.magFilter = options.magFilter;
|
|
58
|
+
texture.needsUpdate = true;
|
|
59
|
+
}
|
|
60
|
+
function cloneConfiguredTexture(texture, options) {
|
|
61
|
+
if (!texture)
|
|
62
|
+
return undefined;
|
|
63
|
+
const nextTexture = texture.clone();
|
|
64
|
+
configureTexture(nextTexture, options);
|
|
65
|
+
return nextTexture;
|
|
66
|
+
}
|
|
67
|
+
function useConfiguredTexture(texture, options) {
|
|
68
|
+
var _a, _b, _c, _d;
|
|
69
|
+
const configuredTexture = useMemo(() => cloneConfiguredTexture(texture, options), [texture]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
return () => configuredTexture === null || configuredTexture === void 0 ? void 0 : configuredTexture.dispose();
|
|
72
|
+
}, [configuredTexture]);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
configureTexture(configuredTexture, options);
|
|
75
|
+
}, [
|
|
76
|
+
configuredTexture,
|
|
77
|
+
options.colorSpace,
|
|
78
|
+
options.repeat,
|
|
79
|
+
(_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0],
|
|
80
|
+
(_b = options.repeatCount) === null || _b === void 0 ? void 0 : _b[1],
|
|
81
|
+
(_c = options.offset) === null || _c === void 0 ? void 0 : _c[0],
|
|
82
|
+
(_d = options.offset) === null || _d === void 0 ? void 0 : _d[1],
|
|
83
|
+
options.generateMipmaps,
|
|
84
|
+
options.minFilter,
|
|
85
|
+
options.magFilter,
|
|
86
|
+
]);
|
|
87
|
+
return configuredTexture;
|
|
60
88
|
}
|
|
61
89
|
export function useMaterialOverrides() {
|
|
62
90
|
return useContext(MaterialOverridesContext);
|
|
@@ -72,14 +100,20 @@ extend({
|
|
|
72
100
|
SpriteNodeMaterial,
|
|
73
101
|
});
|
|
74
102
|
function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
75
|
-
var _a;
|
|
103
|
+
var _a, _b, _c, _d;
|
|
76
104
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
77
105
|
const hasTexture = !!component.properties.texture;
|
|
78
106
|
const hasRepeat = component.properties.repeat;
|
|
79
107
|
const animateOffset = component.properties.animateOffset;
|
|
80
108
|
const isStandardMaterial = materialType === 'standard';
|
|
81
109
|
const isSpriteMaterial = materialType === 'sprite';
|
|
110
|
+
const editorValues = Object.assign(Object.assign({}, component.properties), { generateMipmaps: (_b = component.properties.generateMipmaps) !== null && _b !== void 0 ? _b : true, minFilter: (_c = component.properties.minFilter) !== null && _c !== void 0 ? _c : 'LinearMipmapLinearFilter', magFilter: (_d = component.properties.magFilter) !== null && _d !== void 0 ? _d : 'LinearFilter' });
|
|
82
111
|
const fields = [
|
|
112
|
+
{
|
|
113
|
+
name: 'attach',
|
|
114
|
+
type: 'string',
|
|
115
|
+
label: 'Attach',
|
|
116
|
+
},
|
|
83
117
|
{
|
|
84
118
|
name: 'materialType',
|
|
85
119
|
type: 'select',
|
|
@@ -166,12 +200,12 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
166
200
|
type: 'select',
|
|
167
201
|
label: 'Min Filter',
|
|
168
202
|
options: [
|
|
203
|
+
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
204
|
+
{ value: 'LinearFilter', label: 'Linear' },
|
|
205
|
+
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
169
206
|
{ value: 'NearestFilter', label: 'Nearest' },
|
|
170
207
|
{ value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
|
|
171
208
|
{ value: 'NearestMipmapLinearFilter', label: 'Nearest Mipmap Linear' },
|
|
172
|
-
{ value: 'LinearFilter', label: 'Linear' },
|
|
173
|
-
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
174
|
-
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
175
209
|
],
|
|
176
210
|
},
|
|
177
211
|
{
|
|
@@ -179,17 +213,17 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
179
213
|
type: 'select',
|
|
180
214
|
label: 'Mag Filter',
|
|
181
215
|
options: [
|
|
182
|
-
{ value: 'NearestFilter', label: 'Nearest' },
|
|
183
216
|
{ value: 'LinearFilter', label: 'Linear (Default)' },
|
|
217
|
+
{ value: 'NearestFilter', label: 'Nearest' },
|
|
184
218
|
],
|
|
185
219
|
},
|
|
186
220
|
] : []),
|
|
187
221
|
];
|
|
188
|
-
return (_jsx(FieldRenderer, { fields: fields, values:
|
|
222
|
+
return (_jsx(FieldRenderer, { fields: fields, values: editorValues, onChange: onUpdate }));
|
|
189
223
|
}
|
|
190
224
|
// View for Material component
|
|
191
225
|
function MaterialComponentView({ properties: rawProps }) {
|
|
192
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
226
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
193
227
|
const { getTexture } = useAssetRuntime();
|
|
194
228
|
const properties = rawProps;
|
|
195
229
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
@@ -208,39 +242,21 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
208
242
|
const normalScaleProp = materialSource.normalScale;
|
|
209
243
|
const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
|
|
210
244
|
// 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"]);
|
|
245
|
+
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
246
|
const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
|
|
213
247
|
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
214
248
|
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
215
249
|
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
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
minFilter: resolvedMinFilter,
|
|
227
|
-
magFilter: resolvedMagFilter,
|
|
228
|
-
});
|
|
229
|
-
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], offset === null || offset === void 0 ? void 0 : offset[0], offset === null || offset === void 0 ? void 0 : offset[1], generateMipmaps, resolvedMinFilter, resolvedMagFilter]);
|
|
230
|
-
const finalNormalMap = useMemo(() => {
|
|
231
|
-
if (!normalMapTexture)
|
|
232
|
-
return undefined;
|
|
233
|
-
return cloneConfiguredTexture({
|
|
234
|
-
texture: normalMapTexture,
|
|
235
|
-
repeat,
|
|
236
|
-
repeatCount,
|
|
237
|
-
offset,
|
|
238
|
-
colorSpace: NoColorSpace,
|
|
239
|
-
generateMipmaps,
|
|
240
|
-
minFilter: resolvedMinFilter,
|
|
241
|
-
magFilter: resolvedMagFilter,
|
|
242
|
-
});
|
|
243
|
-
}, [normalMapTexture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], offset === null || offset === void 0 ? void 0 : offset[0], offset === null || offset === void 0 ? void 0 : offset[1], generateMipmaps, resolvedMinFilter, resolvedMagFilter]);
|
|
250
|
+
const textureConfig = {
|
|
251
|
+
repeat,
|
|
252
|
+
repeatCount,
|
|
253
|
+
offset,
|
|
254
|
+
generateMipmaps,
|
|
255
|
+
minFilter: resolvedMinFilter,
|
|
256
|
+
magFilter: resolvedMagFilter,
|
|
257
|
+
};
|
|
258
|
+
const finalTexture = useConfiguredTexture(texture, Object.assign(Object.assign({}, textureConfig), { colorSpace: SRGBColorSpace }));
|
|
259
|
+
const finalNormalMap = useConfiguredTexture(normalMapTexture, Object.assign(Object.assign({}, textureConfig), { colorSpace: NoColorSpace }));
|
|
244
260
|
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
261
|
useFrame((_, delta) => {
|
|
246
262
|
var _a, _b;
|
|
@@ -259,22 +275,34 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
259
275
|
const sharedProps = Object.assign(Object.assign({ map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, side: resolvedSide, onUpdate: (material) => {
|
|
260
276
|
material.needsUpdate = true;
|
|
261
277
|
} }, materialProps), overrides);
|
|
278
|
+
const materialKey = [
|
|
279
|
+
materialType,
|
|
280
|
+
textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
|
|
281
|
+
normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
|
|
282
|
+
repeat ? 'repeat' : 'clamp',
|
|
283
|
+
(_l = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0]) !== null && _l !== void 0 ? _l : 1,
|
|
284
|
+
(_m = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]) !== null && _m !== void 0 ? _m : 1,
|
|
285
|
+
generateMipmaps ? 'mips' : 'no-mips',
|
|
286
|
+
minFilter,
|
|
287
|
+
magFilter,
|
|
288
|
+
].join('|');
|
|
262
289
|
if (materialType === 'basic') {
|
|
263
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({ attach:
|
|
290
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
|
|
264
291
|
}
|
|
265
292
|
if (materialType === 'sprite') {
|
|
266
293
|
const spriteTransparent = materialSource.transparent !== false;
|
|
267
|
-
return (_jsx("spriteNodeMaterial", Object.assign({ attach:
|
|
294
|
+
return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (_o = materialSource.color) !== null && _o !== void 0 ? _o : '#ffffff', opacity: (_p = materialSource.opacity) !== null && _p !== void 0 ? _p : 1, transparent: spriteTransparent, alphaTest: (_q = materialSource.alphaTest) !== null && _q !== void 0 ? _q : 0, depthTest: (_r = materialSource.depthTest) !== null && _r !== void 0 ? _r : false, depthWrite: (_s = materialSource.depthWrite) !== null && _s !== void 0 ? _s : false, toneMapped: (_t = materialSource.toneMapped) !== null && _t !== void 0 ? _t : true, onUpdate: material => {
|
|
268
295
|
material.needsUpdate = true;
|
|
269
|
-
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true })));
|
|
296
|
+
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
|
|
270
297
|
}
|
|
271
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach:
|
|
298
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(_u = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _u !== void 0 ? _u : 1, (_v = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _v !== void 0 ? _v : 1] : undefined }), materialKey));
|
|
272
299
|
}
|
|
273
300
|
const MaterialComponent = {
|
|
274
301
|
name: 'Material',
|
|
275
302
|
Editor: MaterialComponentEditor,
|
|
276
303
|
View: MaterialComponentView,
|
|
277
304
|
defaultProperties: {
|
|
305
|
+
attach: 'material',
|
|
278
306
|
materialType: 'standard',
|
|
279
307
|
color: '#ffffff',
|
|
280
308
|
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;
|