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,100 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Input, Label } from './Input';
|
|
5
|
+
import { useMemo } from 'react';
|
|
6
|
+
import { DoubleSide, RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter } from 'three';
|
|
7
|
+
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
8
|
+
var _a, _b, _c, _d;
|
|
9
|
+
const [textureFiles, setTextureFiles] = useState([]);
|
|
10
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const base = basePath ? `${basePath}/` : '';
|
|
13
|
+
fetch(`/${base}textures/manifest.json`)
|
|
14
|
+
.then(r => r.json())
|
|
15
|
+
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
16
|
+
.catch(console.error);
|
|
17
|
+
}, [basePath]);
|
|
18
|
+
const textInputStyle = {
|
|
19
|
+
flex: 1,
|
|
20
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
21
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
22
|
+
padding: '2px 4px',
|
|
23
|
+
fontSize: '10px',
|
|
24
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
25
|
+
fontFamily: 'monospace',
|
|
26
|
+
outline: 'none',
|
|
27
|
+
};
|
|
28
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, 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: component.properties.color, onChange: e => onUpdate({ color: e.target.value }) }), _jsx("input", { type: "text", style: textInputStyle, value: component.properties.color, onChange: e => onUpdate({ color: e.target.value }) })] })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 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, { children: "Texture File" }), _jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleTextureViewer, { file: component.properties.texture || undefined, basePath: basePath }), _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4 }, children: showPicker ? 'Hide' : 'Change' }), showPicker && (_jsx("div", { style: { position: 'fixed', left: '-10px', top: '50%', transform: 'translate(-100%, -50%)', background: 'rgba(0,0,0,0.9)', padding: 16, border: '1px solid rgba(34, 211, 238, 0.3)', maxHeight: '80vh', overflowY: 'auto', zIndex: 1000 }, children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => {
|
|
29
|
+
onUpdate({ texture: file });
|
|
30
|
+
setShowPicker(false);
|
|
31
|
+
}, 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, { children: "Repeat (X, Y)" }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { value: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: value => {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
|
|
34
|
+
onUpdate({ repeatCount: [value, y] });
|
|
35
|
+
} }), _jsx(Input, { value: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: value => {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const x = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1;
|
|
38
|
+
onUpdate({ repeatCount: [x, value] });
|
|
39
|
+
} })] })] })), _jsxs("div", { style: { 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.generateMipmaps !== false, onChange: e => onUpdate({ generateMipmaps: e.target.checked }) }), _jsx("label", { style: { fontSize: '9px', color: 'rgba(34, 211, 238, 0.6)' }, children: "Generate Mipmaps" })] }), _jsxs("div", { children: [_jsx(Label, { children: "Min Filter" }), _jsxs("select", { style: Object.assign(Object.assign({}, textInputStyle), { width: '100%', cursor: 'pointer' }), value: component.properties.minFilter || 'LinearMipmapLinearFilter', onChange: e => onUpdate({ minFilter: e.target.value }), children: [_jsx("option", { value: "NearestFilter", children: "Nearest" }), _jsx("option", { value: "NearestMipmapNearestFilter", children: "Nearest Mipmap Nearest" }), _jsx("option", { value: "NearestMipmapLinearFilter", children: "Nearest Mipmap Linear" }), _jsx("option", { value: "LinearFilter", children: "Linear" }), _jsx("option", { value: "LinearMipmapNearestFilter", children: "Linear Mipmap Nearest" }), _jsx("option", { value: "LinearMipmapLinearFilter", children: "Linear Mipmap Linear (Default)" })] })] }), _jsxs("div", { style: { marginTop: 4 }, children: [_jsx(Label, { children: "Mag Filter" }), _jsxs("select", { style: Object.assign(Object.assign({}, textInputStyle), { width: '100%', cursor: 'pointer' }), value: component.properties.magFilter || 'LinearFilter', onChange: e => onUpdate({ magFilter: e.target.value }), children: [_jsx("option", { value: "NearestFilter", children: "Nearest" }), _jsx("option", { value: "LinearFilter", children: "Linear (Default)" })] })] })] })] }))] }));
|
|
40
|
+
}
|
|
41
|
+
;
|
|
42
|
+
// View for Material component
|
|
43
|
+
function MaterialComponentView({ properties, loadedTextures }) {
|
|
44
|
+
var _a;
|
|
45
|
+
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
46
|
+
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
47
|
+
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
48
|
+
const generateMipmaps = (properties === null || properties === void 0 ? void 0 : properties.generateMipmaps) !== false;
|
|
49
|
+
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
50
|
+
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
51
|
+
const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
|
|
52
|
+
const minFilterMap = {
|
|
53
|
+
NearestFilter,
|
|
54
|
+
LinearFilter,
|
|
55
|
+
NearestMipmapNearestFilter,
|
|
56
|
+
NearestMipmapLinearFilter,
|
|
57
|
+
LinearMipmapNearestFilter,
|
|
58
|
+
LinearMipmapLinearFilter
|
|
59
|
+
};
|
|
60
|
+
const magFilterMap = {
|
|
61
|
+
NearestFilter,
|
|
62
|
+
LinearFilter
|
|
63
|
+
};
|
|
64
|
+
const finalTexture = useMemo(() => {
|
|
65
|
+
var _a, _b;
|
|
66
|
+
if (!texture)
|
|
67
|
+
return undefined;
|
|
68
|
+
const t = texture.clone();
|
|
69
|
+
if (repeat) {
|
|
70
|
+
t.wrapS = t.wrapT = RepeatWrapping;
|
|
71
|
+
if (repeatCount)
|
|
72
|
+
t.repeat.set(repeatCount[0], repeatCount[1]);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
t.wrapS = t.wrapT = ClampToEdgeWrapping;
|
|
76
|
+
t.repeat.set(1, 1);
|
|
77
|
+
}
|
|
78
|
+
t.colorSpace = SRGBColorSpace;
|
|
79
|
+
t.generateMipmaps = generateMipmaps;
|
|
80
|
+
t.minFilter = (_a = minFilterMap[minFilter]) !== null && _a !== void 0 ? _a : LinearMipmapLinearFilter;
|
|
81
|
+
t.magFilter = (_b = magFilterMap[magFilter]) !== null && _b !== void 0 ? _b : LinearFilter;
|
|
82
|
+
t.needsUpdate = true;
|
|
83
|
+
return t;
|
|
84
|
+
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], generateMipmaps, minFilter, magFilter]);
|
|
85
|
+
if (!properties) {
|
|
86
|
+
return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
|
|
87
|
+
}
|
|
88
|
+
const { color, wireframe = false } = properties;
|
|
89
|
+
return (_jsx("meshStandardMaterial", { color: color, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture, side: DoubleSide }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
|
|
90
|
+
}
|
|
91
|
+
const MaterialComponent = {
|
|
92
|
+
name: 'Material',
|
|
93
|
+
Editor: MaterialComponentEditor,
|
|
94
|
+
View: MaterialComponentView,
|
|
95
|
+
defaultProperties: {
|
|
96
|
+
color: '#ffffff',
|
|
97
|
+
wireframe: false
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
export default MaterialComponent;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { ModelListViewer, SingleModelViewer } from '../../assetviewer/page';
|
|
3
|
+
import { useEffect, useState, useMemo } from 'react';
|
|
4
|
+
import { Label } from './Input';
|
|
5
|
+
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
6
|
+
const [modelFiles, setModelFiles] = useState([]);
|
|
7
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const base = basePath ? `${basePath}/` : '';
|
|
10
|
+
fetch(`/${base}models/manifest.json`)
|
|
11
|
+
.then(r => r.json())
|
|
12
|
+
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
13
|
+
.catch(console.error);
|
|
14
|
+
}, [basePath]);
|
|
15
|
+
const handleModelSelect = (file) => {
|
|
16
|
+
// Remove leading slash for prefab compatibility
|
|
17
|
+
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
18
|
+
onUpdate({ 'filename': filename });
|
|
19
|
+
};
|
|
20
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Model File" }), _jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: component.properties.filename ? `/${component.properties.filename}` : undefined, basePath: basePath }), _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4 }, children: showPicker ? 'Hide' : 'Change' }), showPicker && (_jsx("div", { style: { position: 'fixed', left: '-10px', top: '50%', transform: 'translate(-100%, -50%)', background: 'rgba(0,0,0,0.9)', padding: 16, border: '1px solid rgba(34, 211, 238, 0.3)', maxHeight: '80vh', overflowY: 'auto', zIndex: 1000 }, children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: (file) => {
|
|
21
|
+
handleModelSelect(file);
|
|
22
|
+
setShowPicker(false);
|
|
23
|
+
}, basePath: basePath }, node === null || node === void 0 ? void 0 : node.id) }))] })] }), _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" })] })] });
|
|
24
|
+
}
|
|
25
|
+
// View for Model component
|
|
26
|
+
function ModelComponentView({ properties, loadedModels, children }) {
|
|
27
|
+
// Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
|
|
28
|
+
if (!properties.filename || properties.instanced)
|
|
29
|
+
return _jsx(_Fragment, { children: children });
|
|
30
|
+
const sourceModel = loadedModels === null || loadedModels === void 0 ? void 0 : loadedModels[properties.filename];
|
|
31
|
+
// Clone model once and set up shadows - memoized to avoid cloning on every render
|
|
32
|
+
const clonedModel = useMemo(() => {
|
|
33
|
+
if (!sourceModel)
|
|
34
|
+
return null;
|
|
35
|
+
const clone = sourceModel.clone();
|
|
36
|
+
clone.traverse((obj) => {
|
|
37
|
+
if (obj.isMesh) {
|
|
38
|
+
obj.castShadow = true;
|
|
39
|
+
obj.receiveShadow = true;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
return clone;
|
|
43
|
+
}, [sourceModel]);
|
|
44
|
+
if (!clonedModel)
|
|
45
|
+
return _jsx(_Fragment, { children: children });
|
|
46
|
+
return _jsx("primitive", { object: clonedModel, children: children });
|
|
47
|
+
}
|
|
48
|
+
const ModelComponent = {
|
|
49
|
+
name: 'Model',
|
|
50
|
+
Editor: ModelComponentEditor,
|
|
51
|
+
View: ModelComponentView,
|
|
52
|
+
defaultProperties: {
|
|
53
|
+
filename: '',
|
|
54
|
+
instanced: false
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
export default ModelComponent;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Component } from "./ComponentRegistry";
|
|
2
|
+
export interface PhysicsProps {
|
|
3
|
+
type: "fixed" | "dynamic";
|
|
4
|
+
collider?: string;
|
|
5
|
+
mass?: number;
|
|
6
|
+
restitution?: number;
|
|
7
|
+
friction?: number;
|
|
8
|
+
}
|
|
9
|
+
declare const PhysicsComponent: Component;
|
|
10
|
+
export default PhysicsComponent;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RigidBody } from "@react-three/rapier";
|
|
3
|
+
import { Label } from "./Input";
|
|
4
|
+
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
5
|
+
const { type = 'dynamic', collider = 'hull' } = component.properties;
|
|
6
|
+
const selectStyle = {
|
|
7
|
+
width: '100%',
|
|
8
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
9
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
10
|
+
padding: '2px 4px',
|
|
11
|
+
fontSize: '10px',
|
|
12
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
13
|
+
fontFamily: 'monospace',
|
|
14
|
+
outline: 'none',
|
|
15
|
+
};
|
|
16
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Type" }), _jsxs("select", { style: selectStyle, value: type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] })] }), _jsxs("div", { children: [_jsx(Label, { children: "Collider" }), _jsxs("select", { style: selectStyle, 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)" })] })] })] }));
|
|
17
|
+
}
|
|
18
|
+
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode }) {
|
|
19
|
+
const colliders = properties.collider || (properties.type === 'fixed' ? 'trimesh' : 'hull');
|
|
20
|
+
// In edit mode, include position/rotation in key to force remount when transform changes
|
|
21
|
+
// This ensures the RigidBody debug visualization updates even when physics is paused
|
|
22
|
+
const rbKey = editMode
|
|
23
|
+
? `${properties.type || 'dynamic'}_${colliders}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
24
|
+
: `${properties.type || 'dynamic'}_${colliders}`;
|
|
25
|
+
return (_jsx(RigidBody, { type: properties.type, colliders: colliders, position: position, rotation: rotation, scale: scale, children: children }, rbKey));
|
|
26
|
+
}
|
|
27
|
+
const PhysicsComponent = {
|
|
28
|
+
name: 'Physics',
|
|
29
|
+
Editor: PhysicsComponentEditor,
|
|
30
|
+
View: PhysicsComponentView,
|
|
31
|
+
defaultProperties: { type: 'dynamic', collider: 'hull' }
|
|
32
|
+
};
|
|
33
|
+
export default PhysicsComponent;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useEffect } from "react";
|
|
3
|
+
import { Input, Label } from "./Input";
|
|
4
|
+
function SpotLightComponentEditor({ component, onUpdate }) {
|
|
5
|
+
var _a, _b, _c, _d, _e, _f;
|
|
6
|
+
const props = {
|
|
7
|
+
color: (_a = component.properties.color) !== null && _a !== void 0 ? _a : '#ffffff',
|
|
8
|
+
intensity: (_b = component.properties.intensity) !== null && _b !== void 0 ? _b : 1.0,
|
|
9
|
+
angle: (_c = component.properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6,
|
|
10
|
+
penumbra: (_d = component.properties.penumbra) !== null && _d !== void 0 ? _d : 0.5,
|
|
11
|
+
distance: (_e = component.properties.distance) !== null && _e !== void 0 ? _e : 100,
|
|
12
|
+
castShadow: (_f = component.properties.castShadow) !== null && _f !== void 0 ? _f : true
|
|
13
|
+
};
|
|
14
|
+
const textInputStyle = {
|
|
15
|
+
flex: 1,
|
|
16
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
17
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
18
|
+
padding: '2px 4px',
|
|
19
|
+
fontSize: '10px',
|
|
20
|
+
color: 'rgba(165, 243, 252, 1)',
|
|
21
|
+
fontFamily: 'monospace',
|
|
22
|
+
outline: 'none',
|
|
23
|
+
};
|
|
24
|
+
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: "Angle" }), _jsx(Input, { step: "0.1", min: 0, max: Math.PI, value: props.angle, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { angle: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Penumbra" }), _jsx(Input, { step: "0.1", min: 0, max: 1, value: props.penumbra, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { penumbra: value })) })] }), _jsxs("div", { children: [_jsx(Label, { children: "Distance" }), _jsx(Input, { step: "1", min: 0, value: props.distance, onChange: value => onUpdate(Object.assign(Object.assign({}, component.properties), { distance: 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 })) })] })] });
|
|
25
|
+
}
|
|
26
|
+
function SpotLightView({ properties, editMode }) {
|
|
27
|
+
var _a, _b, _c, _d, _e, _f;
|
|
28
|
+
const color = (_a = properties.color) !== null && _a !== void 0 ? _a : '#ffffff';
|
|
29
|
+
const intensity = (_b = properties.intensity) !== null && _b !== void 0 ? _b : 1.0;
|
|
30
|
+
const angle = (_c = properties.angle) !== null && _c !== void 0 ? _c : Math.PI / 6;
|
|
31
|
+
const penumbra = (_d = properties.penumbra) !== null && _d !== void 0 ? _d : 0.5;
|
|
32
|
+
const distance = (_e = properties.distance) !== null && _e !== void 0 ? _e : 100;
|
|
33
|
+
const castShadow = (_f = properties.castShadow) !== null && _f !== void 0 ? _f : true;
|
|
34
|
+
const spotLightRef = useRef(null);
|
|
35
|
+
const targetRef = useRef(null);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (spotLightRef.current && targetRef.current) {
|
|
38
|
+
spotLightRef.current.target = targetRef.current;
|
|
39
|
+
}
|
|
40
|
+
}, []);
|
|
41
|
+
return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "shadow-mapSize-width": 1024, "shadow-mapSize-height": 1024, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
|
|
42
|
+
}
|
|
43
|
+
const SpotLightComponent = {
|
|
44
|
+
name: 'SpotLight',
|
|
45
|
+
Editor: SpotLightComponentEditor,
|
|
46
|
+
View: SpotLightView,
|
|
47
|
+
defaultProperties: {}
|
|
48
|
+
};
|
|
49
|
+
export default SpotLightComponent;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Vector3Input, Label } from "./Input";
|
|
3
|
+
import { useEditorContext } from "../EditorContext";
|
|
4
|
+
const buttonStyle = {
|
|
5
|
+
padding: '2px 6px',
|
|
6
|
+
background: 'transparent',
|
|
7
|
+
color: 'rgba(255,255,255,0.9)',
|
|
8
|
+
border: '1px solid rgba(255,255,255,0.14)',
|
|
9
|
+
borderRadius: 4,
|
|
10
|
+
cursor: 'pointer',
|
|
11
|
+
font: 'inherit',
|
|
12
|
+
flex: 1,
|
|
13
|
+
};
|
|
14
|
+
function TransformComponentEditor({ component, onUpdate, transformMode, setTransformMode }) {
|
|
15
|
+
const { snapResolution, setSnapResolution } = useEditorContext();
|
|
16
|
+
return _jsxs("div", { style: { display: 'flex', flexDirection: 'column' }, children: [transformMode && setTransformMode && (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs(Label, { children: ["Transform Mode ", snapResolution > 0 && `(Snap: ${snapResolution})`] }), _jsx("div", { style: { display: 'flex', gap: 6 }, children: ["translate", "rotate", "scale"].map(mode => {
|
|
17
|
+
const isActive = transformMode === mode;
|
|
18
|
+
return (_jsx("button", { onClick: () => setTransformMode(mode), style: Object.assign(Object.assign({}, buttonStyle), { background: isActive ? 'rgba(255,255,255,0.10)' : 'transparent' }), onPointerEnter: (e) => {
|
|
19
|
+
if (!isActive)
|
|
20
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
21
|
+
}, onPointerLeave: (e) => {
|
|
22
|
+
if (!isActive)
|
|
23
|
+
e.currentTarget.style.background = 'transparent';
|
|
24
|
+
}, children: mode }, mode));
|
|
25
|
+
}) }), _jsx("div", { style: { marginTop: 6 }, children: _jsxs("button", { onClick: () => setSnapResolution(snapResolution > 0 ? 0 : 0.1), style: Object.assign(Object.assign({}, buttonStyle), { background: snapResolution > 0 ? 'rgba(255,255,255,0.10)' : 'transparent', width: '100%' }), onPointerEnter: (e) => {
|
|
26
|
+
if (snapResolution === 0)
|
|
27
|
+
e.currentTarget.style.background = 'rgba(255,255,255,0.08)';
|
|
28
|
+
}, onPointerLeave: (e) => {
|
|
29
|
+
if (snapResolution === 0)
|
|
30
|
+
e.currentTarget.style.background = 'transparent';
|
|
31
|
+
}, children: ["Snap: ", snapResolution > 0 ? `ON (${snapResolution})` : 'OFF'] }) })] })), _jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }), snap: snapResolution }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }), snap: snapResolution }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }), snap: snapResolution })] });
|
|
32
|
+
}
|
|
33
|
+
const TransformComponent = {
|
|
34
|
+
name: 'Transform',
|
|
35
|
+
Editor: TransformComponentEditor,
|
|
36
|
+
defaultProperties: {
|
|
37
|
+
position: [0, 0, 0],
|
|
38
|
+
rotation: [0, 0, 0],
|
|
39
|
+
scale: [1, 1, 1]
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export default TransformComponent;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import GeometryComponent from './GeometryComponent';
|
|
2
|
+
import TransformComponent from './TransformComponent';
|
|
3
|
+
import MaterialComponent from './MaterialComponent';
|
|
4
|
+
import PhysicsComponent from './PhysicsComponent';
|
|
5
|
+
import SpotLightComponent from './SpotLightComponent';
|
|
6
|
+
import DirectionalLightComponent from './DirectionalLightComponent';
|
|
7
|
+
import ModelComponent from './ModelComponent';
|
|
8
|
+
export default [
|
|
9
|
+
GeometryComponent,
|
|
10
|
+
TransformComponent,
|
|
11
|
+
MaterialComponent,
|
|
12
|
+
PhysicsComponent,
|
|
13
|
+
SpotLightComponent,
|
|
14
|
+
DirectionalLightComponent,
|
|
15
|
+
ModelComponent
|
|
16
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function PrefabEditorPage(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import PrefabEditor from "./PrefabEditor";
|
|
3
|
+
export default function PrefabEditorPage() {
|
|
4
|
+
return _jsx("div", { className: "w-screen h-screen", children: _jsx(PrefabEditor, { children: _jsx("directionalLight", { position: [5, 10, 7.5], intensity: 1, castShadow: true }) }) });
|
|
5
|
+
}
|