react-three-game 0.0.34 → 0.0.36

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.
@@ -1,14 +1,13 @@
1
1
  import React from "react";
2
2
  import { Object3D, Group } from "three";
3
+ import { PhysicsProps } from "./components/PhysicsComponent";
3
4
  export type InstanceData = {
4
5
  id: string;
5
6
  position: [number, number, number];
6
7
  rotation: [number, number, number];
7
8
  scale: [number, number, number];
8
9
  meshPath: string;
9
- physics?: {
10
- type: 'dynamic' | 'fixed';
11
- };
10
+ physics?: PhysicsProps | undefined;
12
11
  };
13
12
  export declare function GameInstanceProvider({ children, models, onSelect, registerRef, selectedId, editMode }: {
14
13
  children: React.ReactNode;
@@ -26,7 +25,5 @@ export declare const GameInstance: React.ForwardRefExoticComponent<{
26
25
  position: [number, number, number];
27
26
  rotation: [number, number, number];
28
27
  scale: [number, number, number];
29
- physics?: {
30
- type: "dynamic" | "fixed";
31
- };
28
+ physics?: PhysicsProps | undefined;
32
29
  } & React.RefAttributes<Group<import("three").Object3DEventMap>>>;
@@ -1,5 +1,5 @@
1
- import { Group } from "three";
2
- import { Prefab } from "./types";
1
+ import { Group, Matrix4, Object3D, Texture } from "three";
2
+ import { Prefab, GameObject as GameObjectType } from "./types";
3
3
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
4
4
  editMode?: boolean;
5
5
  data: Prefab;
@@ -9,4 +9,15 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
9
9
  transformMode?: "translate" | "rotate" | "scale";
10
10
  basePath?: string;
11
11
  } & import("react").RefAttributes<Group<import("three").Object3DEventMap>>>;
12
+ export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
13
+ interface RendererProps {
14
+ gameObject: GameObjectType;
15
+ selectedId?: string | null;
16
+ onSelect?: (id: string) => void;
17
+ registerRef: (id: string, obj: Object3D | null) => void;
18
+ loadedModels: Record<string, Object3D>;
19
+ loadedTextures: Record<string, Texture>;
20
+ editMode?: boolean;
21
+ parentMatrix?: Matrix4;
22
+ }
12
23
  export default PrefabRoot;
@@ -10,19 +10,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  import { MapControls, TransformControls, useHelper } from "@react-three/drei";
13
- import { useState, useRef, useEffect, forwardRef, useCallback } from "react";
14
- import { Vector3, Euler, Quaternion, SRGBColorSpace, TextureLoader, Matrix4, BoxHelper } from "three";
13
+ import { forwardRef, useCallback, useEffect, useRef, useState, } from "react";
14
+ import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
15
15
  import { getComponent, registerComponent } from "./components/ComponentRegistry";
16
+ import components from "./components";
16
17
  import { loadModel } from "../dragdrop/modelLoader";
17
18
  import { GameInstance, GameInstanceProvider } from "./InstanceProvider";
18
19
  import { updateNode } from "./utils";
19
- import components from './components/';
20
- // Register all components
20
+ /* -------------------------------------------------- */
21
+ /* Setup */
22
+ /* -------------------------------------------------- */
21
23
  components.forEach(registerComponent);
24
+ const IDENTITY = new Matrix4();
25
+ /* -------------------------------------------------- */
26
+ /* PrefabRoot */
27
+ /* -------------------------------------------------- */
22
28
  export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, basePath = "" }, ref) => {
23
- const [loadedModels, setLoadedModels] = useState({});
24
- const [loadedTextures, setLoadedTextures] = useState({});
25
- const loadingRefs = useRef(new Set());
29
+ const [models, setModels] = useState({});
30
+ const [textures, setTextures] = useState({});
31
+ const loading = useRef(new Set());
26
32
  const objectRefs = useRef({});
27
33
  const [selectedObject, setSelectedObject] = useState(null);
28
34
  const registerRef = useCallback((id, obj) => {
@@ -30,166 +36,193 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
30
36
  if (id === selectedId)
31
37
  setSelectedObject(obj);
32
38
  }, [selectedId]);
39
+ // Suppress TransformControls scene graph warnings during transitions
33
40
  useEffect(() => {
34
- setSelectedObject(selectedId ? objectRefs.current[selectedId] || null : null);
41
+ const originalError = console.error;
42
+ console.error = (...args) => {
43
+ if (typeof args[0] === 'string' && args[0].includes('TransformControls') && args[0].includes('scene graph')) {
44
+ return; // Suppress this specific error
45
+ }
46
+ originalError.apply(console, args);
47
+ };
48
+ return () => {
49
+ console.error = originalError;
50
+ };
51
+ }, []);
52
+ useEffect(() => {
53
+ var _a;
54
+ setSelectedObject(selectedId ? (_a = objectRefs.current[selectedId]) !== null && _a !== void 0 ? _a : null : null);
35
55
  }, [selectedId]);
56
+ /* ---------------- Transform writeback ---------------- */
36
57
  const onTransformChange = () => {
37
58
  if (!selectedId || !onPrefabChange)
38
59
  return;
39
60
  const obj = objectRefs.current[selectedId];
40
61
  if (!obj)
41
62
  return;
42
- // 1. Get world matrix from the actual Three object
43
- const worldMatrix = obj.matrixWorld.clone();
44
- // 2. Compute parent world matrix from the prefab tree
45
63
  const parentWorld = computeParentWorldMatrix(data.root, selectedId);
46
- const parentInv = parentWorld.clone().invert();
47
- // 3. Convert world -> local
48
- const localMatrix = new Matrix4().multiplyMatrices(parentInv, worldMatrix);
49
- const lp = new Vector3();
50
- const lq = new Quaternion();
51
- const ls = new Vector3();
52
- localMatrix.decompose(lp, lq, ls);
53
- const le = new Euler().setFromQuaternion(lq);
54
- // 4. Write back LOCAL transform into the prefab node
55
- const newRoot = updateNode(data.root, selectedId, (node) => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
64
+ const local = parentWorld.clone().invert().multiply(obj.matrixWorld);
65
+ const { position, rotation, scale } = decompose(local);
66
+ const root = updateNode(data.root, selectedId, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
56
67
  type: "Transform",
57
- properties: {
58
- position: [lp.x, lp.y, lp.z],
59
- rotation: [le.x, le.y, le.z],
60
- scale: [ls.x, ls.y, ls.z],
61
- },
68
+ properties: { position, rotation, scale },
62
69
  } }) })));
