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.
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +3 -6
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +13 -2
- package/dist/tools/prefabeditor/PrefabRoot.js +182 -193
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +7 -0
- package/package.json +1 -1
- package/src/tools/prefabeditor/InstanceProvider.tsx +3 -2
- package/src/tools/prefabeditor/PrefabRoot.tsx +312 -303
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +8 -0
|
@@ -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 {
|
|
14
|
-
import {
|
|
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
|
-
|
|
20
|
-
|
|
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 [
|
|
24
|
-
const [
|
|
25
|
-
const
|
|
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
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
const
|
|
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
|
|
70
|
+
onPrefabChange(Object.assign(Object.assign({}, data), { root }));
|
|
64
71
|
};
|
|
72
|
+
/* ---------------- Asset loading ---------------- */
|
|
65
73
|
useEffect(() => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
190
|
-
const materialDef = material
|
|
191
|
-
const modelDef = model
|
|
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
|
-
|
|
201
|
-
const
|
|
202
|
-
const leafComponents = [];
|
|
233
|
+
const wrappers = [];
|
|
234
|
+
const leaves = [];
|
|
203
235
|
if (gameObject.components) {
|
|
204
236
|
Object.entries(gameObject.components)
|
|
205
|
-
.filter(([
|
|
237
|
+
.filter(([k]) => !["geometry", "material", "model", "transform", "physics"].includes(k))
|
|
206
238
|
.forEach(([key, comp]) => {
|
|
207
|
-
if (!comp ||
|
|
239
|
+
if (!(comp === null || comp === void 0 ? void 0 : comp.type))
|
|
208
240
|
return;
|
|
209
241
|
const def = getComponent(comp.type);
|
|
210
|
-
if (!def ||
|
|
242
|
+
if (!(def === null || def === void 0 ? void 0 : def.View))
|
|
211
243
|
return;
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
249
|
+
leaves.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
|
|
219
250
|
}
|
|
220
251
|
});
|
|
221
252
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
230
|
-
|
|
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
|
-
|
|
235
|
-
coreContent = _jsx(_Fragment, { children: leafComponents });
|
|
261
|
+
core = _jsx(_Fragment, { children: leaves });
|
|
236
262
|
}
|
|
237
|
-
|
|
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
|
@@ -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?:
|
|
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?:
|
|
378
|
+
physics?: PhysicsProps | undefined;
|
|
378
379
|
}>(({
|
|
379
380
|
id,
|
|
380
381
|
modelUrl,
|