react-three-game 0.0.44 → 0.0.45
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 +38 -4
- package/dist/index.d.ts +7 -6
- package/dist/index.js +9 -6
- package/dist/tools/assetviewer/page.js +1 -1
- package/dist/tools/prefabeditor/EditorUI.js +3 -5
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -2
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +27 -27
- package/dist/tools/prefabeditor/components/GeometryComponent.js +40 -21
- package/dist/tools/prefabeditor/components/Input.d.ts +78 -1
- package/dist/tools/prefabeditor/components/Input.js +65 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +57 -26
- package/dist/tools/prefabeditor/components/ModelComponent.js +17 -8
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +25 -14
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +10 -21
- package/dist/tools/prefabeditor/components/TransformComponent.js +27 -19
- package/dist/tools/prefabeditor/page.js +1 -1
- package/package.json +1 -1
- package/src/index.ts +28 -10
- package/src/tools/assetviewer/page.tsx +2 -0
- package/src/tools/prefabeditor/EditorUI.tsx +0 -10
- package/src/tools/prefabeditor/components/ComponentRegistry.ts +3 -5
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +72 -76
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +55 -38
- package/src/tools/prefabeditor/components/Input.tsx +299 -0
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +97 -140
- package/src/tools/prefabeditor/components/ModelComponent.tsx +62 -41
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +29 -33
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +17 -65
- package/src/tools/prefabeditor/components/TransformComponent.tsx +83 -56
- package/src/tools/prefabeditor/page.tsx +1 -1
package/README.md
CHANGED
|
@@ -72,21 +72,29 @@ interface GameObject {
|
|
|
72
72
|
## Custom Components
|
|
73
73
|
|
|
74
74
|
```tsx
|
|
75
|
-
import { Component, registerComponent } from 'react-three-game';
|
|
75
|
+
import { Component, registerComponent, FieldRenderer, FieldDefinition } from 'react-three-game';
|
|
76
76
|
import { useFrame } from '@react-three/fiber';
|
|
77
77
|
|
|
78
|
+
const rotatorFields: FieldDefinition[] = [
|
|
79
|
+
{ name: 'speed', type: 'number', label: 'Speed', step: 0.1 },
|
|
80
|
+
{ name: 'axis', type: 'select', label: 'Axis', options: [
|
|
81
|
+
{ value: 'x', label: 'X' },
|
|
82
|
+
{ value: 'y', label: 'Y' },
|
|
83
|
+
{ value: 'z', label: 'Z' },
|
|
84
|
+
]},
|
|
85
|
+
];
|
|
86
|
+
|
|
78
87
|
const Rotator: Component = {
|
|
79
88
|
name: 'Rotator',
|
|
80
89
|
Editor: ({ component, onUpdate }) => (
|
|
81
|
-
<
|
|
82
|
-
onChange={e => onUpdate({ speed: +e.target.value })} />
|
|
90
|
+
<FieldRenderer fields={rotatorFields} values={component.properties} onChange={onUpdate} />
|
|
83
91
|
),
|
|
84
92
|
View: ({ properties, children }) => {
|
|
85
93
|
const ref = useRef<Group>(null);
|
|
86
94
|
useFrame((_, dt) => { ref.current!.rotation.y += dt * properties.speed });
|
|
87
95
|
return <group ref={ref}>{children}</group>;
|
|
88
96
|
},
|
|
89
|
-
defaultProperties: { speed: 1 }
|
|
97
|
+
defaultProperties: { speed: 1, axis: 'y' }
|
|
90
98
|
};
|
|
91
99
|
|
|
92
100
|
registerComponent(Rotator); // before rendering PrefabEditor
|
|
@@ -94,6 +102,32 @@ registerComponent(Rotator); // before rendering PrefabEditor
|
|
|
94
102
|
|
|
95
103
|
**Wrapper** components accept `children` (animations, controllers). **Leaf** components don't (lights, particles).
|
|
96
104
|
|
|
105
|
+
### Schema-Driven Field Types
|
|
106
|
+
|
|
107
|
+
The `FieldRenderer` component auto-generates editor UI from a field schema:
|
|
108
|
+
|
|
109
|
+
| Type | Description | Options |
|
|
110
|
+
|------|-------------|---------|
|
|
111
|
+
| `vector3` | X/Y/Z inputs with drag-to-scrub | `snap?: number` |
|
|
112
|
+
| `number` | Numeric input | `min?`, `max?`, `step?` |
|
|
113
|
+
| `string` | Text input | `placeholder?` |
|
|
114
|
+
| `color` | Color picker + hex input | — |
|
|
115
|
+
| `boolean` | Checkbox | — |
|
|
116
|
+
| `select` | Dropdown | `options: { value, label }[]` |
|
|
117
|
+
| `custom` | Render function for one-off UI | `render: (props) => ReactNode` |
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// Custom field example for complex one-off UI
|
|
121
|
+
{
|
|
122
|
+
name: 'gradient',
|
|
123
|
+
type: 'custom',
|
|
124
|
+
label: 'Gradient',
|
|
125
|
+
render: ({ value, onChange, values, onChangeMultiple }) => (
|
|
126
|
+
<GradientPicker value={value} onChange={onChange} />
|
|
127
|
+
),
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
97
131
|
## Visual Editor
|
|
98
132
|
|
|
99
133
|
```jsx
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
2
|
+
export * from './helpers';
|
|
3
|
+
export { sound as soundManager } from './helpers/SoundManager';
|
|
2
4
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
3
|
-
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
|
4
5
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
5
|
-
export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
|
|
6
6
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
|
+
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
8
|
+
export * from './tools/prefabeditor/utils';
|
|
9
|
+
export type { PrefabEditorRef } from './tools/prefabeditor/PrefabEditor';
|
|
10
|
+
export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
|
|
7
11
|
export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
|
|
12
|
+
export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
|
|
8
13
|
export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
|
|
9
|
-
export * as editorStyles from './tools/prefabeditor/styles';
|
|
10
|
-
export * from './tools/prefabeditor/utils';
|
|
11
14
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
12
15
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
13
|
-
export { sound as soundManager } from './helpers/SoundManager';
|
|
14
|
-
export * from './helpers';
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
// Core
|
|
1
|
+
// Core
|
|
2
2
|
export { default as GameCanvas } from './shared/GameCanvas';
|
|
3
|
-
//
|
|
3
|
+
// Helpers
|
|
4
|
+
export * from './helpers';
|
|
5
|
+
export { sound as soundManager } from './helpers/SoundManager';
|
|
6
|
+
// Prefab Editor - Components
|
|
4
7
|
export { default as PrefabEditor } from './tools/prefabeditor/PrefabEditor';
|
|
5
8
|
export { default as PrefabRoot } from './tools/prefabeditor/PrefabRoot';
|
|
9
|
+
// Prefab Editor - Component Registry
|
|
6
10
|
export { registerComponent } from './tools/prefabeditor/components/ComponentRegistry';
|
|
7
|
-
|
|
11
|
+
// Prefab Editor - Input Components
|
|
12
|
+
export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
|
|
13
|
+
// Prefab Editor - Styles & Utils
|
|
8
14
|
export * from './tools/prefabeditor/utils';
|
|
9
15
|
// Asset Tools
|
|
10
16
|
export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
|
|
11
17
|
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
12
|
-
export { sound as soundManager } from './helpers/SoundManager';
|
|
13
|
-
// Helpers
|
|
14
|
-
export * from './helpers';
|
|
@@ -103,7 +103,7 @@ function ModelCard({ file, onSelect, basePath = "" }) {
|
|
|
103
103
|
if (error) {
|
|
104
104
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
105
105
|
}
|
|
106
|
-
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
|
|
106
|
+
return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 1 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
|
|
107
107
|
}
|
|
108
108
|
function ModelPreview({ url, onError }) {
|
|
109
109
|
const [model, setModel] = useState(null);
|
|
@@ -15,10 +15,8 @@ import EditorTree from './EditorTree';
|
|
|
15
15
|
import { getAllComponents } from './components/ComponentRegistry';
|
|
16
16
|
import { base, inspector } from './styles';
|
|
17
17
|
import { findNode, updateNode, deleteNode } from './utils';
|
|
18
|
-
import { useEditorContext } from './EditorContext';
|
|
19
18
|
function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }) {
|
|
20
19
|
const [collapsed, setCollapsed] = useState(false);
|
|
21
|
-
const { transformMode, setTransformMode } = useEditorContext();
|
|
22
20
|
const updateNodeHandler = (updater) => {
|
|
23
21
|
if (!prefabData || !setPrefabData || !selectedId)
|
|
24
22
|
return;
|
|
@@ -36,9 +34,9 @@ function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePa
|
|
|
36
34
|
.prefab-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
37
35
|
.prefab-scroll::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.06); border-radius: 8px; }
|
|
38
36
|
.prefab-scroll { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.06) transparent; }
|
|
39
|
-
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler,
|
|
37
|
+
` }), _jsxs("div", { style: inspector.panel, children: [_jsxs("div", { style: base.header, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: "Inspector" }), _jsx("span", { children: collapsed ? '◀' : '▼' })] }), !collapsed && selectedNode && (_jsx(NodeInspector, { node: selectedNode, updateNode: updateNodeHandler, deleteNode: deleteNodeHandler, basePath: basePath }))] }), _jsx("div", { style: { position: 'absolute', top: 8, left: 8, zIndex: 20 }, children: _jsx(EditorTree, { prefabData: prefabData, setPrefabData: setPrefabData, selectedId: selectedId, setSelectedId: setSelectedId, onUndo: onUndo, onRedo: onRedo, canUndo: canUndo, canRedo: canRedo }) })] });
|
|
40
38
|
}
|
|
41
|
-
function NodeInspector({ node, updateNode, deleteNode,
|
|
39
|
+
function NodeInspector({ node, updateNode, deleteNode, basePath }) {
|
|
42
40
|
var _a;
|
|
43
41
|
const ALL_COMPONENTS = getAllComponents();
|
|
44
42
|
const allKeys = Object.keys(ALL_COMPONENTS);
|
|
@@ -58,7 +56,7 @@ function NodeInspector({ node, updateNode, deleteNode, transformMode, setTransfo
|
|
|
58
56
|
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, children: [_jsx("div", { style: { fontSize: 11, fontWeight: 500 }, children: key }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px' }), title: "Remove Component", onClick: () => updateNode(n => {
|
|
59
57
|
const _a = n.components || {}, _b = key, _ = _a[_b], rest = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
|
|
60
58
|
return Object.assign(Object.assign({}, n), { components: rest });
|
|
61
|
-
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath
|
|
59
|
+
}), children: "\u2715" })] }), def.Editor && (_jsx(def.Editor, { component: comp, node: node, onUpdate: (newProps) => updateNode(n => (Object.assign(Object.assign({}, n), { components: Object.assign(Object.assign({}, n.components), { [key]: Object.assign(Object.assign({}, comp), { properties: Object.assign(Object.assign({}, comp.properties), newProps) }) }) }))), basePath: basePath }))] }, key));
|
|
62
60
|
})] }), available.length > 0 && (_jsx("div", { children: _jsxs("div", { style: base.row, children: [_jsx("select", { style: Object.assign(Object.assign({}, base.input), { flex: 1 }), value: addType, onChange: e => setAddType(e.target.value), children: available.map(k => _jsx("option", { value: k, children: k }, k)) }), _jsx("button", { style: base.btn, disabled: !addType, onClick: () => {
|
|
63
61
|
if (!addType)
|
|
64
62
|
return;
|
|
@@ -7,8 +7,6 @@ export interface Component {
|
|
|
7
7
|
component: ComponentData;
|
|
8
8
|
onUpdate: (newComp: any) => void;
|
|
9
9
|
basePath?: string;
|
|
10
|
-
transformMode?: "translate" | "rotate" | "scale";
|
|
11
|
-
setTransformMode?: (m: "translate" | "rotate" | "scale") => void;
|
|
12
10
|
}>;
|
|
13
11
|
defaultProperties: any;
|
|
14
12
|
View?: FC<any>;
|
|
@@ -2,34 +2,34 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useRef, useEffect } from "react";
|
|
3
3
|
import { useFrame } from "@react-three/fiber";
|
|
4
4
|
import { Vector3 } from "three";
|
|
5
|
-
import {
|
|
5
|
+
import { FieldRenderer, Input } from "./Input";
|
|
6
|
+
const smallLabel = { display: 'block', fontSize: '8px', color: 'rgba(34, 211, 238, 0.5)', marginBottom: 2 };
|
|
7
|
+
const directionalLightFields = [
|
|
8
|
+
{ name: 'color', type: 'color', label: 'Color' },
|
|
9
|
+
{ name: 'intensity', type: 'number', label: 'Intensity', step: 0.1, min: 0 },
|
|
10
|
+
{ name: 'castShadow', type: 'boolean', label: 'Cast Shadow' },
|
|
11
|
+
{ name: 'shadowMapSize', type: 'number', label: 'Shadow Map Size', step: 256, min: 256 },
|
|
12
|
+
{
|
|
13
|
+
name: '_shadowCamera',
|
|
14
|
+
type: 'custom',
|
|
15
|
+
label: 'Shadow Camera',
|
|
16
|
+
render: ({ values, onChangeMultiple }) => {
|
|
17
|
+
var _a, _b, _c, _d, _e, _f;
|
|
18
|
+
return (_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: (_a = values.shadowCameraNear) !== null && _a !== void 0 ? _a : 0.1, onChange: v => onChangeMultiple({ shadowCameraNear: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Far" }), _jsx(Input, { step: 1, value: (_b = values.shadowCameraFar) !== null && _b !== void 0 ? _b : 100, onChange: v => onChangeMultiple({ shadowCameraFar: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Top" }), _jsx(Input, { step: 1, value: (_c = values.shadowCameraTop) !== null && _c !== void 0 ? _c : 30, onChange: v => onChangeMultiple({ shadowCameraTop: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Bottom" }), _jsx(Input, { step: 1, value: (_d = values.shadowCameraBottom) !== null && _d !== void 0 ? _d : -30, onChange: v => onChangeMultiple({ shadowCameraBottom: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Left" }), _jsx(Input, { step: 1, value: (_e = values.shadowCameraLeft) !== null && _e !== void 0 ? _e : -30, onChange: v => onChangeMultiple({ shadowCameraLeft: v }) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Right" }), _jsx(Input, { step: 1, value: (_f = values.shadowCameraRight) !== null && _f !== void 0 ? _f : 30, onChange: v => onChangeMultiple({ shadowCameraRight: v }) })] })] }));
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'targetOffset',
|
|
23
|
+
type: 'custom',
|
|
24
|
+
label: 'Target Offset',
|
|
25
|
+
render: ({ value, onChange }) => {
|
|
26
|
+
const offset = value !== null && value !== void 0 ? value : [0, -5, 0];
|
|
27
|
+
return (_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: offset[0], onChange: v => onChange([v, offset[1], offset[2]]) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Y" }), _jsx(Input, { step: 0.5, value: offset[1], onChange: v => onChange([offset[0], v, offset[2]]) })] }), _jsxs("div", { children: [_jsx("label", { style: smallLabel, children: "Z" }), _jsx(Input, { step: 0.5, value: offset[2], onChange: v => onChange([offset[0], offset[1], v]) })] })] }));
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
];
|
|
6
31
|
function DirectionalLightComponentEditor({ component, onUpdate }) {
|
|
7
|
-
|
|
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] })) })] })] })] })] });
|
|
32
|
+
return (_jsx(FieldRenderer, { fields: directionalLightFields, values: component.properties, onChange: onUpdate }));
|
|
33
33
|
}
|
|
34
34
|
function DirectionalLightView({ properties, editMode }) {
|
|
35
35
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Input, Label } from "./Input";
|
|
2
|
+
import { FieldRenderer, Input, Label } from "./Input";
|
|
3
3
|
const GEOMETRY_ARGS = {
|
|
4
4
|
box: {
|
|
5
5
|
labels: ["Width", "Height", "Depth"],
|
|
@@ -17,27 +17,46 @@ const GEOMETRY_ARGS = {
|
|
|
17
17
|
function GeometryComponentEditor({ component, onUpdate, }) {
|
|
18
18
|
const { geometryType, args = [] } = component.properties;
|
|
19
19
|
const schema = GEOMETRY_ARGS[geometryType];
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const fields = [
|
|
21
|
+
{
|
|
22
|
+
name: 'geometryType',
|
|
23
|
+
type: 'select',
|
|
24
|
+
label: 'Type',
|
|
25
|
+
options: [
|
|
26
|
+
{ value: 'box', label: 'Box' },
|
|
27
|
+
{ value: 'sphere', label: 'Sphere' },
|
|
28
|
+
{ value: 'plane', label: 'Plane' },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'args',
|
|
33
|
+
type: 'custom',
|
|
34
|
+
label: '',
|
|
35
|
+
render: ({ values, onChangeMultiple }) => {
|
|
36
|
+
const currentType = values.geometryType;
|
|
37
|
+
const currentSchema = GEOMETRY_ARGS[currentType];
|
|
38
|
+
const currentArgs = values.args || currentSchema.defaults;
|
|
39
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: currentSchema.labels.map((label, i) => {
|
|
40
|
+
var _a;
|
|
41
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: label }), _jsx(Input, { value: (_a = currentArgs[i]) !== null && _a !== void 0 ? _a : currentSchema.defaults[i], step: 0.1, onChange: value => {
|
|
42
|
+
const next = [...currentArgs];
|
|
43
|
+
next[i] = value;
|
|
44
|
+
onChangeMultiple({ args: next });
|
|
45
|
+
} })] }, label));
|
|
46
|
+
}) }));
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
// Handle geometry type change to reset args
|
|
51
|
+
const handleChange = (newValues) => {
|
|
52
|
+
if ('geometryType' in newValues && newValues.geometryType !== geometryType) {
|
|
53
|
+
onUpdate({ geometryType: newValues.geometryType, args: GEOMETRY_ARGS[newValues.geometryType].defaults });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
onUpdate(newValues);
|
|
57
|
+
}
|
|
29
58
|
};
|
|
30
|
-
return (
|
|
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
|
-
})] }));
|
|
59
|
+
return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: handleChange }));
|
|
41
60
|
}
|
|
42
61
|
// View for Geometry component
|
|
43
62
|
function GeometryComponentView({ properties, children }) {
|
|
@@ -1,4 +1,50 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
export type FieldType = 'vector3' | 'number' | 'string' | 'color' | 'boolean' | 'select';
|
|
3
|
+
interface BaseFieldDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
}
|
|
7
|
+
interface Vector3FieldDefinition extends BaseFieldDefinition {
|
|
8
|
+
type: 'vector3';
|
|
9
|
+
snap?: number;
|
|
10
|
+
}
|
|
11
|
+
interface NumberFieldDefinition extends BaseFieldDefinition {
|
|
12
|
+
type: 'number';
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
step?: number;
|
|
16
|
+
}
|
|
17
|
+
interface StringFieldDefinition extends BaseFieldDefinition {
|
|
18
|
+
type: 'string';
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
}
|
|
21
|
+
interface ColorFieldDefinition extends BaseFieldDefinition {
|
|
22
|
+
type: 'color';
|
|
23
|
+
}
|
|
24
|
+
interface BooleanFieldDefinition extends BaseFieldDefinition {
|
|
25
|
+
type: 'boolean';
|
|
26
|
+
}
|
|
27
|
+
interface SelectFieldDefinition extends BaseFieldDefinition {
|
|
28
|
+
type: 'select';
|
|
29
|
+
options: {
|
|
30
|
+
value: string;
|
|
31
|
+
label: string;
|
|
32
|
+
}[];
|
|
33
|
+
}
|
|
34
|
+
interface CustomFieldDefinition extends BaseFieldDefinition {
|
|
35
|
+
type: 'custom';
|
|
36
|
+
render: (props: {
|
|
37
|
+
value: any;
|
|
38
|
+
onChange: (value: any) => void;
|
|
39
|
+
values: Record<string, any>;
|
|
40
|
+
onChangeMultiple: (values: Record<string, any>) => void;
|
|
41
|
+
}) => React.ReactNode;
|
|
42
|
+
}
|
|
43
|
+
export type FieldDefinition = Vector3FieldDefinition | NumberFieldDefinition | StringFieldDefinition | ColorFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | CustomFieldDefinition;
|
|
44
|
+
declare const styles: {
|
|
45
|
+
input: React.CSSProperties;
|
|
46
|
+
label: React.CSSProperties;
|
|
47
|
+
};
|
|
2
48
|
interface InputProps {
|
|
3
49
|
value: number;
|
|
4
50
|
onChange: (value: number) => void;
|
|
@@ -17,4 +63,35 @@ export declare function Vector3Input({ label, value, onChange, snap }: {
|
|
|
17
63
|
onChange: (v: [number, number, number]) => void;
|
|
18
64
|
snap?: number;
|
|
19
65
|
}): import("react/jsx-runtime").JSX.Element;
|
|
20
|
-
export {}
|
|
66
|
+
export declare function ColorInput({ label, value, onChange }: {
|
|
67
|
+
label?: string;
|
|
68
|
+
value: string;
|
|
69
|
+
onChange: (value: string) => void;
|
|
70
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
71
|
+
export declare function StringInput({ label, value, onChange, placeholder }: {
|
|
72
|
+
label?: string;
|
|
73
|
+
value: string;
|
|
74
|
+
onChange: (value: string) => void;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
77
|
+
export declare function BooleanInput({ label, value, onChange }: {
|
|
78
|
+
label?: string;
|
|
79
|
+
value: boolean;
|
|
80
|
+
onChange: (value: boolean) => void;
|
|
81
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
82
|
+
export declare function SelectInput({ label, value, onChange, options }: {
|
|
83
|
+
label?: string;
|
|
84
|
+
value: string;
|
|
85
|
+
onChange: (value: string) => void;
|
|
86
|
+
options: {
|
|
87
|
+
value: string;
|
|
88
|
+
label: string;
|
|
89
|
+
}[];
|
|
90
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
91
|
+
interface FieldRendererProps {
|
|
92
|
+
fields: FieldDefinition[];
|
|
93
|
+
values: Record<string, any>;
|
|
94
|
+
onChange: (values: Record<string, any>) => void;
|
|
95
|
+
}
|
|
96
|
+
export declare function FieldRenderer({ fields, values, onChange }: FieldRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
97
|
+
export { styles };
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Shared Styles
|
|
5
|
+
// ============================================================================
|
|
3
6
|
// Shared styles
|
|
4
7
|
const styles = {
|
|
5
8
|
input: {
|
|
@@ -127,3 +130,65 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
127
130
|
}
|
|
128
131
|
} })] }, key))) })] }));
|
|
129
132
|
}
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Additional Input Components
|
|
135
|
+
// ============================================================================
|
|
136
|
+
export function ColorInput({ label, value, onChange }) {
|
|
137
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx("input", { type: "color", style: {
|
|
138
|
+
height: 20,
|
|
139
|
+
width: 20,
|
|
140
|
+
backgroundColor: 'transparent',
|
|
141
|
+
border: 'none',
|
|
142
|
+
cursor: 'pointer',
|
|
143
|
+
padding: 0,
|
|
144
|
+
}, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign(Object.assign({}, styles.input), { flex: 1 }), value: value, onChange: e => onChange(e.target.value) })] })] }));
|
|
145
|
+
}
|
|
146
|
+
export function StringInput({ label, value, onChange, placeholder }) {
|
|
147
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "text", style: styles.input, value: value, onChange: e => onChange(e.target.value), placeholder: placeholder })] }));
|
|
148
|
+
}
|
|
149
|
+
export function BooleanInput({ label, value, onChange }) {
|
|
150
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
151
|
+
height: 16,
|
|
152
|
+
width: 16,
|
|
153
|
+
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
|
154
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
}, checked: value, onChange: e => onChange(e.target.checked) })] }));
|
|
157
|
+
}
|
|
158
|
+
export function SelectInput({ label, value, onChange, options }) {
|
|
159
|
+
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsx("select", { style: styles.input, value: value, onChange: e => onChange(e.target.value), children: options.map(opt => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
160
|
+
}
|
|
161
|
+
export function FieldRenderer({ fields, values, onChange }) {
|
|
162
|
+
const updateField = (name, value) => {
|
|
163
|
+
onChange({ [name]: value });
|
|
164
|
+
};
|
|
165
|
+
return (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: fields.map(field => {
|
|
166
|
+
var _a, _b;
|
|
167
|
+
const value = values[field.name];
|
|
168
|
+
switch (field.type) {
|
|
169
|
+
case 'vector3':
|
|
170
|
+
return (_jsx(Vector3Input, { label: field.label, value: value !== null && value !== void 0 ? value : [0, 0, 0], onChange: v => updateField(field.name, v), snap: field.snap }, field.name));
|
|
171
|
+
case 'number':
|
|
172
|
+
return (_jsxs("div", { children: [_jsx(Label, { children: field.label }), _jsx(Input, { value: value !== null && value !== void 0 ? value : 0, onChange: v => updateField(field.name, v), min: field.min, max: field.max, step: field.step })] }, field.name));
|
|
173
|
+
case 'string':
|
|
174
|
+
return (_jsx(StringInput, { label: field.label, value: value !== null && value !== void 0 ? value : '', onChange: v => updateField(field.name, v), placeholder: field.placeholder }, field.name));
|
|
175
|
+
case 'color':
|
|
176
|
+
return (_jsx(ColorInput, { label: field.label, value: value !== null && value !== void 0 ? value : '#ffffff', onChange: v => updateField(field.name, v) }, field.name));
|
|
177
|
+
case 'boolean':
|
|
178
|
+
return (_jsx(BooleanInput, { label: field.label, value: value !== null && value !== void 0 ? value : false, onChange: v => updateField(field.name, v) }, field.name));
|
|
179
|
+
case 'select':
|
|
180
|
+
return (_jsx(SelectInput, { label: field.label, value: (_b = value !== null && value !== void 0 ? value : (_a = field.options[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', onChange: v => updateField(field.name, v), options: field.options }, field.name));
|
|
181
|
+
case 'custom':
|
|
182
|
+
return (_jsxs("div", { children: [field.label && _jsx(Label, { children: field.label }), field.render({
|
|
183
|
+
value,
|
|
184
|
+
onChange: v => updateField(field.name, v),
|
|
185
|
+
values,
|
|
186
|
+
onChangeMultiple: onChange,
|
|
187
|
+
})] }, field.name));
|
|
188
|
+
default:
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}) }));
|
|
192
|
+
}
|
|
193
|
+
// Export styles for use in custom field renderers
|
|
194
|
+
export { styles };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { FieldRenderer, Input } from './Input';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
import { DoubleSide, RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter } from 'three';
|
|
7
|
-
function
|
|
8
|
-
var _a, _b, _c, _d;
|
|
7
|
+
function TexturePicker({ value, onChange, basePath }) {
|
|
9
8
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
10
9
|
const [showPicker, setShowPicker] = useState(false);
|
|
11
10
|
useEffect(() => {
|
|
@@ -15,30 +14,62 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
15
14
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
16
15
|
.catch(console.error);
|
|
17
16
|
}, [basePath]);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflowY: 'auto', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleTextureViewer, { file: value || 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: value || undefined, onSelect: (file) => {
|
|
18
|
+
onChange(file);
|
|
19
|
+
setShowPicker(false);
|
|
20
|
+
}, basePath: basePath }) }))] }));
|
|
21
|
+
}
|
|
22
|
+
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
23
|
+
const hasTexture = !!component.properties.texture;
|
|
24
|
+
const hasRepeat = component.properties.repeat;
|
|
25
|
+
const fields = [
|
|
26
|
+
{ name: 'color', type: 'color', label: 'Color' },
|
|
27
|
+
{ name: 'wireframe', type: 'boolean', label: 'Wireframe' },
|
|
28
|
+
{
|
|
29
|
+
name: 'texture',
|
|
30
|
+
type: 'custom',
|
|
31
|
+
label: 'Texture File',
|
|
32
|
+
render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
|
|
33
|
+
},
|
|
34
|
+
// Conditional texture settings
|
|
35
|
+
...(hasTexture ? [
|
|
36
|
+
{ name: 'repeat', type: 'boolean', label: 'Repeat Texture' },
|
|
37
|
+
...(hasRepeat ? [{
|
|
38
|
+
name: 'repeatCount',
|
|
39
|
+
type: 'custom',
|
|
40
|
+
label: 'Repeat (X, Y)',
|
|
41
|
+
render: ({ value, onChange }) => {
|
|
42
|
+
var _a, _b;
|
|
43
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); } }), _jsx(Input, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); } })] }));
|
|
44
|
+
},
|
|
45
|
+
}] : []),
|
|
46
|
+
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
47
|
+
{
|
|
48
|
+
name: 'minFilter',
|
|
49
|
+
type: 'select',
|
|
50
|
+
label: 'Min Filter',
|
|
51
|
+
options: [
|
|
52
|
+
{ value: 'NearestFilter', label: 'Nearest' },
|
|
53
|
+
{ value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
|
|
54
|
+
{ value: 'NearestMipmapLinearFilter', label: 'Nearest Mipmap Linear' },
|
|
55
|
+
{ value: 'LinearFilter', label: 'Linear' },
|
|
56
|
+
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
57
|
+
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'magFilter',
|
|
62
|
+
type: 'select',
|
|
63
|
+
label: 'Mag Filter',
|
|
64
|
+
options: [
|
|
65
|
+
{ value: 'NearestFilter', label: 'Nearest' },
|
|
66
|
+
{ value: 'LinearFilter', label: 'Linear (Default)' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
] : []),
|
|
70
|
+
];
|
|
71
|
+
return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }));
|
|
40
72
|
}
|
|
41
|
-
;
|
|
42
73
|
// View for Material component
|
|
43
74
|
function MaterialComponentView({ properties, loadedTextures }) {
|
|
44
75
|
var _a;
|