63
- onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
70
+ onPrefabChange(Object.assign(Object.assign({}, data), { root }));
64
71
  };
72
+ /* ---------------- Asset loading ---------------- */
65
73
  useEffect(() => {
66
- const loadAssets = () => __awaiter(void 0, void 0, void 0, function* () {
67
- const modelsToLoad = new Set();
68
- const texturesToLoad = new Set();
69
- const traverse = (node) => {
70
- var _a, _b, _c, _d, _e, _f, _g;
71
- if (!node)
72
- return;
73
- if ((_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename) {
74
- modelsToLoad.add(node.components.model.properties.filename);
75
- }
76
- if ((_f = (_e = (_d = node.components) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? void 0 : _f.texture) {
77
- texturesToLoad.add(node.components.material.properties.texture);
78
- }
79
- (_g = node.children) === null || _g === void 0 ? void 0 : _g.forEach(traverse);
80
- };
81
- traverse(data.root);
82
- for (const filename of modelsToLoad) {
83
- if (!loadedModels[filename] && !loadingRefs.current.has(filename)) {
84
- loadingRefs.current.add(filename);
85
- // Load model directly from public root, prepend "/" if not present
86
- const modelPath = filename.startsWith('/') ? filename : `/${filename}`;
87
- const result = yield loadModel(modelPath);
88
- if (result.success && result.model) {
89
- setLoadedModels(prev => (Object.assign(Object.assign({}, prev), { [filename]: result.model })));
90
- }
91
- }
92
- }
93
- const textureLoader = new TextureLoader();
94
- for (const filename of texturesToLoad) {
95
- if (!loadedTextures[filename] && !loadingRefs.current.has(filename)) {
96
- loadingRefs.current.add(filename);
97
- // Load texture directly from public root, prepend "/" if not present
98
- const texturePath = filename.startsWith('/') ? filename : `/${filename}`;
99
- textureLoader.load(texturePath, (texture) => {
100
- texture.colorSpace = SRGBColorSpace;
101
- setLoadedTextures(prev => (Object.assign(Object.assign({}, prev), { [filename]: texture })));
102
- });
103
- }
104
- }
74
+ const modelsToLoad = new Set();
75
+ const texturesToLoad = new Set();
76
+ walk(data.root, node => {
77
+ var _a, _b, _c, _d, _e, _f;
78
+ ((_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename) &&
79
+ modelsToLoad.add(node.components.model.properties.filename);
80
+ ((_f = (_e = (_d = node.components) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? void 0 : _f.texture) &&
81
+ texturesToLoad.add(node.components.material.properties.texture);
105
82
  });
106
- loadAssets();
107
- }, [data, loadedModels, loadedTextures]);
108
- return _jsxs("group", { ref: ref, children: [_jsx(GameInstanceProvider, { models: loadedModels, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, selectedId: selectedId, editMode: editMode, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: new Matrix4() }) }), editMode && _jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedId && selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange }))] })] });
83
+ modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
84
+ if (models[file] || loading.current.has(file))
85
+ return;
86
+ loading.current.add(file);
87
+ const path = file.startsWith("/")
88
+ ? `${basePath}${file}`
89
+ : `${basePath}/${file}`;
90
+ const res = yield loadModel(path);
91
+ res.success && res.model &&
92
+ setModels(m => (Object.assign(Object.assign({}, m), { [file]: res.model })));
93
+ }));
94
+ const loader = new TextureLoader();
95
+ texturesToLoad.forEach(file => {
96
+ if (textures[file] || loading.current.has(file))
97
+ return;
98
+ loading.current.add(file);
99
+ const path = file.startsWith("/")
100
+ ? `${basePath}${file}`
101
+ : `${basePath}/${file}`;
102
+ loader.load(path, tex => {
103
+ tex.colorSpace = SRGBColorSpace;
104
+ setTextures(t => (Object.assign(Object.assign({}, t), { [file]: tex })));
105
+ });
106
+ });
107
+ }, [data, models, textures]);
108
+ /* ---------------- Render ---------------- */
109
+ return (_jsxs("group", { ref: ref, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange }))] }))] }));
109
110
  });
