react-three-game 0.0.56 → 0.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/GameCanvas.js +1 -3
- package/dist/tools/assetviewer/page.js +35 -14
- package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
- package/dist/tools/prefabeditor/Dropdown.js +82 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
- package/dist/tools/prefabeditor/EditorTree.js +138 -56
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
- package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
- package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +25 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
- package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
- package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
- package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
- package/dist/tools/prefabeditor/components/Input.js +73 -21
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
- package/dist/tools/prefabeditor/components/MaterialComponent.js +122 -14
- package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +4 -12
- package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
- package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
- package/dist/tools/prefabeditor/components/index.js +5 -1
- package/dist/tools/prefabeditor/styles.d.ts +5 -2
- package/dist/tools/prefabeditor/styles.js +7 -3
- package/dist/tools/prefabeditor/utils.d.ts +4 -3
- package/dist/tools/prefabeditor/utils.js +53 -5
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/shared/GameCanvas.tsx +0 -3
- package/src/tools/assetviewer/page.tsx +77 -45
- package/src/tools/prefabeditor/Dropdown.tsx +112 -0
- package/src/tools/prefabeditor/EditorContext.tsx +5 -0
- package/src/tools/prefabeditor/EditorTree.tsx +234 -101
- package/src/tools/prefabeditor/EditorUI.tsx +1 -1
- package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
- package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
- package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
- package/src/tools/prefabeditor/components/CameraComponent.tsx +80 -0
- package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +2 -2
- package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
- package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
- package/src/tools/prefabeditor/components/Input.tsx +220 -27
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +178 -16
- package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +11 -17
- package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
- package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
- package/src/tools/prefabeditor/components/index.ts +5 -1
- package/src/tools/prefabeditor/styles.ts +7 -3
- package/src/tools/prefabeditor/utils.ts +55 -4
|
@@ -27,6 +27,28 @@ const styles = {
|
|
|
27
27
|
fontWeight: 500,
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
|
+
function getNumericStep(step, fallback) {
|
|
31
|
+
if (typeof step === 'number' && Number.isFinite(step) && step > 0)
|
|
32
|
+
return step;
|
|
33
|
+
if (typeof step === 'string') {
|
|
34
|
+
const parsed = parseFloat(step);
|
|
35
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
function getStepPrecision(step) {
|
|
41
|
+
var _a;
|
|
42
|
+
if (!Number.isFinite(step) || step <= 0)
|
|
43
|
+
return 3;
|
|
44
|
+
const stepString = step.toString();
|
|
45
|
+
if (stepString.includes('e-')) {
|
|
46
|
+
const exponent = stepString.split('e-')[1];
|
|
47
|
+
return exponent ? parseInt(exponent, 10) : 3;
|
|
48
|
+
}
|
|
49
|
+
const decimal = stepString.split('.')[1];
|
|
50
|
+
return (_a = decimal === null || decimal === void 0 ? void 0 : decimal.length) !== null && _a !== void 0 ? _a : 0;
|
|
51
|
+
}
|
|
30
52
|
export function Input({ value, onChange, step, min, max, style, label }) {
|
|
31
53
|
const [draft, setDraft] = useState(() => value.toString());
|
|
32
54
|
useEffect(() => {
|
|
@@ -48,14 +70,11 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
48
70
|
};
|
|
49
71
|
const dragState = useRef(null);
|
|
50
72
|
const startScrub = (e) => {
|
|
51
|
-
if (!label)
|
|
52
|
-
return;
|
|
53
|
-
e.preventDefault();
|
|
54
73
|
dragState.current = {
|
|
55
74
|
startX: e.clientX,
|
|
56
75
|
startValue: value
|
|
57
76
|
};
|
|
58
|
-
e.
|
|
77
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
59
78
|
document.body.style.cursor = "ew-resize";
|
|
60
79
|
};
|
|
61
80
|
const onScrubMove = (e) => {
|
|
@@ -63,18 +82,21 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
63
82
|
return;
|
|
64
83
|
const { startX, startValue } = dragState.current;
|
|
65
84
|
const dx = e.clientX - startX;
|
|
66
|
-
|
|
85
|
+
const baseStep = getNumericStep(step, 0.1);
|
|
86
|
+
let scrubStep = baseStep;
|
|
67
87
|
if (e.shiftKey)
|
|
68
|
-
|
|
88
|
+
scrubStep /= 10;
|
|
69
89
|
if (e.altKey)
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
scrubStep *= 10;
|
|
91
|
+
const precision = getStepPrecision(scrubStep);
|
|
92
|
+
const deltaSteps = Math.round(dx / 8);
|
|
93
|
+
let nextValue = startValue + deltaSteps * scrubStep;
|
|
72
94
|
// Apply min/max constraints
|
|
73
95
|
if (min !== undefined && nextValue < min)
|
|
74
96
|
nextValue = min;
|
|
75
97
|
if (max !== undefined && nextValue > max)
|
|
76
98
|
nextValue = max;
|
|
77
|
-
setDraft(nextValue.toFixed(
|
|
99
|
+
setDraft(nextValue.toFixed(precision));
|
|
78
100
|
onChange(nextValue);
|
|
79
101
|
};
|
|
80
102
|
const endScrub = (e) => {
|
|
@@ -82,29 +104,29 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
82
104
|
return;
|
|
83
105
|
dragState.current = null;
|
|
84
106
|
document.body.style.cursor = "";
|
|
85
|
-
e.
|
|
107
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
86
108
|
};
|
|
87
109
|
if (label) {
|
|
88
110
|
return (_jsxs("div", { style: {
|
|
89
111
|
display: 'flex',
|
|
90
112
|
alignItems: 'center',
|
|
91
113
|
justifyContent: 'space-between',
|
|
92
|
-
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0,
|
|
114
|
+
}, children: [_jsx("span", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0, userSelect: 'none', flex: '0 0 auto', minWidth: 20 }), children: label }), _jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
93
115
|
if (e.key === 'Enter') {
|
|
94
116
|
e.target.blur();
|
|
95
117
|
}
|
|
96
|
-
}, step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) })] }));
|
|
118
|
+
}, step: step, min: min, max: max, style: Object.assign(Object.assign(Object.assign({}, styles.input), { cursor: 'ew-resize' }), style), onPointerDown: startScrub, onPointerMove: onScrubMove, onPointerUp: endScrub })] }));
|
|
97
119
|
}
|
|
98
120
|
return (_jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
99
121
|
if (e.key === 'Enter') {
|
|
100
122
|
e.target.blur();
|
|
101
123
|
}
|
|
102
|
-
}, step: step, min: min, max: max, style: Object.assign(Object.assign({}, styles.input), style) }));
|
|
124
|
+
}, step: step, min: min, max: max, style: Object.assign(Object.assign(Object.assign({}, styles.input), { cursor: 'ew-resize' }), style), onPointerDown: startScrub, onPointerMove: onScrubMove, onPointerUp: endScrub }));
|
|
103
125
|
}
|
|
104
126
|
export function Label({ children }) {
|
|
105
127
|
return _jsx("label", { style: styles.label, children: children });
|
|
106
128
|
}
|
|
107
|
-
export function Vector3Input({ label, value, onChange, snap }) {
|
|
129
|
+
export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
|
|
108
130
|
const snapValue = (num) => {
|
|
109
131
|
if (!snap)
|
|
110
132
|
return num;
|
|
@@ -125,13 +147,12 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
125
147
|
}
|
|
126
148
|
};
|
|
127
149
|
const startScrub = (e, index) => {
|
|
128
|
-
e.preventDefault();
|
|
129
150
|
dragState.current = {
|
|
130
151
|
index,
|
|
131
152
|
startX: e.clientX,
|
|
132
153
|
startValue: value[index]
|
|
133
154
|
};
|
|
134
|
-
e.
|
|
155
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
135
156
|
document.body.style.cursor = "ew-resize";
|
|
136
157
|
};
|
|
137
158
|
const onScrubMove = (e) => {
|
|
@@ -160,14 +181,14 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
160
181
|
return;
|
|
161
182
|
dragState.current = null;
|
|
162
183
|
document.body.style.cursor = "";
|
|
163
|
-
e.
|
|
184
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
164
185
|
};
|
|
165
186
|
const axes = [
|
|
166
187
|
{ key: "x", color: '#e06c75', index: 0 },
|
|
167
188
|
{ key: "y", color: '#98c379', index: 1 },
|
|
168
189
|
{ key: "z", color: '#61afef', index: 2 }
|
|
169
190
|
];
|
|
170
|
-
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom:
|
|
191
|
+
return (_jsxs("div", { style: { marginBottom: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }, children: [_jsx("label", { style: Object.assign(Object.assign({}, styles.label), { marginBottom: 0 }), children: label }), labelExtra] }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axes.map(({ key, color, index }) => (_jsxs("div", { style: {
|
|
171
192
|
flex: 1,
|
|
172
193
|
display: 'flex',
|
|
173
194
|
alignItems: 'center',
|
|
@@ -177,14 +198,14 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
177
198
|
borderRadius: 3,
|
|
178
199
|
padding: '4px 6px',
|
|
179
200
|
minHeight: 28,
|
|
180
|
-
|
|
201
|
+
cursor: 'ew-resize',
|
|
202
|
+
}, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: [_jsx("span", { style: {
|
|
181
203
|
fontSize: 11,
|
|
182
204
|
fontWeight: 600,
|
|
183
205
|
color,
|
|
184
206
|
width: 12,
|
|
185
|
-
cursor: 'ew-resize',
|
|
186
207
|
userSelect: 'none',
|
|
187
|
-
},
|
|
208
|
+
}, children: key.toUpperCase() }), _jsx("input", { style: {
|
|
188
209
|
flex: 1,
|
|
189
210
|
backgroundColor: 'transparent',
|
|
190
211
|
border: 'none',
|
|
@@ -194,6 +215,7 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
194
215
|
outline: 'none',
|
|
195
216
|
width: '100%',
|
|
196
217
|
minWidth: 0,
|
|
218
|
+
cursor: 'inherit',
|
|
197
219
|
}, type: "text", value: draft[index], onChange: e => {
|
|
198
220
|
const next = [...draft];
|
|
199
221
|
next[index] = e.target.value;
|
|
@@ -233,6 +255,36 @@ export function BooleanInput({ label, value, onChange }) {
|
|
|
233
255
|
export function SelectInput({ label, value, onChange, options }) {
|
|
234
256
|
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, 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))) })] }));
|
|
235
257
|
}
|
|
258
|
+
function bindFieldChange(name, onChange) {
|
|
259
|
+
return (value) => onChange({ [name]: value });
|
|
260
|
+
}
|
|
261
|
+
export function FieldGroup({ children }) {
|
|
262
|
+
return _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: children });
|
|
263
|
+
}
|
|
264
|
+
export function NumberField({ name, label, values, onChange, fallback = 0, step, min, max, style, }) {
|
|
265
|
+
var _a;
|
|
266
|
+
return (_jsx(Input, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), step: step, min: min, max: max, style: style }));
|
|
267
|
+
}
|
|
268
|
+
export function StringField({ name, label, values, onChange, fallback = '', placeholder, }) {
|
|
269
|
+
var _a;
|
|
270
|
+
return (_jsx(StringInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), placeholder: placeholder }));
|
|
271
|
+
}
|
|
272
|
+
export function ColorField({ name, label, values, onChange, fallback = '#ffffff', }) {
|
|
273
|
+
var _a;
|
|
274
|
+
return (_jsx(ColorInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange) }));
|
|
275
|
+
}
|
|
276
|
+
export function BooleanField({ name, label, values, onChange, fallback = false, }) {
|
|
277
|
+
var _a;
|
|
278
|
+
return (_jsx(BooleanInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange) }));
|
|
279
|
+
}
|
|
280
|
+
export function SelectField({ name, label, values, onChange, fallback, options, }) {
|
|
281
|
+
var _a, _b, _c, _d;
|
|
282
|
+
return (_jsx(SelectInput, { label: label, value: (_d = (_b = (_a = values[name]) !== null && _a !== void 0 ? _a : fallback) !== null && _b !== void 0 ? _b : (_c = options[0]) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : '', onChange: bindFieldChange(name, onChange), options: options }));
|
|
283
|
+
}
|
|
284
|
+
export function Vector3Field({ name, label, values, onChange, fallback = [0, 0, 0], snap, labelExtra, }) {
|
|
285
|
+
var _a;
|
|
286
|
+
return (_jsx(Vector3Input, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), snap: snap, labelExtra: labelExtra }));
|
|
287
|
+
}
|
|
236
288
|
export function FieldRenderer({ fields, values, onChange }) {
|
|
237
289
|
const updateField = (name, value) => {
|
|
238
290
|
onChange({ [name]: value });
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { Component } from './ComponentRegistry';
|
|
2
|
-
import { MeshStandardMaterialProperties } from 'three';
|
|
3
|
-
export interface MaterialProps extends Omit<MeshStandardMaterialProperties, 'args'> {
|
|
2
|
+
import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
|
|
3
|
+
export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
|
|
4
|
+
materialType?: 'standard' | 'basic';
|
|
5
|
+
transmission?: number;
|
|
6
|
+
thickness?: number;
|
|
7
|
+
ior?: number;
|
|
4
8
|
texture?: string;
|
|
5
9
|
repeat?: boolean;
|
|
6
10
|
repeatCount?: [number, number];
|
|
7
11
|
generateMipmaps?: boolean;
|
|
8
12
|
minFilter?: string;
|
|
9
13
|
magFilter?: string;
|
|
14
|
+
normalMapTexture?: string;
|
|
15
|
+
normalScale?: [number, number];
|
|
10
16
|
}
|
|
11
17
|
declare const MaterialComponent: Component;
|
|
12
18
|
export default MaterialComponent;
|
|
@@ -11,46 +11,112 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
14
|
-
import { useEffect, useState } from 'react';
|
|
14
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
15
|
+
import { createPortal } from 'react-dom';
|
|
15
16
|
import { FieldRenderer, Input } from './Input';
|
|
16
17
|
import { colors } from '../styles';
|
|
17
18
|
import { useMemo } from 'react';
|
|
18
|
-
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter } from 'three';
|
|
19
|
+
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, Vector2, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
20
|
+
const PICKER_POPUP_WIDTH = 260;
|
|
21
|
+
const PICKER_POPUP_HEIGHT = 360;
|
|
19
22
|
function TexturePicker({ value, onChange, basePath }) {
|
|
20
23
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
21
24
|
const [showPicker, setShowPicker] = useState(false);
|
|
25
|
+
const [popupStyle, setPopupStyle] = useState(null);
|
|
26
|
+
const triggerRef = useRef(null);
|
|
22
27
|
useEffect(() => {
|
|
23
28
|
fetch(`${basePath}/textures/manifest.json`)
|
|
24
29
|
.then(r => r.json())
|
|
25
30
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
26
31
|
.catch(console.error);
|
|
27
32
|
}, [basePath]);
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
35
|
+
return;
|
|
36
|
+
const updatePosition = () => {
|
|
37
|
+
var _a;
|
|
38
|
+
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
39
|
+
if (!rect)
|
|
40
|
+
return;
|
|
41
|
+
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
42
|
+
const fallbackLeft = rect.right + 8;
|
|
43
|
+
const fitsLeft = preferredLeft >= 8;
|
|
44
|
+
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
45
|
+
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
46
|
+
setPopupStyle({
|
|
47
|
+
position: 'fixed',
|
|
48
|
+
left,
|
|
49
|
+
top,
|
|
50
|
+
background: colors.bg,
|
|
51
|
+
padding: 12,
|
|
52
|
+
border: `1px solid ${colors.border}`,
|
|
53
|
+
borderRadius: 6,
|
|
54
|
+
width: PICKER_POPUP_WIDTH,
|
|
55
|
+
height: PICKER_POPUP_HEIGHT,
|
|
56
|
+
overflow: 'hidden',
|
|
57
|
+
zIndex: 1000,
|
|
58
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
updatePosition();
|
|
62
|
+
window.addEventListener('resize', updatePosition);
|
|
63
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
64
|
+
return () => {
|
|
65
|
+
window.removeEventListener('resize', updatePosition);
|
|
66
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
67
|
+
};
|
|
68
|
+
}, [showPicker]);
|
|
28
69
|
// Only show 3D preview for server-hosted textures (starting with / or http)
|
|
29
70
|
const canPreview = value && (value.startsWith('/') || value.startsWith('http'));
|
|
30
71
|
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [canPreview
|
|
31
72
|
? _jsx(SingleTextureViewer, { file: value, basePath: basePath })
|
|
32
73
|
: value
|
|
33
74
|
? _jsx("span", { style: { fontSize: 10, opacity: 0.6, maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value })
|
|
34
|
-
: null, _jsx("button", { onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
75
|
+
: null, _jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
35
76
|
onChange(undefined);
|
|
36
|
-
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker &&
|
|
77
|
+
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(TextureListViewer, { files: textureFiles, selected: value || undefined, onSelect: (file) => {
|
|
37
78
|
onChange(file);
|
|
38
79
|
setShowPicker(false);
|
|
39
|
-
}, basePath: basePath }) }))] }));
|
|
80
|
+
}, basePath: basePath }) }), document.body)] }));
|
|
40
81
|
}
|
|
41
82
|
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
83
|
+
var _a;
|
|
84
|
+
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
42
85
|
const hasTexture = !!component.properties.texture;
|
|
43
86
|
const hasRepeat = component.properties.repeat;
|
|
87
|
+
const isStandardMaterial = materialType === 'standard';
|
|
44
88
|
const fields = [
|
|
89
|
+
{
|
|
90
|
+
name: 'materialType',
|
|
91
|
+
type: 'select',
|
|
92
|
+
label: 'Material Type',
|
|
93
|
+
options: [
|
|
94
|
+
{ value: 'standard', label: 'Standard' },
|
|
95
|
+
{ value: 'basic', label: 'Basic' },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
45
98
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
99
|
+
{ name: 'toneMapped', type: 'boolean', label: 'Tone Mapped' },
|
|
46
100
|
{ name: 'wireframe', type: 'boolean', label: 'Wireframe' },
|
|
47
101
|
{ name: 'transparent', type: 'boolean', label: 'Transparent' },
|
|
48
102
|
{ name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
103
|
+
...(isStandardMaterial ? [
|
|
104
|
+
{ name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
|
|
105
|
+
{ name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
|
|
106
|
+
{ name: 'transmission', type: 'number', label: 'Transmission', min: 0, max: 1, step: 0.01 },
|
|
107
|
+
{ name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
|
|
108
|
+
{ name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
|
|
109
|
+
] : []),
|
|
110
|
+
{
|
|
111
|
+
name: 'side',
|
|
112
|
+
type: 'select',
|
|
113
|
+
label: 'Side',
|
|
114
|
+
options: [
|
|
115
|
+
{ value: 'FrontSide', label: 'Front' },
|
|
116
|
+
{ value: 'BackSide', label: 'Back' },
|
|
117
|
+
{ value: 'DoubleSide', label: 'Double' },
|
|
118
|
+
],
|
|
119
|
+
},
|
|
54
120
|
{
|
|
55
121
|
name: 'texture',
|
|
56
122
|
type: 'custom',
|
|
@@ -69,6 +135,21 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
69
135
|
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { label: "X", 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]); }, min: 0.01, max: 100, step: 0.1 }), _jsx(Input, { label: "Y", 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]); }, min: 0.01, max: 100, step: 0.1 })] }));
|
|
70
136
|
},
|
|
71
137
|
}] : []),
|
|
138
|
+
{
|
|
139
|
+
name: 'normalMapTexture',
|
|
140
|
+
type: 'custom',
|
|
141
|
+
label: 'Normal Map',
|
|
142
|
+
render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
|
|
143
|
+
},
|
|
144
|
+
...(component.properties.normalMapTexture ? [{
|
|
145
|
+
name: 'normalScale',
|
|
146
|
+
type: 'custom',
|
|
147
|
+
label: 'Normal Scale (X, Y)',
|
|
148
|
+
render: ({ value, onChange }) => {
|
|
149
|
+
var _a, _b;
|
|
150
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(Input, { label: "X", 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]); }, min: 0, max: 5, step: 0.01 }), _jsx(Input, { label: "Y", 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]); }, min: 0, max: 5, step: 0.01 })] }));
|
|
151
|
+
},
|
|
152
|
+
}] : []),
|
|
72
153
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
73
154
|
{
|
|
74
155
|
name: 'minFilter',
|
|
@@ -98,7 +179,8 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
98
179
|
}
|
|
99
180
|
// View for Material component
|
|
100
181
|
function MaterialComponentView({ properties, loadedTextures }) {
|
|
101
|
-
var _a;
|
|
182
|
+
var _a, _b, _c;
|
|
183
|
+
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
102
184
|
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
103
185
|
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
104
186
|
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
@@ -106,9 +188,14 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
106
188
|
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
107
189
|
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
108
190
|
const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
|
|
191
|
+
const normalMapTextureName = properties === null || properties === void 0 ? void 0 : properties.normalMapTexture;
|
|
192
|
+
const normalScaleProp = properties === null || properties === void 0 ? void 0 : properties.normalScale;
|
|
193
|
+
const normalMapTexture = normalMapTextureName && loadedTextures ? loadedTextures[normalMapTextureName] : undefined;
|
|
194
|
+
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
109
195
|
// Destructure all material props and separate custom texture handling props
|
|
110
|
-
const
|
|
111
|
-
|
|
196
|
+
const { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp, metalness: _metalness, roughness: _roughness, transmission: _transmission, thickness: _thickness, ior: _ior } = materialSource, materialProps = __rest(materialSource, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side", "metalness", "roughness", "transmission", "thickness", "ior"]);
|
|
197
|
+
const sideMap = { FrontSide, BackSide, DoubleSide };
|
|
198
|
+
const resolvedSide = sideProp ? ((_b = sideMap[sideProp]) !== null && _b !== void 0 ? _b : FrontSide) : FrontSide;
|
|
112
199
|
const minFilterMap = {
|
|
113
200
|
NearestFilter,
|
|
114
201
|
LinearFilter,
|
|
@@ -142,10 +229,29 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
142
229
|
t.needsUpdate = true;
|
|
143
230
|
return t;
|
|
144
231
|
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], generateMipmaps, minFilter, magFilter]);
|
|
232
|
+
const finalNormalMap = useMemo(() => {
|
|
233
|
+
if (!normalMapTexture)
|
|
234
|
+
return undefined;
|
|
235
|
+
const t = normalMapTexture.clone();
|
|
236
|
+
t.colorSpace = LinearSRGBColorSpace;
|
|
237
|
+
t.needsUpdate = true;
|
|
238
|
+
return t;
|
|
239
|
+
}, [normalMapTexture]);
|
|
240
|
+
const normalScaleVec = useMemo(() => {
|
|
241
|
+
var _a, _b;
|
|
242
|
+
if (!finalNormalMap)
|
|
243
|
+
return undefined;
|
|
244
|
+
return new Vector2((_a = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _a !== void 0 ? _a : 1, (_b = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _b !== void 0 ? _b : 1);
|
|
245
|
+
}, [finalNormalMap, normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0], normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]]);
|
|
145
246
|
if (!properties) {
|
|
146
247
|
return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
|
|
147
248
|
}
|
|
148
|
-
|
|
249
|
+
const materialKey = (_c = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _c !== void 0 ? _c : 'no-texture';
|
|
250
|
+
const sharedProps = Object.assign({ map: finalTexture, side: resolvedSide }, materialProps);
|
|
251
|
+
if (materialType === 'basic') {
|
|
252
|
+
return _jsx("meshBasicMaterial", Object.assign({}, sharedProps), materialKey);
|
|
253
|
+
}
|
|
254
|
+
return (_jsx("meshStandardMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: normalScaleVec }), materialKey));
|
|
149
255
|
}
|
|
150
256
|
const MaterialComponent = {
|
|
151
257
|
name: 'Material',
|
|
@@ -153,7 +259,9 @@ const MaterialComponent = {
|
|
|
153
259
|
View: MaterialComponentView,
|
|
154
260
|
nonComposable: true,
|
|
155
261
|
defaultProperties: {
|
|
262
|
+
materialType: 'standard',
|
|
156
263
|
color: '#ffffff',
|
|
264
|
+
toneMapped: true,
|
|
157
265
|
wireframe: false,
|
|
158
266
|
transparent: false,
|
|
159
267
|
opacity: 1,
|
|
@@ -1,24 +1,65 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { ModelListViewer, SingleModelViewer } from '../../assetviewer/page';
|
|
3
|
-
import { useEffect, useState, useMemo } from 'react';
|
|
3
|
+
import { useEffect, useLayoutEffect, useState, useMemo, useRef } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { FieldRenderer } from './Input';
|
|
6
|
+
const PICKER_POPUP_WIDTH = 260;
|
|
7
|
+
const PICKER_POPUP_HEIGHT = 360;
|
|
5
8
|
function ModelPicker({ value, onChange, basePath, nodeId }) {
|
|
6
9
|
const [modelFiles, setModelFiles] = useState([]);
|
|
7
10
|
const [showPicker, setShowPicker] = useState(false);
|
|
11
|
+
const [popupStyle, setPopupStyle] = useState(null);
|
|
12
|
+
const triggerRef = useRef(null);
|
|
8
13
|
useEffect(() => {
|
|
9
14
|
fetch(`${basePath}/models/manifest.json`)
|
|
10
15
|
.then(r => r.json())
|
|
11
16
|
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
12
17
|
.catch(console.error);
|
|
13
18
|
}, [basePath]);
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
21
|
+
return;
|
|
22
|
+
const updatePosition = () => {
|
|
23
|
+
var _a;
|
|
24
|
+
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
25
|
+
if (!rect)
|
|
26
|
+
return;
|
|
27
|
+
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
28
|
+
const fallbackLeft = rect.right + 8;
|
|
29
|
+
const fitsLeft = preferredLeft >= 8;
|
|
30
|
+
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
31
|
+
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
32
|
+
setPopupStyle({
|
|
33
|
+
position: 'fixed',
|
|
34
|
+
left,
|
|
35
|
+
top,
|
|
36
|
+
background: 'rgba(0,0,0,0.9)',
|
|
37
|
+
padding: 12,
|
|
38
|
+
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
39
|
+
borderRadius: 6,
|
|
40
|
+
width: PICKER_POPUP_WIDTH,
|
|
41
|
+
height: PICKER_POPUP_HEIGHT,
|
|
42
|
+
overflow: 'hidden',
|
|
43
|
+
zIndex: 1000,
|
|
44
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
updatePosition();
|
|
48
|
+
window.addEventListener('resize', updatePosition);
|
|
49
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
50
|
+
return () => {
|
|
51
|
+
window.removeEventListener('resize', updatePosition);
|
|
52
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
53
|
+
};
|
|
54
|
+
}, [showPicker]);
|
|
14
55
|
const handleModelSelect = (file) => {
|
|
15
56
|
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
16
57
|
onChange(filename);
|
|
17
58
|
setShowPicker(false);
|
|
18
59
|
};
|
|
19
|
-
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: value ? `/${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 ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
60
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [_jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }), _jsx("button", { ref: triggerRef, 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 ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
20
61
|
onChange(undefined);
|
|
21
|
-
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker &&
|
|
62
|
+
}, style: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)', marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(ModelListViewer, { files: modelFiles, selected: value ? `/${value}` : undefined, onSelect: handleModelSelect, basePath: basePath }, nodeId) }), document.body)] }));
|
|
22
63
|
}
|
|
23
64
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
24
65
|
const fields = [
|
|
@@ -9,91 +9,26 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
12
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
|
-
import {
|
|
15
|
+
import { BooleanField, FieldGroup, NumberField, SelectField } from "./Input";
|
|
16
16
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
17
|
-
const physicsFields = [
|
|
18
|
-
{
|
|
19
|
-
name: 'type',
|
|
20
|
-
type: 'select',
|
|
21
|
-
label: 'Type',
|
|
22
|
-
options: [
|
|
23
|
-
{ value: 'dynamic', label: 'Dynamic' },
|
|
24
|
-
{ value: 'fixed', label: 'Fixed' },
|
|
25
|
-
{ value: 'kinematicPosition', label: 'Kinematic Position' },
|
|
26
|
-
{ value: 'kinematicVelocity', label: 'Kinematic Velocity' },
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
name: 'colliders',
|
|
31
|
-
type: 'select',
|
|
32
|
-
label: 'Collider',
|
|
33
|
-
options: [
|
|
34
|
-
{ value: 'hull', label: 'Hull (convex)' },
|
|
35
|
-
{ value: 'trimesh', label: 'Trimesh (exact)' },
|
|
36
|
-
{ value: 'cuboid', label: 'Cuboid (box)' },
|
|
37
|
-
{ value: 'ball', label: 'Ball (sphere)' },
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'mass',
|
|
42
|
-
type: 'number',
|
|
43
|
-
label: 'Mass',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: 'restitution',
|
|
47
|
-
type: 'number',
|
|
48
|
-
label: 'Restitution (Bounciness)',
|
|
49
|
-
min: 0,
|
|
50
|
-
max: 1,
|
|
51
|
-
step: 0.1,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: 'friction',
|
|
55
|
-
type: 'number',
|
|
56
|
-
label: 'Friction',
|
|
57
|
-
min: 0,
|
|
58
|
-
step: 0.1,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: 'linearDamping',
|
|
62
|
-
type: 'number',
|
|
63
|
-
label: 'Linear Damping',
|
|
64
|
-
min: 0,
|
|
65
|
-
step: 0.1,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'angularDamping',
|
|
69
|
-
type: 'number',
|
|
70
|
-
label: 'Angular Damping',
|
|
71
|
-
min: 0,
|
|
72
|
-
step: 0.1,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'gravityScale',
|
|
76
|
-
type: 'number',
|
|
77
|
-
label: 'Gravity Scale',
|
|
78
|
-
step: 0.1,
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: 'sensor',
|
|
82
|
-
type: 'boolean',
|
|
83
|
-
label: 'Sensor (Trigger Only)',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'activeCollisionTypes',
|
|
87
|
-
type: 'select',
|
|
88
|
-
label: 'Collision Detection',
|
|
89
|
-
options: [
|
|
90
|
-
{ value: '', label: 'Default (Dynamic only)' },
|
|
91
|
-
{ value: 'all', label: 'All (includes kinematic & fixed)' },
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
17
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
96
|
-
return (_jsx(
|
|
18
|
+
return (_jsxs(FieldGroup, { children: [_jsx(SelectField, { name: "type", label: "Type", values: component.properties, onChange: onUpdate, options: [
|
|
19
|
+
{ value: 'dynamic', label: 'Dynamic' },
|
|
20
|
+
{ value: 'fixed', label: 'Fixed' },
|
|
21
|
+
{ value: 'kinematicPosition', label: 'Kinematic Position' },
|
|
22
|
+
{ value: 'kinematicVelocity', label: 'Kinematic Velocity' },
|
|
23
|
+
] }), _jsx(SelectField, { name: "colliders", label: "Collider", values: component.properties, onChange: onUpdate, options: [
|
|
24
|
+
{ value: 'hull', label: 'Hull (convex)' },
|
|
25
|
+
{ value: 'trimesh', label: 'Trimesh (exact)' },
|
|
26
|
+
{ value: 'cuboid', label: 'Cuboid (box)' },
|
|
27
|
+
{ value: 'ball', label: 'Ball (sphere)' },
|
|
28
|
+
] }), _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
|
|
29
|
+
{ value: '', label: 'Default (Dynamic only)' },
|
|
30
|
+
{ value: 'all', label: 'All (includes kinematic & fixed)' },
|
|
31
|
+
] })] }));
|
|
97
32
|
}
|
|
98
33
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
|
|
99
34
|
const { type, colliders, sensor, activeCollisionTypes } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes"]);
|