react-three-game 0.0.95 → 0.0.97
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 +30 -56
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.js +29 -14
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +6 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +59 -35
- package/dist/tools/prefabeditor/components/CameraComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/CameraComponent.js +7 -4
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +9 -7
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +6 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +6 -15
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +9 -3
- package/dist/tools/prefabeditor/components/MaterialComponent.js +90 -74
- package/dist/tools/prefabeditor/components/ModelComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +7 -12
- package/dist/tools/prefabeditor/components/PointLightComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/PointLightComponent.js +5 -2
- package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +1 -1
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +8 -8
- 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,
|
|
@@ -31,32 +33,11 @@ export function normalizeRepeatAxes(value) {
|
|
|
31
33
|
}, []);
|
|
32
34
|
return normalized.length > 0 ? normalized : DEFAULT_REPEAT_AXES;
|
|
33
35
|
}
|
|
34
|
-
function toVector3Tuple(value, fallback) {
|
|
35
|
-
if (!Array.isArray(value) || value.length !== 3)
|
|
36
|
-
return fallback;
|
|
37
|
-
return value.map((entry, index) => {
|
|
38
|
-
const next = typeof entry === 'number' ? entry : Number(entry);
|
|
39
|
-
return Number.isFinite(next) ? next : fallback[index];
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
36
|
export function getRepeatAxesFromModelProperties(properties) {
|
|
43
|
-
var _a;
|
|
44
37
|
if (Array.isArray(properties.repeatAxes)) {
|
|
45
38
|
return normalizeRepeatAxes(properties.repeatAxes);
|
|
46
39
|
}
|
|
47
|
-
|
|
48
|
-
const repeatOffset = toVector3Tuple(properties.repeatOffset, [1, 1, 1]);
|
|
49
|
-
const legacyAxes = [];
|
|
50
|
-
if ((_a = properties.repeatX) !== null && _a !== void 0 ? _a : true) {
|
|
51
|
-
legacyAxes.push({ axis: 'x', count: repeatCount[0], offset: repeatOffset[0] });
|
|
52
|
-
}
|
|
53
|
-
if (properties.repeatY) {
|
|
54
|
-
legacyAxes.push({ axis: 'y', count: repeatCount[1], offset: repeatOffset[1] });
|
|
55
|
-
}
|
|
56
|
-
if (properties.repeatZ) {
|
|
57
|
-
legacyAxes.push({ axis: 'z', count: repeatCount[2], offset: repeatOffset[2] });
|
|
58
|
-
}
|
|
59
|
-
return legacyAxes.length > 0 ? legacyAxes : DEFAULT_REPEAT_AXES;
|
|
40
|
+
return DEFAULT_REPEAT_AXES;
|
|
60
41
|
}
|
|
61
42
|
// Helper functions for comparison
|
|
62
43
|
function arrayEquals(a, b) {
|
|
@@ -141,7 +122,7 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
141
122
|
const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
|
|
142
123
|
let partIndex = 0;
|
|
143
124
|
model.traverse((obj) => {
|
|
144
|
-
if (obj
|
|
125
|
+
if (obj instanceof Mesh) {
|
|
145
126
|
// Clone geometry and bake relative transform
|
|
146
127
|
const geom = obj.geometry.clone();
|
|
147
128
|
geom.applyMatrix4(obj.matrixWorld.clone().premultiply(rootInverse));
|
|
@@ -157,7 +138,9 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
157
138
|
// Cleanup geometries when models change
|
|
158
139
|
useEffect(() => {
|
|
159
140
|
return () => {
|
|
160
|
-
Object.values(flatMeshes).forEach(mesh =>
|
|
141
|
+
Object.values(flatMeshes).forEach(mesh => {
|
|
142
|
+
mesh.geometry.dispose();
|
|
143
|
+
});
|
|
161
144
|
};
|
|
162
145
|
}, [flatMeshes]);
|
|
163
146
|
const instances = useMemo(() => Object.values(instancesById), [instancesById]);
|
|
@@ -194,12 +177,16 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
|
|
|
194
177
|
})] }));
|
|
195
178
|
}
|
|
196
179
|
function InstancedGroup({ modelKey, group, partCount, instancesMap, onSelect, onClick, registerRef, selectedId, editMode }) {
|
|
197
|
-
const
|
|
180
|
+
const instanceEntries = useMemo(() => Array.from({ length: partCount }, (_, i) => {
|
|
181
|
+
const partKey = `${modelKey}__${i}`;
|
|
182
|
+
const Component = instancesMap[partKey];
|
|
183
|
+
return Component ? { partKey, Component } : null;
|
|
184
|
+
}).filter((entry) => Boolean(entry)), [instancesMap, modelKey, partCount]);
|
|
198
185
|
const visibleInstances = useMemo(() => group.instances.filter(instance => instance.visible !== false), [group.instances]);
|
|
199
|
-
return (_jsx(_Fragment, { children: visibleInstances.map(inst => (_jsx(InstanceGroupItem, { instance: inst,
|
|
186
|
+
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
187
|
}
|
|
201
188
|
// Individual instance item with its own click state
|
|
202
|
-
function InstanceGroupItem({ instance,
|
|
189
|
+
function InstanceGroupItem({ instance, instanceEntries, onSelect, onClick, registerRef, selectedId, editMode }) {
|
|
203
190
|
const groupRef = useRef(null);
|
|
204
191
|
const isLocked = Boolean(instance.locked);
|
|
205
192
|
const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
|
|
@@ -217,24 +204,26 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, onClick, re
|
|
|
217
204
|
},
|
|
218
205
|
});
|
|
219
206
|
// Use BoxHelper when object is selected in edit mode
|
|
220
|
-
|
|
207
|
+
const helperTarget = editMode && isSelected && groupRef.current
|
|
208
|
+
? { current: groupRef.current }
|
|
209
|
+
: null;
|
|
210
|
+
useHelper(helperTarget, BoxHelper, 'cyan');
|
|
221
211
|
useEffect(() => {
|
|
222
212
|
if (editMode)
|
|
223
213
|
return;
|
|
224
214
|
registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
|
|
225
215
|
return () => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, null);
|
|
226
216
|
}, [editMode, instance.id, registerRef]);
|
|
227
|
-
return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children:
|
|
217
|
+
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
218
|
}
|
|
229
219
|
export function useInstanceCheck(id) {
|
|
220
|
+
var _a;
|
|
230
221
|
const ctx = useContext(GameInstanceContext);
|
|
231
|
-
|
|
222
|
+
const store = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.store) !== null && _a !== void 0 ? _a : EMPTY_INSTANCE_STORE;
|
|
223
|
+
return useStore(store, state => Boolean(state.instancesById[id] || state.sourceInstanceIdsById[id]));
|
|
232
224
|
}
|
|
233
|
-
export
|
|
225
|
+
export function GameInstance({ id, sourceId, modelUrl, locked = false, position, rotation, scale, visible = true, onClick: _onClick, }) {
|
|
234
226
|
const ctx = useContext(GameInstanceContext);
|
|
235
|
-
const [positionX, positionY, positionZ] = position;
|
|
236
|
-
const [rotationX, rotationY, rotationZ] = rotation;
|
|
237
|
-
const [scaleX, scaleY, scaleZ] = scale;
|
|
238
227
|
const instance = useMemo(() => ({
|
|
239
228
|
id,
|
|
240
229
|
sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
|
|
@@ -244,22 +233,7 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
|
|
|
244
233
|
position,
|
|
245
234
|
rotation,
|
|
246
235
|
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
|
-
]);
|
|
236
|
+
}), [id, sourceId, locked, visible, modelUrl, position, rotation, scale]);
|
|
263
237
|
useEffect(() => {
|
|
264
238
|
if (!ctx)
|
|
265
239
|
return;
|
|
@@ -269,6 +243,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
|
|
|
269
243
|
return () => {
|
|
270
244
|
removeInstance(instance.id);
|
|
271
245
|
};
|
|
272
|
-
}, [ctx
|
|
246
|
+
}, [ctx, instance]);
|
|
273
247
|
return null;
|
|
274
|
-
}
|
|
248
|
+
}
|
|
@@ -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 {
|
|
@@ -34,7 +34,10 @@ function isObjectAttachedToRoot(root, object) {
|
|
|
34
34
|
function SelectionHelper({ object }) {
|
|
35
35
|
const objectRef = useRef(null);
|
|
36
36
|
objectRef.current = object;
|
|
37
|
-
|
|
37
|
+
const helperTarget = objectRef.current
|
|
38
|
+
? { current: objectRef.current }
|
|
39
|
+
: null;
|
|
40
|
+
useHelper(helperTarget, BoxHelper, "cyan");
|
|
38
41
|
return null;
|
|
39
42
|
}
|
|
40
43
|
export const EditorContext = createContext(null);
|
|
@@ -79,6 +82,10 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
79
82
|
const transformControlsRef = useRef(null);
|
|
80
83
|
const onChangeRef = useRef(onChange);
|
|
81
84
|
const isEditMode = mode === PrefabEditorMode.Edit;
|
|
85
|
+
const detachTransformControls = useCallback(() => {
|
|
86
|
+
var _a;
|
|
87
|
+
(_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
|
|
88
|
+
}, []);
|
|
82
89
|
const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
|
|
83
90
|
const getNode = useCallback((nodeId) => { var _a; return (_a = prefabStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null; }, [prefabStore]);
|
|
84
91
|
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 +165,13 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
158
165
|
updateMode(initialMode);
|
|
159
166
|
}, [initialMode, updateMode]);
|
|
160
167
|
const loadPrefab = useCallback((prefab, options) => {
|
|
161
|
-
var _a
|
|
162
|
-
(
|
|
168
|
+
var _a;
|
|
169
|
+
detachTransformControls();
|
|
163
170
|
const before = prefabStore.getState();
|
|
164
171
|
prefabStore.getState().replacePrefab(prefab);
|
|
165
172
|
const after = prefabStore.getState();
|
|
166
173
|
if (after !== before && (options === null || options === void 0 ? void 0 : options.notifyChange) !== false) {
|
|
167
|
-
(
|
|
174
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, prefab);
|
|
168
175
|
}
|
|
169
176
|
if (options === null || options === void 0 ? void 0 : options.resetHistory) {
|
|
170
177
|
setSelectedId(null);
|
|
@@ -175,7 +182,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
175
182
|
else {
|
|
176
183
|
setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
|
|
177
184
|
}
|
|
178
|
-
}, [prefabStore]);
|
|
185
|
+
}, [detachTransformControls, prefabStore]);
|
|
179
186
|
useEffect(() => {
|
|
180
187
|
if (initialPrefab)
|
|
181
188
|
loadPrefab(initialPrefab, { resetHistory: true, notifyChange: false });
|
|
@@ -205,17 +212,25 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
205
212
|
const importPrefab = useCallback((prefab) => {
|
|
206
213
|
add(regenerateIds(prefab.root));
|
|
207
214
|
}, [add]);
|
|
208
|
-
const applyHistory = (index) => {
|
|
209
|
-
var _a
|
|
210
|
-
(
|
|
215
|
+
const applyHistory = useCallback((index) => {
|
|
216
|
+
var _a;
|
|
217
|
+
detachTransformControls();
|
|
211
218
|
prefabStore.getState().replacePrefab(history[index]);
|
|
212
|
-
(
|
|
219
|
+
(_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, history[index]);
|
|
213
220
|
historyIndexRef.current = index;
|
|
214
221
|
setHistoryIndex(index);
|
|
215
222
|
setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
|
|
216
|
-
};
|
|
217
|
-
const undo = () =>
|
|
218
|
-
|
|
223
|
+
}, [detachTransformControls, history, prefabStore]);
|
|
224
|
+
const undo = useCallback(() => {
|
|
225
|
+
if (historyIndex > 0) {
|
|
226
|
+
applyHistory(historyIndex - 1);
|
|
227
|
+
}
|
|
228
|
+
}, [applyHistory, historyIndex]);
|
|
229
|
+
const redo = useCallback(() => {
|
|
230
|
+
if (historyIndex < history.length - 1) {
|
|
231
|
+
applyHistory(historyIndex + 1);
|
|
232
|
+
}
|
|
233
|
+
}, [applyHistory, history.length, historyIndex]);
|
|
219
234
|
useEffect(() => {
|
|
220
235
|
if (!isEditMode)
|
|
221
236
|
return;
|
|
@@ -233,7 +248,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
233
248
|
};
|
|
234
249
|
window.addEventListener('keydown', handleKeyDown);
|
|
235
250
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
236
|
-
}, [isEditMode,
|
|
251
|
+
}, [isEditMode, redo, undo]);
|
|
237
252
|
const handleScreenshot = useCallback(() => {
|
|
238
253
|
const canvas = canvasRef.current;
|
|
239
254
|
if (!canvas)
|
|
@@ -383,7 +398,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
|
|
|
383
398
|
}
|
|
384
399
|
(_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
|
|
385
400
|
}
|
|
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 }))] }))] }) }) });
|
|
401
|
+
: 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
402
|
});
|
|
388
403
|
PrefabEditor.displayName = "PrefabEditor";
|
|
389
404
|
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]);
|
|
@@ -335,14 +358,9 @@ export function GameObjectRenderer(props) {
|
|
|
335
358
|
function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onEditNodeClick, onClick, isVisible = true }) {
|
|
336
359
|
var _a, _b;
|
|
337
360
|
const gameObject = usePrefabNode(nodeId);
|
|
338
|
-
|
|
339
|
-
return null;
|
|
340
|
-
const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
|
|
361
|
+
const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
|
|
341
362
|
const localTransform = getNodeTransformProps(gameObject);
|
|
342
|
-
const isLocked = Boolean(gameObject.locked);
|
|
343
|
-
const nodeVisible = isVisible && !gameObject.hidden;
|
|
344
|
-
const metadataProps = getNodeMetadataProps(gameObject);
|
|
345
|
-
const groupProps = Object.assign(Object.assign({}, metadataProps), { visible: nodeVisible, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
|
|
363
|
+
const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
|
|
346
364
|
const modelUrl = (_b = (_a = analyzedComponents.model) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
|
|
347
365
|
const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl), [gameObject, modelUrl, parentMatrix]);
|
|
348
366
|
const groupRef = useRef(null);
|
|
@@ -353,9 +371,15 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
353
371
|
}
|
|
354
372
|
}, [editMode, nodeId, registerRef]);
|
|
355
373
|
const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
|
|
374
|
+
if (!gameObject)
|
|
375
|
+
return;
|
|
356
376
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
357
377
|
onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
|
|
358
378
|
});
|
|
379
|
+
if (!gameObject)
|
|
380
|
+
return null;
|
|
381
|
+
const nodeVisible = isVisible && !gameObject.hidden;
|
|
382
|
+
const groupProps = Object.assign(Object.assign({}, getNodeMetadataProps(gameObject)), { visible: nodeVisible, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
|
|
359
383
|
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)));
|
|
360
384
|
if (editMode) {
|
|
361
385
|
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] }));
|
|
@@ -365,20 +389,17 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
|
|
|
365
389
|
function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, }) {
|
|
366
390
|
const gameObject = usePrefabNode(nodeId);
|
|
367
391
|
const childIds = usePrefabChildIds(nodeId);
|
|
368
|
-
|
|
369
|
-
return null;
|
|
370
|
-
const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
|
|
392
|
+
const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
|
|
371
393
|
const isSelected = selectedId === nodeId;
|
|
372
|
-
const isLocked = Boolean(gameObject.locked);
|
|
373
|
-
const nodeVisible = isVisible && !gameObject.hidden;
|
|
374
|
-
const stillInstanced = useInstanceCheck(nodeId);
|
|
375
|
-
const metadataProps = getNodeMetadataProps(gameObject);
|
|
394
|
+
const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
|
|
376
395
|
const groupRef = useRef(null);
|
|
377
396
|
const handleGroupRef = useCallback((object) => {
|
|
378
397
|
groupRef.current = object;
|
|
379
398
|
registerRef(nodeId, object);
|
|
380
399
|
}, [nodeId, registerRef]);
|
|
381
400
|
const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
|
|
401
|
+
if (!gameObject)
|
|
402
|
+
return;
|
|
382
403
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
|
|
383
404
|
onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
|
|
384
405
|
});
|
|
@@ -391,7 +412,10 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
|
|
|
391
412
|
}
|
|
392
413
|
: undefined;
|
|
393
414
|
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
394
|
-
|
|
415
|
+
if (!gameObject)
|
|
416
|
+
return null;
|
|
417
|
+
const nodeVisible = isVisible && !gameObject.hidden;
|
|
418
|
+
const metadataProps = getNodeMetadataProps(gameObject);
|
|
395
419
|
const transform = getNodeTransformProps(gameObject);
|
|
396
420
|
const transformProps = {
|
|
397
421
|
position: transform.position,
|
|
@@ -422,7 +446,7 @@ function getModelRepeatSettings(node) {
|
|
|
422
446
|
};
|
|
423
447
|
}
|
|
424
448
|
function buildRepeatedInstances(gameObject, parentMatrix, modelUrl) {
|
|
425
|
-
if (!modelUrl)
|
|
449
|
+
if (!gameObject || !modelUrl)
|
|
426
450
|
return [];
|
|
427
451
|
const transform = getNodeTransformProps(gameObject);
|
|
428
452
|
const repeat = getModelRepeatSettings(gameObject);
|
|
@@ -39,10 +39,13 @@ function CameraComponentView({ properties, children }) {
|
|
|
39
39
|
const halfWidth = halfHeight * aspect;
|
|
40
40
|
const perspectiveCameraRef = useRef(null);
|
|
41
41
|
const orthographicCameraRef = useRef(null);
|
|
42
|
-
const
|
|
43
|
-
? orthographicCameraRef
|
|
44
|
-
: perspectiveCameraRef;
|
|
45
|
-
|
|
42
|
+
const activeCamera = projection === 'orthographic'
|
|
43
|
+
? orthographicCameraRef.current
|
|
44
|
+
: perspectiveCameraRef.current;
|
|
45
|
+
const helperTarget = editMode && isSelected && activeCamera
|
|
46
|
+
? { current: activeCamera }
|
|
47
|
+
: null;
|
|
48
|
+
useHelper(helperTarget, CameraHelper);
|
|
46
49
|
useFrame(() => {
|
|
47
50
|
if (!editMode || !isSelected)
|
|
48
51
|
return;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { FC } from "react";
|
|
2
|
-
import { ComponentData, GameObject } from "../types";
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
import type { ComponentData, GameObject } from "../types";
|
|
3
3
|
export type AssetRef = {
|
|
4
4
|
type: "model" | "texture" | "sound";
|
|
5
5
|
path: string;
|
|
6
6
|
};
|
|
7
|
+
export declare function assetRef(type: AssetRef["type"], path: unknown): AssetRef | null;
|
|
8
|
+
export declare function assetRefs(...refs: Array<AssetRef | null | undefined>): AssetRef[];
|
|
7
9
|
/** Props every component View receives from the renderer. */
|
|
8
|
-
export interface ComponentViewProps<P = Record<string,
|
|
10
|
+
export interface ComponentViewProps<P = Record<string, unknown>> {
|
|
9
11
|
/** This component's own data from the prefab JSON. */
|
|
10
12
|
properties: P;
|
|
11
13
|
/** Children to render for components that wrap the current subtree. */
|
|
@@ -22,15 +24,15 @@ export interface Component {
|
|
|
22
24
|
Editor: FC<{
|
|
23
25
|
node?: GameObject;
|
|
24
26
|
component: ComponentData;
|
|
25
|
-
onUpdate: (newComp:
|
|
27
|
+
onUpdate: (newComp: Record<string, unknown>) => void;
|
|
26
28
|
basePath?: string;
|
|
27
29
|
}>;
|
|
28
|
-
defaultProperties:
|
|
30
|
+
defaultProperties: Record<string, unknown>;
|
|
29
31
|
View?: FC<ComponentViewProps>;
|
|
30
32
|
/** Declare which asset paths this component references (for asset loading). */
|
|
31
|
-
getAssetRefs?: (properties: Record<string,
|
|
33
|
+
getAssetRefs?: (properties: Record<string, unknown>) => AssetRef[];
|
|
32
34
|
}
|
|
33
35
|
export declare function registerComponent(component: Component): void;
|
|
34
36
|
export declare function getComponentDef(name: string): Component | undefined;
|
|
35
37
|
export declare function getAllComponentDefs(): Record<string, Component>;
|
|
36
|
-
export declare function getComponentAssetRefs(componentType: string, properties: Record<string,
|
|
38
|
+
export declare function getComponentAssetRefs(componentType: string, properties: Record<string, unknown>): AssetRef[];
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export function assetRef(type, path) {
|
|
2
|
+
return typeof path === "string" ? { type, path } : null;
|
|
3
|
+
}
|
|
4
|
+
export function assetRefs(...refs) {
|
|
5
|
+
return refs.filter((ref) => ref != null);
|
|
6
|
+
}
|
|
1
7
|
const REGISTRY = {};
|
|
2
8
|
export function registerComponent(component) {
|
|
3
9
|
REGISTRY[component.name] = component;
|
|
@@ -122,7 +122,10 @@ function DirectionalLightView({ properties, children }) {
|
|
|
122
122
|
const targetRef = useRef(null);
|
|
123
123
|
const shadowCameraRef = useRef(null);
|
|
124
124
|
const [shadowCamera, setShadowCamera] = useState(null);
|
|
125
|
-
|
|
125
|
+
const helperTarget = editMode && isSelected && castShadow && shadowCameraRef.current
|
|
126
|
+
? { current: shadowCameraRef.current }
|
|
127
|
+
: null;
|
|
128
|
+
useHelper(helperTarget, CameraHelper);
|
|
126
129
|
// Use a local target object so node transforms rotate the light direction naturally.
|
|
127
130
|
useEffect(() => {
|
|
128
131
|
if (directionalLightRef.current && targetRef.current) {
|
|
@@ -131,7 +134,7 @@ function DirectionalLightView({ properties, children }) {
|
|
|
131
134
|
shadowCameraRef.current = nextShadowCamera;
|
|
132
135
|
setShadowCamera(castShadow ? nextShadowCamera : null);
|
|
133
136
|
}
|
|
134
|
-
}
|
|
137
|
+
});
|
|
135
138
|
useEffect(() => {
|
|
136
139
|
var _a;
|
|
137
140
|
const shadow = (_a = directionalLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
|
|
@@ -139,19 +142,7 @@ function DirectionalLightView({ properties, children }) {
|
|
|
139
142
|
return;
|
|
140
143
|
shadow.needsUpdate = true;
|
|
141
144
|
shadow.camera.updateProjectionMatrix();
|
|
142
|
-
}
|
|
143
|
-
castShadow,
|
|
144
|
-
shadowMapSize,
|
|
145
|
-
shadowBias,
|
|
146
|
-
shadowNormalBias,
|
|
147
|
-
shadowAutoUpdate,
|
|
148
|
-
shadowCameraNear,
|
|
149
|
-
shadowCameraFar,
|
|
150
|
-
shadowCameraTop,
|
|
151
|
-
shadowCameraBottom,
|
|
152
|
-
shadowCameraLeft,
|
|
153
|
-
shadowCameraRight,
|
|
154
|
-
]);
|
|
145
|
+
});
|
|
155
146
|
useFrame(() => {
|
|
156
147
|
if (!directionalLightRef.current || !targetRef.current)
|
|
157
148
|
return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ThreeElement } from '@react-three/fiber';
|
|
3
|
-
import { Component } from './ComponentRegistry';
|
|
3
|
+
import type { Component } from './ComponentRegistry';
|
|
4
4
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
|
|
5
|
-
import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
5
|
+
import type { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
6
6
|
declare module '@react-three/fiber' {
|
|
7
7
|
interface ThreeElements {
|
|
8
8
|
meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
|
|
@@ -10,7 +10,7 @@ declare module '@react-three/fiber' {
|
|
|
10
10
|
spriteNodeMaterial: ThreeElement<typeof SpriteNodeMaterial>;
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
13
|
+
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale' | 'side'> {
|
|
14
14
|
materialType?: 'standard' | 'basic' | 'sprite';
|
|
15
15
|
transmission?: number;
|
|
16
16
|
thickness?: number;
|
|
@@ -28,8 +28,14 @@ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & Mes
|
|
|
28
28
|
magFilter?: string;
|
|
29
29
|
normalMapTexture?: string;
|
|
30
30
|
normalScale?: [number, number];
|
|
31
|
+
side?: keyof typeof SIDE_MAP;
|
|
31
32
|
}
|
|
32
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
|
+
};
|
|
33
39
|
export declare function useMaterialOverrides(): MaterialOverrides;
|
|
34
40
|
export declare function MaterialOverridesProvider({ overrides, children, }: {
|
|
35
41
|
overrides: MaterialOverrides;
|
|
@@ -13,6 +13,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
|
13
13
|
import { createContext, useContext, useMemo, useRef } from 'react';
|
|
14
14
|
import { extend } from '@react-three/fiber';
|
|
15
15
|
import { useFrame } from '@react-three/fiber';
|
|
16
|
+
import { assetRef, assetRefs } from './ComponentRegistry';
|
|
16
17
|
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
17
18
|
import { useAssetRuntime } from '../assetRuntime';
|
|
18
19
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
|
|
@@ -24,6 +25,39 @@ function Vector2Editor({ label, value, onChange, min, max, step, }) {
|
|
|
24
25
|
}
|
|
25
26
|
const EMPTY_MATERIAL_OVERRIDES = Object.freeze({});
|
|
26
27
|
const MaterialOverridesContext = createContext(EMPTY_MATERIAL_OVERRIDES);
|
|
28
|
+
const SIDE_MAP = { FrontSide, BackSide, DoubleSide };
|
|
29
|
+
const MIN_FILTER_MAP = {
|
|
30
|
+
NearestFilter,
|
|
31
|
+
LinearFilter,
|
|
32
|
+
NearestMipmapNearestFilter,
|
|
33
|
+
NearestMipmapLinearFilter,
|
|
34
|
+
LinearMipmapNearestFilter,
|
|
35
|
+
LinearMipmapLinearFilter,
|
|
36
|
+
};
|
|
37
|
+
const MAG_FILTER_MAP = {
|
|
38
|
+
NearestFilter,
|
|
39
|
+
LinearFilter,
|
|
40
|
+
};
|
|
41
|
+
function cloneConfiguredTexture({ texture, repeat, repeatCount, offset, colorSpace, generateMipmaps, minFilter, magFilter, }) {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
const clonedTexture = texture.clone();
|
|
44
|
+
if (repeat) {
|
|
45
|
+
clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping;
|
|
46
|
+
if (repeatCount)
|
|
47
|
+
clonedTexture.repeat.set(repeatCount[0], repeatCount[1]);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
clonedTexture.wrapS = clonedTexture.wrapT = ClampToEdgeWrapping;
|
|
51
|
+
clonedTexture.repeat.set(1, 1);
|
|
52
|
+
}
|
|
53
|
+
clonedTexture.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
54
|
+
clonedTexture.colorSpace = colorSpace;
|
|
55
|
+
clonedTexture.generateMipmaps = generateMipmaps;
|
|
56
|
+
clonedTexture.minFilter = minFilter;
|
|
57
|
+
clonedTexture.magFilter = magFilter;
|
|
58
|
+
clonedTexture.needsUpdate = true;
|
|
59
|
+
return clonedTexture;
|
|
60
|
+
}
|
|
27
61
|
export function useMaterialOverrides() {
|
|
28
62
|
return useContext(MaterialOverridesContext);
|
|
29
63
|
}
|
|
@@ -37,7 +71,7 @@ extend({
|
|
|
37
71
|
MeshStandardNodeMaterial,
|
|
38
72
|
SpriteNodeMaterial,
|
|
39
73
|
});
|
|
40
|
-
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
74
|
+
function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
41
75
|
var _a;
|
|
42
76
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
43
77
|
const hasTexture = !!component.properties.texture;
|
|
@@ -155,94 +189,83 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
155
189
|
}
|
|
156
190
|
// View for Material component
|
|
157
191
|
function MaterialComponentView({ properties: rawProps }) {
|
|
158
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
192
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
159
193
|
const { getTexture } = useAssetRuntime();
|
|
160
194
|
const properties = rawProps;
|
|
161
|
-
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
162
|
-
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
163
|
-
const offset = properties === null || properties === void 0 ? void 0 : properties.offset;
|
|
164
|
-
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
165
|
-
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
166
|
-
const animateOffset = properties === null || properties === void 0 ? void 0 : properties.animateOffset;
|
|
167
|
-
const offsetSpeed = properties === null || properties === void 0 ? void 0 : properties.offsetSpeed;
|
|
168
|
-
const generateMipmaps = (properties === null || properties === void 0 ? void 0 : properties.generateMipmaps) !== false;
|
|
169
|
-
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
170
|
-
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
171
|
-
const texture = textureName ? (_b = getTexture(textureName)) !== null && _b !== void 0 ? _b : undefined : undefined;
|
|
172
|
-
const normalMapTextureName = properties === null || properties === void 0 ? void 0 : properties.normalMapTexture;
|
|
173
|
-
const normalScaleProp = properties === null || properties === void 0 ? void 0 : properties.normalScale;
|
|
174
|
-
const normalMapTexture = normalMapTextureName ? (_c = getTexture(normalMapTextureName)) !== null && _c !== void 0 ? _c : undefined : undefined;
|
|
175
195
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
196
|
+
const materialType = (_a = materialSource.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
197
|
+
const textureName = materialSource.texture;
|
|
198
|
+
const normalMapTextureName = materialSource.normalMapTexture;
|
|
199
|
+
const offset = materialSource.offset;
|
|
200
|
+
const repeat = materialSource.repeat;
|
|
201
|
+
const repeatCount = materialSource.repeatCount;
|
|
202
|
+
const animateOffset = materialSource.animateOffset;
|
|
203
|
+
const offsetSpeed = materialSource.offsetSpeed;
|
|
204
|
+
const generateMipmaps = materialSource.generateMipmaps !== false;
|
|
205
|
+
const minFilter = (_b = materialSource.minFilter) !== null && _b !== void 0 ? _b : 'LinearMipmapLinearFilter';
|
|
206
|
+
const magFilter = (_c = materialSource.magFilter) !== null && _c !== void 0 ? _c : 'LinearFilter';
|
|
207
|
+
const texture = textureName ? getTexture(textureName) : undefined;
|
|
208
|
+
const normalScaleProp = materialSource.normalScale;
|
|
209
|
+
const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
|
|
176
210
|
// Destructure all material props and separate custom texture handling props
|
|
177
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"]);
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
LinearFilter,
|
|
183
|
-
NearestMipmapNearestFilter,
|
|
184
|
-
NearestMipmapLinearFilter,
|
|
185
|
-
LinearMipmapNearestFilter,
|
|
186
|
-
LinearMipmapLinearFilter
|
|
187
|
-
};
|
|
188
|
-
const magFilterMap = {
|
|
189
|
-
NearestFilter,
|
|
190
|
-
LinearFilter
|
|
191
|
-
};
|
|
192
|
-
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]);
|
|
212
|
+
const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
|
|
213
|
+
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
214
|
+
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
215
|
+
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]);
|
|
193
216
|
const finalTexture = useMemo(() => {
|
|
194
|
-
var _a, _b, _c, _d;
|
|
195
217
|
if (!texture)
|
|
196
218
|
return undefined;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
return cloneConfiguredTexture({
|
|
220
|
+
texture,
|
|
221
|
+
repeat,
|
|
222
|
+
repeatCount,
|
|
223
|
+
offset,
|
|
224
|
+
colorSpace: SRGBColorSpace,
|
|
225
|
+
generateMipmaps,
|
|
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: LinearSRGBColorSpace,
|
|
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]);
|
|
244
|
+
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];
|
|
216
245
|
useFrame((_, delta) => {
|
|
217
246
|
var _a, _b;
|
|
218
|
-
if (!finalTexture || !animateOffset)
|
|
247
|
+
if ((!finalTexture && !finalNormalMap) || !animateOffset)
|
|
219
248
|
return;
|
|
220
249
|
const nextX = animatedOffsetRef.current[0] + ((_a = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
|
|
221
250
|
const nextY = animatedOffsetRef.current[1] + ((_b = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
|
|
222
251
|
animatedOffsetRef.current = [nextX, nextY];
|
|
223
|
-
finalTexture.offset.set(nextX, nextY);
|
|
252
|
+
finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.offset.set(nextX, nextY);
|
|
253
|
+
finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.offset.set(nextX, nextY);
|
|
224
254
|
});
|
|
225
|
-
const
|
|
226
|
-
if (!normalMapTexture)
|
|
227
|
-
return undefined;
|
|
228
|
-
const t = normalMapTexture.clone();
|
|
229
|
-
t.colorSpace = LinearSRGBColorSpace;
|
|
230
|
-
t.needsUpdate = true;
|
|
231
|
-
return t;
|
|
232
|
-
}, [normalMapTexture]);
|
|
255
|
+
const overrides = useMaterialOverrides();
|
|
233
256
|
if (!properties) {
|
|
234
257
|
return _jsx("meshStandardNodeMaterial", { attach: "material", color: "red", wireframe: true });
|
|
235
258
|
}
|
|
236
|
-
const
|
|
259
|
+
const materialKey = `${materialType}:${textureName !== null && textureName !== void 0 ? textureName : 'none'}:${normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'none'}`;
|
|
237
260
|
const sharedProps = Object.assign(Object.assign({ map: finalTexture, side: resolvedSide }, materialProps), overrides);
|
|
238
261
|
if (materialType === 'basic') {
|
|
239
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: "material" }, sharedProps));
|
|
262
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: "material" }, sharedProps), materialKey);
|
|
240
263
|
}
|
|
241
264
|
if (materialType === 'sprite') {
|
|
242
|
-
const spriteTransparent =
|
|
243
|
-
return (_jsx("spriteNodeMaterial", Object.assign({ attach: "material", map: finalTexture, color: (
|
|
265
|
+
const spriteTransparent = materialSource.transparent !== false;
|
|
266
|
+
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));
|
|
244
267
|
}
|
|
245
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: "material" }, sharedProps, { normalMap: finalNormalMap, normalScale: finalNormalMap ? [(
|
|
268
|
+
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));
|
|
246
269
|
}
|
|
247
270
|
const MaterialComponent = {
|
|
248
271
|
name: 'Material',
|
|
@@ -262,13 +285,6 @@ const MaterialComponent = {
|
|
|
262
285
|
metalness: 0,
|
|
263
286
|
roughness: 1
|
|
264
287
|
},
|
|
265
|
-
getAssetRefs: (properties) =>
|
|
266
|
-
const refs = [];
|
|
267
|
-
if (properties.texture)
|
|
268
|
-
refs.push({ type: 'texture', path: properties.texture });
|
|
269
|
-
if (properties.normalMapTexture)
|
|
270
|
-
refs.push({ type: 'texture', path: properties.normalMapTexture });
|
|
271
|
-
return refs;
|
|
272
|
-
},
|
|
288
|
+
getAssetRefs: (properties) => assetRefs(assetRef('texture', properties.texture), assetRef('texture', properties.normalMapTexture)),
|
|
273
289
|
};
|
|
274
290
|
export default MaterialComponent;
|
|
@@ -1,6 +1,8 @@
|
|
|
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';
|
|
5
|
+
import { assetRef, assetRefs } from './ComponentRegistry';
|
|
4
6
|
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
|
|
5
7
|
import { useAssetRuntime } from '../assetRuntime';
|
|
6
8
|
import { useEditorContext } from '../PrefabEditor';
|
|
@@ -46,23 +48,20 @@ function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
|
46
48
|
// View for Model component
|
|
47
49
|
function ModelComponentView({ properties, children }) {
|
|
48
50
|
const { getModel } = useAssetRuntime();
|
|
49
|
-
|
|
50
|
-
if (!properties.filename || properties.instanced)
|
|
51
|
-
return _jsx(_Fragment, { children: children });
|
|
52
|
-
const sourceModel = getModel(properties.filename);
|
|
51
|
+
const sourceModel = properties.filename ? getModel(properties.filename) : null;
|
|
53
52
|
// Clone model once and set up shadows - memoized to avoid cloning on every render
|
|
54
53
|
const clonedModel = useMemo(() => {
|
|
55
|
-
if (!sourceModel)
|
|
54
|
+
if (!sourceModel || !properties.filename || properties.instanced)
|
|
56
55
|
return null;
|
|
57
56
|
const clone = sourceModel.clone();
|
|
58
57
|
clone.traverse((obj) => {
|
|
59
|
-
if (obj
|
|
58
|
+
if (obj instanceof Mesh) {
|
|
60
59
|
obj.castShadow = true;
|
|
61
60
|
obj.receiveShadow = true;
|
|
62
61
|
}
|
|
63
62
|
});
|
|
64
63
|
return clone;
|
|
65
|
-
}, [sourceModel]);
|
|
64
|
+
}, [properties.filename, properties.instanced, sourceModel]);
|
|
66
65
|
if (!clonedModel)
|
|
67
66
|
return _jsx(_Fragment, { children: children });
|
|
68
67
|
return _jsx("primitive", { object: clonedModel, children: children });
|
|
@@ -72,10 +71,6 @@ const ModelComponent = {
|
|
|
72
71
|
Editor: ModelComponentEditor,
|
|
73
72
|
View: ModelComponentView,
|
|
74
73
|
defaultProperties: {},
|
|
75
|
-
getAssetRefs: (properties) =>
|
|
76
|
-
if (properties.filename)
|
|
77
|
-
return [{ type: 'model', path: properties.filename }];
|
|
78
|
-
return [];
|
|
79
|
-
},
|
|
74
|
+
getAssetRefs: (properties) => assetRefs(assetRef('model', properties.filename)),
|
|
80
75
|
};
|
|
81
76
|
export default ModelComponent;
|
|
@@ -37,7 +37,10 @@ function PointLightView({ properties, children }) {
|
|
|
37
37
|
const shadowCameraNear = merged.shadowCameraNear;
|
|
38
38
|
const shadowCameraFar = merged.shadowCameraFar;
|
|
39
39
|
const lightRef = useRef(null);
|
|
40
|
-
|
|
40
|
+
const helperTarget = editMode && isSelected && lightRef.current
|
|
41
|
+
? { current: lightRef.current }
|
|
42
|
+
: null;
|
|
43
|
+
useHelper(helperTarget, PointLightHelper, 0.5, color);
|
|
41
44
|
useEffect(() => {
|
|
42
45
|
var _a;
|
|
43
46
|
const shadow = (_a = lightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
|
|
@@ -45,7 +48,7 @@ function PointLightView({ properties, children }) {
|
|
|
45
48
|
return;
|
|
46
49
|
shadow.needsUpdate = true;
|
|
47
50
|
shadow.camera.updateProjectionMatrix();
|
|
48
|
-
}
|
|
51
|
+
});
|
|
49
52
|
return (_jsxs(_Fragment, { children: [_jsx("pointLight", { ref: lightRef, color: color, intensity: intensity, distance: distance, decay: decay, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar }), editMode && isSelected ? (_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 10, 8] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] })) : null, children] }));
|
|
50
53
|
}
|
|
51
54
|
const PointLightComponent = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { assetRef, assetRefs } from "./ComponentRegistry";
|
|
2
3
|
import { useHelper } from "@react-three/drei";
|
|
3
4
|
import { useRef, useEffect } from "react";
|
|
4
5
|
import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
|
|
@@ -50,12 +51,15 @@ function SpotLightView({ properties, children }) {
|
|
|
50
51
|
const textureMap = merged.map ? (_a = getTexture(merged.map)) !== null && _a !== void 0 ? _a : undefined : undefined;
|
|
51
52
|
const spotLightRef = useRef(null);
|
|
52
53
|
const targetRef = useRef(null);
|
|
53
|
-
|
|
54
|
+
const helperTarget = editMode && isSelected && spotLightRef.current
|
|
55
|
+
? { current: spotLightRef.current }
|
|
56
|
+
: null;
|
|
57
|
+
useHelper(helperTarget, SpotLightHelper, color);
|
|
54
58
|
useEffect(() => {
|
|
55
59
|
if (spotLightRef.current && targetRef.current) {
|
|
56
60
|
spotLightRef.current.target = targetRef.current;
|
|
57
61
|
}
|
|
58
|
-
}
|
|
62
|
+
});
|
|
59
63
|
useEffect(() => {
|
|
60
64
|
var _a;
|
|
61
65
|
const shadow = (_a = spotLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
|
|
@@ -63,7 +67,7 @@ function SpotLightView({ properties, children }) {
|
|
|
63
67
|
return;
|
|
64
68
|
shadow.needsUpdate = true;
|
|
65
69
|
shadow.camera.updateProjectionMatrix();
|
|
66
|
-
}
|
|
70
|
+
});
|
|
67
71
|
useFrame(() => {
|
|
68
72
|
var _a;
|
|
69
73
|
if ((_a = spotLightRef.current) === null || _a === void 0 ? void 0 : _a.target) {
|
|
@@ -77,10 +81,6 @@ const SpotLightComponent = {
|
|
|
77
81
|
Editor: SpotLightComponentEditor,
|
|
78
82
|
View: SpotLightView,
|
|
79
83
|
defaultProperties: {},
|
|
80
|
-
getAssetRefs: (properties) =>
|
|
81
|
-
if (properties.map)
|
|
82
|
-
return [{ type: 'texture', path: properties.map }];
|
|
83
|
-
return [];
|
|
84
|
-
},
|
|
84
|
+
getAssetRefs: (properties) => assetRefs(assetRef('texture', properties.map)),
|
|
85
85
|
};
|
|
86
86
|
export default SpotLightComponent;
|