110
- function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = new Matrix4(), }) {
111
- var _a, _b, _c, _d, _e, _f;
112
- // Early return if gameObject is null or undefined
113
- if (!gameObject)
114
- return null;
115
- if (gameObject.disabled === true || gameObject.hidden === true)
111
+ /* -------------------------------------------------- */
112
+ /* Renderer Switch */
113
+ /* -------------------------------------------------- */
114
+ export function GameObjectRenderer(props) {
115
+ var _a, _b, _c;
116
+ const node = props.gameObject;
117
+ if (!node || node.hidden || node.disabled)
116
118
  return null;
117
- // Build context object for passing to helper functions
118
- const ctx = { gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode };
119
- // --- 1. Compute transforms (local + world) ---
120
- const transformProps = getNodeTransformProps(gameObject);
121
- const localMatrix = new Matrix4().compose(new Vector3(...transformProps.position), new Quaternion().setFromEuler(new Euler(...transformProps.rotation)), new Vector3(...transformProps.scale));
122
- const worldMatrix = parentMatrix.clone().multiply(localMatrix);
123
- // --- 2. Handle selection interaction (edit mode only) ---
119
+ return ((_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced)
120
+ ? _jsx(InstancedNode, Object.assign({}, props))
121
+ : _jsx(StandardNode, Object.assign({}, props));
122
+ }
123
+ /* -------------------------------------------------- */
124
+ /* InstancedNode (terminal) */
125
+ /* -------------------------------------------------- */
126
+ function isPhysicsProps(v) {
127
+ return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic";
128
+ }
129
+ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode }) {
130
+ var _a, _b, _c, _d, _e, _f, _g;
131
+ const world = parentMatrix.clone().multiply(compose(gameObject));
132
+ const { position, rotation, scale } = decompose(world);
133
+ const physicsProps = isPhysicsProps((_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics) === null || _b === void 0 ? void 0 : _b.properties)
134
+ ? (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.physics) === null || _d === void 0 ? void 0 : _d.properties
135
+ : undefined;
136
+ return (_jsx(GameInstance, { id: gameObject.id, modelUrl: (_g = (_f = (_e = gameObject.components) === null || _e === void 0 ? void 0 : _e.model) === null || _f === void 0 ? void 0 : _f.properties) === null || _g === void 0 ? void 0 : _g.filename, position: position, rotation: rotation, scale: scale, physics: editMode ? undefined : physicsProps }));
137
+ }
138
+ /* -------------------------------------------------- */
139
+ /* StandardNode */
140
+ /* -------------------------------------------------- */
141
+ function StandardNode({ gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
142
+ var _a, _b, _c;
143
+ const groupRef = useRef(null);
124
144
  const clickValid = useRef(false);
125
- const handlePointerDown = (e) => {
145
+ const isSelected = selectedId === gameObject.id;
146
+ const helperRef = groupRef;
147
+ useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
148
+ useEffect(() => {
149
+ registerRef(gameObject.id, groupRef.current);
150
+ return () => registerRef(gameObject.id, null);
151
+ }, [gameObject.id, registerRef]);
152
+ const world = parentMatrix.clone().multiply(compose(gameObject));
153
+ const onDown = (e) => {
126
154
  e.stopPropagation();
127
155
  clickValid.current = true;
128
156
  };
129
- const handlePointerMove = () => {
130
- if (clickValid.current)
131
- clickValid.current = false;
132
- };
133
- const handlePointerUp = (e) => {
157
+ const onUp = (e) => {
134
158
  if (clickValid.current) {
135
159
  e.stopPropagation();
136
160
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
137
161
  }
138
162
  clickValid.current = false;
139
163
  };
140
- // --- 3. If instanced model, short-circuit to GameInstance (terminal node) ---
141
- const isInstanced = !!((_c = (_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced);
142
- if (isInstanced) {
143
- return renderInstancedNode(gameObject, worldMatrix, ctx);
144
- }
145
- // --- 4. Render core content using component system ---
146
- const core = renderCoreNode(gameObject, ctx, parentMatrix);
147
- // --- 5. Render children recursively (always relative transforms) ---
148
- const children = ((_d = gameObject.children) !== null && _d !== void 0 ? _d : []).map((child) => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: worldMatrix }, child.id)));
149
- // --- 6. Inner content group with full transform and selection helper ---
150
- const groupRef = useRef(null);
151
- const isSelected = selectedId === gameObject.id;
152
- // Show BoxHelper when selected in edit mode
153
- useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
154
- useEffect(() => {
155
- registerRef(gameObject.id, groupRef.current);
156
- }, [gameObject.id, registerRef]);
157
- const innerGroup = (_jsxs("group", { ref: groupRef, position: transformProps.position, rotation: transformProps.rotation, scale: transformProps.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [core, children] }));
158
- // --- 7. Wrap with physics if needed (RigidBody as outer parent, no transform) ---
159
- const physics = (_e = gameObject.components) === null || _e === void 0 ? void 0 : _e.physics;
160
- // Determine if model is safe/ready for physics. No model => safe; model => only safe once loaded.
161
- const modelReady = !((_f = gameObject.components) === null || _f === void 0 ? void 0 : _f.model) ||
162
- !!loadedModels[gameObject.components.model.properties.filename];
163
- if (physics && !editMode && modelReady) {
164
- const physicsDef = getComponent('Physics');
165
- if (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) {
166
- return (_jsx(physicsDef.View, { properties: physics.properties, children: innerGroup }));
167
- }
164
+ const inner = (_jsxs("group", Object.assign({ ref: groupRef }, getNodeTransformProps(gameObject), { onPointerDown: onDown, onPointerMove: () => (clickValid.current = false), onPointerUp: onUp, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_a = gameObject.children) === null || _a === void 0 ? void 0 : _a.map(child => (_jsx(GameObjectRenderer, { child, gameObject: child, selectedId: selectedId, onSelect: onSelect, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] })));
165
+ const physics = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.physics;
166
+ const ready = !((_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) ||
167
+ loadedModels[gameObject.components.model.properties.filename];
168
+ if (physics && !editMode && ready) {
169
+ const def = getComponent("Physics");
170
+ return (def === null || def === void 0 ? void 0 : def.View)
171
+ ? _jsx(def.View, { properties: physics.properties, children: inner })
172
+ : inner;
168
173
  }
169
- return innerGroup;
174
+ return inner;
175
+ }
176
+ function walk(node, fn) {
177
+ var _a;
178
+ fn(node);
179
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => walk(c, fn));
180
+ }
181
+ function compose(node) {
182
+ const { position, rotation, scale } = getNodeTransformProps(node);
183
+ return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
184
+ }
185
+ function decompose(m) {
186
+ const p = new Vector3(), q = new Quaternion(), s = new Vector3();
187
+ m.decompose(p, q, s);
188
+ const e = new Euler().setFromQuaternion(q);
189
+ return {
190
+ position: [p.x, p.y, p.z],
191
+ rotation: [e.x, e.y, e.z],
192
+ scale: [s.x, s.y, s.z],
193
+ };
170
194
  }
171
- // Helper: render an instanced GameInstance (terminal node)
172
- function renderInstancedNode(gameObject, worldMatrix, ctx) {
173
- var _a, _b, _c, _d;
174
- const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
175
- const wp = new Vector3();
176
- const wq = new Quaternion();
177
- const ws = new Vector3();
178
- worldMatrix.decompose(wp, wq, ws);
179
- const we = new Euler().setFromQuaternion(wq);
180
- const modelUrl = (_d = (_c = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.properties) === null || _d === void 0 ? void 0 : _d.filename;
181
- return (_jsx(GameInstance, { id: gameObject.id, modelUrl: modelUrl, position: [wp.x, wp.y, wp.z], rotation: [we.x, we.y, we.z], scale: [ws.x, ws.y, ws.z], physics: ctx.editMode ? undefined : physics === null || physics === void 0 ? void 0 : physics.properties }));
195
+ function getNodeTransformProps(node) {
196
+ var _a, _b, _c, _d, _e;
197
+ 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;
198
+ return {
199
+ position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
200
+ rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
201
+ scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
202
+ };
203
+ }
204
+ function computeParentWorldMatrix(root, targetId) {
205
+ let result = null;
206
+ const visit = (node, parent) => {
207
+ var _a;
208
+ if (node.id === targetId) {
209
+ result = parent.clone();
210
+ return;
211
+ }
212
+ const world = parent.clone().multiply(compose(node));
213
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => !result && visit(c, world));
214
+ };
215
+ visit(root, IDENTITY);
216
+ return result !== null && result !== void 0 ? result : IDENTITY;
182
217
  }
183
- // Helper: render main content for a non-instanced node using the component system
184
218
  function renderCoreNode(gameObject, ctx, parentMatrix) {
185
219
  var _a, _b, _c;
186
220
  const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
187
221
  const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
188
222
  const model = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model;
189
- const geometryDef = geometry ? getComponent('Geometry') : undefined;
190
- const materialDef = material ? getComponent('Material') : undefined;
191
- const modelDef = model ? getComponent('Model') : undefined;
192
- // Context props for all component Views
223
+ const geometryDef = geometry && getComponent("Geometry");
224
+ const materialDef = material && getComponent("Material");
225
+ const modelDef = model && getComponent("Model");
193
226
  const contextProps = {
194
227
  loadedModels: ctx.loadedModels,
195
228
  loadedTextures: ctx.loadedTextures,
@@ -197,80 +230,36 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
197
230
  parentMatrix,
198
231
  registerRef: ctx.registerRef,
199
232
  };
200
- // Collect wrapper and leaf components (excluding transform/physics which are handled separately)
201
- const wrapperComponents = [];
202
- const leafComponents = [];
233
+ const wrappers = [];
234
+ const leaves = [];
203
235
  if (gameObject.components) {
204
236
  Object.entries(gameObject.components)
205
- .filter(([key]) => !['geometry', 'material', 'model', 'transform', 'physics'].includes(key))
237
+ .filter(([k]) => !["geometry", "material", "model", "transform", "physics"].includes(k))
206
238
  .forEach(([key, comp]) => {
207
- if (!comp || !comp.type)
239
+ if (!(comp === null || comp === void 0 ? void 0 : comp.type))
208
240
  return;
209
241
  const def = getComponent(comp.type);
210
- if (!def || !def.View)
242
+ if (!(def === null || def === void 0 ? void 0 : def.View))
211
243
  return;
212
- // Components that accept children are wrappers, others are leaves
213
- const viewString = def.View.toString();
214
- if (viewString.includes('children')) {
215
- wrapperComponents.push({ key, View: def.View, properties: comp.properties });
244
+ // crude but works with your existing component API
245
+ if (def.View.toString().includes("children")) {
246
+ wrappers.push({ key, View: def.View, properties: comp.properties });
216
247
  }
217
248
  else {
218
- leafComponents.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
249
+ leaves.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
219
250
  }
220
251
  });
221
252
  }
222
- // Build core content based on what components exist
223
- let coreContent;
224
- // Priority: Model > Geometry + Material > Empty
225
- if (model && modelDef && modelDef.View) {
226
- // Model component wraps its children (including material override)
227
- coreContent = (_jsxs(modelDef.View, Object.assign({ properties: model.properties }, contextProps, { children: [material && materialDef && materialDef.View && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leafComponents] })));
253
+ let core;
254
+ if (model && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)) {
255
+ core = (_jsxs(modelDef.View, Object.assign({ properties: model.properties }, contextProps, { children: [material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leaves] })));
228
256
  }
229
- else if (geometry && geometryDef && geometryDef.View) {
230
- // Geometry + Material = mesh
231
- coreContent = (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && materialDef && materialDef.View && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leafComponents] }));
257
+ else if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
258
+ core = (_jsxs("mesh", { castShadow: true, receiveShadow: true, children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps)), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, Object.assign({ properties: material.properties }, contextProps), "material")), leaves] }));
232
259
  }
