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.
Files changed (35) hide show
  1. package/README.md +109 -304
  2. package/dist/index.d.ts +15 -8
  3. package/dist/index.js +11 -8
  4. package/dist/shared/GameCanvas.d.ts +1 -2
  5. package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
  6. package/dist/tools/prefabeditor/EditorTree.d.ts +6 -6
  7. package/dist/tools/prefabeditor/EditorTree.js +92 -142
  8. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +4 -11
  9. package/dist/tools/prefabeditor/EditorTreeMenus.js +16 -25
  10. package/dist/tools/prefabeditor/EditorUI.d.ts +5 -5
  11. package/dist/tools/prefabeditor/EditorUI.js +14 -11
  12. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -30
  13. package/dist/tools/prefabeditor/GameEvents.js +0 -7
  14. package/dist/tools/prefabeditor/PrefabEditor.d.ts +12 -13
  15. package/dist/tools/prefabeditor/PrefabEditor.js +168 -138
  16. package/dist/tools/prefabeditor/PrefabRoot.d.ts +8 -5
  17. package/dist/tools/prefabeditor/PrefabRoot.js +141 -123
  18. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -3
  19. package/dist/tools/prefabeditor/components/CameraComponent.js +2 -2
  20. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
  21. package/dist/tools/prefabeditor/components/ModelComponent.js +0 -1
  22. package/dist/tools/prefabeditor/components/SpotLightComponent.js +2 -2
  23. package/dist/tools/prefabeditor/components/TextComponent.js +2 -3
  24. package/dist/tools/prefabeditor/components/TransformComponent.js +9 -14
  25. package/dist/tools/prefabeditor/prefabStore.d.ts +42 -0
  26. package/dist/tools/prefabeditor/prefabStore.js +347 -0
  27. package/dist/tools/prefabeditor/sceneApi.d.ts +44 -0
  28. package/dist/tools/prefabeditor/sceneApi.js +161 -0
  29. package/dist/tools/prefabeditor/styles.d.ts +2 -1
  30. package/dist/tools/prefabeditor/styles.js +2 -12
  31. package/dist/tools/prefabeditor/utils.d.ts +15 -36
  32. package/dist/tools/prefabeditor/utils.js +36 -162
  33. package/package.json +4 -3
  34. package/dist/tools/prefabeditor/EventSystem.d.ts +0 -7
  35. package/dist/tools/prefabeditor/EventSystem.js +0 -23
@@ -2,57 +2,36 @@ import { GameObject, Prefab } from "./types";
2
2
  import { Matrix4, Object3D, Vector3 } from 'three';
3
3
  export interface ExportGLBOptions {
4
4
  filename?: string;
5
- binary?: boolean;
6
- onComplete?: (result: ArrayBuffer | object) => void;
7
- onError?: (error: any) => void;
8
5
  }
9
- /** Save a prefab as JSON file, showing a Save As dialog when supported */
6
+ /** Save scene JSON, showing a Save As dialog when supported */
10
7
  export declare function saveJson(data: Prefab, filename: string): Promise<void>;
11
- /** Load a prefab from JSON file */
8
+ /** Load scene JSON from a file */
12
9
  export declare function loadJson(): Promise<Prefab | undefined>;
13
10
  /**
14
- * Export a Three.js scene or object to GLB format
15
- * @param sceneRoot - The Three.js Object3D to export
16
- * @param options - Export options
17
- * @returns Promise that resolves when export is complete
11
+ * Export a Three.js scene or object to GLB binary data
18
12
  */
19
- export declare function exportGLB(sceneRoot: Object3D, options?: ExportGLBOptions): Promise<ArrayBuffer | object>;
13
+ export declare function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer>;
20
14
  /**
21
- * Export a Three.js scene to GLB and return the ArrayBuffer without downloading
22
- * @param sceneRoot - The Three.js Object3D to export
23
- * @returns Promise that resolves with the GLB data as ArrayBuffer
15
+ * Export a Three.js scene or object to GLB and trigger a download
24
16
  */
