react-three-game 0.0.34 → 0.0.35

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) => {
@@ -31,165 +37,179 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
31
37
  setSelectedObject(obj);
32
38
  }, [selectedId]);
33
39
  useEffect(() => {
34
- setSelectedObject(selectedId ? objectRefs.current[selectedId] || null : null);
40
+ var _a;
41
+ setSelectedObject(selectedId ? (_a = objectRefs.current[selectedId]) !== null && _a !== void 0 ? _a : null : null);
35
42
  }, [selectedId]);
43
+ /* ---------------- Transform writeback ---------------- */
36
44
  const onTransformChange = () => {
37
45
  if (!selectedId || !onPrefabChange)
38
46
  return;
39
47
  const obj = objectRefs.current[selectedId];
40
48
  if (!obj)
41
49
  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
50
  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: {
51
+ const local = parentWorld.clone().invert().multiply(obj.matrixWorld);
52
+ const { position, rotation, scale } = decompose(local);
53
+ const root = updateNode(data.root, selectedId, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
56
54
  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
- },
55
+ properties: { position, rotation, scale },
62
56
  } }) })));
63
- onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
57
+ onPrefabChange(Object.assign(Object.assign({}, data), { root }));
64
58
  };
59
+ /* ---------------- Asset loading ---------------- */
65
60
  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
- }
61
+ const modelsToLoad = new Set();
62
+ const texturesToLoad = new Set();
63
+ walk(data.root, node => {
64
+ var _a, _b, _c, _d, _e, _f;
65
+ ((_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) &&
66
+ modelsToLoad.add(node.components.model.properties.filename);
67
+ ((_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) &&
68
+ texturesToLoad.add(node.components.material.properties.texture);
69
+ });
70
+ modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
71
+ if (models[file] || loading.current.has(file))
72
+ return;
73
+ loading.current.add(file);
74
+ const path = file.startsWith("/")
75
+ ? `${basePath}${file}`
76
+ : `${basePath}/${file}`;
77
+ const res = yield loadModel(path);
78
+ res.success && res.model &&
79
+ setModels(m => (Object.assign(Object.assign({}, m), { [file]: res.model })));
80
+ }));
81
+ const loader = new TextureLoader();
82
+ texturesToLoad.forEach(file => {
83
+ if (textures[file] || loading.current.has(file))
84
+ return;
85
+ loading.current.add(file);
86
+ const path = file.startsWith("/")
87
+ ? `${basePath}${file}`
88
+ : `${basePath}/${file}`;
89
+ loader.load(path, tex => {
90
+ tex.colorSpace = SRGBColorSpace;
91
+ setTextures(t => (Object.assign(Object.assign({}, t), { [file]: tex })));
92
+ });
105
93
  });
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 }))] })] });
94
+ }, [data, models, textures]);
95
+ /* ---------------- Render ---------------- */
96
+ 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
97
  });
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)
98
+ /* -------------------------------------------------- */
99
+ /* Renderer Switch */
100
+ /* -------------------------------------------------- */
101
+ export function GameObjectRenderer(props) {
102
+ var _a, _b, _c;
103
+ const node = props.gameObject;
104
+ if (!node || node.hidden || node.disabled)
116
105
  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) ---
