react-three-game 0.0.41 → 0.0.42
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/README.md +0 -17
- package/dist/helpers/SoundManager.d.ts +35 -0
- package/dist/helpers/SoundManager.js +93 -0
- package/dist/helpers/index.d.ts +35 -0
- package/dist/helpers/index.js +44 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/index.umd.js +4347 -163
- package/dist/shared/GameCanvas.d.ts +9 -0
- package/dist/shared/GameCanvas.js +47 -0
- package/dist/tools/assetviewer/page.d.ts +35 -0
- package/dist/tools/assetviewer/page.js +166 -0
- package/dist/tools/dragdrop/DragDropLoader.d.ts +9 -0
- package/dist/tools/dragdrop/DragDropLoader.js +78 -0
- package/dist/tools/dragdrop/modelLoader.d.ts +7 -0
- package/dist/tools/dragdrop/modelLoader.js +52 -0
- package/dist/tools/dragdrop/page.d.ts +1 -0
- package/dist/tools/dragdrop/page.js +11 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +11 -0
- package/dist/tools/prefabeditor/EditorContext.js +9 -0
- package/dist/tools/prefabeditor/EditorTree.d.ts +12 -0
- package/dist/tools/prefabeditor/EditorTree.js +150 -0
- package/dist/tools/prefabeditor/EditorUI.d.ts +14 -0
- package/dist/tools/prefabeditor/EditorUI.js +71 -0
- package/dist/tools/prefabeditor/EventSystem.d.ts +7 -0
- package/dist/tools/prefabeditor/EventSystem.js +23 -0
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +30 -0
- package/dist/tools/prefabeditor/InstanceProvider.js +254 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +16 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +140 -0
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +28 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +293 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +18 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +13 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +78 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +66 -0
- package/dist/tools/prefabeditor/components/Input.d.ts +20 -0
- package/dist/tools/prefabeditor/components/Input.js +129 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +100 -0
- package/dist/tools/prefabeditor/components/ModelComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/ModelComponent.js +57 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +33 -0
- package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +49 -0
- package/dist/tools/prefabeditor/components/TransformComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/TransformComponent.js +42 -0
- package/dist/tools/prefabeditor/components/index.d.ts +2 -0
- package/dist/tools/prefabeditor/components/index.js +16 -0
- package/dist/tools/prefabeditor/page.d.ts +1 -0
- package/dist/tools/prefabeditor/page.js +5 -0
- package/dist/tools/prefabeditor/styles.d.ts +1809 -0
- package/dist/tools/prefabeditor/styles.js +167 -0
- package/dist/tools/prefabeditor/types.d.ts +19 -0
- package/dist/tools/prefabeditor/types.js +1 -0
- package/dist/tools/prefabeditor/utils.d.ts +26 -0
- package/dist/tools/prefabeditor/utils.js +131 -0
- package/package.json +2 -4
- package/.claude/settings.local.json +0 -9
- package/vite.config.ts +0 -34
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { MapControls, TransformControls, useHelper } from "@react-three/drei";
|
|
12
|
+
import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
13
|
+
import { BoxHelper, Euler, Matrix4, Quaternion, SRGBColorSpace, TextureLoader, Vector3, } from "three";
|
|
14
|
+
import { getComponent, registerComponent } from "./components/ComponentRegistry";
|
|
15
|
+
import components from "./components";
|
|
16
|
+
import { loadModel } from "../dragdrop/modelLoader";
|
|
17
|
+
import { GameInstance, GameInstanceProvider, useInstanceCheck } from "./InstanceProvider";
|
|
18
|
+
import { updateNode } from "./utils";
|
|
19
|
+
import { EditorContext } from "./EditorContext";
|
|
20
|
+
components.forEach(registerComponent);
|
|
21
|
+
const IDENTITY = new Matrix4();
|
|
22
|
+
export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, onClick, basePath = "" }, ref) => {
|
|
23
|
+
var _a, _b;
|
|
24
|
+
// optional editor context
|
|
25
|
+
const editorContext = useContext(EditorContext);
|
|
26
|
+
const transformMode = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.transformMode) !== null && _a !== void 0 ? _a : "translate";
|
|
27
|
+
const snapResolution = (_b = editorContext === null || editorContext === void 0 ? void 0 : editorContext.snapResolution) !== null && _b !== void 0 ? _b : 0;
|
|
28
|
+
// prefab root state
|
|
29
|
+
const [models, setModels] = useState({});
|
|
30
|
+
const [textures, setTextures] = useState({});
|
|
31
|
+
const loading = useRef(new Set());
|
|
32
|
+
const objectRefs = useRef({});
|
|
33
|
+
const [selectedObject, setSelectedObject] = useState(null);
|
|
34
|
+
const rootRef = useRef(null);
|
|
35
|
+
useImperativeHandle(ref, () => ({
|
|
36
|
+
root: rootRef.current
|
|
37
|
+
}), []);
|
|
38
|
+
const registerRef = useCallback((id, obj) => {
|
|
39
|
+
objectRefs.current[id] = obj;
|
|
40
|
+
if (id === selectedId)
|
|
41
|
+
setSelectedObject(obj);
|
|
42
|
+
}, [selectedId]);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const originalError = console.error;
|
|
45
|
+
console.error = (...args) => {
|
|
46
|
+
if (typeof args[0] === 'string' && args[0].includes('TransformControls') && args[0].includes('scene graph'))
|
|
47
|
+
return;
|
|
48
|
+
originalError.apply(console, args);
|
|
49
|
+
};
|
|
50
|
+
return () => { console.error = originalError; };
|
|
51
|
+
}, []);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
var _a;
|
|
54
|
+
setSelectedObject(selectedId ? (_a = objectRefs.current[selectedId]) !== null && _a !== void 0 ? _a : null : null);
|
|
55
|
+
}, [selectedId]);
|
|
56
|
+
const onTransformChange = () => {
|
|
57
|
+
if (!selectedId || !onPrefabChange)
|
|
58
|
+
return;
|
|
59
|
+
const obj = objectRefs.current[selectedId];
|
|
60
|
+
if (!obj)
|
|
61
|
+
return;
|
|
62
|
+
const parentWorld = computeParentWorldMatrix(data.root, selectedId);
|
|
63
|
+
const local = parentWorld.clone().invert().multiply(obj.matrixWorld);
|
|
64
|
+
const { position, rotation, scale } = decompose(local);
|
|
65
|
+
const root = updateNode(data.root, selectedId, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { transform: {
|
|
66
|
+
type: "Transform",
|
|
67
|
+
properties: { position, rotation, scale },
|
|
68
|
+
} }) })));
|
|
69
|
+
onPrefabChange(Object.assign(Object.assign({}, data), { root }));
|
|
70
|
+
};
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const modelsToLoad = new Set();
|
|
73
|
+
const texturesToLoad = new Set();
|
|
74
|
+
walk(data.root, node => {
|
|
75
|
+
var _a, _b, _c, _d, _e, _f;
|
|
76
|
+
((_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) &&
|
|
77
|
+
modelsToLoad.add(node.components.model.properties.filename);
|
|
78
|
+
((_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) &&
|
|
79
|
+
texturesToLoad.add(node.components.material.properties.texture);
|
|
80
|
+
});
|
|
81
|
+
modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
if (models[file] || loading.current.has(file))
|
|
83
|
+
return;
|
|
84
|
+
loading.current.add(file);
|
|
85
|
+
const path = file.startsWith("/")
|
|
86
|
+
? `${basePath}${file}`
|
|
87
|
+
: `${basePath}/${file}`;
|
|
88
|
+
const res = yield loadModel(path);
|
|
89
|
+
res.success && res.model &&
|
|
90
|
+
setModels(m => (Object.assign(Object.assign({}, m), { [file]: res.model })));
|
|
91
|
+
}));
|
|
92
|
+
const loader = new TextureLoader();
|
|
93
|
+
texturesToLoad.forEach(file => {
|
|
94
|
+
if (textures[file] || loading.current.has(file))
|
|
95
|
+
return;
|
|
96
|
+
loading.current.add(file);
|
|
97
|
+
const path = file.startsWith("/")
|
|
98
|
+
? `${basePath}${file}`
|
|
99
|
+
: `${basePath}/${file}`;
|
|
100
|
+
loader.load(path, tex => {
|
|
101
|
+
tex.colorSpace = SRGBColorSpace;
|
|
102
|
+
setTextures(t => (Object.assign(Object.assign({}, t), { [file]: tex })));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}, [data, models, textures]);
|
|
106
|
+
return (_jsxs("group", { ref: rootRef, 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, onClick: onClick, 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, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
|
|
107
|
+
});
|
|
108
|
+
export function GameObjectRenderer(props) {
|
|
109
|
+
var _a, _b, _c;
|
|
110
|
+
const node = props.gameObject;
|
|
111
|
+
if (!node || node.hidden || node.disabled)
|
|
112
|
+
return null;
|
|
113
|
+
const isInstanced = (_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;
|
|
114
|
+
const prevInstancedRef = useRef(undefined);
|
|
115
|
+
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (prevInstancedRef.current !== undefined && prevInstancedRef.current !== isInstanced) {
|
|
118
|
+
setIsTransitioning(true);
|
|
119
|
+
const timer = setTimeout(() => setIsTransitioning(false), 100);
|
|
120
|
+
return () => clearTimeout(timer);
|
|
121
|
+
}
|
|
122
|
+
prevInstancedRef.current = isInstanced;
|
|
123
|
+
}, [isInstanced]);
|
|
124
|
+
if (isTransitioning)
|
|
125
|
+
return null;
|
|
126
|
+
const key = `${node.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
127
|
+
return isInstanced
|
|
128
|
+
? _jsx(InstancedNode, Object.assign({}, props), key)
|
|
129
|
+
: _jsx(StandardNode, Object.assign({}, props), key);
|
|
130
|
+
}
|
|
131
|
+
function isPhysicsProps(v) {
|
|
132
|
+
return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic";
|
|
133
|
+
}
|
|
134
|
+
function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, registerRef, selectedId: _selectedId, onSelect, onClick }) {
|
|
135
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
136
|
+
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
137
|
+
const { position: worldPosition, rotation: worldRotation, scale: worldScale } = decompose(world);
|
|
138
|
+
const localTransform = getNodeTransformProps(gameObject);
|
|
139
|
+
const physicsProps = isPhysicsProps((_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics) === null || _b === void 0 ? void 0 : _b.properties)
|
|
140
|
+
? (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.physics) === null || _d === void 0 ? void 0 : _d.properties
|
|
141
|
+
: undefined;
|
|
142
|
+
const groupRef = useRef(null);
|
|
143
|
+
const clickValid = useRef(false);
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (editMode) {
|
|
146
|
+
registerRef(gameObject.id, groupRef.current);
|
|
147
|
+
return () => registerRef(gameObject.id, null);
|
|
148
|
+
}
|
|
149
|
+
}, [gameObject.id, registerRef, editMode]);
|
|
150
|
+
const 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;
|
|
151
|
+
if (editMode) {
|
|
152
|
+
return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale, onPointerDown: (e) => { e.stopPropagation(); clickValid.current = true; }, onPointerMove: () => { clickValid.current = false; }, onPointerUp: (e) => {
|
|
153
|
+
if (clickValid.current) {
|
|
154
|
+
e.stopPropagation();
|
|
155
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
|
|
156
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
|
|
157
|
+
}
|
|
158
|
+
clickValid.current = false;
|
|
159
|
+
}, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx(GameInstance, { id: gameObject.id, modelUrl: modelUrl, position: worldPosition, rotation: worldRotation, scale: worldScale, physics: physicsProps })] }));
|
|
160
|
+
}
|
|
161
|
+
return (_jsx(GameInstance, { id: gameObject.id, modelUrl: (_k = (_j = (_h = gameObject.components) === null || _h === void 0 ? void 0 : _h.model) === null || _j === void 0 ? void 0 : _j.properties) === null || _k === void 0 ? void 0 : _k.filename, position: worldPosition, rotation: worldRotation, scale: worldScale, physics: physicsProps }));
|
|
162
|
+
}
|
|
163
|
+
function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
|
|
164
|
+
var _a, _b, _c, _d, _e, _f;
|
|
165
|
+
const groupRef = useRef(null);
|
|
166
|
+
const helperRef = useRef(null);
|
|
167
|
+
const clickValid = useRef(false);
|
|
168
|
+
const isSelected = selectedId === gameObject.id;
|
|
169
|
+
const stillInstanced = useInstanceCheck(gameObject.id);
|
|
170
|
+
useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
registerRef(gameObject.id, groupRef.current);
|
|
173
|
+
return () => registerRef(gameObject.id, null);
|
|
174
|
+
}, [gameObject.id, registerRef]);
|
|
175
|
+
const world = parentMatrix.clone().multiply(compose(gameObject));
|
|
176
|
+
const onDown = (e) => {
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
clickValid.current = true;
|
|
179
|
+
};
|
|
180
|
+
const onUp = (e) => {
|
|
181
|
+
if (clickValid.current) {
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
|
|
184
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
|
|
185
|
+
}
|
|
186
|
+
clickValid.current = false;
|
|
187
|
+
};
|
|
188
|
+
const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
|
|
189
|
+
const ready = !((_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.model) ||
|
|
190
|
+
loadedModels[gameObject.components.model.properties.filename];
|
|
191
|
+
const hasPhysics = physics && ready && !stillInstanced;
|
|
192
|
+
const transform = getNodeTransformProps(gameObject);
|
|
193
|
+
const physicsDef = hasPhysics ? getComponent("Physics") : null;
|
|
194
|
+
const isInstanced = (_e = (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.properties) === null || _e === void 0 ? void 0 : _e.instanced;
|
|
195
|
+
const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
|
|
196
|
+
const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
|
|
197
|
+
if (editMode) {
|
|
198
|
+
return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey)) : null] }));
|
|
199
|
+
}
|
|
200
|
+
if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
|
|
201
|
+
return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey));
|
|
202
|
+
}
|
|
203
|
+
return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, onPointerDown: onDown, onPointerMove: () => (clickValid.current = false), onPointerUp: onUp, children: inner }));
|
|
204
|
+
}
|
|
205
|
+
function walk(node, fn) {
|
|
206
|
+
var _a;
|
|
207
|
+
fn(node);
|
|
208
|
+
(_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => walk(c, fn));
|
|
209
|
+
}
|
|
210
|
+
function compose(node) {
|
|
211
|
+
const { position, rotation, scale } = getNodeTransformProps(node);
|
|
212
|
+
return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
|
|
213
|
+
}
|
|
214
|
+
function decompose(m) {
|
|
215
|
+
const p = new Vector3(), q = new Quaternion(), s = new Vector3();
|
|
216
|
+
m.decompose(p, q, s);
|
|
217
|
+
const e = new Euler().setFromQuaternion(q);
|
|
218
|
+
return {
|
|
219
|
+
position: [p.x, p.y, p.z],
|
|
220
|
+
rotation: [e.x, e.y, e.z],
|
|
221
|
+
scale: [s.x, s.y, s.z],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function getNodeTransformProps(node) {
|
|
225
|
+
var _a, _b, _c, _d, _e;
|
|
226
|
+
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;
|
|
227
|
+
return {
|
|
228
|
+
position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
|
|
229
|
+
rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
|
|
230
|
+
scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function computeParentWorldMatrix(root, targetId) {
|
|
234
|
+
let result = null;
|
|
235
|
+
const visit = (node, parent) => {
|
|
236
|
+
var _a;
|
|
237
|
+
if (node.id === targetId) {
|
|
238
|
+
result = parent.clone();
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const world = parent.clone().multiply(compose(node));
|
|
242
|
+
(_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(c => !result && visit(c, world));
|
|
243
|
+
};
|
|
244
|
+
visit(root, IDENTITY);
|
|
245
|
+
return result !== null && result !== void 0 ? result : IDENTITY;
|
|
246
|
+
}
|
|
247
|
+
function renderCoreNode(gameObject, ctx, parentMatrix) {
|
|
248
|
+
var _a, _b, _c;
|
|
249
|
+
const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
|
|
250
|
+
const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
|
|
251
|
+
const model = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model;
|
|
252
|
+
const geometryDef = geometry && getComponent("Geometry");
|
|
253
|
+
const materialDef = material && getComponent("Material");
|
|
254
|
+
const modelDef = model && getComponent("Model");
|
|
255
|
+
const contextProps = {
|
|
256
|
+
loadedModels: ctx.loadedModels,
|
|
257
|
+
loadedTextures: ctx.loadedTextures,
|
|
258
|
+
editMode: ctx.editMode,
|
|
259
|
+
parentMatrix,
|
|
260
|
+
registerRef: ctx.registerRef,
|
|
261
|
+
};
|
|
262
|
+
const wrappers = [];
|
|
263
|
+
const leaves = [];
|
|
264
|
+
if (gameObject.components) {
|
|
265
|
+
Object.entries(gameObject.components)
|
|
266
|
+
.filter(([k]) => !["geometry", "material", "model", "transform", "physics"].includes(k))
|
|
267
|
+
.forEach(([key, comp]) => {
|
|
268
|
+
if (!(comp === null || comp === void 0 ? void 0 : comp.type))
|
|
269
|
+
return;
|
|
270
|
+
const def = getComponent(comp.type);
|
|
271
|
+
if (!(def === null || def === void 0 ? void 0 : def.View))
|
|
272
|
+
return;
|
|
273
|
+
if (def.View.toString().includes("children")) {
|
|
274
|
+
wrappers.push({ key, View: def.View, properties: comp.properties });
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
leaves.push(_jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key));
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
let core;
|
|
282
|
+
if (model && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)) {
|
|
283
|
+
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] })));
|
|
284
|
+
}
|
|
285
|
+
else if (geometry && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
|
|
286
|
+
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] }));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
core = _jsx(_Fragment, { children: leaves });
|
|
290
|
+
}
|
|
291
|
+
return wrappers.reduce((acc, { key, View, properties }) => (_jsx(View, Object.assign({ properties: properties }, contextProps, { children: acc }), key)), core);
|
|
292
|
+
}
|
|
293
|
+
export default PrefabRoot;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FC } from "react";
|
|
2
|
+
import { ComponentData, GameObject } from "../types";
|
|
3
|
+
export interface Component {
|
|
4
|
+
name: string;
|
|
5
|
+
Editor: FC<{
|
|
6
|
+
node?: GameObject;
|
|
7
|
+
component: ComponentData;
|
|
8
|
+
onUpdate: (newComp: any) => void;
|
|
9
|
+
basePath?: string;
|
|
10
|
+
transformMode?: "translate" | "rotate" | "scale";
|
|
11
|
+
setTransformMode?: (m: "translate" | "rotate" | "scale") => void;
|
|
12
|
+
}>;
|
|
13
|
+
defaultProperties: any;
|
|
14
|
+
View?: FC<any>;
|
|
15
|
+
}
|
|
16
|
+
export declare function registerComponent(component: Component): void;
|
|
17
|
+
export declare function getComponent(name: string): Component | undefined;
|
|
18
|
+
export declare function getAllComponents(): Record<string, Component>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const REGISTRY = {};
|
|
2
|
+
export function registerComponent(component) {
|
|
3
|
+
if (REGISTRY[component.name]) {
|
|
4
|
+
throw new Error(`Component with name ${component.name} already registered.`);
|
|
5
|
+
}
|
|
6
|
+
REGISTRY[component.name] = component;
|
|
7
|
+
}
|
|
8
|
+
export function getComponent(name) {
|
|
9
|
+
return REGISTRY[name];
|
|
10
|
+
}
|
|
11
|
+
export function getAllComponents() {
|
|
12
|
+
return Object.assign({}, REGISTRY);
|
|
13
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect } from "react";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { Vector3 } from "three";
|
|
5
|
+
import { Input, Label } from "./Input";
|
|
6
|
+
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
8
|
+
const props = {
|
|
9
|
+
color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
|
|
10
|
+
intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
|
|
11
|
+
castShadow: (_c = component.properties.castShadow) !== null && _c !== void 0 ? _c : true,
|
|
12
|
+
shadowMapSize: (_d = component.properties.shadowMapSize) !== null && _d !== void 0 ? _d : 1024,
|
|
13
|
+
shadowCameraNear: (_e = component.properties.shadowCameraNear) !== null && _e !== void 0 ? _e : 0.1,
|
|
14
|
+
shadowCameraFar: (_f = component.properties.shadowCameraFar) !== null && _f !== void 0 ? _f : 100,
|
|
15
|
+
shadowCameraTop: (_g = component.properties.shadowCameraTop) !== null && _g !== void 0 ? _g : 30,
|
|
16
|
+
shadowCameraBottom: (_h = component.properties.shadowCameraBottom) !== null && _h !== void 0 ? _h : -30,
|
|
17
|
+
shadowCameraLeft: (_j = component.properties.shadowCameraLeft) !== null && _j !== void 0 ? _j : -30,
|
|
18
|
+
shadowCameraRight: (_k = component.properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30,
|
|
19
|
+
targetOffset: (_l = component.properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0]
|
|
20
|
+
};
|
|
21
|
+
const textInputStyle = {
|
|
22
|
+
flex: 1,
|
|
23
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
24
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
25
|
+
padding: '2px 4px',
|
|
26
|
+
fontSize: '10px',
|
|
27
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
28
|
+
fontFamily: 'monospace',
|
|
29
|
+
outline: 'none',
|
|
30
|
+
};
|
|
31
|
+
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
32
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Color" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: { height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) }), _jsx("input", { type: "text", style: textInputStyle, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { color: e.target.value })) })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Intensity" }), _jsx(Input, { step: "0.1", value: props.intensity, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { intensity: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Cast Shadow" }), _jsx("input", { type: "checkbox", style: { height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }, checked: props.castShadow, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { castShadow: e.target.checked })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Shadow Map Size" }), _jsx(Input, { step: "256", value: props.shadowMapSize, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowMapSize: value })) })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Shadow Camera" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Near" }), _jsx(Input, { step: "0.1", value: props.shadowCameraNear, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraNear: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: "1", value: props.shadowCameraFar, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraFar: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: "1", value: props.shadowCameraTop, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraTop: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: "1", value: props.shadowCameraBottom, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraBottom: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: "1", value: props.shadowCameraLeft, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraLeft: value })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: "1", value: props.shadowCameraRight, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { shadowCameraRight: value })) })] })] })] }), _jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Target Offset" }), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }, children: [_jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "X" }), _jsx(Input, { step: "0.5", value: props.targetOffset[0], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [value, props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: "0.5", value: props.targetOffset[1], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], value, props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: "0.5", value: props.targetOffset[2], onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { targetOffset: [props.targetOffset[0], props.targetOffset[1], value] })) })] })] })] })] });
|
|
33
|
+
}
|
|
34
|
+
function DirectionalLightView({ properties, editMode }) {
|
|
35
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
36
|
+
const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
|
|
37
|
+
const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
|
|
38
|
+
const castShadow = (_c = properties.castShadow) !== null && _c !== void 0 ? _c : true;
|
|
39
|
+
const shadowMapSize = (_d = properties.shadowMapSize) !== null && _d !== void 0 ? _d : 1024;
|
|
40
|
+
const shadowCameraNear = (_e = properties.shadowCameraNear) !== null && _e !== void 0 ? _e : 0.1;
|
|
41
|
+
const shadowCameraFar = (_f = properties.shadowCameraFar) !== null && _f !== void 0 ? _f : 100;
|
|
42
|
+
const shadowCameraTop = (_g = properties.shadowCameraTop) !== null && _g !== void 0 ? _g : 30;
|
|
43
|
+
const shadowCameraBottom = (_h = properties.shadowCameraBottom) !== null && _h !== void 0 ? _h : -30;
|
|
44
|
+
const shadowCameraLeft = (_j = properties.shadowCameraLeft) !== null && _j !== void 0 ? _j : -30;
|
|
45
|
+
const shadowCameraRight = (_k = properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30;
|
|
46
|
+
const targetOffset = (_l = properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0];
|
|
47
|
+
const directionalLightRef = useRef(null);
|
|
48
|
+
const targetRef = useRef(null);
|
|
49
|
+
// Set up light target reference when both refs are ready
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (directionalLightRef.current && targetRef.current) {
|
|
52
|
+
directionalLightRef.current.target = targetRef.current;
|
|
53
|
+
}
|
|
54
|
+
}, []);
|
|
55
|
+
// Update target world position based on light position + offset
|
|
56
|
+
useFrame(() => {
|
|
57
|
+
if (!directionalLightRef.current || !targetRef.current)
|
|
58
|
+
return;
|
|
59
|
+
const lightWorldPos = new Vector3();
|
|
60
|
+
directionalLightRef.current.getWorldPosition(lightWorldPos);
|
|
61
|
+
// Target is positioned relative to light's world position
|
|
62
|
+
targetRef.current.position.set(lightWorldPos.x + targetOffset[0], lightWorldPos.y + targetOffset[1], lightWorldPos.z + targetOffset[2]);
|
|
63
|
+
});
|
|
64
|
+
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) => {
|
|
65
|
+
const points = [
|
|
66
|
+
new Vector3(0, 0, 0),
|
|
67
|
+
new Vector3(targetOffset[0], targetOffset[1], targetOffset[2])
|
|
68
|
+
];
|
|
69
|
+
geo.setFromPoints(points);
|
|
70
|
+
} }), _jsx("lineBasicMaterial", { color: color, opacity: 0.6, transparent: true })] })] }))] }));
|
|
71
|
+
}
|
|
72
|
+
const DirectionalLightComponent = {
|
|
73
|
+
name: 'DirectionalLight',
|
|
74
|
+
Editor: DirectionalLightComponentEditor,
|
|
75
|
+
View: DirectionalLightView,
|
|
76
|
+
defaultProperties: {}
|
|
77
|
+
};
|
|
78
|
+
export default DirectionalLightComponent;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Input, Label } from "./Input";
|
|
3
|
+
const GEOMETRY_ARGS = {
|
|
4
|
+
box: {
|
|
5
|
+
labels: ["Width", "Height", "Depth"],
|
|
6
|
+
defaults: [1, 1, 1],
|
|
7
|
+
},
|
|
8
|
+
sphere: {
|
|
9
|
+
labels: ["Radius", "Width Segments", "Height Segments"],
|
|
10
|
+
defaults: [1, 32, 16],
|
|
11
|
+
},
|
|
12
|
+
plane: {
|
|
13
|
+
labels: ["Width", "Height"],
|
|
14
|
+
defaults: [1, 1],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
function GeometryComponentEditor({ component, onUpdate, }) {
|
|
18
|
+
const { geometryType, args = [] } = component.properties;
|
|
19
|
+
const schema = GEOMETRY_ARGS[geometryType];
|
|
20
|
+
const selectStyle = {
|
|
21
|
+
width: '100%',
|
|
22
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
23
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
24
|
+
padding: '2px 4px',
|
|
25
|
+
fontSize: '10px',
|
|
26
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
27
|
+
fontFamily: 'monospace',
|
|
28
|
+
outline: 'none',
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Type" }), _jsxs("select", { style: selectStyle, value: geometryType, onChange: e => {
|
|
31
|
+
const type = e.target.value;
|
|
32
|
+
onUpdate({ geometryType: type, args: GEOMETRY_ARGS[type].defaults });
|
|
33
|
+
}, children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] }), schema.labels.map((label, i) => {
|
|
34
|
+
var _a;
|
|
35
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = args[i]) !== null && _a !== void 0 ? _a : schema.defaults[i], step: "0.1", onChange: value => {
|
|
36
|
+
const next = [...args];
|
|
37
|
+
next[i] = value;
|
|
38
|
+
onUpdate({ args: next });
|
|
39
|
+
} })] }, label));
|
|
40
|
+
})] }));
|
|
41
|
+
}
|
|
42
|
+
// View for Geometry component
|
|
43
|
+
function GeometryComponentView({ properties, children }) {
|
|
44
|
+
const { geometryType, args = [] } = properties;
|
|
45
|
+
// Only return the geometry node, do not wrap in mesh or group
|
|
46
|
+
switch (geometryType) {
|
|
47
|
+
case "box":
|
|
48
|
+
return _jsx("boxGeometry", { args: args });
|
|
49
|
+
case "sphere":
|
|
50
|
+
return _jsx("sphereGeometry", { args: args });
|
|
51
|
+
case "plane":
|
|
52
|
+
return _jsx("planeGeometry", { args: args });
|
|
53
|
+
default:
|
|
54
|
+
return _jsx("boxGeometry", { args: [1, 1, 1] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const GeometryComponent = {
|
|
58
|
+
name: 'Geometry',
|
|
59
|
+
Editor: GeometryComponentEditor,
|
|
60
|
+
View: GeometryComponentView,
|
|
61
|
+
defaultProperties: {
|
|
62
|
+
geometryType: 'box',
|
|
63
|
+
args: GEOMETRY_ARGS.box.defaults,
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export default GeometryComponent;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface InputProps {
|
|
3
|
+
value: number;
|
|
4
|
+
onChange: (value: number) => void;
|
|
5
|
+
step?: string | number;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
export declare function Input({ value, onChange, step, min, max, style }: InputProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export declare function Label({ children }: {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function Vector3Input({ label, value, onChange, snap }: {
|
|
15
|
+
label: string;
|
|
16
|
+
value: [number, number, number];
|
|
17
|
+
onChange: (v: [number, number, number]) => void;
|
|
18
|
+
snap?: number;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
// Shared styles
|
|
4
|
+
const styles = {
|
|
5
|
+
input: {
|
|
6
|
+
width: '100%',
|
|
7
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
8
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
9
|
+
padding: '2px 4px',
|
|
10
|
+
fontSize: '10px',
|
|
11
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
12
|
+
fontFamily: 'monospace',
|
|
13
|
+
outline: 'none',
|
|
14
|
+
},
|
|
15
|
+
label: {
|
|
16
|
+
display: 'block',
|
|
17
|
+
fontSize: '9px',
|
|
18
|
+
color: 'rgba(34, 211, 238, 0.6)',
|
|
19
|
+
textTransform: 'uppercase',
|
|
20
|
+
letterSpacing: '0.05em',
|
|
21
|
+
marginBottom: 2,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
export function Input({ value, onChange, step, min, max, style }) {
|
|
25
|
+
return (_jsx("input", { type: "number", value: value, onChange: (e) => onChange(parseFloat(e.target.value)), step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) }));
|
|
26
|
+
}
|
|
27
|
+
export function Label({ children }) {
|
|
28
|
+
return _jsx("label", { style: styles.label, children: children });
|
|
29
|
+
}
|
|
30
|
+
export function Vector3Input({ label, value, onChange, snap }) {
|
|
31
|
+
const snapValue = (num) => {
|
|
32
|
+
if (!snap)
|
|
33
|
+
return num;
|
|
34
|
+
return Math.round(num / snap) * snap;
|
|
35
|
+
};
|
|
36
|
+
const [draft, setDraft] = useState(() => value.map(v => v.toString()));
|
|
37
|
+
// Sync external changes (gizmo, undo, etc.)
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
setDraft(value.map(v => v.toString()));
|
|
40
|
+
}, [value[0], value[1], value[2]]);
|
|
41
|
+
const dragState = useRef(null);
|
|
42
|
+
const commit = (index) => {
|
|
43
|
+
const num = parseFloat(draft[index]);
|
|
44
|
+
if (Number.isFinite(num)) {
|
|
45
|
+
const next = [...value];
|
|
46
|
+
next[index] = snapValue(num);
|
|
47
|
+
onChange(next);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const startScrub = (e, index) => {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
dragState.current = {
|
|
53
|
+
index,
|
|
54
|
+
startX: e.clientX,
|
|
55
|
+
startValue: value[index]
|
|
56
|
+
};
|
|
57
|
+
e.target.setPointerCapture(e.pointerId);
|
|
58
|
+
document.body.style.cursor = "ew-resize";
|
|
59
|
+
};
|
|
60
|
+
const onScrubMove = (e) => {
|
|
61
|
+
if (!dragState.current)
|
|
62
|
+
return;
|
|
63
|
+
const { index, startX, startValue } = dragState.current;
|
|
64
|
+
const dx = e.clientX - startX;
|
|
65
|
+
let speed = 0.02;
|
|
66
|
+
if (e.shiftKey)
|
|
67
|
+
speed *= 0.1; // fine
|
|
68
|
+
if (e.altKey)
|
|
69
|
+
speed *= 5; // coarse
|
|
70
|
+
const rawValue = startValue + dx * speed;
|
|
71
|
+
const nextValue = snapValue(rawValue);
|
|
72
|
+
const next = [...value];
|
|
73
|
+
next[index] = nextValue;
|
|
74
|
+
setDraft(d => {
|
|
75
|
+
const copy = [...d];
|
|
76
|
+
copy[index] = nextValue.toFixed(3);
|
|
77
|
+
return copy;
|
|
78
|
+
});
|
|
79
|
+
onChange(next);
|
|
80
|
+
};
|
|
81
|
+
const endScrub = (e) => {
|
|
82
|
+
if (!dragState.current)
|
|
83
|
+
return;
|
|
84
|
+
dragState.current = null;
|
|
85
|
+
document.body.style.cursor = "";
|
|
86
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
87
|
+
};
|
|
88
|
+
const axes = [
|
|
89
|
+
{ key: "x", color: 'rgba(248, 113, 113, 1)', index: 0 },
|
|
90
|
+
{ key: "y", color: 'rgba(134, 239, 172, 1)', index: 1 },
|
|
91
|
+
{ key: "z", color: 'rgba(96, 165, 250, 1)', index: 2 }
|
|
92
|
+
];
|
|
93
|
+
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 4 }), children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
|
|
94
|
+
flex: 1,
|
|
95
|
+
display: 'flex',
|
|
96
|
+
alignItems: 'center',
|
|
97
|
+
gap: 4,
|
|
98
|
+
backgroundColor: 'rgba(0, 0, 0, 0.3)',
|
|
99
|
+
border: '1px solid rgba(34, 211, 238, 0.2)',
|
|
100
|
+
borderRadius: 4,
|
|
101
|
+
padding: '4px 6px',
|
|
102
|
+
minHeight: 32,
|
|
103
|
+
}, children: [_jsx("span", { style: {
|
|
104
|
+
fontSize: '12px',
|
|
105
|
+
fontWeight: 'bold',
|
|
106
|
+
color,
|
|
107
|
+
width: 12,
|
|
108
|
+
cursor: 'ew-resize',
|
|
109
|
+
userSelect: 'none',
|
|
110
|
+
}, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: key.toUpperCase() }), _jsx("input", { style: {
|
|
111
|
+
flex: 1,
|
|
112
|
+
backgroundColor: 'transparent',
|
|
113
|
+
border: 'none',
|
|
114
|
+
fontSize: '12px',
|
|
115
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
116
|
+
fontFamily: 'monospace',
|
|
117
|
+
outline: 'none',
|
|
118
|
+
width: '100%',
|
|
119
|
+
minWidth: 0,
|
|
120
|
+
}, type: "text", value: draft[index], onChange: e => {
|
|
121
|
+
const next = [...draft];
|
|
122
|
+
next[index] = e.target.value;
|
|
123
|
+
setDraft(next);
|
|
124
|
+
}, onBlur: () => commit(index), onKeyDown: e => {
|
|
125
|
+
if (e.key === "Enter") {
|
|
126
|
+
e.target.blur();
|
|
127
|
+
}
|
|
128
|
+
} })] }, key))) })] }));
|
|
129
|
+
}
|