25
- export declare function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer>;
17
+ export declare function exportGLB(sceneRoot: Object3D, options?: ExportGLBOptions): Promise<ArrayBuffer>;
26
18
  export declare function focusCameraOnObject(object: Object3D, camera: Object3D, target: Vector3, update?: () => void): void;
27
19
  export declare function decompose(m: Matrix4): {
28
20
  position: [number, number, number];
29
21
  rotation: [number, number, number];
30
22
  scale: [number, number, number];
31
23
  };
32
- export declare function computeParentWorldMatrix(root: GameObject, targetId: string): Matrix4;
33
- /** Find a node by ID in the tree */
34
- export declare function findNode(root: GameObject, id: string): GameObject | null;
35
- /** Find the parent of a node by ID */
36
- export declare function findParent(root: GameObject, id: string): GameObject | null;
37
- /** Find all nodes matching a predicate */
38
- export declare function findAll(root: GameObject, predicate: (node: GameObject) => boolean): GameObject[];
39
- /** Find all nodes that have a specific component type */
40
- export declare function findByComponent(root: GameObject, componentType: string): GameObject[];
41
- /** Get a flattened list of all nodes */
42
- export declare function flatten(root: GameObject): GameObject[];
43
- /** Immutably update a node by ID */
44
- export declare function updateNode(root: GameObject, id: string, update: (node: GameObject) => GameObject): GameObject;
45
- /** Immutably insert a node under a parent ID, defaulting to the root when the parent is missing */
46
- export declare function insertNode(root: GameObject, node: GameObject, parentId?: string): GameObject;
47
- /** Immutably delete a node by ID */
48
- export declare function deleteNode(root: GameObject, id: string): GameObject | null;
49
- /** Deep clone a node with new IDs */
50
- export declare function cloneNode(node: GameObject): GameObject;
24
+ /** Compute the parent world matrix for a node using the normalized store data */
25
+ export declare function computeParentWorldMatrix(state: {
26
+ nodesById: Record<string, {
27
+ components?: Record<string, {
28
+ properties?: Record<string, any>;
29
+ } | undefined>;
30
+ }>;
31
+ parentIdById: Record<string, string | null>;
32
+ }, targetId: string): Matrix4;
51
33
  /** Recursively update all IDs in a node tree */
52
34
  export declare function regenerateIds(node: GameObject): GameObject;
53
- /** Get component data from a node */
54
- export declare function getComponent<T = any>(node: GameObject, type: string): T | undefined;
55
- export declare function updateNodeById(root: GameObject, id: string, updater: (node: GameObject) => GameObject): GameObject;
56
35
  /** Create a GameObject node for a 3D model file */
57
36
  export declare function createModelNode(filename: string, name?: string): GameObject;
58
37
  /** Create a GameObject node for an image as a textured plane */
@@ -9,14 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
11
11
  import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from 'three';
