react-three-game 0.0.67 → 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -304
- package/dist/index.d.ts +15 -8
- package/dist/index.js +11 -8
- package/dist/shared/GameCanvas.d.ts +1 -2
- package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
- package/dist/tools/prefabeditor/EditorTree.d.ts +6 -6
- package/dist/tools/prefabeditor/EditorTree.js +92 -142
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +4 -11
- package/dist/tools/prefabeditor/EditorTreeMenus.js +16 -25
- package/dist/tools/prefabeditor/EditorUI.d.ts +5 -5
- package/dist/tools/prefabeditor/EditorUI.js +14 -11
- package/dist/tools/prefabeditor/GameEvents.d.ts +0 -30
- package/dist/tools/prefabeditor/GameEvents.js +0 -7
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +12 -13
- package/dist/tools/prefabeditor/PrefabEditor.js +168 -138
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +8 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +141 -123
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -3
- package/dist/tools/prefabeditor/components/CameraComponent.js +2 -2
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +0 -1
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/TextComponent.js +2 -3
- package/dist/tools/prefabeditor/components/TransformComponent.js +9 -14
- package/dist/tools/prefabeditor/prefabStore.d.ts +42 -0
- package/dist/tools/prefabeditor/prefabStore.js +347 -0
- package/dist/tools/prefabeditor/sceneApi.d.ts +44 -0
- package/dist/tools/prefabeditor/sceneApi.js +161 -0
- package/dist/tools/prefabeditor/styles.d.ts +2 -1
- package/dist/tools/prefabeditor/styles.js +2 -12
- package/dist/tools/prefabeditor/utils.d.ts +15 -36
- package/dist/tools/prefabeditor/utils.js +36 -162
- package/package.json +4 -3
- package/dist/tools/prefabeditor/EventSystem.d.ts +0 -7
- package/dist/tools/prefabeditor/EventSystem.js +0 -23
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type StoreApi } from "zustand/vanilla";
|
|
3
|
+
import { GameObject, Prefab } from "./types";
|
|
4
|
+
export type PrefabNodeRecord = Omit<GameObject, "children">;
|
|
5
|
+
type PrefabAssetRefCounts = Record<string, number>;
|
|
6
|
+
type PrefabDocumentSnapshot = {
|
|
7
|
+
prefabId?: string;
|
|
8
|
+
prefabName?: string;
|
|
9
|
+
rootId: string;
|
|
10
|
+
nodesById: Record<string, PrefabNodeRecord>;
|
|
11
|
+
childIdsById: Record<string, string[]>;
|
|
12
|
+
parentIdById: Record<string, string | null>;
|
|
13
|
+
revision: number;
|
|
14
|
+
assetManifestKey: string;
|
|
15
|
+
assetRefCounts: PrefabAssetRefCounts;
|
|
16
|
+
};
|
|
17
|
+
export interface PrefabStoreState extends PrefabDocumentSnapshot {
|
|
18
|
+
replacePrefab: (prefab: Prefab) => void;
|
|
19
|
+
updateNode: (id: string, update: (node: PrefabNodeRecord) => PrefabNodeRecord) => void;
|
|
20
|
+
updateNodes: (updates: Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
update: (node: PrefabNodeRecord) => PrefabNodeRecord;
|
|
23
|
+
}>) => void;
|
|
24
|
+
addChild: (parentId: string, node: GameObject) => void;
|
|
25
|
+
deleteNode: (id: string) => void;
|
|
26
|
+
duplicateNode: (id: string) => string | null;
|
|
27
|
+
toggleNodeFlag: (id: string, key: "disabled" | "locked") => void;
|
|
28
|
+
moveNode: (draggedId: string, targetId: string, position: "before" | "inside") => void;
|
|
29
|
+
}
|
|
30
|
+
export type PrefabStoreApi = StoreApi<PrefabStoreState>;
|
|
31
|
+
export declare function PrefabStoreProvider({ store, children, }: {
|
|
32
|
+
store: PrefabStoreApi;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}): import("react").FunctionComponentElement<import("react").ProviderProps<PrefabStoreApi | null>>;
|
|
35
|
+
export declare function usePrefabStoreApi(): PrefabStoreApi;
|
|
36
|
+
export declare function usePrefabStore<T>(selector: (state: PrefabStoreState) => T): T;
|
|
37
|
+
export declare function usePrefabRootId(): string;
|
|
38
|
+
export declare function usePrefabNode(nodeId: string | null | undefined): PrefabNodeRecord | null;
|
|
39
|
+
export declare function usePrefabChildIds(nodeId: string | null | undefined): string[];
|
|
40
|
+
export declare function createPrefabStore(prefab: Prefab): PrefabStoreApi;
|
|
41
|
+
export declare function prefabStoreToPrefab(state: Pick<PrefabDocumentSnapshot, "prefabId" | "prefabName" | "rootId" | "nodesById" | "childIdsById">): Prefab;
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { createContext, createElement, useContext } from "react";
|
|
13
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
|
14
|
+
import { useStore } from "zustand";
|
|
15
|
+
import { createStore } from "zustand/vanilla";
|
|
16
|
+
const PrefabStoreContext = createContext(null);
|
|
17
|
+
const EMPTY_CHILD_IDS = [];
|
|
18
|
+
export function PrefabStoreProvider({ store, children, }) {
|
|
19
|
+
return createElement(PrefabStoreContext.Provider, { value: store }, children);
|
|
20
|
+
}
|
|
21
|
+
export function usePrefabStoreApi() {
|
|
22
|
+
const store = useContext(PrefabStoreContext);
|
|
23
|
+
if (!store) {
|
|
24
|
+
throw new Error("usePrefabStoreApi must be used within PrefabStoreProvider");
|
|
25
|
+
}
|
|
26
|
+
return store;
|
|
27
|
+
}
|
|
28
|
+
export function usePrefabStore(selector) {
|
|
29
|
+
return useStore(usePrefabStoreApi(), selector);
|
|
30
|
+
}
|
|
31
|
+
export function usePrefabRootId() {
|
|
32
|
+
return usePrefabStore(state => state.rootId);
|
|
33
|
+
}
|
|
34
|
+
export function usePrefabNode(nodeId) {
|
|
35
|
+
return usePrefabStore(state => { var _a; return nodeId ? (_a = state.nodesById[nodeId]) !== null && _a !== void 0 ? _a : null : null; });
|
|
36
|
+
}
|
|
37
|
+
export function usePrefabChildIds(nodeId) {
|
|
38
|
+
return usePrefabStore(state => { var _a; return nodeId ? (_a = state.childIdsById[nodeId]) !== null && _a !== void 0 ? _a : EMPTY_CHILD_IDS : EMPTY_CHILD_IDS; });
|
|
39
|
+
}
|
|
40
|
+
export function createPrefabStore(prefab) {
|
|
41
|
+
return createStore()(subscribeWithSelector((set, get) => (Object.assign(Object.assign({}, createDocumentState(prefab)), { replacePrefab: (nextPrefab) => {
|
|
42
|
+
set(createDocumentState(nextPrefab, get().revision + 1));
|
|
43
|
+
}, updateNode: (id, update) => {
|
|
44
|
+
const state = get();
|
|
45
|
+
const node = state.nodesById[id];
|
|
46
|
+
if (!node)
|
|
47
|
+
return;
|
|
48
|
+
const nextNode = update(node);
|
|
49
|
+
if (nextNode === node)
|
|
50
|
+
return;
|
|
51
|
+
const nextAssetRefCounts = updateAssetRefsForNodeChange(state.assetRefCounts, node, nextNode);
|
|
52
|
+
set(createMutationPatch(state, {
|
|
53
|
+
nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
|
|
54
|
+
}, nextAssetRefCounts));
|
|
55
|
+
}, updateNodes: (updates) => {
|
|
56
|
+
if (updates.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const state = get();
|
|
59
|
+
let nextNodesById = null;
|
|
60
|
+
let nextAssetRefCounts = state.assetRefCounts;
|
|
61
|
+
for (const { id, update } of updates) {
|
|
62
|
+
const currentNode = (nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : state.nodesById)[id];
|
|
63
|
+
if (!currentNode)
|
|
64
|
+
continue;
|
|
65
|
+
const nextNode = update(currentNode);
|
|
66
|
+
if (nextNode === currentNode)
|
|
67
|
+
continue;
|
|
68
|
+
nextNodesById !== null && nextNodesById !== void 0 ? nextNodesById : (nextNodesById = Object.assign({}, state.nodesById));
|
|
69
|
+
nextNodesById[id] = nextNode;
|
|
70
|
+
nextAssetRefCounts = updateAssetRefsForNodeChange(nextAssetRefCounts, currentNode, nextNode);
|
|
71
|
+
}
|
|
72
|
+
if (!nextNodesById)
|
|
73
|
+
return;
|
|
74
|
+
set(createMutationPatch(state, { nodesById: nextNodesById }, nextAssetRefCounts));
|
|
75
|
+
}, addChild: (parentId, node) => {
|
|
76
|
+
var _a;
|
|
77
|
+
const state = get();
|
|
78
|
+
if (!state.nodesById[parentId])
|
|
79
|
+
return;
|
|
80
|
+
const nextNodesById = Object.assign({}, state.nodesById);
|
|
81
|
+
const nextChildIdsById = Object.assign({}, state.childIdsById);
|
|
82
|
+
const nextParentIdById = Object.assign({}, state.parentIdById);
|
|
83
|
+
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
84
|
+
insertSubtree(node, parentId, nextNodesById, nextChildIdsById, nextParentIdById);
|
|
85
|
+
nextChildIdsById[parentId] = [...((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []), node.id];
|
|
86
|
+
addAssetRefs(nextAssetRefCounts, collectSubtreeAssetRefs(node));
|
|
87
|
+
set(createMutationPatch(state, {
|
|
88
|
+
nodesById: nextNodesById,
|
|
89
|
+
childIdsById: nextChildIdsById,
|
|
90
|
+
parentIdById: nextParentIdById,
|
|
91
|
+
}, nextAssetRefCounts));
|
|
92
|
+
}, deleteNode: (id) => {
|
|
93
|
+
var _a;
|
|
94
|
+
const state = get();
|
|
95
|
+
if (id === state.rootId || !state.nodesById[id])
|
|
96
|
+
return;
|
|
97
|
+
const parentId = state.parentIdById[id];
|
|
98
|
+
if (!parentId)
|
|
99
|
+
return;
|
|
100
|
+
const idsToDelete = collectSubtreeIds(id, state.childIdsById);
|
|
101
|
+
const nextNodesById = Object.assign({}, state.nodesById);
|
|
102
|
+
const nextChildIdsById = Object.assign({}, state.childIdsById);
|
|
103
|
+
const nextParentIdById = Object.assign({}, state.parentIdById);
|
|
104
|
+
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
105
|
+
removeAssetRefs(nextAssetRefCounts, collectAssetRefsForIds(idsToDelete, state.nodesById));
|
|
106
|
+
idsToDelete.forEach(nodeId => {
|
|
107
|
+
delete nextNodesById[nodeId];
|
|
108
|
+
delete nextChildIdsById[nodeId];
|
|
109
|
+
delete nextParentIdById[nodeId];
|
|
110
|
+
});
|
|
111
|
+
nextChildIdsById[parentId] = ((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : []).filter(childId => childId !== id);
|
|
112
|
+
set(createMutationPatch(state, {
|
|
113
|
+
nodesById: nextNodesById,
|
|
114
|
+
childIdsById: nextChildIdsById,
|
|
115
|
+
parentIdById: nextParentIdById,
|
|
116
|
+
}, nextAssetRefCounts));
|
|
117
|
+
}, duplicateNode: (id) => {
|
|
118
|
+
var _a;
|
|
119
|
+
const state = get();
|
|
120
|
+
if (id === state.rootId || !state.nodesById[id])
|
|
121
|
+
return null;
|
|
122
|
+
const parentId = state.parentIdById[id];
|
|
123
|
+
if (!parentId)
|
|
124
|
+
return null;
|
|
125
|
+
const nextNodesById = Object.assign({}, state.nodesById);
|
|
126
|
+
const nextChildIdsById = Object.assign({}, state.childIdsById);
|
|
127
|
+
const nextParentIdById = Object.assign({}, state.parentIdById);
|
|
128
|
+
const duplicatedRootId = cloneSubtreeIntoMaps(id, parentId, state, nextNodesById, nextChildIdsById, nextParentIdById);
|
|
129
|
+
if (!duplicatedRootId)
|
|
130
|
+
return null;
|
|
131
|
+
const siblings = [...((_a = nextChildIdsById[parentId]) !== null && _a !== void 0 ? _a : [])];
|
|
132
|
+
const currentIndex = siblings.findIndex(childId => childId === id);
|
|
133
|
+
if (currentIndex === -1) {
|
|
134
|
+
siblings.push(duplicatedRootId);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
siblings.splice(currentIndex + 1, 0, duplicatedRootId);
|
|
138
|
+
}
|
|
139
|
+
nextChildIdsById[parentId] = siblings;
|
|
140
|
+
const nextAssetRefCounts = Object.assign({}, state.assetRefCounts);
|
|
141
|
+
addAssetRefs(nextAssetRefCounts, collectAssetRefsForIds(collectSubtreeIds(id, state.childIdsById), state.nodesById));
|
|
142
|
+
set(createMutationPatch(state, {
|
|
143
|
+
nodesById: nextNodesById,
|
|
144
|
+
childIdsById: nextChildIdsById,
|
|
145
|
+
parentIdById: nextParentIdById,
|
|
146
|
+
}, nextAssetRefCounts));
|
|
147
|
+
return duplicatedRootId;
|
|
148
|
+
}, toggleNodeFlag: (id, key) => {
|
|
149
|
+
const state = get();
|
|
150
|
+
const node = state.nodesById[id];
|
|
151
|
+
if (!node)
|
|
152
|
+
return;
|
|
153
|
+
const nextNode = Object.assign(Object.assign({}, node), { [key]: !node[key] });
|
|
154
|
+
set(createMutationPatch(state, {
|
|
155
|
+
nodesById: Object.assign(Object.assign({}, state.nodesById), { [id]: nextNode }),
|
|
156
|
+
}));
|
|
157
|
+
}, moveNode: (draggedId, targetId, position) => {
|
|
158
|
+
var _a, _b, _c;
|
|
159
|
+
const state = get();
|
|
160
|
+
if (draggedId === state.rootId || draggedId === targetId)
|
|
161
|
+
return;
|
|
162
|
+
if (!state.nodesById[draggedId] || !state.nodesById[targetId])
|
|
163
|
+
return;
|
|
164
|
+
if (isDescendant(targetId, draggedId, state.parentIdById))
|
|
165
|
+
return;
|
|
166
|
+
const currentParentId = state.parentIdById[draggedId];
|
|
167
|
+
if (!currentParentId)
|
|
168
|
+
return;
|
|
169
|
+
const destinationParentId = position === "inside"
|
|
170
|
+
? targetId
|
|
171
|
+
: state.parentIdById[targetId];
|
|
172
|
+
if (!destinationParentId)
|
|
173
|
+
return;
|
|
174
|
+
if (destinationParentId === draggedId || isDescendant(destinationParentId, draggedId, state.parentIdById))
|
|
175
|
+
return;
|
|
176
|
+
const nextChildIdsById = Object.assign({}, state.childIdsById);
|
|
177
|
+
const nextParentIdById = Object.assign(Object.assign({}, state.parentIdById), { [draggedId]: destinationParentId });
|
|
178
|
+
const sourceChildren = [...((_a = nextChildIdsById[currentParentId]) !== null && _a !== void 0 ? _a : [])].filter(childId => childId !== draggedId);
|
|
179
|
+
nextChildIdsById[currentParentId] = sourceChildren;
|
|
180
|
+
if (position === "inside") {
|
|
181
|
+
nextChildIdsById[destinationParentId] = [...((_b = nextChildIdsById[destinationParentId]) !== null && _b !== void 0 ? _b : []), draggedId];
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const destinationChildren = destinationParentId === currentParentId
|
|
185
|
+
? [...sourceChildren]
|
|
186
|
+
: [...((_c = nextChildIdsById[destinationParentId]) !== null && _c !== void 0 ? _c : [])];
|
|
187
|
+
const targetIndex = destinationChildren.findIndex(childId => childId === targetId);
|
|
188
|
+
if (targetIndex === -1)
|
|
189
|
+
return;
|
|
190
|
+
destinationChildren.splice(targetIndex, 0, draggedId);
|
|
191
|
+
nextChildIdsById[destinationParentId] = destinationChildren;
|
|
192
|
+
}
|
|
193
|
+
set(createMutationPatch(state, {
|
|
194
|
+
childIdsById: nextChildIdsById,
|
|
195
|
+
parentIdById: nextParentIdById,
|
|
196
|
+
}));
|
|
197
|
+
} }))));
|
|
198
|
+
}
|
|
199
|
+
export function prefabStoreToPrefab(state) {
|
|
200
|
+
return {
|
|
201
|
+
id: state.prefabId,
|
|
202
|
+
name: state.prefabName,
|
|
203
|
+
root: denormalizeNode(state.rootId, state.nodesById, state.childIdsById),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function createDocumentState(prefab, revision = 0) {
|
|
207
|
+
const nodesById = {};
|
|
208
|
+
const childIdsById = {};
|
|
209
|
+
const parentIdById = {};
|
|
210
|
+
insertSubtree(prefab.root, null, nodesById, childIdsById, parentIdById);
|
|
211
|
+
const assetRefCounts = createAssetRefCounts(nodesById);
|
|
212
|
+
return {
|
|
213
|
+
prefabId: prefab.id,
|
|
214
|
+
prefabName: prefab.name,
|
|
215
|
+
rootId: prefab.root.id,
|
|
216
|
+
nodesById,
|
|
217
|
+
childIdsById,
|
|
218
|
+
parentIdById,
|
|
219
|
+
revision,
|
|
220
|
+
assetManifestKey: getAssetManifestKey(assetRefCounts),
|
|
221
|
+
assetRefCounts,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function createMutationPatch(state, patch, nextAssetRefCounts = state.assetRefCounts) {
|
|
225
|
+
const assetRefsChanged = nextAssetRefCounts !== state.assetRefCounts;
|
|
226
|
+
return Object.assign(Object.assign(Object.assign({}, patch), { revision: state.revision + 1 }), (assetRefsChanged ? {
|
|
227
|
+
assetRefCounts: nextAssetRefCounts,
|
|
228
|
+
assetManifestKey: getAssetManifestKey(nextAssetRefCounts),
|
|
229
|
+
} : null));
|
|
230
|
+
}
|
|
231
|
+
function denormalizeNode(id, nodesById, childIdsById) {
|
|
232
|
+
var _a;
|
|
233
|
+
const node = nodesById[id];
|
|
234
|
+
return Object.assign(Object.assign({}, node), { children: ((_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []).map(childId => denormalizeNode(childId, nodesById, childIdsById)) });
|
|
235
|
+
}
|
|
236
|
+
function collectSubtreeIds(id, childIdsById) {
|
|
237
|
+
var _a;
|
|
238
|
+
const ids = [id];
|
|
239
|
+
for (const childId of (_a = childIdsById[id]) !== null && _a !== void 0 ? _a : []) {
|
|
240
|
+
ids.push(...collectSubtreeIds(childId, childIdsById));
|
|
241
|
+
}
|
|
242
|
+
return ids;
|
|
243
|
+
}
|
|
244
|
+
function insertSubtree(node, parentId, nodesById, childIdsById, parentIdById) {
|
|
245
|
+
var _a;
|
|
246
|
+
const { children } = node, nodeRecord = __rest(node, ["children"]);
|
|
247
|
+
nodesById[node.id] = nodeRecord;
|
|
248
|
+
childIdsById[node.id] = (_a = children === null || children === void 0 ? void 0 : children.map(child => child.id)) !== null && _a !== void 0 ? _a : [];
|
|
249
|
+
parentIdById[node.id] = parentId;
|
|
250
|
+
children === null || children === void 0 ? void 0 : children.forEach(child => insertSubtree(child, node.id, nodesById, childIdsById, parentIdById));
|
|
251
|
+
}
|
|
252
|
+
function cloneSubtreeIntoMaps(id, parentId, source, nodesById, childIdsById, parentIdById) {
|
|
253
|
+
var _a, _b;
|
|
254
|
+
const originalNode = source.nodesById[id];
|
|
255
|
+
if (!originalNode)
|
|
256
|
+
return null;
|
|
257
|
+
const clonedId = crypto.randomUUID();
|
|
258
|
+
const clonedNode = Object.assign(Object.assign({}, originalNode), { id: clonedId, name: `${(_a = originalNode.name) !== null && _a !== void 0 ? _a : originalNode.id} Copy` });
|
|
259
|
+
nodesById[clonedId] = clonedNode;
|
|
260
|
+
parentIdById[clonedId] = parentId;
|
|
261
|
+
const clonedChildIds = ((_b = source.childIdsById[id]) !== null && _b !== void 0 ? _b : [])
|
|
262
|
+
.map(childId => cloneSubtreeIntoMaps(childId, clonedId, source, nodesById, childIdsById, parentIdById))
|
|
263
|
+
.filter((childId) => Boolean(childId));
|
|
264
|
+
childIdsById[clonedId] = clonedChildIds;
|
|
265
|
+
return clonedId;
|
|
266
|
+
}
|
|
267
|
+
function isDescendant(id, potentialAncestorId, parentIdById) {
|
|
268
|
+
let currentId = id;
|
|
269
|
+
while (currentId) {
|
|
270
|
+
if (currentId === potentialAncestorId)
|
|
271
|
+
return true;
|
|
272
|
+
currentId = parentIdById[currentId];
|
|
273
|
+
}
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
function createAssetRefCounts(nodesById) {
|
|
277
|
+
const assetRefCounts = {};
|
|
278
|
+
Object.values(nodesById).forEach(node => addAssetRefs(assetRefCounts, getAssetRefs(node)));
|
|
279
|
+
return assetRefCounts;
|
|
280
|
+
}
|
|
281
|
+
function updateAssetRefsForNodeChange(assetRefCounts, currentNode, nextNode) {
|
|
282
|
+
const currentRefs = getAssetRefs(currentNode);
|
|
283
|
+
const nextRefs = getAssetRefs(nextNode);
|
|
284
|
+
if (sameStringArrays(currentRefs, nextRefs)) {
|
|
285
|
+
return assetRefCounts;
|
|
286
|
+
}
|
|
287
|
+
const nextAssetRefCounts = Object.assign({}, assetRefCounts);
|
|
288
|
+
removeAssetRefs(nextAssetRefCounts, currentRefs);
|
|
289
|
+
addAssetRefs(nextAssetRefCounts, nextRefs);
|
|
290
|
+
return nextAssetRefCounts;
|
|
291
|
+
}
|
|
292
|
+
function collectSubtreeAssetRefs(node) {
|
|
293
|
+
var _a;
|
|
294
|
+
const refs = getAssetRefs(node);
|
|
295
|
+
(_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(child => refs.push(...collectSubtreeAssetRefs(child)));
|
|
296
|
+
return refs;
|
|
297
|
+
}
|
|
298
|
+
function collectAssetRefsForIds(ids, nodesById) {
|
|
299
|
+
return ids.flatMap(id => getAssetRefs(nodesById[id]));
|
|
300
|
+
}
|
|
301
|
+
function getAssetRefs(node) {
|
|
302
|
+
var _a;
|
|
303
|
+
const refs = [];
|
|
304
|
+
Object.values((_a = node === null || node === void 0 ? void 0 : node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
|
|
305
|
+
var _a, _b, _c, _d;
|
|
306
|
+
if (!(component === null || component === void 0 ? void 0 : component.type))
|
|
307
|
+
return;
|
|
308
|
+
if (component.type === "Model" && ((_a = component.properties) === null || _a === void 0 ? void 0 : _a.filename)) {
|
|
309
|
+
refs.push(`model:${component.properties.filename}`);
|
|
310
|
+
}
|
|
311
|
+
if (component.type === "Material") {
|
|
312
|
+
if ((_b = component.properties) === null || _b === void 0 ? void 0 : _b.texture)
|
|
313
|
+
refs.push(`texture:${component.properties.texture}`);
|
|
314
|
+
if ((_c = component.properties) === null || _c === void 0 ? void 0 : _c.normalMapTexture)
|
|
315
|
+
refs.push(`texture:${component.properties.normalMapTexture}`);
|
|
316
|
+
}
|
|
317
|
+
if (component.type === "SpotLight" && ((_d = component.properties) === null || _d === void 0 ? void 0 : _d.map)) {
|
|
318
|
+
refs.push(`texture:${component.properties.map}`);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
return refs.sort();
|
|
322
|
+
}
|
|
323
|
+
function addAssetRefs(assetRefCounts, refs) {
|
|
324
|
+
refs.forEach(ref => {
|
|
325
|
+
var _a;
|
|
326
|
+
assetRefCounts[ref] = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function removeAssetRefs(assetRefCounts, refs) {
|
|
330
|
+
refs.forEach(ref => {
|
|
331
|
+
var _a;
|
|
332
|
+
const nextCount = ((_a = assetRefCounts[ref]) !== null && _a !== void 0 ? _a : 0) - 1;
|
|
333
|
+
if (nextCount > 0) {
|
|
334
|
+
assetRefCounts[ref] = nextCount;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
delete assetRefCounts[ref];
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function getAssetManifestKey(assetRefCounts) {
|
|
341
|
+
return Object.keys(assetRefCounts).sort().join("|");
|
|
342
|
+
}
|
|
343
|
+
function sameStringArrays(left, right) {
|
|
344
|
+
if (left.length !== right.length)
|
|
345
|
+
return false;
|
|
346
|
+
return left.every((value, index) => value === right[index]);
|
|
347
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GameObject } from "./types";
|
|
2
|
+
export interface SpawnOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
parentId?: string;
|
|
5
|
+
select?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export type EntityData = Omit<GameObject, "children">;
|
|
8
|
+
export type PropertyPath = string | Array<string | number>;
|
|
9
|
+
export interface EntityComponent<TProperties = Record<string, any>> {
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly type: string;
|
|
12
|
+
get: <TValue = unknown>(path?: PropertyPath) => TValue | undefined;
|
|
13
|
+
set: (path: PropertyPath, value: unknown) => void;
|
|
14
|
+
update: (update: (properties: TProperties) => TProperties) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface Entity {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
set: (data: EntityData) => void;
|
|
19
|
+
update: (update: (node: EntityData) => EntityData) => void;
|
|
20
|
+
getComponent: <TProperties = Record<string, any>>(name: string) => EntityComponent<TProperties> | null;
|
|
21
|
+
}
|
|
22
|
+
export type EntityUpdate = (node: EntityData) => EntityData;
|
|
23
|
+
export type SceneUpdates = Record<string, EntityUpdate>;
|
|
24
|
+
export interface Scene {
|
|
25
|
+
readonly rootId: string;
|
|
26
|
+
find: (id: string) => Entity | null;
|
|
27
|
+
get: (id: string) => Entity;
|
|
28
|
+
update: {
|
|
29
|
+
(id: string, update: EntityUpdate): void;
|
|
30
|
+
(updates: SceneUpdates): void;
|
|
31
|
+
};
|
|
32
|
+
add: (node: GameObject, options?: SpawnOptions) => Entity;
|
|
33
|
+
remove: (id: string) => void;
|
|
34
|
+
}
|
|
35
|
+
interface SceneAdapter {
|
|
36
|
+
getRootId: () => string;
|
|
37
|
+
getNode: (id: string) => EntityData | null;
|
|
38
|
+
updateNode: (id: string, update: (node: EntityData) => EntityData) => void;
|
|
39
|
+
updateNodes: (updates: Record<string, (node: EntityData) => EntityData>) => void;
|
|
40
|
+
addNode: (node: GameObject, options?: SpawnOptions) => string;
|
|
41
|
+
removeNode: (id: string) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare function createScene(adapter: SceneAdapter): Scene;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
function missingNode(id) {
|
|
2
|
+
throw new Error(`Scene node not found: ${id}`);
|
|
3
|
+
}
|
|
4
|
+
function normalizePath(path) {
|
|
5
|
+
if (path === undefined) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(path)) {
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
11
|
+
return path.split(".").filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
function getValueAtPath(value, path) {
|
|
14
|
+
const segments = normalizePath(path);
|
|
15
|
+
let current = value;
|
|
16
|
+
for (const segment of segments) {
|
|
17
|
+
if (current == null || typeof current !== "object") {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
current = current[segment];
|
|
21
|
+
}
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
function setValueAtPath(value, path, nextValue) {
|
|
25
|
+
const segments = normalizePath(path);
|
|
26
|
+
if (segments.length === 0) {
|
|
27
|
+
return nextValue;
|
|
28
|
+
}
|
|
29
|
+
const cloneBranch = (current, index) => {
|
|
30
|
+
const segment = segments[index];
|
|
31
|
+
const source = current == null ? undefined : current;
|
|
32
|
+
const container = Array.isArray(source)
|
|
33
|
+
? [...source]
|
|
34
|
+
: source && typeof source === "object"
|
|
35
|
+
? Object.assign({}, source) : typeof segment === "number"
|
|
36
|
+
? []
|
|
37
|
+
: {};
|
|
38
|
+
if (index === segments.length - 1) {
|
|
39
|
+
container[segment] = nextValue;
|
|
40
|
+
return container;
|
|
41
|
+
}
|
|
42
|
+
const child = source && typeof source === "object"
|
|
43
|
+
? source[segment]
|
|
44
|
+
: undefined;
|
|
45
|
+
container[segment] = cloneBranch(child, index + 1);
|
|
46
|
+
return container;
|
|
47
|
+
};
|
|
48
|
+
return cloneBranch(value, 0);
|
|
49
|
+
}
|
|
50
|
+
function findComponentEntry(node, name) {
|
|
51
|
+
if (!node.components) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const direct = node.components[name];
|
|
55
|
+
if (direct) {
|
|
56
|
+
return [name, direct];
|
|
57
|
+
}
|
|
58
|
+
const normalizedName = name.toLowerCase();
|
|
59
|
+
for (const [key, component] of Object.entries(node.components)) {
|
|
60
|
+
if (!component) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (key.toLowerCase() === normalizedName || component.type.toLowerCase() === normalizedName) {
|
|
64
|
+
return [key, component];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export function createScene(adapter) {
|
|
70
|
+
const findNode = (id) => adapter.getNode(id) ? createNode(id) : null;
|
|
71
|
+
const getNode = (id) => { var _a; return (_a = findNode(id)) !== null && _a !== void 0 ? _a : missingNode(id); };
|
|
72
|
+
function createComponent(entityId, componentKey, componentType) {
|
|
73
|
+
return {
|
|
74
|
+
key: componentKey,
|
|
75
|
+
type: componentType,
|
|
76
|
+
get(path) {
|
|
77
|
+
var _a;
|
|
78
|
+
const node = adapter.getNode(entityId);
|
|
79
|
+
const component = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
80
|
+
if (!component) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return getValueAtPath(component.properties, path);
|
|
84
|
+
},
|
|
85
|
+
set(path, value) {
|
|
86
|
+
adapter.updateNode(entityId, node => {
|
|
87
|
+
var _a;
|
|
88
|
+
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
89
|
+
if (!component) {
|
|
90
|
+
return node;
|
|
91
|
+
}
|
|
92
|
+
return Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [componentKey]: Object.assign(Object.assign({}, component), { properties: setValueAtPath(component.properties, path, value) }) }) });
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
update(update) {
|
|
96
|
+
adapter.updateNode(entityId, node => {
|
|
97
|
+
var _a;
|
|
98
|
+
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
99
|
+
if (!component) {
|
|
100
|
+
return node;
|
|
101
|
+
}
|
|
102
|
+
const nextProperties = update(component.properties);
|
|
103
|
+
if (nextProperties === component.properties) {
|
|
104
|
+
return node;
|
|
105
|
+
}
|
|
106
|
+
return Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [componentKey]: Object.assign(Object.assign({}, component), { properties: nextProperties }) }) });
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createNode(id) {
|
|
112
|
+
return {
|
|
113
|
+
id,
|
|
114
|
+
set(data) {
|
|
115
|
+
adapter.updateNode(id, () => data);
|
|
116
|
+
},
|
|
117
|
+
update(update) {
|
|
118
|
+
adapter.updateNode(id, update);
|
|
119
|
+
},
|
|
120
|
+
getComponent(name) {
|
|
121
|
+
const node = adapter.getNode(id);
|
|
122
|
+
if (!node) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const entry = findComponentEntry(node, name);
|
|
126
|
+
if (!entry) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const [componentKey, component] = entry;
|
|
130
|
+
return createComponent(id, componentKey, component.type);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function update(idOrUpdates, mutate) {
|
|
135
|
+
if (typeof idOrUpdates === "string") {
|
|
136
|
+
if (!mutate) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
adapter.updateNode(idOrUpdates, mutate);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(idOrUpdates).length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
adapter.updateNodes(idOrUpdates);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
get rootId() {
|
|
149
|
+
return adapter.getRootId();
|
|
150
|
+
},
|
|
151
|
+
find: findNode,
|
|
152
|
+
get: getNode,
|
|
153
|
+
update,
|
|
154
|
+
add(node, options) {
|
|
155
|
+
return createNode(adapter.addNode(node, options));
|
|
156
|
+
},
|
|
157
|
+
remove(id) {
|
|
158
|
+
adapter.removeNode(id);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -897,6 +897,8 @@ export declare const inspector: {
|
|
|
897
897
|
maxHeight: string;
|
|
898
898
|
overflowY: "auto";
|
|
899
899
|
overflowX: "hidden";
|
|
900
|
+
scrollbarWidth: "thin";
|
|
901
|
+
scrollbarColor: string;
|
|
900
902
|
boxSizing: "border-box";
|
|
901
903
|
display: string;
|
|
902
904
|
flexDirection: "column";
|
|
@@ -1813,7 +1815,6 @@ export declare const toolbar: {
|
|
|
1813
1815
|
cursor: string;
|
|
1814
1816
|
};
|
|
1815
1817
|
};
|
|
1816
|
-
export declare const scrollbarCSS: string;
|
|
1817
1818
|
export declare const componentCard: {
|
|
1818
1819
|
container: React.CSSProperties;
|
|
1819
1820
|
};
|
|
@@ -98,6 +98,8 @@ export const inspector = {
|
|
|
98
98
|
maxHeight: '80vh',
|
|
99
99
|
overflowY: 'auto',
|
|
100
100
|
overflowX: 'hidden',
|
|
101
|
+
scrollbarWidth: 'thin',
|
|
102
|
+
scrollbarColor: `${colors.bgLight} transparent`,
|
|
101
103
|
boxSizing: 'border-box',
|
|
102
104
|
display: 'flex',
|
|
103
105
|
flexDirection: 'column',
|
|
@@ -182,18 +184,6 @@ export const toolbar = {
|
|
|
182
184
|
cursor: 'not-allowed',
|
|
183
185
|
},
|
|
184
186
|
};
|
|
185
|
-
// Shared scrollbar CSS (inject via <style> tag since CSS can't be bundled)
|
|
186
|
-
export const scrollbarCSS = `
|
|
187
|
-
.prefab-scroll::-webkit-scrollbar,
|
|
188
|
-
.tree-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
189
|
-
.prefab-scroll::-webkit-scrollbar-track,
|
|
190
|
-
.tree-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
191
|
-
.prefab-scroll::-webkit-scrollbar-thumb,
|
|
192
|
-
.tree-scroll::-webkit-scrollbar-thumb { background: ${colors.border}; border-radius: 3px; }
|
|
193
|
-
.prefab-scroll::-webkit-scrollbar-thumb:hover,
|
|
194
|
-
.tree-scroll::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
195
|
-
.prefab-scroll { scrollbar-width: thin; scrollbar-color: ${colors.border} transparent; }
|
|
196
|
-
`;
|
|
197
187
|
// Reusable component card style for inspector sections
|
|
198
188
|
export const componentCard = {
|
|
199
189
|
container: {
|