233
260
  else {
234
- // No visual component - just render leaves
235
- coreContent = _jsx(_Fragment, { children: leafComponents });
261
+ core = _jsx(_Fragment, { children: leaves });
236
262
  }
237
- // Wrap core content with wrapper components (in order)
238
- return wrapperComponents.reduce((content, { key, View, properties }) => {
239
- return _jsx(View, Object.assign({ properties: properties }, contextProps, { children: content }), key);
240
- }, coreContent);
263
+ return wrappers.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), core);
241
264
  }
242
265
  export default PrefabRoot;
243
- function getNodeTransformProps(node) {
244
- var _a, _b, _c, _d, _e;
245
- 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;
246
- return {
247
- position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
248
- rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
249
- scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
250
- };
251
- }
252
- function computeParentWorldMatrix(root, targetId) {
253
- var _a;
254
- const identity = new Matrix4();
255
- function traverse(node, parentWorld) {
256
- if (node.id === targetId) {
257
- // parentWorld is what we want
258
- return parentWorld.clone();
259
- }
260
- const { position, rotation, scale } = getNodeTransformProps(node);
261
- const localPos = new Vector3(...position);
262
- const localRot = new Euler(...rotation);
263
- const localScale = new Vector3(...scale);
264
- const localMat = new Matrix4().compose(localPos, new Quaternion().setFromEuler(localRot), localScale);
265
- const worldMat = parentWorld.clone().multiply(localMat);
266
- if (node.children) {
267
- for (const child of node.children) {
268
- const res = traverse(child, worldMat);
269
- if (res)
270
- return res;
271
- }
272
- }
273
- return null;
274
- }
275
- return (_a = traverse(root, identity)) !== null && _a !== void 0 ? _a : identity;
276
- }
@@ -1,3 +1,10 @@
1
1
  import { Component } from "./ComponentRegistry";