12
- /** Save a prefab as JSON file, showing a Save As dialog when supported */
12
+ /** Save scene JSON, showing a Save As dialog when supported */
13
13
  export function saveJson(data, filename) {
14
14
  return __awaiter(this, void 0, void 0, function* () {
15
15
  const json = JSON.stringify(data, null, 2);
16
16
  if ('showSaveFilePicker' in window) {
17
17
  try {
18
18
  const handle = yield window.showSaveFilePicker({
19
- suggestedName: `${filename || 'prefab'}.json`,
19
+ suggestedName: `${filename || 'scene'}.json`,
20
20
  types: [{ description: 'JSON', accept: { 'application/json': ['.json'] } }],
21
21
  });
22
22
  const writable = yield handle.createWritable();
@@ -32,11 +32,11 @@ export function saveJson(data, filename) {
32
32
  // Fallback for browsers without File System Access API
33
33
  const a = document.createElement('a');
34
34
  a.href = "data:text/json;charset=utf-8," + encodeURIComponent(json);
35
- a.download = `${filename || 'prefab'}.json`;
35
+ a.download = `${filename || 'scene'}.json`;
36
36
  a.click();
37
37
  });
38
38
  }
39
- /** Load a prefab from JSON file */
39
+ /** Load scene JSON from a file */
40
40
  export function loadJson() {
41
41
  return new Promise(resolve => {
42
42
  const input = document.createElement('input');
@@ -56,7 +56,7 @@ export function loadJson() {
56
56
  resolve(JSON.parse(text));
57
57
  }
58
58
  catch (err) {
59
- console.error('Error parsing prefab JSON:', err);
59
+ console.error('Error parsing scene JSON:', err);
60
60
  resolve(undefined);
61
61
  }
62
62
  };
@@ -66,44 +66,30 @@ export function loadJson() {
66
66
  });
67
67
  }
68
68
  /**
69
- * Export a Three.js scene or object to GLB format
70
- * @param sceneRoot - The Three.js Object3D to export
71
- * @param options - Export options
72
- * @returns Promise that resolves when export is complete
69
+ * Export a Three.js scene or object to GLB binary data
73
70
  */
74
- export function exportGLB(sceneRoot, options = {}) {
75
- const { filename = 'scene.glb', binary = true, onComplete, onError } = options;
71
+ export function exportGLBData(sceneRoot) {
76
72
  return new Promise((resolve, reject) => {
77
- const exporter = new GLTFExporter();
78
- exporter.parse(sceneRoot, (result) => {
79
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(result);
80
- resolve(result);
81
- // Trigger download if filename is provided
82
- if (filename) {
83
- const blob = new Blob([result], { type: binary ? 'application/octet-stream' : 'application/json' });
84
- const url = URL.createObjectURL(blob);
85
- const a = document.createElement('a');
86
- a.href = url;
87
- a.download = filename;
88
- a.click();
89
- URL.revokeObjectURL(url);
90
- }
91
- }, (error) => {
92
- console.error('Error exporting GLB:', error);
93
- onError === null || onError === void 0 ? void 0 : onError(error);
94
- reject(error);
95
- }, { binary });
73
+ new GLTFExporter().parse(sceneRoot, (result) => resolve(result), (error) => reject(error), { binary: true });
96
74
  });
97
75
  }
98
76
  /**
99
- * Export a Three.js scene to GLB and return the ArrayBuffer without downloading
100
- * @param sceneRoot - The Three.js Object3D to export
101
- * @returns Promise that resolves with the GLB data as ArrayBuffer
77
+ * Export a Three.js scene or object to GLB and trigger a download
102
78
  */
103
- export function exportGLBData(sceneRoot) {
104
- return __awaiter(this, void 0, void 0, function* () {
105
- const result = yield exportGLB(sceneRoot, { filename: '', binary: true });
106
- return result;
79
+ export function exportGLB(sceneRoot_1) {
80
+ return __awaiter(this, arguments, void 0, function* (sceneRoot, options = {}) {
81
+ const { filename = 'scene.glb' } = options;
82
+ const data = yield exportGLBData(sceneRoot);
83
+ if (filename) {
84
+ const blob = new Blob([data], { type: 'application/octet-stream' });
85
+ const url = URL.createObjectURL(blob);
86
+ const a = document.createElement('a');
87
+ a.href = url;
88
+ a.download = filename;
89
+ a.click();
90
+ URL.revokeObjectURL(url);
91
+ }
92
+ return data;
107
93
  });
108
94
  }
109
95
  export function focusCameraOnObject(object, camera, target, update) {
@@ -143,139 +129,27 @@ export function decompose(m) {
143
129
  scale: [s.x, s.y, s.z],
144
130
  };
145
131
  }
146
- function compose(node) {
147
- var _a, _b, _c, _d, _e;
148
- const t = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.transform) === null || _b === void 0 ? void 0 : _b.properties;
149
- const position = (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0];
150
- const rotation = (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0];
151
- const scale = (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1];
152
- return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
153
- }
154
- export function computeParentWorldMatrix(root, targetId) {
155
- const identity = new Matrix4();
156
- let result = null;
157
- const visit = (node, parent) => {
158
- var _a;
159
- if (node.id === targetId) {
160
- result = parent.clone();
161
- return;
162
- }
163
- const world = parent.clone().multiply(compose(node));
164
- (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(child => !result && visit(child, world));
165
- };
166
- visit(root, identity);
167
- return result !== null && result !== void 0 ? result : identity;
168
- }
169
- /** Find a node by ID in the tree */
170
- export function findNode(root, id) {
171
- var _a;
172
- if (root.id === id)
173
- return root;
174
- for (const child of (_a = root.children) !== null && _a !== void 0 ? _a : []) {
175
- const found = findNode(child, id);
176
- if (found)
177
- return found;
178
- }
179
- return null;
180
- }
181
- /** Find the parent of a node by ID */
182
- export function findParent(root, id) {
183
- var _a;
184
- for (const child of (_a = root.children) !== null && _a !== void 0 ? _a : []) {
185
- if (child.id === id)
186
- return root;
187
- const found = findParent(child, id);
188
- if (found)
189
- return found;
190
- }
191
- return null;
192
- }
193
- /** Find all nodes matching a predicate */
194
- export function findAll(root, predicate) {
195
- var _a;
196
- const results = [];
197
- if (predicate(root))
198
- results.push(root);
199
- for (const child of (_a = root.children) !== null && _a !== void 0 ? _a : []) {
200
- results.push(...findAll(child, predicate));
201
- }
202
- return results;
203
- }
204
- /** Find all nodes that have a specific component type */
205
- export function findByComponent(root, componentType) {
206
- return findAll(root, node => { var _a; return Object.values((_a = node.components) !== null && _a !== void 0 ? _a : {}).some(c => (c === null || c === void 0 ? void 0 : c.type) === componentType); });
207
- }
208
- /** Get a flattened list of all nodes */
209
- export function flatten(root) {
210
- return findAll(root, () => true);
211
- }
212
- /** Immutably update a node by ID */
213
- export function updateNode(root, id, update) {
214
- if (root.id === id)
215
- return update(root);
216
- if (!root.children)
217
- return root;
218
- return Object.assign(Object.assign({}, root), { children: root.children.map(child => updateNode(child, id, update)) });
219
- }
220
- /** Immutably insert a node under a parent ID, defaulting to the root when the parent is missing */
221
- export function insertNode(root, node, parentId) {
222
- var _a, _b;
223
- if (!parentId || parentId === root.id) {
224
- return Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), node] });
132
+ /** Compute the parent world matrix for a node using the normalized store data */
133
+ export function computeParentWorldMatrix(state, targetId) {
134
+ var _a, _b, _c, _d, _e, _f;
135
+ const parentWorld = new Matrix4();
136
+ const chain = [];
137
+ let currentId = state.parentIdById[targetId];
138
+ while (currentId) {
139
+ chain.unshift(currentId);
140
+ currentId = state.parentIdById[currentId];
225
141
  }
226
- const nextRoot = updateNode(root, parentId, parent => {
227
- var _a;
228
- return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []), node] }));
229
- });
230
- if (nextRoot === root) {
231
- return Object.assign(Object.assign({}, root), { children: [...((_b = root.children) !== null && _b !== void 0 ? _b : []), node] });
142
+ for (const nodeId of chain) {
143
+ const transform = (_c = (_b = (_a = state.nodesById[nodeId]) === null || _a === void 0 ? void 0 : _a.components) === null || _b === void 0 ? void 0 : _b.transform) === null || _c === void 0 ? void 0 : _c.properties;
144
+ parentWorld.multiply(new Matrix4().compose(new Vector3(...((_d = transform === null || transform === void 0 ? void 0 : transform.position) !== null && _d !== void 0 ? _d : [0, 0, 0])), new Quaternion().setFromEuler(new Euler(...((_e = transform === null || transform === void 0 ? void 0 : transform.rotation) !== null && _e !== void 0 ? _e : [0, 0, 0]))), new Vector3(...((_f = transform === null || transform === void 0 ? void 0 : transform.scale) !== null && _f !== void 0 ? _f : [1, 1, 1]))));
232
145
  }
233
- return nextRoot;
234
- }
235
- /** Immutably delete a node by ID */
236
- export function deleteNode(root, id) {
237
- if (root.id === id)
238
- return null;
239
- if (!root.children)
240
- return root;
241
- return Object.assign(Object.assign({}, root), { children: root.children
242
- .map(child => deleteNode(child, id))
243
- .filter((child) => child !== null) });
244
- }
245
- /** Deep clone a node with new IDs */
246
- export function cloneNode(node) {
247
- var _a, _b;
248
- return Object.assign(Object.assign({}, node), { id: crypto.randomUUID(), name: `${(_a = node.name) !== null && _a !== void 0 ? _a : node.id} Copy`, children: (_b = node.children) === null || _b === void 0 ? void 0 : _b.map(cloneNode) });
146
+ return parentWorld;
249
147
  }
250
148
  /** Recursively update all IDs in a node tree */
251
149
  export function regenerateIds(node) {
252
150
  var _a;
253
151
  return Object.assign(Object.assign({}, node), { id: crypto.randomUUID(), children: (_a = node.children) === null || _a === void 0 ? void 0 : _a.map(regenerateIds) });
254
152
  }
255
- /** Get component data from a node */
256
- export function getComponent(node, type) {
257
- var _a;
258
- const comp = Object.values((_a = node.components) !== null && _a !== void 0 ? _a : {}).find(c => (c === null || c === void 0 ? void 0 : c.type) === type);
259
- return comp === null || comp === void 0 ? void 0 : comp.properties;
260
- }
261
- export function updateNodeById(root, id, updater) {
262
- if (root.id === id) {
263
- return updater(root);
264
- }
265
- if (!root.children) {
266
- return root;
267
- }
268
- let didChange = false;
269
- const newChildren = root.children.map(child => {
270
- const updated = updateNodeById(child, id, updater);
271
- if (updated !== child)
272
- didChange = true;
273
- return updated;
274
- });
275
- if (!didChange)
276
- return root;
277
- return Object.assign(Object.assign({}, root), { children: newChildren });
278
- }
279
153
  /** Create a GameObject node for a 3D model file */
280
154
  export function createModelNode(filename, name) {
281
155
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.67",
4
- "description": "high performance 3D game engine for React",
3
+ "version": "0.0.69",
4
+ "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -48,6 +48,7 @@
48
48
  "vite": "^7.3.1"
49
49
  },
50
50
  "dependencies": {
51
- "react-error-boundary": "^6.1.0"
51
+ "react-error-boundary": "^6.1.0",
52
+ "zustand": "^5.0.12"
52
53
  }
53
54
  }
@@ -1,7 +0,0 @@
1
- interface EventSystemRef {
2
- fire: (eventType: string, data?: any) => void;
3
- }
4
- declare const EventSystemHook: import("react").ForwardRefExoticComponent<{
5
- entityId: string;
6
- } & import("react").RefAttributes<EventSystemRef>>;
7
- export default EventSystemHook;
@@ -1,23 +0,0 @@
1
- import { useRef, useImperativeHandle, forwardRef, useCallback } from 'react';
2
- const EventSystemHook = forwardRef(({ entityId }, ref) => {
3
- const targetRef = useRef(typeof window !== 'undefined' ? window : null);
4
- // Fire a global JS event with entityId as source
5
- const fire = useCallback((eventType, data) => {
6
- if (!targetRef.current)
7
- return;
8
- const event = new CustomEvent(eventType, {
9
- detail: {
10
- entityId,
11
- data,
12
- },
13
- });
14
- targetRef.current.dispatchEvent(event);
15
- }, [entityId]);
16
- // Expose ref API
17
- useImperativeHandle(ref, () => ({
18
- fire,
19
- }), [fire]);
20
- return null;
21
- });
22
- EventSystemHook.displayName = 'EventSystemHook';
23
- export default EventSystemHook;