react-three-game 0.0.29 → 0.0.30
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 +18 -0
- package/assets/architecture.png +0 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -1
- package/dist/tools/prefabeditor/components/MaterialComponent.js +2 -2
- package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +3 -3
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/TransformComponent.js +2 -2
- package/package.json +1 -1
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +35 -35
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +2 -2
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +19 -19
- package/src/tools/prefabeditor/components/ModelComponent.tsx +6 -6
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +6 -6
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +15 -15
- package/src/tools/prefabeditor/components/TransformComponent.tsx +9 -9
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ npm i react-three-game @react-three/fiber @react-three/rapier three
|
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|

|
|
10
|
+

|
|
10
11
|
|
|
11
12
|
## Usage
|
|
12
13
|
|
|
@@ -132,3 +133,20 @@ npm run release # build + publish
|
|
|
132
133
|
---
|
|
133
134
|
|
|
134
135
|
React 19 · Three.js WebGPU · TypeScript 5 · Rapier WASM · MIT License
|
|
136
|
+
|
|
137
|
+
## Manifest generation script
|
|
138
|
+
|
|
139
|
+
A small helper script is included to auto-generate asset manifests from the `public` folder. See `docs/generate-manifests.sh`.
|
|
140
|
+
|
|
141
|
+
- What it does: searches `public/models` for `.glb`/`.fbx`, `public/textures` for `.jpg`/`.png`, and `public/sound` for `.mp3`/`.wav`, then writes JSON arrays to `public/models/manifest.json`, `public/textures/manifest.json`, and `public/sound/manifest.json`. These manifest files are used top populate the Asset Viewer in the the Editor.
|
|
142
|
+
- How to run:
|
|
143
|
+
|
|
144
|
+
1. Make it executable (once):
|
|
145
|
+
|
|
146
|
+
chmod +x docs/generate-manifests.sh
|
|
147
|
+
|
|
148
|
+
2. Run the script from the repo root (zsh/bash):
|
|
149
|
+
|
|
150
|
+
./docs/generate-manifests.sh
|
|
151
|
+
|
|
152
|
+
The script is intentionally simple and portable (uses `find`/`sed`). If you need different file types or output formatting, edit `docs/generate-manifests.sh`.
|
|
Binary file
|
|
@@ -17,7 +17,7 @@ function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
|
17
17
|
shadowCameraRight: (_k = component.properties.shadowCameraRight) !== null && _k !== void 0 ? _k : 30,
|
|
18
18
|
targetOffset: (_l = component.properties.targetOffset) !== null && _l !== void 0 ? _l : [0, -5, 0]
|
|
19
19
|
};
|
|
20
|
-
return _jsxs("div", {
|
|
20
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, 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: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, 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", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Shadow Map Size" }), _jsx("input", { type: "number", step: "256", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowMapSize, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowMapSize': parseFloat(e.target.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: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Near" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraNear, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraNear': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Far" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraFar, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraFar': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Top" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraTop, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraTop': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Bottom" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraBottom, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraBottom': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Left" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraLeft, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraLeft': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Right" }), _jsx("input", { type: "number", step: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.shadowCameraRight, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'shadowCameraRight': parseFloat(e.target.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: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "X" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[0], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [parseFloat(e.target.value), props.targetOffset[1], props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Y" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[1], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [props.targetOffset[0], parseFloat(e.target.value), props.targetOffset[2]] })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }, children: "Z" }), _jsx("input", { type: "number", step: "0.5", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.targetOffset[2], onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'targetOffset': [props.targetOffset[0], props.targetOffset[1], parseFloat(e.target.value)] })) })] })] })] })] });
|
|
21
21
|
}
|
|
22
22
|
function DirectionalLightView({ properties, editMode }) {
|
|
23
23
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
function GeometryComponentEditor({ component, onUpdate }) {
|
|
3
|
-
return _jsxs("div", { children: [_jsx("label", {
|
|
3
|
+
return _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Type" }), _jsxs("select", { style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: component.properties.geometryType, onChange: e => onUpdate({ geometryType: e.target.value }), children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] });
|
|
4
4
|
}
|
|
5
5
|
// View for Geometry component
|
|
6
6
|
function GeometryComponentView({ properties, children }) {
|
|
@@ -11,11 +11,11 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
11
11
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
12
12
|
.catch(console.error);
|
|
13
13
|
}, [basePath]);
|
|
14
|
-
return (_jsxs("div", {
|
|
14
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, 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: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", style: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Texture" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }), basePath: basePath }) })] }), component.properties.texture && (_jsxs("div", { style: { borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 4, marginTop: 4 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }, children: [_jsx("input", { type: "checkbox", style: { width: 12, height: 12 }, checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Repeat (X, Y)" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "number", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
|
|
15
15
|
var _a, _b;
|
|
16
16
|
const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
|
|
17
17
|
onUpdate({ 'repeatCount': [parseFloat(e.target.value), y] });
|
|
18
|
-
} }), _jsx("input", { type: "number",
|
|
18
|
+
} }), _jsx("input", { type: "number", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: e => {
|
|
19
19
|
var _a, _b;
|
|
20
20
|
const x = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1;
|
|
21
21
|
onUpdate({ 'repeatCount': [x, parseFloat(e.target.value)] });
|
|
@@ -15,7 +15,7 @@ function ModelComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
15
15
|
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
16
16
|
onUpdate({ 'filename': filename });
|
|
17
17
|
};
|
|
18
|
-
return _jsxs("div", { children: [_jsxs("div", {
|
|
18
|
+
return _jsxs("div", { children: [_jsxs("div", { style: { marginBottom: 4 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Model" }), _jsx("div", { style: { maxHeight: 128, overflowY: 'auto' }, children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect, basePath: basePath }) })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), style: { width: 12, height: 12 } }), _jsx("label", { htmlFor: "instanced-checkbox", style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Instanced" })] })] });
|
|
19
19
|
}
|
|
20
20
|
// View for Model component
|
|
21
21
|
function ModelComponentView({ properties, loadedModels, children }) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { RigidBody } from "@react-three/rapier";
|
|
3
|
-
const selectClass =
|
|
4
|
-
const labelClass =
|
|
3
|
+
const selectClass = { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' };
|
|
4
|
+
const labelClass = { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 };
|
|
5
5
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
6
6
|
const { type = 'dynamic', collider = 'hull' } = component.properties;
|
|
7
|
-
return (_jsxs("div", { children: [_jsx("label", {
|
|
7
|
+
return (_jsxs("div", { children: [_jsx("label", { style: labelClass, children: "Type" }), _jsxs("select", { style: 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", { style: Object.assign(Object.assign({}, labelClass), { marginTop: 8 }), children: "Collider" }), _jsxs("select", { style: 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)" })] })] }));
|
|
8
8
|
}
|
|
9
9
|
function PhysicsComponentView({ properties, editMode, children }) {
|
|
10
10
|
if (editMode)
|
|
@@ -10,7 +10,7 @@ function SpotLightComponentEditor({ component, onUpdate }) {
|
|
|
10
10
|
distance: (_e = component.properties.distance) !== null && _e !== void 0 ? _e : 100,
|
|
11
11
|
castShadow: (_f = component.properties.castShadow) !== null && _f !== void 0 ? _f : true
|
|
12
12
|
};
|
|
13
|
-
return _jsxs("div", {
|
|
13
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, 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: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.color, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'color': e.target.value })) })] })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.intensity, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'intensity': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Angle" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: Math.PI, style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.angle, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'angle': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Penumbra" }), _jsx("input", { type: "number", step: "0.1", min: "0", max: "1", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.penumbra, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'penumbra': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, children: "Distance" }), _jsx("input", { type: "number", step: "1", min: "0", style: { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }, value: props.distance, onChange: e => onUpdate(Object.assign(Object.assign({}, component.properties), { 'distance': parseFloat(e.target.value) })) })] }), _jsxs("div", { children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }, 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 })) })] })] });
|
|
14
14
|
}
|
|
15
15
|
function SpotLightView({ properties, editMode }) {
|
|
16
16
|
var _a, _b, _c, _d, _e, _f;
|
|
@@ -15,7 +15,7 @@ function TransformComponentEditor({ component, onUpdate, transformMode, setTrans
|
|
|
15
15
|
background: 'rgba(255,255,255,0.10)',
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
|
-
return _jsxs("div", {
|
|
18
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [transformMode && setTransformMode && (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: "Transform Mode" }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign(Object.assign({}, s.button), { flex: 1 }), (transformMode === mode ? s.buttonActive : {})), onPointerEnter: (e) => {
|
|
19
19
|
if (transformMode !== mode)
|
|
20
20
|
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
21
21
|
}, onPointerLeave: (e) => {
|
|
@@ -90,7 +90,7 @@ export function Vector3Input({ label, value, onChange }) {
|
|
|
90
90
|
{ key: "y", color: "green", index: 1 },
|
|
91
91
|
{ key: "z", color: "blue", index: 2 }
|
|
92
92
|
];
|
|
93
|
-
return (_jsxs("div", {
|
|
93
|
+
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }, children: label }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: { flex: 1, display: 'flex', alignItems: 'center', gap: 4, backgroundColor: 'rgba(0, 0, 0, 0.3)', border: '1px solid rgba(34, 211, 238, 0.2)', borderRadius: 4, padding: '4px 6px', minHeight: 32 }, children: [_jsx("span", { style: { fontSize: '12px', fontWeight: 'bold', color: color === 'red' ? 'rgba(248, 113, 113, 1)' : color === 'green' ? 'rgba(134, 239, 172, 1)' : 'rgba(96, 165, 250, 1)', width: 12, cursor: 'ew-resize', userSelect: 'none' }, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: key.toUpperCase() }), _jsx("input", { style: { flex: 1, backgroundColor: 'transparent', fontSize: '12px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none', width: '100%', minWidth: 0 }, type: "text", value: draft[index], onChange: e => {
|
|
94
94
|
const next = [...draft];
|
|
95
95
|
next[index] = e.target.value;
|
|
96
96
|
setDraft(next);
|
package/package.json
CHANGED
|
@@ -18,127 +18,127 @@ function DirectionalLightComponentEditor({ component, onUpdate }: { component: a
|
|
|
18
18
|
targetOffset: component.properties.targetOffset ?? [0, -5, 0]
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
return <div
|
|
21
|
+
return <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
22
22
|
<div>
|
|
23
|
-
<label
|
|
24
|
-
<div
|
|
23
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Color</label>
|
|
24
|
+
<div style={{ display: 'flex', gap: 2 }}>
|
|
25
25
|
<input
|
|
26
26
|
type="color"
|
|
27
|
-
|
|
27
|
+
style={{ height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }}
|
|
28
28
|
value={props.color}
|
|
29
29
|
onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
|
|
30
30
|
/>
|
|
31
31
|
<input
|
|
32
32
|
type="text"
|
|
33
|
-
|
|
33
|
+
style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
34
34
|
value={props.color}
|
|
35
35
|
onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
|
|
36
36
|
/>
|
|
37
37
|
</div>
|
|
38
38
|
</div>
|
|
39
39
|
<div>
|
|
40
|
-
<label
|
|
40
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Intensity</label>
|
|
41
41
|
<input
|
|
42
42
|
type="number"
|
|
43
43
|
step="0.1"
|
|
44
|
-
|
|
44
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
45
45
|
value={props.intensity}
|
|
46
46
|
onChange={e => onUpdate({ ...component.properties, 'intensity': parseFloat(e.target.value) })}
|
|
47
47
|
/>
|
|
48
48
|
</div>
|
|
49
49
|
<div>
|
|
50
|
-
<label
|
|
50
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Cast Shadow</label>
|
|
51
51
|
<input
|
|
52
52
|
type="checkbox"
|
|
53
|
-
|
|
53
|
+
style={{ height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }}
|
|
54
54
|
checked={props.castShadow}
|
|
55
55
|
onChange={e => onUpdate({ ...component.properties, 'castShadow': e.target.checked })}
|
|
56
56
|
/>
|
|
57
57
|
</div>
|
|
58
58
|
<div>
|
|
59
|
-
<label
|
|
59
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Shadow Map Size</label>
|
|
60
60
|
<input
|
|
61
61
|
type="number"
|
|
62
62
|
step="256"
|
|
63
|
-
|
|
63
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
64
64
|
value={props.shadowMapSize}
|
|
65
65
|
onChange={e => onUpdate({ ...component.properties, 'shadowMapSize': parseFloat(e.target.value) })}
|
|
66
66
|
/>
|
|
67
67
|
</div>
|
|
68
|
-
<div
|
|
69
|
-
<label
|
|
70
|
-
<div
|
|
68
|
+
<div style={{ borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }}>
|
|
69
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>Shadow Camera</label>
|
|
70
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
|
|
71
71
|
<div>
|
|
72
|
-
<label
|
|
72
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Near</label>
|
|
73
73
|
<input
|
|
74
74
|
type="number"
|
|
75
75
|
step="0.1"
|
|
76
|
-
|
|
76
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
77
77
|
value={props.shadowCameraNear}
|
|
78
78
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraNear': parseFloat(e.target.value) })}
|
|
79
79
|
/>
|
|
80
80
|
</div>
|
|
81
81
|
<div>
|
|
82
|
-
<label
|
|
82
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Far</label>
|
|
83
83
|
<input
|
|
84
84
|
type="number"
|
|
85
85
|
step="1"
|
|
86
|
-
|
|
86
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
87
87
|
value={props.shadowCameraFar}
|
|
88
88
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraFar': parseFloat(e.target.value) })}
|
|
89
89
|
/>
|
|
90
90
|
</div>
|
|
91
91
|
<div>
|
|
92
|
-
<label
|
|
92
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Top</label>
|
|
93
93
|
<input
|
|
94
94
|
type="number"
|
|
95
95
|
step="1"
|
|
96
|
-
|
|
96
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
97
97
|
value={props.shadowCameraTop}
|
|
98
98
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraTop': parseFloat(e.target.value) })}
|
|
99
99
|
/>
|
|
100
100
|
</div>
|
|
101
101
|
<div>
|
|
102
|
-
<label
|
|
102
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Bottom</label>
|
|
103
103
|
<input
|
|
104
104
|
type="number"
|
|
105
105
|
step="1"
|
|
106
|
-
|
|
106
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
107
107
|
value={props.shadowCameraBottom}
|
|
108
108
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraBottom': parseFloat(e.target.value) })}
|
|
109
109
|
/>
|
|
110
110
|
</div>
|
|
111
111
|
<div>
|
|
112
|
-
<label
|
|
112
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Left</label>
|
|
113
113
|
<input
|
|
114
114
|
type="number"
|
|
115
115
|
step="1"
|
|
116
|
-
|
|
116
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
117
117
|
value={props.shadowCameraLeft}
|
|
118
118
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraLeft': parseFloat(e.target.value) })}
|
|
119
119
|
/>
|
|
120
120
|
</div>
|
|
121
121
|
<div>
|
|
122
|
-
<label
|
|
122
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Right</label>
|
|
123
123
|
<input
|
|
124
124
|
type="number"
|
|
125
125
|
step="1"
|
|
126
|
-
|
|
126
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
127
127
|
value={props.shadowCameraRight}
|
|
128
128
|
onChange={e => onUpdate({ ...component.properties, 'shadowCameraRight': parseFloat(e.target.value) })}
|
|
129
129
|
/>
|
|
130
130
|
</div>
|
|
131
131
|
</div>
|
|
132
132
|
</div>
|
|
133
|
-
<div
|
|
134
|
-
<label
|
|
135
|
-
<div
|
|
133
|
+
<div style={{ borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 8, marginTop: 8 }}>
|
|
134
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>Target Offset</label>
|
|
135
|
+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4 }}>
|
|
136
136
|
<div>
|
|
137
|
-
<label
|
|
137
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>X</label>
|
|
138
138
|
<input
|
|
139
139
|
type="number"
|
|
140
140
|
step="0.5"
|
|
141
|
-
|
|
141
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
142
142
|
value={props.targetOffset[0]}
|
|
143
143
|
onChange={e => onUpdate({
|
|
144
144
|
...component.properties,
|
|
@@ -147,11 +147,11 @@ function DirectionalLightComponentEditor({ component, onUpdate }: { component: a
|
|
|
147
147
|
/>
|
|
148
148
|
</div>
|
|
149
149
|
<div>
|
|
150
|
-
<label
|
|
150
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Y</label>
|
|
151
151
|
<input
|
|
152
152
|
type="number"
|
|
153
153
|
step="0.5"
|
|
154
|
-
|
|
154
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
155
155
|
value={props.targetOffset[1]}
|
|
156
156
|
onChange={e => onUpdate({
|
|
157
157
|
...component.properties,
|
|
@@ -160,11 +160,11 @@ function DirectionalLightComponentEditor({ component, onUpdate }: { component: a
|
|
|
160
160
|
/>
|
|
161
161
|
</div>
|
|
162
162
|
<div>
|
|
163
|
-
<label
|
|
163
|
+
<label style={{ display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 }}>Z</label>
|
|
164
164
|
<input
|
|
165
165
|
type="number"
|
|
166
166
|
step="0.5"
|
|
167
|
-
|
|
167
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
168
168
|
value={props.targetOffset[2]}
|
|
169
169
|
onChange={e => onUpdate({
|
|
170
170
|
...component.properties,
|
|
@@ -2,9 +2,9 @@ import { Component } from "./ComponentRegistry";
|
|
|
2
2
|
|
|
3
3
|
function GeometryComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
|
|
4
4
|
return <div>
|
|
5
|
-
<label
|
|
5
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Type</label>
|
|
6
6
|
<select
|
|
7
|
-
|
|
7
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
8
8
|
value={component.properties.geometryType}
|
|
9
9
|
onChange={e => onUpdate({ geometryType: e.target.value })}
|
|
10
10
|
>
|
|
@@ -14,37 +14,37 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }: { compo
|
|
|
14
14
|
}, [basePath]);
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
<div
|
|
18
|
-
<div
|
|
19
|
-
<label
|
|
20
|
-
<div
|
|
17
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
18
|
+
<div style={{ marginBottom: 4 }}>
|
|
19
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Color</label>
|
|
20
|
+
<div style={{ display: 'flex', gap: 2 }}>
|
|
21
21
|
<input
|
|
22
22
|
type="color"
|
|
23
|
-
|
|
23
|
+
style={{ height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }}
|
|
24
24
|
value={component.properties.color}
|
|
25
25
|
onChange={e => onUpdate({ 'color': e.target.value })}
|
|
26
26
|
/>
|
|
27
27
|
<input
|
|
28
28
|
type="text"
|
|
29
|
-
|
|
29
|
+
style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
30
30
|
value={component.properties.color}
|
|
31
31
|
onChange={e => onUpdate({ 'color': e.target.value })}
|
|
32
32
|
/>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
35
|
-
<div
|
|
35
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }}>
|
|
36
36
|
<input
|
|
37
37
|
type="checkbox"
|
|
38
|
-
|
|
38
|
+
style={{ width: 12, height: 12 }}
|
|
39
39
|
checked={component.properties.wireframe || false}
|
|
40
40
|
onChange={e => onUpdate({ 'wireframe': e.target.checked })}
|
|
41
41
|
/>
|
|
42
|
-
<label
|
|
42
|
+
<label style={{ fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }}>Wireframe</label>
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
<div>
|
|
46
|
-
<label
|
|
47
|
-
<div
|
|
46
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Texture</label>
|
|
47
|
+
<div style={{ maxHeight: 128, overflowY: 'auto' }}>
|
|
48
48
|
<TextureListViewer
|
|
49
49
|
files={textureFiles}
|
|
50
50
|
selected={component.properties.texture || undefined}
|
|
@@ -55,24 +55,24 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }: { compo
|
|
|
55
55
|
</div>
|
|
56
56
|
|
|
57
57
|
{component.properties.texture && (
|
|
58
|
-
<div
|
|
59
|
-
<div
|
|
58
|
+
<div style={{ borderTop: '1px solid rgba(34, 211, 238, 0.2)', paddingTop: 4, marginTop: 4 }}>
|
|
59
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginBottom: 4 }}>
|
|
60
60
|
<input
|
|
61
61
|
type="checkbox"
|
|
62
|
-
|
|
62
|
+
style={{ width: 12, height: 12 }}
|
|
63
63
|
checked={component.properties.repeat || false}
|
|
64
64
|
onChange={e => onUpdate({ 'repeat': e.target.checked })}
|
|
65
65
|
/>
|
|
66
|
-
<label
|
|
66
|
+
<label style={{ fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }}>Repeat Texture</label>
|
|
67
67
|
</div>
|
|
68
68
|
|
|
69
69
|
{component.properties.repeat && (
|
|
70
70
|
<div>
|
|
71
|
-
<label
|
|
72
|
-
<div
|
|
71
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Repeat (X, Y)</label>
|
|
72
|
+
<div style={{ display: 'flex', gap: 2 }}>
|
|
73
73
|
<input
|
|
74
74
|
type="number"
|
|
75
|
-
|
|
75
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
76
76
|
value={component.properties.repeatCount?.[0] ?? 1}
|
|
77
77
|
onChange={e => {
|
|
78
78
|
const y = component.properties.repeatCount?.[1] ?? 1;
|
|
@@ -81,7 +81,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }: { compo
|
|
|
81
81
|
/>
|
|
82
82
|
<input
|
|
83
83
|
type="number"
|
|
84
|
-
|
|
84
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
85
85
|
value={component.properties.repeatCount?.[1] ?? 1}
|
|
86
86
|
onChange={e => {
|
|
87
87
|
const x = component.properties.repeatCount?.[0] ?? 1;
|
|
@@ -20,9 +20,9 @@ function ModelComponentEditor({ component, onUpdate, basePath = "" }: { componen
|
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
return <div>
|
|
23
|
-
<div
|
|
24
|
-
<label
|
|
25
|
-
<div
|
|
23
|
+
<div style={{ marginBottom: 4 }}>
|
|
24
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Model</label>
|
|
25
|
+
<div style={{ maxHeight: 128, overflowY: 'auto' }}>
|
|
26
26
|
<ModelListViewer
|
|
27
27
|
files={modelFiles}
|
|
28
28
|
selected={component.properties.filename ? `/${component.properties.filename}` : undefined}
|
|
@@ -31,15 +31,15 @@ function ModelComponentEditor({ component, onUpdate, basePath = "" }: { componen
|
|
|
31
31
|
/>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
34
|
-
<div
|
|
34
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
35
35
|
<input
|
|
36
36
|
type="checkbox"
|
|
37
37
|
id="instanced-checkbox"
|
|
38
38
|
checked={component.properties.instanced || false}
|
|
39
39
|
onChange={e => onUpdate({ 'instanced': e.target.checked })}
|
|
40
|
-
|
|
40
|
+
style={{ width: 12, height: 12 }}
|
|
41
41
|
/>
|
|
42
|
-
<label htmlFor="instanced-checkbox"
|
|
42
|
+
<label htmlFor="instanced-checkbox" style={{ fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }}>Instanced</label>
|
|
43
43
|
</div>
|
|
44
44
|
</div>;
|
|
45
45
|
}
|
|
@@ -2,21 +2,21 @@ import { RigidBody } from "@react-three/rapier";
|
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
3
|
import { Component } from "./ComponentRegistry";
|
|
4
4
|
|
|
5
|
-
const selectClass =
|
|
6
|
-
const labelClass =
|
|
5
|
+
const selectClass = { width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' };
|
|
6
|
+
const labelClass = { display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 };
|
|
7
7
|
|
|
8
8
|
function PhysicsComponentEditor({ component, onUpdate }: { component: { properties: { type?: 'dynamic' | 'fixed'; collider?: string;[k: string]: any } }; onUpdate: (props: Partial<Record<string, any>>) => void }) {
|
|
9
9
|
const { type = 'dynamic', collider = 'hull' } = component.properties;
|
|
10
10
|
return (
|
|
11
11
|
<div>
|
|
12
|
-
<label
|
|
13
|
-
<select
|
|
12
|
+
<label style={labelClass}>Type</label>
|
|
13
|
+
<select style={selectClass as any} value={type} onChange={e => onUpdate({ type: e.target.value })}>
|
|
14
14
|
<option value="dynamic">Dynamic</option>
|
|
15
15
|
<option value="fixed">Fixed</option>
|
|
16
16
|
</select>
|
|
17
17
|
|
|
18
|
-
<label
|
|
19
|
-
<select
|
|
18
|
+
<label style={{ ...labelClass, marginTop: 8 }}>Collider</label>
|
|
19
|
+
<select style={selectClass as any} value={collider} onChange={e => onUpdate({ collider: e.target.value })}>
|
|
20
20
|
<option value="hull">Hull (convex)</option>
|
|
21
21
|
<option value="trimesh">Trimesh (exact)</option>
|
|
22
22
|
<option value="cuboid">Cuboid (box)</option>
|
|
@@ -11,74 +11,74 @@ function SpotLightComponentEditor({ component, onUpdate }: { component: any; onU
|
|
|
11
11
|
castShadow: component.properties.castShadow ?? true
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
return <div
|
|
14
|
+
return <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
15
15
|
<div>
|
|
16
|
-
<label
|
|
17
|
-
<div
|
|
16
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Color</label>
|
|
17
|
+
<div style={{ display: 'flex', gap: 2 }}>
|
|
18
18
|
<input
|
|
19
19
|
type="color"
|
|
20
|
-
|
|
20
|
+
style={{ height: 20, width: 20, backgroundColor: 'transparent', border: 'none', cursor: 'pointer' }}
|
|
21
21
|
value={props.color}
|
|
22
22
|
onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
|
|
23
23
|
/>
|
|
24
24
|
<input
|
|
25
25
|
type="text"
|
|
26
|
-
|
|
26
|
+
style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
27
27
|
value={props.color}
|
|
28
28
|
onChange={e => onUpdate({ ...component.properties, 'color': e.target.value })}
|
|
29
29
|
/>
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
<div>
|
|
33
|
-
<label
|
|
33
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Intensity</label>
|
|
34
34
|
<input
|
|
35
35
|
type="number"
|
|
36
36
|
step="0.1"
|
|
37
|
-
|
|
37
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
38
38
|
value={props.intensity}
|
|
39
39
|
onChange={e => onUpdate({ ...component.properties, 'intensity': parseFloat(e.target.value) })}
|
|
40
40
|
/>
|
|
41
41
|
</div>
|
|
42
42
|
<div>
|
|
43
|
-
<label
|
|
43
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Angle</label>
|
|
44
44
|
<input
|
|
45
45
|
type="number"
|
|
46
46
|
step="0.1"
|
|
47
47
|
min="0"
|
|
48
48
|
max={Math.PI}
|
|
49
|
-
|
|
49
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
50
50
|
value={props.angle}
|
|
51
51
|
onChange={e => onUpdate({ ...component.properties, 'angle': parseFloat(e.target.value) })}
|
|
52
52
|
/>
|
|
53
53
|
</div>
|
|
54
54
|
<div>
|
|
55
|
-
<label
|
|
55
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Penumbra</label>
|
|
56
56
|
<input
|
|
57
57
|
type="number"
|
|
58
58
|
step="0.1"
|
|
59
59
|
min="0"
|
|
60
60
|
max="1"
|
|
61
|
-
|
|
61
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
62
62
|
value={props.penumbra}
|
|
63
63
|
onChange={e => onUpdate({ ...component.properties, 'penumbra': parseFloat(e.target.value) })}
|
|
64
64
|
/>
|
|
65
65
|
</div>
|
|
66
66
|
<div>
|
|
67
|
-
<label
|
|
67
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Distance</label>
|
|
68
68
|
<input
|
|
69
69
|
type="number"
|
|
70
70
|
step="1"
|
|
71
71
|
min="0"
|
|
72
|
-
|
|
72
|
+
style={{ width: '100%', backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', padding: '2px 4px', fontSize: '10px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none' }}
|
|
73
73
|
value={props.distance}
|
|
74
74
|
onChange={e => onUpdate({ ...component.properties, 'distance': parseFloat(e.target.value) })}
|
|
75
75
|
/>
|
|
76
76
|
</div>
|
|
77
77
|
<div>
|
|
78
|
-
<label
|
|
78
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 2 }}>Cast Shadow</label>
|
|
79
79
|
<input
|
|
80
80
|
type="checkbox"
|
|
81
|
-
|
|
81
|
+
style={{ height: 16, width: 16, backgroundColor: 'rgba(0, 0, 0, 0.4)', border: '1px solid rgba(34, 211, 238, 0.3)', cursor: 'pointer' }}
|
|
82
82
|
checked={props.castShadow}
|
|
83
83
|
onChange={e => onUpdate({ ...component.properties, 'castShadow': e.target.checked })}
|
|
84
84
|
/>
|
|
@@ -22,10 +22,10 @@ function TransformComponentEditor({ component, onUpdate, transformMode, setTrans
|
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
return <div
|
|
25
|
+
return <div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
26
26
|
{transformMode && setTransformMode && (
|
|
27
|
-
<div
|
|
28
|
-
<label
|
|
27
|
+
<div style={{ marginBottom: 8 }}>
|
|
28
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>Transform Mode</label>
|
|
29
29
|
<div style={{ display: 'flex', gap: 6 }}>
|
|
30
30
|
{["translate", "rotate", "scale"].map(mode => (
|
|
31
31
|
<button
|
|
@@ -151,20 +151,20 @@ export function Vector3Input({
|
|
|
151
151
|
] as const;
|
|
152
152
|
|
|
153
153
|
return (
|
|
154
|
-
<div
|
|
155
|
-
<label
|
|
154
|
+
<div style={{ marginBottom: 8 }}>
|
|
155
|
+
<label style={{ display: 'block', fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 4 }}>
|
|
156
156
|
{label}
|
|
157
157
|
</label>
|
|
158
158
|
|
|
159
|
-
<div
|
|
159
|
+
<div style={{ display: 'flex', gap: 4 }}>
|
|
160
160
|
{axes.map(({ key, color, index }) => (
|
|
161
161
|
<div
|
|
162
162
|
key={key}
|
|
163
|
-
|
|
163
|
+
style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 4, backgroundColor: 'rgba(0, 0, 0, 0.3)', border: '1px solid rgba(34, 211, 238, 0.2)', borderRadius: 4, padding: '4px 6px', minHeight: 32 }}
|
|
164
164
|
>
|
|
165
165
|
{/* SCRUB HANDLE */}
|
|
166
166
|
<span
|
|
167
|
-
|
|
167
|
+
style={{ fontSize: '12px', fontWeight: 'bold', color: color === 'red' ? 'rgba(248, 113, 113, 1)' : color === 'green' ? 'rgba(134, 239, 172, 1)' : 'rgba(96, 165, 250, 1)', width: 12, cursor: 'ew-resize', userSelect: 'none' }}
|
|
168
168
|
onPointerDown={e => startScrub(e, index)}
|
|
169
169
|
onPointerMove={onScrubMove}
|
|
170
170
|
onPointerUp={endScrub}
|
|
@@ -174,7 +174,7 @@ export function Vector3Input({
|
|
|
174
174
|
|
|
175
175
|
{/* TEXT INPUT */}
|
|
176
176
|
<input
|
|
177
|
-
|
|
177
|
+
style={{ flex: 1, backgroundColor: 'transparent', fontSize: '12px', color: 'rgba(165, 243, 252, 1)', fontFamily: 'monospace', outline: 'none', width: '100%', minWidth: 0 }}
|
|
178
178
|
type="text"
|
|
179
179
|
value={draft[index]}
|
|
180
180
|
onChange={e => {
|