react-three-game 0.0.94 → 0.0.96
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/tools/prefabeditor/InstanceProvider.d.ts +7 -7
- package/dist/tools/prefabeditor/InstanceProvider.js +29 -34
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.js +25 -13
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +6 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +116 -52
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +14 -5
- package/dist/tools/prefabeditor/components/MaterialComponent.js +123 -85
- package/dist/tools/prefabeditor/components/ModelComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +5 -7
- package/dist/tools/prefabeditor/components/SpriteComponent.d.ts +8 -0
- package/dist/tools/prefabeditor/components/SpriteComponent.js +27 -0
- package/dist/tools/prefabeditor/components/index.js +22 -14
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { ThreeEvent } from '@react-three/fiber';
|
|
3
|
-
import { Object3D
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ThreeEvent } from '@react-three/fiber';
|
|
3
|
+
import type { Object3D } from "three";
|
|
4
4
|
export type RepeatAxisConfig = {
|
|
5
5
|
axis: 'x' | 'y' | 'z';
|
|
6
6
|
count: number;
|
|
@@ -8,7 +8,7 @@ export type RepeatAxisConfig = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare const DEFAULT_REPEAT_AXES: RepeatAxisConfig[];
|
|
10
10
|
export declare function normalizeRepeatAxes(value: unknown): RepeatAxisConfig[];
|
|
11
|
-
export declare function getRepeatAxesFromModelProperties(properties: Record<string,
|
|
11
|
+
export declare function getRepeatAxesFromModelProperties(properties: Record<string, unknown>): RepeatAxisConfig[];
|
|
12
12
|
export type InstanceData = {
|
|
13
13
|
id: string;
|
|
14
14
|
sourceId: string;
|
|
@@ -20,7 +20,7 @@ export type InstanceData = {
|
|
|
20
20
|
meshPath: string;
|
|
21
21
|
};
|
|
22
22
|
export declare function GameInstanceProvider({ children, models, onSelect, onClick, registerRef, selectedId, editMode }: {
|
|
23
|
-
children:
|
|
23
|
+
children: ReactNode;
|
|
24
24
|
models: {
|
|
25
25
|
[filename: string]: Object3D;
|
|
26
26
|
};
|
|
@@ -31,7 +31,7 @@ export declare function GameInstanceProvider({ children, models, onSelect, onCli
|
|
|
31
31
|
editMode?: boolean;
|
|
32
32
|
}): import("react/jsx-runtime").JSX.Element;
|
|
33
33
|
export declare function useInstanceCheck(id: string): boolean;
|
|
34
|
-
export declare
|
|
34
|
+
export declare function GameInstance({ id, sourceId, modelUrl, locked, position, rotation, scale, visible, onClick: _onClick, }: {
|
|
35
35
|
id: string;
|
|
36
36
|
sourceId?: string;
|
|
37
37
|
modelUrl: string;
|
|
@@ -41,4 +41,4 @@ export declare const GameInstance: React.ForwardRefExoticComponent<{
|
|
|
41
41
|
scale: [number, number, number];
|
|
42
42
|
visible?: boolean;
|
|
43
43
|
onClick?: (event: ThreeEvent<PointerEvent>, nodeId: string, object: Object3D | null) => void;
|
|
44
|
-
}
|
|
44
|
+
}): null;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { createContext, useContext, useMemo, useRef, useState, useEffect } from "react";
|
|
3
3
|
import { Merged, useHelper } from '@react-three/drei';
|
|
4
4
|
import { Mesh, Matrix4, BoxHelper } from "three";
|
|
5
5
|
import { useStore } from "zustand";
|
|
6
6
|
import { createStore } from "zustand/vanilla";
|
|
7
7
|
import { usePointerEvents } from "./usePointerEvents";
|
|
8
8
|
export const DEFAULT_REPEAT_AXES = [{ axis: 'x', count: 1, offset: 1 }];
|
|
9
|
+
const EMPTY_INSTANCE_STORE = createInstanceRegistryStore();
|
|
9
10
|
export function normalizeRepeatAxes(value) {
|
|
10
11
|
if (!Array.isArray(value)) {
|
|
11
12
|
return DEFAULT_REPEAT_AXES;
|
|
@@ -14,14 +15,15 @@ export function normalizeRepeatAxes(value) {
|
|
|
14
15
|
const normalized = value.reduce((result, entry) => {
|
|
15
16
|
if (!entry || typeof entry !== 'object')
|
|
16
17
|
return result;
|
|
17
|
-
const
|
|
18
|
+
const record = entry;
|
|
19
|
+
const axisValue = record.axis;
|
|
18
20
|
if (axisValue !== 'x' && axisValue !== 'y' && axisValue !== 'z')
|
|
19
21
|
return result;
|
|
20
22
|
if (seen.has(axisValue))
|
|
21
23
|
return result;
|
|
22
24
|
seen.add(axisValue);
|
|
23
|
-
const countValue = Number(
|
|
24
|
-
const offsetValue = Number(
|
|
25
|
+
const countValue = Number(record.count);
|
|
26
|
+
const offsetValue = Number(record.offset);
|
|
25
27
|
result.push({
|
|
26
28
|
axis: axisValue,
|
|
27
29
|
count: Number.isFinite(countValue) ? Math.max(1, Math.floor(countValue)) : 1,
|
|
@@ -141,7 +143,7 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
141
143
|
const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
|
|
142
144
|
let partIndex = 0;
|
|
143
145
|
model.traverse((obj) => {
|
|
144
|
-
if (obj
|
|
146
|
+
if (obj instanceof Mesh) {
|
|
145
147
|
// Clone geometry and bake relative transform
|
|
146
148
|
const geom = obj.geometry.clone();
|
|
147
149
|
geom.applyMatrix4(obj.matrixWorld.clone().premultiply(rootInverse));
|
|
@@ -157,7 +159,9 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
157
159
|
// Cleanup geometries when models change
|
|
158
160
|
useEffect(() => {
|
|
159
161
|
return () => {
|
|
160
|
-
Object.values(flatMeshes).forEach(mesh =>
|
|
162
|
+
Object.values(flatMeshes).forEach(mesh => {
|
|
163
|
+
mesh.geometry.dispose();
|
|
164
|
+
});
|
|
161
165
|
};
|
|
162
166
|
}, [flatMeshes]);
|
|
163
167
|
const instances = useMemo(() => Object.values(instancesById), [instancesById]);
|
|
@@ -194,12 +198,16 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
194
198
|
})] }));
|
|
195
199
|
}
|
|
196
200
|
function InstancedGroup({ modelKey, group, partCount, instancesMap, onSelect, onClick, registerRef, selectedId, editMode }) {
|
|
197
|
-
const
|
|
201
|
+
const instanceEntries = useMemo(() => Array.from({ length: partCount }, (_, i) => {
|
|
202
|
+
const partKey = `${modelKey}__${i}`;
|
|
203
|
+
const Component = instancesMap[partKey];
|
|
204
|
+
return Component ? { partKey, Component } : null;
|
|
205
|
+
}).filter((entry) => Boolean(entry)), [instancesMap, modelKey, partCount]);
|
|
198
206
|
const visibleInstances = useMemo(() => group.instances.filter(instance => instance.visible !== false), [group.instances]);
|
|
199
|
-
return (_jsx(_Fragment, { children: visibleInstances.map(inst => (_jsx(InstanceGroupItem, { instance: inst,
|
|
207
|
+
return (_jsx(_Fragment, { children: visibleInstances.map(inst => (_jsx(InstanceGroupItem, { instance: inst, instanceEntries: instanceEntries, onSelect: onSelect, onClick: onClick, registerRef: registerRef, selectedId: selectedId, editMode: editMode }, inst.id))) }));
|
|
200
208
|
}
|
|
201
209
|
// Individual instance item with its own click state
|
|
202
|
-
function InstanceGroupItem({ instance,
|
|
210
|
+
function InstanceGroupItem({ instance, instanceEntries, onSelect, onClick, registerRef, selectedId, editMode }) {
|
|
203
211
|
const groupRef = useRef(null);
|
|
204
212
|
const isLocked = Boolean(instance.locked);
|
|
205
213
|
const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
|
|
@@ -217,24 +225,26 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, onClick, re
|
|
|
217
225
|
},
|
|
218
226
|
});
|
|
219
227
|
// Use BoxHelper when object is selected in edit mode
|
|
220
|
-
|
|
228
|
+
const helperTarget = editMode && isSelected && groupRef.current
|
|
229
|
+
? { current: groupRef.current }
|
|
230
|
+
: null;
|
|
231
|
+
useHelper(helperTarget, BoxHelper, 'cyan');
|
|
221
232
|
useEffect(() => {
|
|
222
233
|
if (editMode)
|
|
223
234
|
return;
|
|
224
235
|
registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
|
|
225
236
|
return () => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, null);
|
|
226
237
|
}, [editMode, instance.id, registerRef]);
|
|
227
|
-
return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children:
|
|
238
|
+
return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children: instanceEntries.map(({ partKey, Component }) => _jsx(Component, {}, partKey)) })));
|
|
228
239
|
}
|
|
229
240
|
export function useInstanceCheck(id) {
|
|
241
|
+
var _a;
|
|
230
242
|
const ctx = useContext(GameInstanceContext);
|
|
231
|
-
|
|
243
|
+
const store = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.store) !== null && _a !== void 0 ? _a : EMPTY_INSTANCE_STORE;
|
|
244
|
+
return useStore(store, state => Boolean(state.instancesById[id] || state.sourceInstanceIdsById[id]));
|
|
232
245
|
}
|
|
233
|
-
export
|
|
246
|
+
export function GameInstance({ id, sourceId, modelUrl, locked = false, position, rotation, scale, visible = true, onClick: _onClick, }) {
|
|
234
247
|
const ctx = useContext(GameInstanceContext);
|
|
235
|
-
const [positionX, positionY, positionZ] = position;
|
|
236
|
-
const [rotationX, rotationY, rotationZ] = rotation;
|
|
237
|
-
const [scaleX, scaleY, scaleZ] = scale;
|
|
238
248
|
const instance = useMemo(() => ({
|
|
239
249
|
id,
|
|
240
250
|
sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
|
|
@@ -244,22 +254,7 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
|
|
|
244
254
|
position,
|
|
245
255
|
rotation,
|
|
246
256
|
scale,
|
|
247
|
-
}), [
|
|
248
|
-
id,
|
|
249
|
-
sourceId,
|
|
250
|
-
locked,
|
|
251
|
-
visible,
|
|
252
|
-
modelUrl,
|
|
253
|
-
positionX,
|
|
254
|
-
positionY,
|
|
255
|
-
positionZ,
|
|
256
|
-
rotationX,
|
|
257
|
-
rotationY,
|
|
258
|
-
rotationZ,
|
|
259
|
-
scaleX,
|
|
260
|
-
scaleY,
|
|
261
|
-
scaleZ,
|
|
262
|
-
]);
|
|
257
|
+
}), [id, sourceId, locked, visible, modelUrl, position, rotation, scale]);
|
|
263
258
|
useEffect(() => {
|
|
264
259
|
if (!ctx)
|
|
265
260
|
return;
|
|
@@ -269,6 +264,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
|
|
|
269
264
|
return () => {
|
|
270
265
|
removeInstance(instance.id);
|
|
271
266
|
};
|
|
272
|
-
}, [ctx
|
|
267
|
+
}, [ctx, instance]);
|
|
273
268
|
return null;
|
|
274
|
-
}
|
|
269
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import GameCanvas from "../../shared/GameCanvas";
|
|
2
|
-
import { Prefab } from "./types";
|
|
2
|
+
import type { Prefab } from "./types";
|
|
3
3
|
import { PrefabEditorMode, type Scene } from "./PrefabRoot";
|
|
4
4
|
import type { ExportGLBOptions } from "./utils";
|
|
5
5
|
export interface PrefabEditorRef extends Scene {
|
|
@@ -79,6 +79,10 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
79
79
|
const transformControlsRef = useRef(null);
|
|
80
80
|
const onChangeRef = useRef(onChange);
|
|
81
81
|
const isEditMode = mode === PrefabEditorMode.Edit;
|
|
82
|
+
const detachTransformControls = useCallback(() => {
|
|
83
|
+
var _a;
|
|
84
|
+
(_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
|
|
85
|
+
}, []);
|
|
82
86
|
const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
|
|
83
87
|
const getNode = useCallback((nodeId) => { var _a; return (_a = prefabStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null; }, [prefabStore]);
|
|
84
88
|
const getRoot = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
|
|
@@ -158,13 +162,13 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
158
162
|
updateMode(initialMode);
|
|
159
163
|
}, [initialMode, updateMode]);
|
|
160
164
|
const loadPrefab = useCallback((prefab, options) => {
|
|
161
|
-
var _a
|
|
162
|
-
(
|
|
165
|
+
var _a;
|
|
166
|
+
detachTransformControls();
|
|
163
167
|
const before = prefabStore.getState();
|
|
164
168
|
prefabStore.getState().replacePrefab(prefab);
|
|
165
169
|
const after = prefabStore.getState();
|
|
166
170
|
if (after !== before && (options === null || options === void 0 ? void 0 : options.notifyChange) !== false) {
|
|
167
|
-
(
|
|
171
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, prefab);
|
|
168
172
|
}
|
|
169
173
|
if (options === null || options === void 0 ? void 0 : options.resetHistory) {
|
|
170
174
|
setSelectedId(null);
|
|
@@ -175,7 +179,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
175
179
|
else {
|
|
176
180
|
setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
|
|
177
181
|
}
|
|
178
|
-
}, [prefabStore]);
|
|
182
|
+
}, [detachTransformControls, prefabStore]);
|
|
179
183
|
useEffect(() => {
|
|
180
184
|
if (initialPrefab)
|
|
181
185
|
loadPrefab(initialPrefab, { resetHistory: true, notifyChange: false });
|
|
@@ -205,17 +209,25 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
205
209
|
const importPrefab = useCallback((prefab) => {
|
|
206
210
|
add(regenerateIds(prefab.root));
|
|
207
211
|
}, [add]);
|
|
208
|
-
const applyHistory = (index) => {
|
|
209
|
-
var _a
|
|
210
|
-
(
|
|
212
|
+
const applyHistory = useCallback((index) => {
|
|
213
|
+
var _a;
|
|
214
|
+
detachTransformControls();
|
|
211
215
|
prefabStore.getState().replacePrefab(history[index]);
|
|
212
|
-
(
|
|
216
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, history[index]);
|
|
213
217
|
historyIndexRef.current = index;
|
|
214
218
|
setHistoryIndex(index);
|
|
215
219
|
setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
|
|
216
|
-
};
|
|
217
|
-
const undo = () =>
|
|
218
|
-
|
|
220
|
+
}, [detachTransformControls, history, prefabStore]);
|
|
221
|
+
const undo = useCallback(() => {
|
|
222
|
+
if (historyIndex > 0) {
|
|
223
|
+
applyHistory(historyIndex - 1);
|
|
224
|
+
}
|
|
225
|
+
}, [applyHistory, historyIndex]);
|
|
226
|
+
const redo = useCallback(() => {
|
|
227
|
+
if (historyIndex < history.length - 1) {
|
|
228
|
+
applyHistory(historyIndex + 1);
|
|
229
|
+
}
|
|
230
|
+
}, [applyHistory, history.length, historyIndex]);
|
|
219
231
|
useEffect(() => {
|
|
220
232
|
if (!isEditMode)
|
|
221
233
|
return;
|
|
@@ -233,7 +245,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
233
245
|
};
|
|
234
246
|
window.addEventListener('keydown', handleKeyDown);
|
|
235
247
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
236
|
-
}, [isEditMode,
|
|
248
|
+
}, [isEditMode, redo, undo]);
|
|
237
249
|
const handleScreenshot = useCallback(() => {
|
|
238
250
|
const canvas = canvasRef.current;
|
|
239
251
|
if (!canvas)
|
|
@@ -383,7 +395,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
383
395
|
}
|
|
384
396
|
(_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
|
|
385
397
|
}
|
|
386
|
-
: canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) });
|
|
398
|
+
: canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: transformMode === "translate" ? "world" : "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { type: "button", style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) });
|
|
387
399
|
});
|
|
388
400
|
PrefabEditor.displayName = "PrefabEditor";
|
|
389
401
|
export default PrefabEditor;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Matrix4
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { Matrix4 } from "three";
|
|
2
|
+
import type { Object3D, Texture } from "three";
|
|
3
|
+
import type { ThreeEvent } from "@react-three/fiber";
|
|
4
|
+
import type { GameObject as GameObjectType, Prefab } from "./types";
|
|
5
|
+
import type { LoadedModels } from "../dragdrop";
|
|
6
|
+
import type { PrefabStoreApi } from "./prefabStore";
|
|
6
7
|
export declare enum PrefabEditorMode {
|
|
7
8
|
Edit = "edit",
|
|
8
9
|
Play = "play"
|
|
@@ -11,14 +11,14 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
14
|
-
import { Euler, Matrix4
|
|
14
|
+
import { Euler, Matrix4 } from "three";
|
|
15
15
|
import { useStore } from "zustand";
|
|
16
16
|
import { useClickValid } from "./useClickValid";
|
|
17
17
|
import { findComponent, getNodeUserData } from "./types";
|
|
18
18
|
import { getComponentDef, getComponentAssetRefs, registerComponent } from "./components/ComponentRegistry";
|
|
19
19
|
import { builtinComponents } from "./components";
|
|
20
20
|
import { loadModel, loadSound, loadTexture } from "../dragdrop";
|
|
21
|
-
import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties
|
|
21
|
+
import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties } from "./InstanceProvider";
|
|
22
22
|
import { composeTransform, decompose } from "./utils";
|
|
23
23
|
import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
|
|
24
24
|
import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
|
|
@@ -29,6 +29,14 @@ const IDENTITY = new Matrix4();
|
|
|
29
29
|
const EMPTY_MODELS = {};
|
|
30
30
|
const EMPTY_TEXTURES = {};
|
|
31
31
|
const EMPTY_SOUNDS = {};
|
|
32
|
+
const EMPTY_NODE_COMPONENTS = {
|
|
33
|
+
geometry: undefined,
|
|
34
|
+
material: undefined,
|
|
35
|
+
model: undefined,
|
|
36
|
+
sprite: undefined,
|
|
37
|
+
clickEventName: null,
|
|
38
|
+
composition: [],
|
|
39
|
+
};
|
|
32
40
|
/** Resolve a relative or absolute asset file path against a base path. */
|
|
33
41
|
function resolveAssetPath(basePath, file) {
|
|
34
42
|
if (file.startsWith("http://") || file.startsWith("https://"))
|
|
@@ -84,6 +92,9 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
84
92
|
throw new Error("PrefabRoot requires either a `data` or `store` prop");
|
|
85
93
|
});
|
|
86
94
|
const resolvedStore = store !== null && store !== void 0 ? store : ownedStore;
|
|
95
|
+
if (!resolvedStore) {
|
|
96
|
+
throw new Error("PrefabRoot requires either a `data` or `store` prop");
|
|
97
|
+
}
|
|
87
98
|
const usesOwnedStore = resolvedStore === ownedStore;
|
|
88
99
|
const rootId = useStore(resolvedStore, state => state.rootId);
|
|
89
100
|
const assetManifestKey = useStore(resolvedStore, state => state.assetManifestKey);
|
|
@@ -155,6 +166,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
155
166
|
}
|
|
156
167
|
}, [data, resolvedStore, usesOwnedStore]);
|
|
157
168
|
useEffect(() => {
|
|
169
|
+
void assetManifestKey;
|
|
158
170
|
const syncAssets = (snapshot = resolvedStore.getState()) => {
|
|
159
171
|
const modelsToLoad = new Set();
|
|
160
172
|
const texturesToLoad = new Set();
|
|
@@ -187,23 +199,34 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
|
|
|
187
199
|
}
|
|
188
200
|
});
|
|
189
201
|
};
|
|
190
|
-
modelsToLoad.forEach(file =>
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
202
|
+
modelsToLoad.forEach(file => {
|
|
203
|
+
loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
|
|
204
|
+
const loadedModel = result.model;
|
|
205
|
+
if (result.success && loadedModel) {
|
|
206
|
+
setModels(currentModels => (Object.assign(Object.assign({}, currentModels), { [file]: loadedModel })));
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}));
|
|
210
|
+
});
|
|
211
|
+
texturesToLoad.forEach(file => {
|
|
212
|
+
loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
|
|
213
|
+
const loadedTexture = result.texture;
|
|
214
|
+
if (result.success && loadedTexture) {
|
|
215
|
+
setTextures(currentTextures => (Object.assign(Object.assign({}, currentTextures), { [file]: loadedTexture })));
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}));
|
|
219
|
+
});
|
|
220
|
+
soundsToLoad.forEach(file => {
|
|
221
|
+
loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
|
|
222
|
+
const loadedSound = result.sound;
|
|
223
|
+
if (result.success && loadedSound) {
|
|
224
|
+
soundManager.setBuffer(file, loadedSound);
|
|
225
|
+
setSounds(currentSounds => (Object.assign(Object.assign({}, currentSounds), { [file]: loadedSound })));
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}));
|
|
229
|
+
});
|
|
207
230
|
};
|
|
208
231
|
syncAssets();
|
|
209
232
|
}, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
|
|
@@ -240,11 +263,12 @@ function getClickEventName(component) {
|
|
|
240
263
|
return typeof eventName === 'string' && eventName.trim() ? eventName.trim() : null;
|
|
241
264
|
}
|
|
242
265
|
function analyzeNodeComponents(node) {
|
|
243
|
-
var _a, _b, _c;
|
|
266
|
+
var _a, _b, _c, _d;
|
|
244
267
|
let bufferGeometry;
|
|
245
268
|
let geometry;
|
|
246
269
|
let material;
|
|
247
270
|
let model;
|
|
271
|
+
let sprite;
|
|
248
272
|
const composition = [];
|
|
249
273
|
for (const [key, component] of Object.entries((_a = node.components) !== null && _a !== void 0 ? _a : {})) {
|
|
250
274
|
if (!(component === null || component === void 0 ? void 0 : component.type))
|
|
@@ -264,6 +288,9 @@ function analyzeNodeComponents(node) {
|
|
|
264
288
|
case "Model":
|
|
265
289
|
model = component;
|
|
266
290
|
break;
|
|
291
|
+
case "Sprite":
|
|
292
|
+
sprite = component;
|
|
293
|
+
break;
|
|
267
294
|
default: {
|
|
268
295
|
const def = getComponentDef(component.type);
|
|
269
296
|
if (!(def === null || def === void 0 ? void 0 : def.View))
|
|
@@ -281,7 +308,8 @@ function analyzeNodeComponents(node) {
|
|
|
281
308
|
geometry: bufferGeometry !== null && bufferGeometry !== void 0 ? bufferGeometry : geometry,
|
|
282
309
|
material,
|
|
283
310
|
model,
|
|
284
|
-
|
|
311
|
+
sprite,
|
|
312
|
+
clickEventName: (_d = (_c = (_b = getClickEventName(bufferGeometry)) !== null && _b !== void 0 ? _b : getClickEventName(geometry)) !== null && _c !== void 0 ? _c : getClickEventName(model)) !== null && _d !== void 0 ? _d : getClickEventName(sprite),
|
|
285
313
|
composition,
|
|
286
314
|
};
|
|
287
315
|
}
|
|
@@ -330,13 +358,11 @@ export function GameObjectRenderer(props) {
|
|
|
330
358
|
function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onEditNodeClick, onClick, isVisible = true }) {
|
|
331
359
|
var _a, _b;
|
|
332
360
|
const gameObject = usePrefabNode(nodeId);
|
|
333
|
-
|
|
334
|
-
return null;
|
|
335
|
-
const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
|
|
361
|
+
const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
|
|
336
362
|
const localTransform = getNodeTransformProps(gameObject);
|
|
337
|
-
const isLocked = Boolean(gameObject.locked);
|
|
338
|
-
const nodeVisible = isVisible && !gameObject.hidden;
|
|
339
|
-
const metadataProps = getNodeMetadataProps(gameObject);
|
|
363
|
+
const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
|
|
364
|
+
const nodeVisible = isVisible && !(gameObject === null || gameObject === void 0 ? void 0 : gameObject.hidden);
|
|
365
|
+
const metadataProps = gameObject ? getNodeMetadataProps(gameObject) : { name: '', userData: {} };
|
|
340
366
|
const groupProps = Object.assign(Object.assign({}, metadataProps), { visible: nodeVisible, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
|
|
341
367
|
const modelUrl = (_b = (_a = analyzedComponents.model) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
|
|
342
368
|
const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl), [gameObject, modelUrl, parentMatrix]);
|
|
@@ -348,9 +374,13 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
348
374
|
}
|
|
349
375
|
}, [editMode, nodeId, registerRef]);
|
|
350
376
|
const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
|
|
377
|
+
if (!gameObject)
|
|
378
|
+
return;
|
|
351
379
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
352
380
|
onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
|
|
353
381
|
});
|
|
382
|
+
if (!gameObject)
|
|
383
|
+
return null;
|
|
354
384
|
const renderedInstances = instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, visible: nodeVisible, locked: isLocked, onClick: onClick }, instance.id)));
|
|
355
385
|
if (editMode) {
|
|
356
386
|
return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), renderedInstances] }));
|
|
@@ -360,20 +390,19 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
360
390
|
function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, }) {
|
|
361
391
|
const gameObject = usePrefabNode(nodeId);
|
|
362
392
|
const childIds = usePrefabChildIds(nodeId);
|
|
363
|
-
|
|
364
|
-
return null;
|
|
365
|
-
const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
|
|
393
|
+
const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
|
|
366
394
|
const isSelected = selectedId === nodeId;
|
|
367
|
-
const isLocked = Boolean(gameObject.locked);
|
|
368
|
-
const nodeVisible = isVisible && !gameObject.hidden;
|
|
369
|
-
const
|
|
370
|
-
const metadataProps = getNodeMetadataProps(gameObject);
|
|
395
|
+
const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
|
|
396
|
+
const nodeVisible = isVisible && !(gameObject === null || gameObject === void 0 ? void 0 : gameObject.hidden);
|
|
397
|
+
const metadataProps = gameObject ? getNodeMetadataProps(gameObject) : { name: '', userData: {} };
|
|
371
398
|
const groupRef = useRef(null);
|
|
372
399
|
const handleGroupRef = useCallback((object) => {
|
|
373
400
|
groupRef.current = object;
|
|
374
401
|
registerRef(nodeId, object);
|
|
375
402
|
}, [nodeId, registerRef]);
|
|
376
403
|
const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
|
|
404
|
+
if (!gameObject)
|
|
405
|
+
return;
|
|
377
406
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
378
407
|
onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
|
|
379
408
|
});
|
|
@@ -386,7 +415,6 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
|
|
|
386
415
|
}
|
|
387
416
|
: undefined;
|
|
388
417
|
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
389
|
-
const ready = isNodeReady(analyzedComponents.model, loadedModels);
|
|
390
418
|
const transform = getNodeTransformProps(gameObject);
|
|
391
419
|
const transformProps = {
|
|
392
420
|
position: transform.position,
|
|
@@ -395,6 +423,8 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
|
|
|
395
423
|
};
|
|
396
424
|
const groupProps = Object.assign(Object.assign({}, metadataProps), transformProps);
|
|
397
425
|
const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, onEditNodeClick: onEditNodeClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode, isVisible: nodeVisible });
|
|
426
|
+
if (!gameObject)
|
|
427
|
+
return null;
|
|
398
428
|
const inner = renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes);
|
|
399
429
|
const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
|
|
400
430
|
const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, { visible: nodeVisible }, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
|
|
@@ -417,7 +447,7 @@ function getModelRepeatSettings(node) {
|
|
|
417
447
|
};
|
|
418
448
|
}
|
|
419
449
|
function buildRepeatedInstances(gameObject, parentMatrix, modelUrl) {
|
|
420
|
-
if (!modelUrl)
|
|
450
|
+
if (!gameObject || !modelUrl)
|
|
421
451
|
return [];
|
|
422
452
|
const transform = getNodeTransformProps(gameObject);
|
|
423
453
|
const repeat = getModelRepeatSettings(gameObject);
|
|
@@ -471,24 +501,58 @@ function getNodeTransformProps(node) {
|
|
|
471
501
|
function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes) {
|
|
472
502
|
var _a, _b, _c, _d;
|
|
473
503
|
const geometry = analyzedComponents.geometry;
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
504
|
+
const model = analyzedComponents.model;
|
|
505
|
+
const material = analyzedComponents.material;
|
|
506
|
+
const sprite = analyzedComponents.sprite;
|
|
507
|
+
const shapeKind = (sprite === null || sprite === void 0 ? void 0 : sprite.type) ? 'sprite' : (geometry === null || geometry === void 0 ? void 0 : geometry.type) ? 'mesh' : (model === null || model === void 0 ? void 0 : model.type) ? 'model' : 'none';
|
|
508
|
+
let materialContent = null;
|
|
509
|
+
switch (shapeKind) {
|
|
510
|
+
case 'sprite': {
|
|
511
|
+
const materialDef = (material === null || material === void 0 ? void 0 : material.type) ? getComponentDef(material.type) : undefined;
|
|
512
|
+
if ((material === null || material === void 0 ? void 0 : material.properties) && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View)) {
|
|
513
|
+
const materialIsSprite = material.properties.materialType === 'sprite';
|
|
514
|
+
materialContent = (_jsx(materialDef.View, { properties: Object.assign(Object.assign({}, material.properties), { materialType: 'sprite', transparent: materialIsSprite ? material.properties.transparent : true, depthTest: materialIsSprite ? material.properties.depthTest : false, depthWrite: materialIsSprite ? material.properties.depthWrite : false }) }, "material"));
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
case 'mesh': {
|
|
519
|
+
const materialDef = (material === null || material === void 0 ? void 0 : material.type) ? getComponentDef(material.type) : undefined;
|
|
520
|
+
if ((material === null || material === void 0 ? void 0 : material.properties) && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View)) {
|
|
521
|
+
materialContent = _jsx(materialDef.View, { properties: material.properties }, "material");
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
484
525
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
526
|
+
let primaryContent = null;
|
|
527
|
+
let contentChildren = childNodes;
|
|
528
|
+
switch (shapeKind) {
|
|
529
|
+
case 'sprite': {
|
|
530
|
+
primaryContent = (_jsxs("sprite", Object.assign({ center: (_b = (_a = sprite === null || sprite === void 0 ? void 0 : sprite.properties) === null || _a === void 0 ? void 0 : _a.center) !== null && _b !== void 0 ? _b : [0.5, 0.5] }, primaryClickHandlers, { children: [materialContent, childNodes] })));
|
|
531
|
+
contentChildren = null;
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
case 'mesh': {
|
|
535
|
+
const geometryDef = (geometry === null || geometry === void 0 ? void 0 : geometry.type) ? getComponentDef(geometry.type) : undefined;
|
|
536
|
+
if (!(geometry === null || geometry === void 0 ? void 0 : geometry.properties) || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
|
|
537
|
+
break;
|
|
538
|
+
const GeometryView = geometryDef.View;
|
|
539
|
+
const geometryProperties = (_c = geometry.properties) !== null && _c !== void 0 ? _c : {};
|
|
540
|
+
const visible = geometryProperties.visible !== false;
|
|
541
|
+
primaryContent = (_jsxs("mesh", Object.assign({ visible: visible, castShadow: visible && geometryProperties.castShadow !== false, receiveShadow: visible && geometryProperties.receiveShadow !== false }, primaryClickHandlers, { children: [_jsx(GeometryView, { properties: geometry.properties }), materialContent] })));
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
case 'model': {
|
|
545
|
+
if (!(model === null || model === void 0 ? void 0 : model.type) || ((_d = model.properties) === null || _d === void 0 ? void 0 : _d.instanced) || !isNodeReady(model, loadedModels))
|
|
546
|
+
break;
|
|
547
|
+
const modelDef = getComponentDef(model.type);
|
|
548
|
+
if (!(modelDef === null || modelDef === void 0 ? void 0 : modelDef.View))
|
|
549
|
+
break;
|
|
550
|
+
const modelContent = _jsx(modelDef.View, { properties: model.properties });
|
|
551
|
+
primaryContent = primaryClickHandlers ? _jsx("group", Object.assign({}, primaryClickHandlers, { children: modelContent })) : modelContent;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
490
554
|
}
|
|
491
|
-
let content = _jsxs(_Fragment, { children: [primaryContent,
|
|
555
|
+
let content = _jsxs(_Fragment, { children: [primaryContent, contentChildren] });
|
|
492
556
|
for (const { key, View, properties } of analyzedComponents.composition) {
|
|
493
557
|
content = (_jsx(View, { properties: properties, children: content }, key));
|
|
494
558
|
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ThreeElement } from '@react-three/fiber';
|
|
3
|
-
import { Component } from './ComponentRegistry';
|
|
4
|
-
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
3
|
+
import type { Component } from './ComponentRegistry';
|
|
4
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
|
|
5
|
+
import type { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
6
6
|
declare module '@react-three/fiber' {
|
|
7
7
|
interface ThreeElements {
|
|
8
8
|
meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
|
|
9
9
|
meshStandardNodeMaterial: ThreeElement<typeof MeshStandardNodeMaterial>;
|
|
10
|
+
spriteNodeMaterial: ThreeElement<typeof SpriteNodeMaterial>;
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
|
-
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
13
|
-
materialType?: 'standard' | 'basic';
|
|
13
|
+
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale' | 'side'> {
|
|
14
|
+
materialType?: 'standard' | 'basic' | 'sprite';
|
|
14
15
|
transmission?: number;
|
|
15
16
|
thickness?: number;
|
|
16
17
|
ior?: number;
|
|
18
|
+
rotation?: number;
|
|
19
|
+
sizeAttenuation?: boolean;
|
|
17
20
|
texture?: string;
|
|
18
21
|
offset?: [number, number];
|
|
19
22
|
repeat?: boolean;
|
|
@@ -25,8 +28,14 @@ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & Mes
|
|
|
25
28
|
magFilter?: string;
|
|
26
29
|
normalMapTexture?: string;
|
|
27
30
|
normalScale?: [number, number];
|
|
31
|
+
side?: keyof typeof SIDE_MAP;
|
|
28
32
|
}
|
|
29
33
|
export type MaterialOverrides = Record<string, unknown>;
|
|
34
|
+
declare const SIDE_MAP: {
|
|
35
|
+
readonly FrontSide: 0;
|
|
36
|
+
readonly BackSide: 1;
|
|
37
|
+
readonly DoubleSide: 2;
|
|
38
|
+
};
|
|
30
39
|
export declare function useMaterialOverrides(): MaterialOverrides;
|
|
31
40
|
export declare function MaterialOverridesProvider({ overrides, children, }: {
|
|
32
41
|
overrides: MaterialOverrides;
|
|
@@ -15,7 +15,7 @@ import { extend } from '@react-three/fiber';
|
|
|
15
15
|
import { useFrame } from '@react-three/fiber';
|
|
16
16
|
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
17
17
|
import { useAssetRuntime } from '../assetRuntime';
|
|
18
|
-
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
18
|
+
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
|
|
19
19
|
import { TexturePicker } from '../../assetviewer/page';
|
|
20
20
|
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
21
21
|
function Vector2Editor({ label, value, onChange, min, max, step, }) {
|
|
@@ -24,6 +24,39 @@ function Vector2Editor({ label, value, onChange, min, max, step, }) {
|
|
|
24
24
|
}
|
|
25
25
|
const EMPTY_MATERIAL_OVERRIDES = Object.freeze({});
|
|
26
26
|
const MaterialOverridesContext = createContext(EMPTY_MATERIAL_OVERRIDES);
|
|
27
|
+
const SIDE_MAP = { FrontSide, BackSide, DoubleSide };
|
|
28
|
+
const MIN_FILTER_MAP = {
|
|
29
|
+
NearestFilter,
|
|
30
|
+
LinearFilter,
|
|
31
|
+
NearestMipmapNearestFilter,
|
|
32
|
+
NearestMipmapLinearFilter,
|
|
33
|
+
LinearMipmapNearestFilter,
|
|
34
|
+
LinearMipmapLinearFilter,
|
|
35
|
+
};
|
|
36
|
+
const MAG_FILTER_MAP = {
|
|
37
|
+
NearestFilter,
|
|
38
|
+
LinearFilter,
|
|
39
|
+
};
|
|
40
|
+
function cloneConfiguredTexture({ texture, repeat, repeatCount, offset, colorSpace, generateMipmaps, minFilter, magFilter, }) {
|
|
41
|
+
var _a, _b;
|
|
42
|
+
const clonedTexture = texture.clone();
|
|
43
|
+
if (repeat) {
|
|
44
|
+
clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping;
|
|
45
|
+
if (repeatCount)
|
|
46
|
+
clonedTexture.repeat.set(repeatCount[0], repeatCount[1]);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
clonedTexture.wrapS = clonedTexture.wrapT = ClampToEdgeWrapping;
|
|
50
|
+
clonedTexture.repeat.set(1, 1);
|
|
51
|
+
}
|
|
52
|
+
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);
|
|
53
|
+
clonedTexture.colorSpace = colorSpace;
|
|
54
|
+
clonedTexture.generateMipmaps = generateMipmaps;
|
|
55
|
+
clonedTexture.minFilter = minFilter;
|
|
56
|
+
clonedTexture.magFilter = magFilter;
|
|
57
|
+
clonedTexture.needsUpdate = true;
|
|
58
|
+
return clonedTexture;
|
|
59
|
+
}
|
|
27
60
|
export function useMaterialOverrides() {
|
|
28
61
|
return useContext(MaterialOverridesContext);
|
|
29
62
|
}
|
|
@@ -35,14 +68,16 @@ export function MaterialOverridesProvider({ overrides, children, }) {
|
|
|
35
68
|
extend({
|
|
36
69
|
MeshBasicNodeMaterial,
|
|
37
70
|
MeshStandardNodeMaterial,
|
|
71
|
+
SpriteNodeMaterial,
|
|
38
72
|
});
|
|
39
|
-
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
73
|
+
function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
40
74
|
var _a;
|
|
41
75
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
42
76
|
const hasTexture = !!component.properties.texture;
|
|
43
77
|
const hasRepeat = component.properties.repeat;
|
|
44
78
|
const animateOffset = component.properties.animateOffset;
|
|
45
79
|
const isStandardMaterial = materialType === 'standard';
|
|
80
|
+
const isSpriteMaterial = materialType === 'sprite';
|
|
46
81
|
const fields = [
|
|
47
82
|
{
|
|
48
83
|
name: 'materialType',
|
|
@@ -51,13 +86,22 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
51
86
|
options: [
|
|
52
87
|
{ value: 'standard', label: 'Standard' },
|
|
53
88
|
{ value: 'basic', label: 'Basic' },
|
|
89
|
+
{ value: 'sprite', label: 'Sprite' },
|
|
54
90
|
],
|
|
55
91
|
},
|
|
56
92
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
57
93
|
{ name: 'toneMapped', type: 'boolean', label: 'Tone Mapped' },
|
|
58
|
-
|
|
94
|
+
...(!isSpriteMaterial ? [
|
|
95
|
+
{ name: 'wireframe', type: 'boolean', label: 'Wireframe' },
|
|
96
|
+
] : []),
|
|
59
97
|
{ name: 'transparent', type: 'boolean', label: 'Transparent' },
|
|
60
98
|
{ name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
|
|
99
|
+
...(isSpriteMaterial ? [
|
|
100
|
+
{ name: 'rotation', type: 'number', label: 'Rotation', step: 0.01 },
|
|
101
|
+
{ name: 'sizeAttenuation', type: 'boolean', label: 'Size Attenuation' },
|
|
102
|
+
{ name: 'depthTest', type: 'boolean', label: 'Depth Test' },
|
|
103
|
+
{ name: 'depthWrite', type: 'boolean', label: 'Depth Write' },
|
|
104
|
+
] : []),
|
|
61
105
|
...(isStandardMaterial ? [
|
|
62
106
|
{ name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
|
|
63
107
|
{ name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
|
|
@@ -65,16 +109,16 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
65
109
|
{ name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
|
|
66
110
|
{ name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
|
|
67
111
|
] : []),
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
...(!isSpriteMaterial ? [{
|
|
113
|
+
name: 'side',
|
|
114
|
+
type: 'select',
|
|
115
|
+
label: 'Side',
|
|
116
|
+
options: [
|
|
117
|
+
{ value: 'FrontSide', label: 'Front' },
|
|
118
|
+
{ value: 'BackSide', label: 'Back' },
|
|
119
|
+
{ value: 'DoubleSide', label: 'Double' },
|
|
120
|
+
],
|
|
121
|
+
}] : []),
|
|
78
122
|
{
|
|
79
123
|
name: 'texture',
|
|
80
124
|
type: 'custom',
|
|
@@ -103,13 +147,13 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
103
147
|
label: 'Speed (X, Y)',
|
|
104
148
|
render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Speed", value: value, onChange: onChange, step: 0.01 })),
|
|
105
149
|
}] : []),
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
...(component.properties.normalMapTexture ? [{
|
|
150
|
+
...(!isSpriteMaterial ? [{
|
|
151
|
+
name: 'normalMapTexture',
|
|
152
|
+
type: 'custom',
|
|
153
|
+
label: 'Normal Map',
|
|
154
|
+
render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
|
|
155
|
+
}] : []),
|
|
156
|
+
...(!isSpriteMaterial && component.properties.normalMapTexture ? [{
|
|
113
157
|
name: 'normalScale',
|
|
114
158
|
type: 'custom',
|
|
115
159
|
label: 'Normal Scale (X, Y)',
|
|
@@ -144,90 +188,83 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
144
188
|
}
|
|
145
189
|
// View for Material component
|
|
146
190
|
function MaterialComponentView({ properties: rawProps }) {
|
|
147
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
191
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
148
192
|
const { getTexture } = useAssetRuntime();
|
|
149
193
|
const properties = rawProps;
|
|
150
|
-
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
151
|
-
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
152
|
-
const offset = properties === null || properties === void 0 ? void 0 : properties.offset;
|
|
153
|
-
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
154
|
-
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
155
|
-
const animateOffset = properties === null || properties === void 0 ? void 0 : properties.animateOffset;
|
|
156
|
-
const offsetSpeed = properties === null || properties === void 0 ? void 0 : properties.offsetSpeed;
|
|
157
|
-
const generateMipmaps = (properties === null || properties === void 0 ? void 0 : properties.generateMipmaps) !== false;
|
|
158
|
-
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
159
|
-
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
160
|
-
const texture = textureName ? (_b = getTexture(textureName)) !== null && _b !== void 0 ? _b : undefined : undefined;
|
|
161
|
-
const normalMapTextureName = properties === null || properties === void 0 ? void 0 : properties.normalMapTexture;
|
|
162
|
-
const normalScaleProp = properties === null || properties === void 0 ? void 0 : properties.normalScale;
|
|
163
|
-
const normalMapTexture = normalMapTextureName ? (_c = getTexture(normalMapTextureName)) !== null && _c !== void 0 ? _c : undefined : undefined;
|
|
164
194
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
195
|
+
const materialType = (_a = materialSource.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
196
|
+
const textureName = materialSource.texture;
|
|
197
|
+
const normalMapTextureName = materialSource.normalMapTexture;
|
|
198
|
+
const offset = materialSource.offset;
|
|
199
|
+
const repeat = materialSource.repeat;
|
|
200
|
+
const repeatCount = materialSource.repeatCount;
|
|
201
|
+
const animateOffset = materialSource.animateOffset;
|
|
202
|
+
const offsetSpeed = materialSource.offsetSpeed;
|
|
203
|
+
const generateMipmaps = materialSource.generateMipmaps !== false;
|
|
204
|
+
const minFilter = (_b = materialSource.minFilter) !== null && _b !== void 0 ? _b : 'LinearMipmapLinearFilter';
|
|
205
|
+
const magFilter = (_c = materialSource.magFilter) !== null && _c !== void 0 ? _c : 'LinearFilter';
|
|
206
|
+
const texture = textureName ? getTexture(textureName) : undefined;
|
|
207
|
+
const normalScaleProp = materialSource.normalScale;
|
|
208
|
+
const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
|
|
165
209
|
// Destructure all material props and separate custom texture handling props
|
|
166
|
-
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, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "offset", "repeat", "repeatCount", "animateOffset", "offsetSpeed", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side"]);
|
|
167
|
-
const
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
LinearFilter,
|
|
172
|
-
NearestMipmapNearestFilter,
|
|
173
|
-
NearestMipmapLinearFilter,
|
|
174
|
-
LinearMipmapNearestFilter,
|
|
175
|
-
LinearMipmapLinearFilter
|
|
176
|
-
};
|
|
177
|
-
const magFilterMap = {
|
|
178
|
-
NearestFilter,
|
|
179
|
-
LinearFilter
|
|
180
|
-
};
|
|
181
|
-
const animatedOffsetRef = useRef([(_e = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _e !== void 0 ? _e : 0, (_f = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _f !== void 0 ? _f : 0]);
|
|
210
|
+
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"]);
|
|
211
|
+
const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
|
|
212
|
+
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
213
|
+
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
214
|
+
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]);
|
|
182
215
|
const finalTexture = useMemo(() => {
|
|
183
|
-
var _a, _b, _c, _d;
|
|
184
216
|
if (!texture)
|
|
185
217
|
return undefined;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
218
|
+
return cloneConfiguredTexture({
|
|
219
|
+
texture,
|
|
220
|
+
repeat,
|
|
221
|
+
repeatCount,
|
|
222
|
+
offset,
|
|
223
|
+
colorSpace: SRGBColorSpace,
|
|
224
|
+
generateMipmaps,
|
|
225
|
+
minFilter: resolvedMinFilter,
|
|
226
|
+
magFilter: resolvedMagFilter,
|
|
227
|
+
});
|
|
228
|
+
}, [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]);
|
|
229
|
+
const finalNormalMap = useMemo(() => {
|
|
230
|
+
if (!normalMapTexture)
|
|
231
|
+
return undefined;
|
|
232
|
+
return cloneConfiguredTexture({
|
|
233
|
+
texture: normalMapTexture,
|
|
234
|
+
repeat,
|
|
235
|
+
repeatCount,
|
|
236
|
+
offset,
|
|
237
|
+
colorSpace: LinearSRGBColorSpace,
|
|
238
|
+
generateMipmaps,
|
|
239
|
+
minFilter: resolvedMinFilter,
|
|
240
|
+
magFilter: resolvedMagFilter,
|
|
241
|
+
});
|
|
242
|
+
}, [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]);
|
|
243
|
+
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];
|
|
205
244
|
useFrame((_, delta) => {
|
|
206
245
|
var _a, _b;
|
|
207
|
-
if (!finalTexture || !animateOffset)
|
|
246
|
+
if ((!finalTexture && !finalNormalMap) || !animateOffset)
|
|
208
247
|
return;
|
|
209
248
|
const nextX = animatedOffsetRef.current[0] + ((_a = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
|
|
210
249
|
const nextY = animatedOffsetRef.current[1] + ((_b = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
|
|
211
250
|
animatedOffsetRef.current = [nextX, nextY];
|
|
212
|
-
finalTexture.offset.set(nextX, nextY);
|
|
251
|
+
finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.offset.set(nextX, nextY);
|
|
252
|
+
finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.offset.set(nextX, nextY);
|
|
213
253
|
});
|
|
214
|
-
const
|
|
215
|
-
if (!normalMapTexture)
|
|
216
|
-
return undefined;
|
|
217
|
-
const t = normalMapTexture.clone();
|
|
218
|
-
t.colorSpace = LinearSRGBColorSpace;
|
|
219
|
-
t.needsUpdate = true;
|
|
220
|
-
return t;
|
|
221
|
-
}, [normalMapTexture]);
|
|
254
|
+
const overrides = useMaterialOverrides();
|
|
222
255
|
if (!properties) {
|
|
223
|
-
return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
|
|
256
|
+
return _jsx("meshStandardNodeMaterial", { attach: "material", color: "red", wireframe: true });
|
|
224
257
|
}
|
|
225
|
-
const
|
|
258
|
+
const materialKey = `${materialType}:${textureName !== null && textureName !== void 0 ? textureName : 'none'}:${normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'none'}`;
|
|
226
259
|
const sharedProps = Object.assign(Object.assign({ map: finalTexture, side: resolvedSide }, materialProps), overrides);
|
|
227
260
|
if (materialType === 'basic') {
|
|
228
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps));
|
|
261
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: "material" }, sharedProps), materialKey);
|
|
262
|
+
}
|
|
263
|
+
if (materialType === 'sprite') {
|
|
264
|
+
const spriteTransparent = materialSource.transparent !== false;
|
|
265
|
+
return (_jsx("spriteNodeMaterial", Object.assign({ attach: "material", map: finalTexture, 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 }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
|
|
229
266
|
}
|
|
230
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: finalNormalMap ? [(
|
|
267
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: "material" }, sharedProps, { normalMap: finalNormalMap, 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));
|
|
231
268
|
}
|
|
232
269
|
const MaterialComponent = {
|
|
233
270
|
name: 'Material',
|
|
@@ -240,6 +277,7 @@ const MaterialComponent = {
|
|
|
240
277
|
wireframe: false,
|
|
241
278
|
transparent: false,
|
|
242
279
|
opacity: 1,
|
|
280
|
+
sizeAttenuation: true,
|
|
243
281
|
offset: [0, 0],
|
|
244
282
|
animateOffset: false,
|
|
245
283
|
offsetSpeed: [0, 0],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelPicker } from '../../assetviewer/page';
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
|
+
import { Mesh } from 'three';
|
|
4
5
|
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
|
|
5
6
|
import { useAssetRuntime } from '../assetRuntime';
|
|
6
7
|
import { useEditorContext } from '../PrefabEditor';
|
|
@@ -46,23 +47,20 @@ function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
|
46
47
|
// View for Model component
|
|
47
48
|
function ModelComponentView({ properties, children }) {
|
|
48
49
|
const { getModel } = useAssetRuntime();
|
|
49
|
-
|
|
50
|
-
if (!properties.filename || properties.instanced)
|
|
51
|
-
return _jsx(_Fragment, { children: children });
|
|
52
|
-
const sourceModel = getModel(properties.filename);
|
|
50
|
+
const sourceModel = properties.filename ? getModel(properties.filename) : null;
|
|
53
51
|
// Clone model once and set up shadows - memoized to avoid cloning on every render
|
|
54
52
|
const clonedModel = useMemo(() => {
|
|
55
|
-
if (!sourceModel)
|
|
53
|
+
if (!sourceModel || !properties.filename || properties.instanced)
|
|
56
54
|
return null;
|
|
57
55
|
const clone = sourceModel.clone();
|
|
58
56
|
clone.traverse((obj) => {
|
|
59
|
-
if (obj
|
|
57
|
+
if (obj instanceof Mesh) {
|
|
60
58
|
obj.castShadow = true;
|
|
61
59
|
obj.receiveShadow = true;
|
|
62
60
|
}
|
|
63
61
|
});
|
|
64
62
|
return clone;
|
|
65
|
-
}, [sourceModel]);
|
|
63
|
+
}, [properties.filename, properties.instanced, sourceModel]);
|
|
66
64
|
if (!clonedModel)
|
|
67
65
|
return _jsx(_Fragment, { children: children });
|
|
68
66
|
return _jsx("primitive", { object: clonedModel, children: children });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { BooleanField, FieldRenderer, NumberInput, StringField } from './Input';
|
|
3
|
+
function Vector2Editor({ value, onChange, min, max, step, }) {
|
|
4
|
+
var _a, _b;
|
|
5
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(NumberInput, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 0, onChange: x => { var _a; return onChange([x, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 0]); }, min: min, max: max, step: step, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } }), _jsx(NumberInput, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 0, onChange: y => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 0, y]); }, min: min, max: max, step: step, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }));
|
|
6
|
+
}
|
|
7
|
+
function SpriteComponentEditor({ component, onUpdate, }) {
|
|
8
|
+
const fields = [
|
|
9
|
+
{
|
|
10
|
+
name: 'center',
|
|
11
|
+
type: 'custom',
|
|
12
|
+
label: 'Center',
|
|
13
|
+
render: ({ value, onChange }) => (_jsx(Vector2Editor, { value: value, onChange: onChange, min: 0, max: 1, step: 0.01 })),
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
return (_jsxs(_Fragment, { children: [_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }), _jsxs("div", { style: { marginTop: 8 }, 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, fallback: "node:click" })) : null] })] }));
|
|
17
|
+
}
|
|
18
|
+
const SpriteComponent = {
|
|
19
|
+
name: 'Sprite',
|
|
20
|
+
Editor: SpriteComponentEditor,
|
|
21
|
+
defaultProperties: {
|
|
22
|
+
center: [0.5, 0.5],
|
|
23
|
+
emitClickEvent: false,
|
|
24
|
+
clickEventName: 'node:click',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export default SpriteComponent;
|
|
@@ -1,28 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
1
|
+
// biome-ignore assist/source/organizeImports: <in order of display in the editor>
|
|
2
|
+
import TransformComponent from "./TransformComponent";
|
|
3
|
+
import GeometryComponent from "./GeometryComponent";
|
|
4
|
+
import BufferGeometryComponent from "./BufferGeometryComponent";
|
|
5
|
+
import ModelComponent from "./ModelComponent";
|
|
6
|
+
import SpriteComponent from "./SpriteComponent";
|
|
7
|
+
import TextComponent from "./TextComponent";
|
|
8
|
+
import MaterialComponent from "./MaterialComponent";
|
|
9
|
+
import SpotLightComponent from "./SpotLightComponent";
|
|
10
|
+
import PointLightComponent from "./PointLightComponent";
|
|
11
|
+
import DirectionalLightComponent from "./DirectionalLightComponent";
|
|
12
|
+
import AmbientLightComponent from "./AmbientLightComponent";
|
|
13
|
+
import EnvironmentComponent from "./EnvironmentComponent";
|
|
14
|
+
import CameraComponent from "./CameraComponent";
|
|
15
|
+
import SoundComponent from "./SoundComponent";
|
|
16
|
+
import DataComponent from "./DataComponent";
|
|
17
|
+
// this controls the order of components in the editor, and also which components are available to add
|
|
15
18
|
export const builtinComponents = [
|
|
16
19
|
TransformComponent,
|
|
20
|
+
// geometry components
|
|
17
21
|
GeometryComponent,
|
|
18
22
|
BufferGeometryComponent,
|
|
19
23
|
ModelComponent,
|
|
24
|
+
SpriteComponent,
|
|
20
25
|
TextComponent,
|
|
26
|
+
// material components
|
|
21
27
|
MaterialComponent,
|
|
28
|
+
// light components
|
|
22
29
|
SpotLightComponent,
|
|
23
30
|
PointLightComponent,
|
|
24
31
|
DirectionalLightComponent,
|
|
25
32
|
AmbientLightComponent,
|
|
33
|
+
// other components
|
|
26
34
|
EnvironmentComponent,
|
|
27
35
|
CameraComponent,
|
|
28
36
|
SoundComponent,
|