2
+ export interface PhysicsProps {
3
+ type: "fixed" | "dynamic";
4
+ collider?: string;
5
+ mass?: number;
6
+ restitution?: number;
7
+ friction?: number;
8
+ }
2
9
  declare const PhysicsComponent: Component;
3
10
  export default PhysicsComponent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.34",
3
+ "version": "0.0.36",
4
4
  "description": "Batteries included React Three Fiber game engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useMemo, useRef, useState, useEffect,
2
2
  import { Merged, useHelper } from '@react-three/drei';
3
3
  import { InstancedRigidBodies } from "@react-three/rapier";
4
4
  import { Mesh, Matrix4, Object3D, Group, Vector3, Quaternion, Euler, InstancedMesh, BoxHelper } from "three";
5
+ import { PhysicsProps } from "./components/PhysicsComponent";
5
6
 
6
7
  // --- Types ---
7
8
  export type InstanceData = {
@@ -10,7 +11,7 @@ export type InstanceData = {
10
11
  rotation: [number, number, number];
11
12
  scale: [number, number, number];
12
13
  meshPath: string;
13
- physics?: { type: 'dynamic' | 'fixed' };
14
+ physics?: PhysicsProps | undefined;
14
15
  };
15
16
 
16
17
  // Helper functions for comparison
@@ -374,7 +375,7 @@ export const GameInstance = React.forwardRef<Group, {
374
375
  position: [number, number, number];
375
376
  rotation: [number, number, number];
376
377
  scale: [number, number, number];
377
- physics?: { type: 'dynamic' | 'fixed' };
378
+ physics?: PhysicsProps | undefined;
378
379
  }>(({
379
380
  id,
380
381
  modelUrl,