react-three-game 0.0.55 → 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/ContactShadow.d.ts +8 -0
- package/dist/shared/ContactShadow.js +32 -0
- package/dist/shared/GameCanvas.js +1 -3
- package/dist/tools/assetviewer/page.js +36 -15
- package/dist/tools/dragdrop/DragDropLoader.js +17 -40
- package/dist/tools/dragdrop/modelLoader.d.ts +5 -0
- package/dist/tools/dragdrop/modelLoader.js +39 -0
- 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 +139 -70
- package/dist/tools/prefabeditor/EditorUI.js +5 -10
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.js +70 -3
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +136 -35
- 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 +100 -47
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
- package/dist/tools/prefabeditor/components/MaterialComponent.js +129 -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 +6 -11
- package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
- package/dist/tools/prefabeditor/components/TransformComponent.js +31 -19
- package/dist/tools/prefabeditor/components/index.js +5 -1
- package/dist/tools/prefabeditor/styles.d.ts +17 -4
- package/dist/tools/prefabeditor/styles.js +69 -32
- package/dist/tools/prefabeditor/utils.d.ts +8 -3
- package/dist/tools/prefabeditor/utils.js +92 -6
- package/package.json +1 -1
- package/react-three-game-skill/react-three-game/rules/LIGHTING.md +6 -0
- package/src/index.ts +7 -0
- package/src/shared/ContactShadow.tsx +74 -0
- package/src/shared/GameCanvas.tsx +0 -3
- package/src/tools/assetviewer/page.tsx +78 -46
- package/src/tools/dragdrop/DragDropLoader.tsx +7 -39
- package/src/tools/dragdrop/modelLoader.ts +36 -0
- package/src/tools/prefabeditor/Dropdown.tsx +112 -0
- package/src/tools/prefabeditor/EditorContext.tsx +5 -0
- package/src/tools/prefabeditor/EditorTree.tsx +237 -115
- package/src/tools/prefabeditor/EditorUI.tsx +6 -11
- package/src/tools/prefabeditor/PrefabEditor.tsx +77 -5
- package/src/tools/prefabeditor/PrefabRoot.tsx +228 -59
- 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 +247 -53
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +191 -20
- package/src/tools/prefabeditor/components/ModelComponent.tsx +52 -5
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
- package/src/tools/prefabeditor/components/SpotLightComponent.tsx +14 -16
- package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
- package/src/tools/prefabeditor/components/TransformComponent.tsx +78 -20
- package/src/tools/prefabeditor/components/index.ts +5 -1
- package/src/tools/prefabeditor/styles.ts +71 -32
- package/src/tools/prefabeditor/utils.ts +96 -5
|
@@ -1,30 +1,54 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { colors } from '../styles';
|
|
3
4
|
// ============================================================================
|
|
4
|
-
// Shared Styles
|
|
5
|
+
// Shared Styles (derived from shared color tokens)
|
|
5
6
|
// ============================================================================
|
|
6
|
-
// Shared styles
|
|
7
7
|
const styles = {
|
|
8
8
|
input: {
|
|
9
9
|
width: '80px',
|
|
10
|
-
backgroundColor:
|
|
11
|
-
border:
|
|
12
|
-
padding: '
|
|
13
|
-
fontSize: '
|
|
14
|
-
color:
|
|
10
|
+
backgroundColor: colors.bgInput,
|
|
11
|
+
border: `1px solid ${colors.border}`,
|
|
12
|
+
padding: '3px 6px',
|
|
13
|
+
fontSize: '11px',
|
|
14
|
+
color: colors.text,
|
|
15
15
|
fontFamily: 'monospace',
|
|
16
16
|
outline: 'none',
|
|
17
17
|
textAlign: 'right',
|
|
18
|
+
borderRadius: 3,
|
|
18
19
|
},
|
|
19
20
|
label: {
|
|
20
21
|
display: 'block',
|
|
21
|
-
fontSize: '
|
|
22
|
-
color:
|
|
22
|
+
fontSize: '10px',
|
|
23
|
+
color: colors.textMuted,
|
|
23
24
|
textTransform: 'uppercase',
|
|
24
25
|
letterSpacing: '0.05em',
|
|
25
26
|
marginBottom: 2,
|
|
27
|
+
fontWeight: 500,
|
|
26
28
|
},
|
|
27
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
|
+
}
|
|
28
52
|
export function Input({ value, onChange, step, min, max, style, label }) {
|
|
29
53
|
const [draft, setDraft] = useState(() => value.toString());
|
|
30
54
|
useEffect(() => {
|
|
@@ -46,14 +70,11 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
46
70
|
};
|
|
47
71
|
const dragState = useRef(null);
|
|
48
72
|
const startScrub = (e) => {
|
|
49
|
-
if (!label)
|
|
50
|
-
return;
|
|
51
|
-
e.preventDefault();
|
|
52
73
|
dragState.current = {
|
|
53
74
|
startX: e.clientX,
|
|
54
75
|
startValue: value
|
|
55
76
|
};
|
|
56
|
-
e.
|
|
77
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
57
78
|
document.body.style.cursor = "ew-resize";
|
|
58
79
|
};
|
|
59
80
|
const onScrubMove = (e) => {
|
|
@@ -61,18 +82,21 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
61
82
|
return;
|
|
62
83
|
const { startX, startValue } = dragState.current;
|
|
63
84
|
const dx = e.clientX - startX;
|
|
64
|
-
|
|
85
|
+
const baseStep = getNumericStep(step, 0.1);
|
|
86
|
+
let scrubStep = baseStep;
|
|
65
87
|
if (e.shiftKey)
|
|
66
|
-
|
|
88
|
+
scrubStep /= 10;
|
|
67
89
|
if (e.altKey)
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
scrubStep *= 10;
|
|
91
|
+
const precision = getStepPrecision(scrubStep);
|
|
92
|
+
const deltaSteps = Math.round(dx / 8);
|
|
93
|
+
let nextValue = startValue + deltaSteps * scrubStep;
|
|
70
94
|
// Apply min/max constraints
|
|
71
95
|
if (min !== undefined && nextValue < min)
|
|
72
96
|
nextValue = min;
|
|
73
97
|
if (max !== undefined && nextValue > max)
|
|
74
98
|
nextValue = max;
|
|
75
|
-
setDraft(nextValue.toFixed(
|
|
99
|
+
setDraft(nextValue.toFixed(precision));
|
|
76
100
|
onChange(nextValue);
|
|
77
101
|
};
|
|
78
102
|
const endScrub = (e) => {
|
|
@@ -80,29 +104,29 @@ export function Input({ value, onChange, step, min, max, style, label }) {
|
|
|
80
104
|
return;
|
|
81
105
|
dragState.current = null;
|
|
82
106
|
document.body.style.cursor = "";
|
|
83
|
-
e.
|
|
107
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
84
108
|
};
|
|
85
109
|
if (label) {
|
|
86
110
|
return (_jsxs("div", { style: {
|
|
87
111
|
display: 'flex',
|
|
88
112
|
alignItems: 'center',
|
|
89
113
|
justifyContent: 'space-between',
|
|
90
|
-
}, 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 => {
|
|
91
115
|
if (e.key === 'Enter') {
|
|
92
116
|
e.target.blur();
|
|
93
117
|
}
|
|
94
|
-
}, 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 })] }));
|
|
95
119
|
}
|
|
96
120
|
return (_jsx("input", { type: "text", value: draft, onChange: handleChange, onBlur: handleBlur, onKeyDown: e => {
|
|
97
121
|
if (e.key === 'Enter') {
|
|
98
122
|
e.target.blur();
|
|
99
123
|
}
|
|
100
|
-
}, 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 }));
|
|
101
125
|
}
|
|
102
126
|
export function Label({ children }) {
|
|
103
127
|
return _jsx("label", { style: styles.label, children: children });
|
|
104
128
|
}
|
|
105
|
-
export function Vector3Input({ label, value, onChange, snap }) {
|
|
129
|
+
export function Vector3Input({ label, value, onChange, snap, labelExtra }) {
|
|
106
130
|
const snapValue = (num) => {
|
|
107
131
|
if (!snap)
|
|
108
132
|
return num;
|
|
@@ -123,13 +147,12 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
123
147
|
}
|
|
124
148
|
};
|
|
125
149
|
const startScrub = (e, index) => {
|
|
126
|
-
e.preventDefault();
|
|
127
150
|
dragState.current = {
|
|
128
151
|
index,
|
|
129
152
|
startX: e.clientX,
|
|
130
153
|
startValue: value[index]
|
|
131
154
|
};
|
|
132
|
-
e.
|
|
155
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
133
156
|
document.body.style.cursor = "ew-resize";
|
|
134
157
|
};
|
|
135
158
|
const onScrubMove = (e) => {
|
|
@@ -158,40 +181,41 @@ export function Vector3Input({ label, value, onChange, snap }) {
|
|
|
158
181
|
return;
|
|
159
182
|
dragState.current = null;
|
|
160
183
|
document.body.style.cursor = "";
|
|
161
|
-
e.
|
|
184
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
162
185
|
};
|
|
163
186
|
const axes = [
|
|
164
|
-
{ key: "x", color: '
|
|
165
|
-
{ key: "y", color: '
|
|
166
|
-
{ key: "z", color: '
|
|
187
|
+
{ key: "x", color: '#e06c75', index: 0 },
|
|
188
|
+
{ key: "y", color: '#98c379', index: 1 },
|
|
189
|
+
{ key: "z", color: '#61afef', index: 2 }
|
|
167
190
|
];
|
|
168
|
-
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: {
|
|
169
192
|
flex: 1,
|
|
170
193
|
display: 'flex',
|
|
171
194
|
alignItems: 'center',
|
|
172
195
|
gap: 4,
|
|
173
|
-
backgroundColor:
|
|
174
|
-
border:
|
|
175
|
-
borderRadius:
|
|
196
|
+
backgroundColor: colors.bgInput,
|
|
197
|
+
border: `1px solid ${colors.border}`,
|
|
198
|
+
borderRadius: 3,
|
|
176
199
|
padding: '4px 6px',
|
|
177
|
-
minHeight:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
minHeight: 28,
|
|
201
|
+
cursor: 'ew-resize',
|
|
202
|
+
}, onPointerDown: e => startScrub(e, index), onPointerMove: onScrubMove, onPointerUp: endScrub, children: [_jsx("span", { style: {
|
|
203
|
+
fontSize: 11,
|
|
204
|
+
fontWeight: 600,
|
|
181
205
|
color,
|
|
182
206
|
width: 12,
|
|
183
|
-
cursor: 'ew-resize',
|
|
184
207
|
userSelect: 'none',
|
|
185
|
-
},
|
|
208
|
+
}, children: key.toUpperCase() }), _jsx("input", { style: {
|
|
186
209
|
flex: 1,
|
|
187
210
|
backgroundColor: 'transparent',
|
|
188
211
|
border: 'none',
|
|
189
|
-
fontSize:
|
|
190
|
-
color:
|
|
212
|
+
fontSize: 11,
|
|
213
|
+
color: colors.text,
|
|
191
214
|
fontFamily: 'monospace',
|
|
192
215
|
outline: 'none',
|
|
193
216
|
width: '100%',
|
|
194
217
|
minWidth: 0,
|
|
218
|
+
cursor: 'inherit',
|
|
195
219
|
}, type: "text", value: draft[index], onChange: e => {
|
|
196
220
|
const next = [...draft];
|
|
197
221
|
next[index] = e.target.value;
|
|
@@ -209,11 +233,11 @@ export function ColorInput({ label, value, onChange }) {
|
|
|
209
233
|
return (_jsxs("div", { children: [label && _jsx(Label, { children: label }), _jsxs("div", { style: { display: 'flex', gap: 4, justifyContent: 'space-between' }, children: [_jsx("input", { type: "color", style: {
|
|
210
234
|
height: 32,
|
|
211
235
|
width: 48,
|
|
212
|
-
backgroundColor:
|
|
213
|
-
border:
|
|
214
|
-
borderRadius:
|
|
236
|
+
backgroundColor: colors.bgInput,
|
|
237
|
+
border: `1px solid ${colors.border}`,
|
|
238
|
+
borderRadius: 3,
|
|
215
239
|
cursor: 'pointer',
|
|
216
|
-
padding:
|
|
240
|
+
padding: 2,
|
|
217
241
|
flexShrink: 0,
|
|
218
242
|
}, value: value, onChange: e => onChange(e.target.value) }), _jsx("input", { type: "text", style: Object.assign({}, styles.input), value: value, onChange: e => onChange(e.target.value) })] })] }));
|
|
219
243
|
}
|
|
@@ -224,14 +248,43 @@ export function BooleanInput({ label, value, onChange }) {
|
|
|
224
248
|
return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
|
|
225
249
|
height: 16,
|
|
226
250
|
width: 16,
|
|
227
|
-
|
|
228
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
251
|
+
accentColor: colors.accent,
|
|
229
252
|
cursor: 'pointer',
|
|
230
253
|
}, checked: value, onChange: e => onChange(e.target.checked) })] }));
|
|
231
254
|
}
|
|
232
255
|
export function SelectInput({ label, value, onChange, options }) {
|
|
233
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))) })] }));
|
|
234
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
|
+
}
|
|
235
288
|
export function FieldRenderer({ fields, values, onChange }) {
|
|
236
289
|
const updateField = (name, value) => {
|
|
237
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,39 +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';
|
|
17
|
+
import { colors } from '../styles';
|
|
16
18
|
import { useMemo } from 'react';
|
|
17
|
-
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;
|
|
18
22
|
function TexturePicker({ value, onChange, basePath }) {
|
|
19
23
|
const [textureFiles, setTextureFiles] = useState([]);
|
|
20
24
|
const [showPicker, setShowPicker] = useState(false);
|
|
25
|
+
const [popupStyle, setPopupStyle] = useState(null);
|
|
26
|
+
const triggerRef = useRef(null);
|
|
21
27
|
useEffect(() => {
|
|
22
28
|
fetch(`${basePath}/textures/manifest.json`)
|
|
23
29
|
.then(r => r.json())
|
|
24
30
|
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
25
31
|
.catch(console.error);
|
|
26
32
|
}, [basePath]);
|
|
27
|
-
|
|
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]);
|
|
69
|
+
// Only show 3D preview for server-hosted textures (starting with / or http)
|
|
70
|
+
const canPreview = value && (value.startsWith('/') || value.startsWith('http'));
|
|
71
|
+
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [canPreview
|
|
72
|
+
? _jsx(SingleTextureViewer, { file: value, basePath: basePath })
|
|
73
|
+
: value
|
|
74
|
+
? _jsx("span", { style: { fontSize: 10, opacity: 0.6, maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value })
|
|
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: () => {
|
|
28
76
|
onChange(undefined);
|
|
29
|
-
}, style: { padding: '4px 8px', backgroundColor:
|
|
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) => {
|
|
30
78
|
onChange(file);
|
|
31
79
|
setShowPicker(false);
|
|
32
|
-
}, basePath: basePath }) }))] }));
|
|
80
|
+
}, basePath: basePath }) }), document.body)] }));
|
|
33
81
|
}
|
|
34
82
|
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
83
|
+
var _a;
|
|
84
|
+
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
35
85
|
const hasTexture = !!component.properties.texture;
|
|
36
86
|
const hasRepeat = component.properties.repeat;
|
|
87
|
+
const isStandardMaterial = materialType === 'standard';
|
|
37
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
|
+
},
|
|
38
98
|
{ name: 'color', type: 'color', label: 'Color' },
|
|
99
|
+
{ name: 'toneMapped', type: 'boolean', label: 'Tone Mapped' },
|
|
39
100
|
{ name: 'wireframe', type: 'boolean', label: 'Wireframe' },
|
|
40
101
|
{ name: 'transparent', type: 'boolean', label: 'Transparent' },
|
|
41
102
|
{ name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
},
|
|
47
120
|
{
|
|
48
121
|
name: 'texture',
|
|
49
122
|
type: 'custom',
|
|
@@ -62,6 +135,21 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
62
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 })] }));
|
|
63
136
|
},
|
|
64
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
|
+
}] : []),
|
|
65
153
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
66
154
|
{
|
|
67
155
|
name: 'minFilter',
|
|
@@ -91,7 +179,8 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
91
179
|
}
|
|
92
180
|
// View for Material component
|
|
93
181
|
function MaterialComponentView({ properties, loadedTextures }) {
|
|
94
|
-
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';
|
|
95
184
|
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
96
185
|
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
97
186
|
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
@@ -99,9 +188,14 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
99
188
|
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
100
189
|
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
101
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 : {};
|
|
102
195
|
// Destructure all material props and separate custom texture handling props
|
|
103
|
-
const
|
|
104
|
-
|
|
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;
|
|
105
199
|
const minFilterMap = {
|
|
106
200
|
NearestFilter,
|
|
107
201
|
LinearFilter,
|
|
@@ -135,10 +229,29 @@ function MaterialComponentView({ properties, loadedTextures }) {
|
|
|
135
229
|
t.needsUpdate = true;
|
|
136
230
|
return t;
|
|
137
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]]);
|
|
138
246
|
if (!properties) {
|
|
139
247
|
return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
|
|
140
248
|
}
|
|
141
|
-
|
|
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));
|
|
142
255
|
}
|
|
143
256
|
const MaterialComponent = {
|
|
144
257
|
name: 'Material',
|
|
@@ -146,7 +259,9 @@ const MaterialComponent = {
|
|
|
146
259
|
View: MaterialComponentView,
|
|
147
260
|
nonComposable: true,
|
|
148
261
|
defaultProperties: {
|
|
262
|
+
materialType: 'standard',
|
|
149
263
|
color: '#ffffff',
|
|
264
|
+
toneMapped: true,
|
|
150
265
|
wireframe: false,
|
|
151
266
|
transparent: false,
|
|
152
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,
|
|
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 = [
|