react-three-game 0.0.21 → 0.0.23
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 +8 -9
- package/dist/tools/prefabeditor/InstanceProvider.js +100 -130
- package/dist/tools/prefabeditor/PrefabRoot.js +6 -15
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +10 -46
- package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +13 -9
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +2 -14
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/package.json +1 -1
- package/src/tools/prefabeditor/InstanceProvider.tsx +197 -207
- package/src/tools/prefabeditor/PrefabRoot.tsx +20 -33
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +20 -61
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +10 -8
- package/src/tools/prefabeditor/components/ModelComponent.tsx +13 -9
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +6 -5
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +2 -0
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { RigidBodyProps } from "@react-three/rapier";
|
|
3
|
+
import { Object3D } from "three";
|
|
3
4
|
export type InstanceData = {
|
|
4
5
|
id: string;
|
|
6
|
+
meshPath: string;
|
|
5
7
|
position: [number, number, number];
|
|
6
8
|
rotation: [number, number, number];
|
|
7
9
|
scale: [number, number, number];
|
|
8
|
-
meshPath: string;
|
|
9
10
|
physics?: {
|
|
10
|
-
type: '
|
|
11
|
+
type: RigidBodyProps['type'];
|
|
11
12
|
};
|
|
12
13
|
};
|
|
13
14
|
export declare function GameInstanceProvider({ children, models, onSelect, registerRef }: {
|
|
14
15
|
children: React.ReactNode;
|
|
15
|
-
models:
|
|
16
|
-
[filename: string]: Object3D;
|
|
17
|
-
};
|
|
16
|
+
models: Record<string, Object3D>;
|
|
18
17
|
onSelect?: (id: string | null) => void;
|
|
19
18
|
registerRef?: (id: string, obj: Object3D | null) => void;
|
|
20
19
|
}): import("react/jsx-runtime").JSX.Element;
|
|
21
|
-
export declare
|
|
20
|
+
export declare function GameInstance({ id, modelUrl, position, rotation, scale, physics }: {
|
|
22
21
|
id: string;
|
|
23
22
|
modelUrl: string;
|
|
24
23
|
position: [number, number, number];
|
|
25
24
|
rotation: [number, number, number];
|
|
26
25
|
scale: [number, number, number];
|
|
27
26
|
physics?: {
|
|
28
|
-
type:
|
|
27
|
+
type: RigidBodyProps['type'];
|
|
29
28
|
};
|
|
30
|
-
}
|
|
29
|
+
}): null;
|
|
@@ -1,164 +1,137 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import
|
|
2
|
+
import { createContext, useContext, useMemo, useRef, useState, useEffect, useCallback } from "react";
|
|
3
3
|
import { Merged } from '@react-three/drei';
|
|
4
4
|
import { InstancedRigidBodies } from "@react-three/rapier";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (a.length !== b.length)
|
|
11
|
-
return false;
|
|
12
|
-
for (let i = 0; i < a.length; i++) {
|
|
13
|
-
if (a[i] !== b[i])
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
function instanceEquals(a, b) {
|
|
5
|
+
import { useFrame } from "@react-three/fiber";
|
|
6
|
+
import { Mesh, Matrix4, Euler, Quaternion, Vector3 } from "three";
|
|
7
|
+
// --- Helpers ---
|
|
8
|
+
const arraysEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
9
|
+
const instancesEqual = (a, b) => {
|
|
19
10
|
var _a, _b;
|
|
20
11
|
return a.id === b.id &&
|
|
21
12
|
a.meshPath === b.meshPath &&
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
(
|
|
13
|
+
((_a = a.physics) === null || _a === void 0 ? void 0 : _a.type) === ((_b = b.physics) === null || _b === void 0 ? void 0 : _b.type) &&
|
|
14
|
+
arraysEqual(a.position, b.position) &&
|
|
15
|
+
arraysEqual(a.rotation, b.rotation) &&
|
|
16
|
+
arraysEqual(a.scale, b.scale);
|
|
17
|
+
};
|
|
18
|
+
// Reusable objects for matrix computation (avoid allocations in hot paths)
|
|
19
|
+
const _matrix = new Matrix4();
|
|
20
|
+
const _position = new Vector3();
|
|
21
|
+
const _quaternion = new Quaternion();
|
|
22
|
+
const _euler = new Euler();
|
|
23
|
+
const _scale = new Vector3();
|
|
24
|
+
function composeMatrix(position, rotation, scale, target = _matrix) {
|
|
25
|
+
_position.set(...position);
|
|
26
|
+
_quaternion.setFromEuler(_euler.set(...rotation));
|
|
27
|
+
_scale.set(...scale);
|
|
28
|
+
return target.compose(_position, _quaternion, _scale);
|
|
26
29
|
}
|
|
30
|
+
// --- Context ---
|
|
27
31
|
const GameInstanceContext = createContext(null);
|
|
32
|
+
// --- Provider ---
|
|
28
33
|
export function GameInstanceProvider({ children, models, onSelect, registerRef }) {
|
|
29
34
|
const [instances, setInstances] = useState([]);
|
|
30
35
|
const addInstance = useCallback((instance) => {
|
|
31
36
|
setInstances(prev => {
|
|
32
37
|
const idx = prev.findIndex(i => i.id === instance.id);
|
|
33
|
-
if (idx
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return copy;
|
|
41
|
-
}
|
|
42
|
-
// Add new
|
|
43
|
-
return [...prev, instance];
|
|
38
|
+
if (idx === -1)
|
|
39
|
+
return [...prev, instance];
|
|
40
|
+
if (instancesEqual(prev[idx], instance))
|
|
41
|
+
return prev;
|
|
42
|
+
const updated = [...prev];
|
|
43
|
+
updated[idx] = instance;
|
|
44
|
+
return updated;
|
|
44
45
|
});
|
|
45
46
|
}, []);
|
|
46
47
|
const removeInstance = useCallback((id) => {
|
|
47
|
-
setInstances(prev =>
|
|
48
|
-
if (!prev.find(i => i.id === id))
|
|
49
|
-
return prev;
|
|
50
|
-
return prev.filter(i => i.id !== id);
|
|
51
|
-
});
|
|
48
|
+
setInstances(prev => prev.filter(i => i.id !== id));
|
|
52
49
|
}, []);
|
|
53
|
-
//
|
|
54
|
-
const {
|
|
55
|
-
const
|
|
56
|
-
const
|
|
50
|
+
// Extract mesh parts from models with baked local transforms
|
|
51
|
+
const { meshParts, partCounts } = useMemo(() => {
|
|
52
|
+
const meshParts = {};
|
|
53
|
+
const partCounts = {};
|
|
57
54
|
Object.entries(models).forEach(([modelKey, model]) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const rootInverse = new Matrix4().copy(root.matrixWorld).invert();
|
|
55
|
+
model.updateWorldMatrix(false, true);
|
|
56
|
+
const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
|
|
61
57
|
let partIndex = 0;
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
flatMeshes[partKey] = new Mesh(geom, obj.material);
|
|
58
|
+
model.traverse((child) => {
|
|
59
|
+
if (child.isMesh) {
|
|
60
|
+
const mesh = child;
|
|
61
|
+
const geometry = mesh.geometry.clone();
|
|
62
|
+
geometry.applyMatrix4(mesh.matrixWorld.clone().premultiply(rootInverse));
|
|
63
|
+
meshParts[`${modelKey}__${partIndex}`] = new Mesh(geometry, mesh.material);
|
|
69
64
|
partIndex++;
|
|
70
65
|
}
|
|
71
66
|
});
|
|
72
|
-
|
|
67
|
+
partCounts[modelKey] = partIndex;
|
|
73
68
|
});
|
|
74
|
-
return {
|
|
69
|
+
return { meshParts, partCounts };
|
|
75
70
|
}, [models]);
|
|
76
|
-
//
|
|
71
|
+
// Cleanup cloned geometries
|
|
72
|
+
useEffect(() => () => {
|
|
73
|
+
Object.values(meshParts).forEach(mesh => mesh.geometry.dispose());
|
|
74
|
+
}, [meshParts]);
|
|
75
|
+
// Group instances by model + physics type
|
|
77
76
|
const grouped = useMemo(() => {
|
|
78
|
-
var _a;
|
|
79
77
|
const groups = {};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
instances.forEach(inst => {
|
|
79
|
+
var _a, _b, _c;
|
|
80
|
+
const physicsType = (_b = (_a = inst.physics) === null || _a === void 0 ? void 0 : _a.type) !== null && _b !== void 0 ? _b : 'none';
|
|
81
|
+
const key = `${inst.meshPath}__${physicsType}`;
|
|
82
|
+
(_c = groups[key]) !== null && _c !== void 0 ? _c : (groups[key] = { physicsType, instances: [] });
|
|
85
83
|
groups[key].instances.push(inst);
|
|
86
|
-
}
|
|
84
|
+
});
|
|
87
85
|
return groups;
|
|
88
86
|
}, [instances]);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
instances,
|
|
93
|
-
meshes: flatMeshes,
|
|
94
|
-
modelParts
|
|
95
|
-
}, children: [children, Object.entries(grouped).map(([key, group]) => {
|
|
96
|
-
if (group.physicsType === 'none')
|
|
97
|
-
return null;
|
|
87
|
+
const contextValue = useMemo(() => ({ addInstance, removeInstance }), [addInstance, removeInstance]);
|
|
88
|
+
return (_jsxs(GameInstanceContext.Provider, { value: contextValue, children: [children, Object.entries(grouped).map(([key, group]) => {
|
|
89
|
+
var _a;
|
|
98
90
|
const modelKey = group.instances[0].meshPath;
|
|
99
|
-
const partCount =
|
|
91
|
+
const partCount = (_a = partCounts[modelKey]) !== null && _a !== void 0 ? _a : 0;
|
|
100
92
|
if (partCount === 0)
|
|
101
93
|
return null;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (group.physicsType !== 'none')
|
|
105
|
-
return null;
|
|
106
|
-
const modelKey = group.instances[0].meshPath;
|
|
107
|
-
const partCount = modelParts[modelKey] || 0;
|
|
108
|
-
if (partCount === 0)
|
|
109
|
-
return null;
|
|
110
|
-
// Create mesh subset for this specific model
|
|
111
|
-
const meshesForModel = {};
|
|
112
|
-
for (let i = 0; i < partCount; i++) {
|
|
113
|
-
const partKey = `${modelKey}__${i}`;
|
|
114
|
-
meshesForModel[partKey] = flatMeshes[partKey];
|
|
94
|
+
if (group.physicsType !== 'none') {
|
|
95
|
+
return (_jsx(PhysicsInstances, { instances: group.instances, physicsType: group.physicsType, modelKey: modelKey, partCount: partCount, meshParts: meshParts }, key));
|
|
115
96
|
}
|
|
116
|
-
|
|
97
|
+
const modelMeshes = Object.fromEntries(Array.from({ length: partCount }, (_, i) => [`${modelKey}__${i}`, meshParts[`${modelKey}__${i}`]]));
|
|
98
|
+
return (_jsx(Merged, { meshes: modelMeshes, castShadow: true, receiveShadow: true, children: (Components) => (_jsx(StaticInstances, { instances: group.instances, modelKey: modelKey, partCount: partCount, Components: Components, onSelect: onSelect, registerRef: registerRef })) }, key));
|
|
117
99
|
})] }));
|
|
118
100
|
}
|
|
119
|
-
//
|
|
120
|
-
function
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
101
|
+
// --- Physics Instances ---
|
|
102
|
+
function PhysicsInstances({ instances, physicsType, modelKey, partCount, meshParts }) {
|
|
103
|
+
const meshRefs = useRef([]);
|
|
104
|
+
const rigidBodyInstances = useMemo(() => instances.map(({ id, position, rotation, scale }) => ({ key: id, position, rotation, scale })), [instances]);
|
|
105
|
+
// Sync visual matrices each frame (physics updates position/rotation, we need to apply scale)
|
|
106
|
+
useFrame(() => {
|
|
107
|
+
meshRefs.current.forEach(mesh => {
|
|
108
|
+
if (!mesh)
|
|
109
|
+
return;
|
|
110
|
+
instances.forEach((inst, i) => {
|
|
111
|
+
mesh.setMatrixAt(i, composeMatrix(inst.position, inst.rotation, inst.scale));
|
|
112
|
+
});
|
|
113
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
return (_jsx(InstancedRigidBodies, { instances: rigidBodyInstances, type: physicsType, colliders: physicsType === 'fixed' ? 'trimesh' : 'hull', children: Array.from({ length: partCount }, (_, i) => {
|
|
117
|
+
const mesh = meshParts[`${modelKey}__${i}`];
|
|
118
|
+
return mesh ? (_jsx("instancedMesh", { ref: el => { meshRefs.current[i] = el; }, args: [mesh.geometry, mesh.material, instances.length], frustumCulled: false, castShadow: true, receiveShadow: true }, i)) : null;
|
|
130
119
|
}) }));
|
|
131
120
|
}
|
|
132
|
-
//
|
|
133
|
-
function
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
e.stopPropagation();
|
|
137
|
-
clickValid.current = true;
|
|
138
|
-
};
|
|
139
|
-
const handlePointerMove = () => {
|
|
140
|
-
if (clickValid.current)
|
|
141
|
-
clickValid.current = false;
|
|
142
|
-
};
|
|
143
|
-
const handlePointerUp = (e, id) => {
|
|
144
|
-
if (clickValid.current) {
|
|
145
|
-
e.stopPropagation();
|
|
146
|
-
onSelect === null || onSelect === void 0 ? void 0 : onSelect(id);
|
|
147
|
-
}
|
|
148
|
-
clickValid.current = false;
|
|
149
|
-
};
|
|
150
|
-
return (_jsx(_Fragment, { children: group.instances.map(inst => (_jsx("group", { ref: (el) => { registerRef === null || registerRef === void 0 ? void 0 : registerRef(inst.id, el); }, position: inst.position, rotation: inst.rotation, scale: inst.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: (e) => handlePointerUp(e, inst.id), children: Array.from({ length: partCount }).map((_, i) => {
|
|
151
|
-
const Instance = instancesMap[`${modelKey}__${i}`];
|
|
152
|
-
if (!Instance)
|
|
153
|
-
return null;
|
|
154
|
-
return _jsx(Instance, {}, i);
|
|
155
|
-
}) }, inst.id))) }));
|
|
121
|
+
// --- Static Instances (non-physics) ---
|
|
122
|
+
function StaticInstances({ instances, modelKey, partCount, Components, onSelect, registerRef }) {
|
|
123
|
+
const Parts = useMemo(() => Array.from({ length: partCount }, (_, i) => Components[`${modelKey}__${i}`]).filter(Boolean), [Components, modelKey, partCount]);
|
|
124
|
+
return (_jsx(_Fragment, { children: instances.map(inst => (_jsx(InstanceItem, { instance: inst, Parts: Parts, onSelect: onSelect, registerRef: registerRef }, inst.id))) }));
|
|
156
125
|
}
|
|
157
|
-
//
|
|
158
|
-
|
|
126
|
+
// --- Single Instance ---
|
|
127
|
+
function InstanceItem({ instance, Parts, onSelect, registerRef }) {
|
|
128
|
+
const moved = useRef(false);
|
|
129
|
+
return (_jsx("group", { ref: el => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, el), position: instance.position, rotation: instance.rotation, scale: instance.scale, onPointerDown: e => { e.stopPropagation(); moved.current = false; }, onPointerMove: () => { moved.current = true; }, onPointerUp: e => { e.stopPropagation(); if (!moved.current)
|
|
130
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(instance.id); }, children: Parts.map((Part, i) => _jsx(Part, {}, i)) }));
|
|
131
|
+
}
|
|
132
|
+
// --- GameInstance (declarative registration) ---
|
|
133
|
+
export function GameInstance({ id, modelUrl, position, rotation, scale, physics }) {
|
|
159
134
|
const ctx = useContext(GameInstanceContext);
|
|
160
|
-
const addInstance = ctx === null || ctx === void 0 ? void 0 : ctx.addInstance;
|
|
161
|
-
const removeInstance = ctx === null || ctx === void 0 ? void 0 : ctx.removeInstance;
|
|
162
135
|
const instance = useMemo(() => ({
|
|
163
136
|
id,
|
|
164
137
|
meshPath: modelUrl,
|
|
@@ -168,13 +141,10 @@ export const GameInstance = React.forwardRef(({ id, modelUrl, position, rotation
|
|
|
168
141
|
physics,
|
|
169
142
|
}), [id, modelUrl, position, rotation, scale, physics]);
|
|
170
143
|
useEffect(() => {
|
|
171
|
-
if (!
|
|
144
|
+
if (!ctx)
|
|
172
145
|
return;
|
|
173
|
-
addInstance(instance);
|
|
174
|
-
return () =>
|
|
175
|
-
|
|
176
|
-
};
|
|
177
|
-
}, [addInstance, removeInstance, instance]);
|
|
178
|
-
// No visual rendering - provider handles all instanced visuals
|
|
146
|
+
ctx.addInstance(instance);
|
|
147
|
+
return () => ctx.removeInstance(id);
|
|
148
|
+
}, [ctx, instance, id]);
|
|
179
149
|
return null;
|
|
180
|
-
}
|
|
150
|
+
}
|
|
@@ -146,26 +146,17 @@ function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loa
|
|
|
146
146
|
const core = renderCoreNode(gameObject, ctx, parentMatrix);
|
|
147
147
|
// --- 5. Render children recursively (always relative transforms) ---
|
|
148
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.
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
rotation: transformProps.rotation,
|
|
153
|
-
onPointerDown: handlePointerDown,
|
|
154
|
-
onPointerMove: handlePointerMove,
|
|
155
|
-
onPointerUp: handlePointerUp,
|
|
156
|
-
};
|
|
157
|
-
// --- 7. Check if physics is needed ---
|
|
149
|
+
// --- 6. Inner content group with full transform ---
|
|
150
|
+
const innerGroup = (_jsxs("group", { ref: (el) => registerRef(gameObject.id, el), position: transformProps.position, rotation: transformProps.rotation, scale: transformProps.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [core, children] }));
|
|
151
|
+
// --- 7. Wrap with physics if needed (RigidBody as outer parent, no transform) ---
|
|
158
152
|
const physics = (_e = gameObject.components) === null || _e === void 0 ? void 0 : _e.physics;
|
|
159
|
-
|
|
160
|
-
// --- 8. Final structure: RigidBody outside with position/rotation, scale inside ---
|
|
161
|
-
if (hasPhysics) {
|
|
153
|
+
if (physics && !editMode) {
|
|
162
154
|
const physicsDef = getComponent('Physics');
|
|
163
155
|
if (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) {
|
|
164
|
-
return (_jsx(physicsDef.View,
|
|
156
|
+
return (_jsx(physicsDef.View, { properties: physics.properties, children: innerGroup }));
|
|
165
157
|
}
|
|
166
158
|
}
|
|
167
|
-
|
|
168
|
-
return (_jsxs("group", Object.assign({ ref: (el) => registerRef(gameObject.id, el), scale: transformProps.scale }, wrapperProps, { children: [core, children] })));
|
|
159
|
+
return innerGroup;
|
|
169
160
|
}
|
|
170
161
|
// Helper: render an instanced GameInstance (terminal node)
|
|
171
162
|
function renderInstancedNode(gameObject, worldMatrix, ctx) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useEffect } from "react";
|
|
3
|
-
import { useFrame
|
|
4
|
-
import {
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { Vector3 } from "three";
|
|
5
5
|
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
6
6
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
7
7
|
const props = {
|
|
@@ -32,60 +32,24 @@ function DirectionalLightView({ properties, editMode }) {
|
|
|
32
32
|
const shadowCameraLeft = (_j = properties.shadowCameraLeft) !== null && _j !== void 0 ? _j : -30;
|
|
33
33
|
const shadowCameraRight = (_k = properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30;
|
|
34
34
|
const targetOffset = (_l = properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0];
|
|
35
|
-
const { scene } = useThree();
|
|
36
35
|
const directionalLightRef = useRef(null);
|
|
37
|
-
const targetRef = useRef(
|
|
38
|
-
|
|
39
|
-
// Add target to scene once
|
|
36
|
+
const targetRef = useRef(null);
|
|
37
|
+
// Set up light target reference when both refs are ready
|
|
40
38
|
useEffect(() => {
|
|
41
|
-
|
|
42
|
-
scene.add(target);
|
|
43
|
-
return () => {
|
|
44
|
-
scene.remove(target);
|
|
45
|
-
};
|
|
46
|
-
}, [scene]);
|
|
47
|
-
// Set up light target reference once
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (directionalLightRef.current) {
|
|
39
|
+
if (directionalLightRef.current && targetRef.current) {
|
|
50
40
|
directionalLightRef.current.target = targetRef.current;
|
|
51
41
|
}
|
|
52
42
|
}, []);
|
|
53
|
-
// Update target position
|
|
43
|
+
// Update target world position based on light position + offset
|
|
54
44
|
useFrame(() => {
|
|
55
|
-
if (!directionalLightRef.current)
|
|
45
|
+
if (!directionalLightRef.current || !targetRef.current)
|
|
56
46
|
return;
|
|
57
47
|
const lightWorldPos = new Vector3();
|
|
58
48
|
directionalLightRef.current.getWorldPosition(lightWorldPos);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (!targetRef.current.position.equals(newTargetPos)) {
|
|
62
|
-
targetRef.current.position.copy(newTargetPos);
|
|
63
|
-
if (directionalLightRef.current.shadow) {
|
|
64
|
-
directionalLightRef.current.shadow.needsUpdate = true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// Update camera helper in edit mode
|
|
68
|
-
if (editMode && cameraHelperRef.current) {
|
|
69
|
-
cameraHelperRef.current.update();
|
|
70
|
-
}
|
|
49
|
+
// Target is positioned relative to light's world position
|
|
50
|
+
targetRef.current.position.set(lightWorldPos.x + targetOffset[0], lightWorldPos.y + targetOffset[1], lightWorldPos.z + targetOffset[2]);
|
|
71
51
|
});
|
|
72
|
-
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
var _a;
|
|
75
|
-
if (editMode && ((_a = directionalLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow.camera)) {
|
|
76
|
-
const helper = new CameraHelper(directionalLightRef.current.shadow.camera);
|
|
77
|
-
cameraHelperRef.current = helper;
|
|
78
|
-
scene.add(helper);
|
|
79
|
-
return () => {
|
|
80
|
-
if (cameraHelperRef.current) {
|
|
81
|
-
scene.remove(cameraHelperRef.current);
|
|
82
|
-
cameraHelperRef.current.dispose();
|
|
83
|
-
cameraHelperRef.current = null;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
}, [editMode, scene]);
|
|
88
|
-
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize": [shadowMapSize, shadowMapSize], "shadow-bias": -0.001, "shadow-normalBias": 0.02, children: _jsx("orthographicCamera", { attach: "shadow-camera", near: shadowCameraNear, far: shadowCameraFar, top: shadowCameraTop, bottom: shadowCameraBottom, left: shadowCameraLeft, right: shadowCameraRight }) }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
|
|
52
|
+
return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": -0.001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { onUpdate: (geo) => {
|
|
89
53
|
const points = [
|
|
90
54
|
new Vector3(0, 0, 0),
|
|
91
55
|
new Vector3(targetOffset[0], targetOffset[1], targetOffset[2])
|
|
@@ -53,7 +53,7 @@ function MaterialComponentView({ properties, loadedTextures, isSelected }) {
|
|
|
53
53
|
}
|
|
54
54
|
const { color, wireframe = false } = properties;
|
|
55
55
|
const displayColor = isSelected ? "cyan" : color;
|
|
56
|
-
return _jsx("meshStandardMaterial", { color: displayColor, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture, side: DoubleSide }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture');
|
|
56
|
+
return (_jsx("meshStandardMaterial", { color: displayColor, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture, side: DoubleSide }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
|
|
57
57
|
}
|
|
58
58
|
const MaterialComponent = {
|
|
59
59
|
name: 'Material',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelListViewer } from '../../assetviewer/page';
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
3
|
+
import { useEffect, useState, useMemo } from 'react';
|
|
4
4
|
function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
5
5
|
const [modelFiles, setModelFiles] = useState([]);
|
|
6
6
|
useEffect(() => {
|
|
@@ -22,19 +22,23 @@ function ModelComponentView({ properties, loadedModels, children }) {
|
|
|
22
22
|
// Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
|
|
23
23
|
if (!properties.filename || properties.instanced)
|
|
24
24
|
return _jsx(_Fragment, { children: children });
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const sourceModel = loadedModels === null || loadedModels === void 0 ? void 0 : loadedModels[properties.filename];
|
|
26
|
+
// Clone model once and set up shadows - memoized to avoid cloning on every render
|
|
27
|
+
const clonedModel = useMemo(() => {
|
|
28
|
+
if (!sourceModel)
|
|
29
|
+
return null;
|
|
30
|
+
const clone = sourceModel.clone();
|
|
31
|
+
clone.traverse((obj) => {
|
|
29
32
|
if (obj.isMesh) {
|
|
30
33
|
obj.castShadow = true;
|
|
31
34
|
obj.receiveShadow = true;
|
|
32
35
|
}
|
|
33
36
|
});
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
return clone;
|
|
38
|
+
}, [sourceModel]);
|
|
39
|
+
if (!clonedModel)
|
|
40
|
+
return _jsx(_Fragment, { children: children });
|
|
41
|
+
return _jsx("primitive", { object: clonedModel, children: children });
|
|
38
42
|
}
|
|
39
43
|
const ModelComponent = {
|
|
40
44
|
name: 'Model',
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
-
var t = {};
|
|
3
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
-
t[p] = s[p];
|
|
5
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
-
t[p[i]] = s[p[i]];
|
|
9
|
-
}
|
|
10
|
-
return t;
|
|
11
|
-
};
|
|
12
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
2
|
import { RigidBody } from "@react-three/rapier";
|
|
14
3
|
const selectClass = "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50";
|
|
@@ -17,12 +6,11 @@ function PhysicsComponentEditor({ component, onUpdate }) {
|
|
|
17
6
|
const { type, collider = 'hull' } = component.properties;
|
|
18
7
|
return (_jsxs("div", { children: [_jsx("label", { className: labelClass, children: "Type" }), _jsxs("select", { className: selectClass, value: type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] }), _jsx("label", { className: `${labelClass} mt-2`, children: "Collider" }), _jsxs("select", { className: selectClass, value: collider, onChange: e => onUpdate({ collider: e.target.value }), children: [_jsx("option", { value: "hull", children: "Hull (convex)" }), _jsx("option", { value: "trimesh", children: "Trimesh (exact)" }), _jsx("option", { value: "cuboid", children: "Cuboid (box)" }), _jsx("option", { value: "ball", children: "Ball (sphere)" })] })] }));
|
|
19
8
|
}
|
|
20
|
-
function PhysicsComponentView(
|
|
21
|
-
var { properties, editMode, children } = _a, rigidBodyProps = __rest(_a, ["properties", "editMode", "children"]);
|
|
9
|
+
function PhysicsComponentView({ properties, editMode, children }) {
|
|
22
10
|
if (editMode)
|
|
23
11
|
return _jsx(_Fragment, { children: children });
|
|
24
12
|
const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
|
|
25
|
-
return (_jsx(RigidBody,
|
|
13
|
+
return (_jsx(RigidBody, { type: properties.type, colliders: colliders, children: children }));
|
|
26
14
|
}
|
|
27
15
|
const PhysicsComponent = {
|
|
28
16
|
name: 'Physics',
|
|
@@ -27,7 +27,7 @@ function SpotLightView({ properties, editMode }) {
|
|
|
27
27
|
spotLightRef.current.target = targetRef.current;
|
|
28
28
|
}
|
|
29
29
|
}, []);
|
|
30
|
-
return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
|
|
30
|
+
return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "shadow-mapSize-width": 1024, "shadow-mapSize-height": 1024, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
|
|
31
31
|
}
|
|
32
32
|
const SpotLightComponent = {
|
|
33
33
|
name: 'SpotLight',
|