106
+ 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)
107
+ ? _jsx(InstancedNode, Object.assign({}, props))
108
+ : _jsx(StandardNode, Object.assign({}, props));
109
+ }
110
+ /* -------------------------------------------------- */
111
+ /* InstancedNode (terminal) */
112
+ /* -------------------------------------------------- */
113
+ function isPhysicsProps(v) {
114
+ return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic";
115
+ }
116
+ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode }) {
117
+ var _a, _b, _c, _d, _e, _f, _g;
118
+ const world = parentMatrix.clone().multiply(compose(gameObject));
119
+ const { position, rotation, scale } = decompose(world);
120
+ const physicsProps = isPhysicsProps((_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics) === null || _b === void 0 ? void 0 : _b.properties)
121
+ ? (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.physics) === null || _d === void 0 ? void 0 : _d.properties
122
+ : undefined;
123
+ 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 }));
124
+ }
125
+ /* -------------------------------------------------- */
126
+ /* StandardNode */
127
+ /* -------------------------------------------------- */
128
+ function StandardNode({ gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
129
+ var _a, _b, _c;
130
+ const groupRef = useRef(null);
124
131
  const clickValid = useRef(false);
125
- const handlePointerDown = (e) => {
132
+ const isSelected = selectedId === gameObject.id;
133
+ const helperRef = groupRef;
134
+ useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
135
+ useEffect(() => {
136
+ registerRef(gameObject.id, groupRef.current);
137
+ return () => registerRef(gameObject.id, null);
138
+ }, [gameObject.id, registerRef]);
139
+ const world = parentMatrix.clone().multiply(compose(gameObject));
140
+ const onDown = (e) => {
126
141
  e.stopPropagation();
127
142
  clickValid.current = true;
128
143
  };
129
- const handlePointerMove = () => {
130
- if (clickValid.current)
131
- clickValid.current = false;
132
- };
133
- const handlePointerUp = (e) => {
144
+ const onUp = (e) => {
134
145
  if (clickValid.current) {
135
146
  e.stopPropagation();
136
147
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
137
148
  }
138
149
  clickValid.current = false;
139
150
  };
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
- }
151
+ 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)))] })));
152
+ const physics = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.physics;
153
+ const ready = !((_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) ||
154
+ loadedModels[gameObject.components.model.properties.filename];
155
+ if (physics && !editMode && ready) {
156
+ const def = getComponent("Physics");
157
+ return (def === null || def === void 0 ? void 0 : def.View)
158
+ ? _jsx(def.View, { properties: physics.properties, children: inner })
159
+ : inner;
168
160
  }
169
- return innerGroup;
161
+ return inner;
162
+ }
163
+ function walk(node, fn) {
164
+ var _a;
165
+ fn(node);
166
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => walk(c, fn));
167
+ }
168
+ function compose(node) {
169
+ const { position, rotation, scale } = getNodeTransformProps(node);
170
+ return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
171
+ }
172
+ function decompose(m) {
173
+ const p = new Vector3(), q = new Quaternion(), s = new Vector3();
174
+ m.decompose(p, q, s);
175
+ const e = new Euler().setFromQuaternion(q);
176
+ return {
177
+ position: [p.x, p.y, p.z],
178
+ rotation: [e.x, e.y, e.z],
179
+ scale: [s.x, s.y, s.z],
180
+ };
181
+ }
182
+ function getNodeTransformProps(node) {
183
+ var _a, _b, _c, _d, _e;
184
+ 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;
185
+ return {
186
+ position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
187
+ rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
188
+ scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
189
+ };
170
190
  }
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 }));
191
+ function computeParentWorldMatrix(root, targetId) {
192
+ let result = null;
193
+ const visit = (node, parent) => {
194
+ var _a;
195
+ if (node.id === targetId) {
196
+ result = parent.clone();
197
+ return;
198
+ }
199
+ const world = parent.clone().multiply(compose(node));
200
+ (_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => !result && visit(c, world));
201
+ };
202
+ visit(root, IDENTITY);
203
+ return result !== null && result !== void 0 ? result : IDENTITY;
182
204
  }
183
- // Helper: render main content for a non-instanced node using the component system
184
205
  function renderCoreNode(gameObject, ctx, parentMatrix) {
185
206
  var _a, _b, _c;
186
207
  const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
187
208
  const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
188
209
  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
210
+ const geometryDef = geometry && getComponent("Geometry");
211
+ const materialDef = material && getComponent("Material");
212
+ const modelDef = model && getComponent("Model");
193
213
  const contextProps = {
194
214
  loadedModels: ctx.loadedModels,
195
215
  loadedTextures: ctx.loadedTextures,
@@ -197,80 +217,36 @@ function renderCoreNode(gameObject, ctx, parentMatrix) {
197
217
  parentMatrix,
198
218
  registerRef: ctx.registerRef,
199
219
  };
200
- // Collect wrapper and leaf components (excluding transform/physics which are handled separately)
201
- const wrapperComponents = [];
202
- const leafComponents = [];
220
+ const wrappers = [];
221
+ const leaves = [];
203
222
  if (gameObject.components) {
204
223
  Object.entries(gameObject.components)
205
- .filter(([key]) => !['geometry', 'material', 'model', 'transform', 'physics'].includes(key))
224
+ .filter(([k]) => !["geometry", "material", "model", "transform", "physics"].includes(k))
206
225
  .forEach(([key, comp]) => {
207
- if (!comp || !comp.type)
226
+ if (!(comp === null || comp === void 0 ? void 0 : comp.type))
208
227
  return;
209
228
  const def = getComponent(comp.type);
210
- if (!def || !def.View)
229
+ if (!(def === null || def === void 0 ? void 0 : def.View))
211
230
  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 });
231
+ // crude but works with your existing component API
232
+ if (def.View.toString().includes("children")) {
233
+ wrappers.push({ key, View: def.View, properties: comp.properties });
216
234
  }
217
235
  else {
218
- leafComponents.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
236
+ leaves.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
219
237
  }
220
238
  });
221
239
  }
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] })));
240
+ let core;
241
+ if (model && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)) {
242
+ 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
243
  }
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] }));
244
+ else if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
245
+ 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
246
  }
233
247
  else {
234
- // No visual component - just render leaves
235
- coreContent = _jsx(_Fragment, { children: leafComponents });
248
+ core = _jsx(_Fragment, { children: leaves });
236
249
  }
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);
250
+ return wrappers.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), core);
241
251
  }
242
252
  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.35",
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,