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.
Files changed (59) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/shared/GameCanvas.js +1 -3
  4. package/dist/tools/assetviewer/page.js +35 -14
  5. package/dist/tools/prefabeditor/Dropdown.d.ts +15 -0
  6. package/dist/tools/prefabeditor/Dropdown.js +82 -0
  7. package/dist/tools/prefabeditor/EditorContext.d.ts +5 -0
  8. package/dist/tools/prefabeditor/EditorTree.js +138 -56
  9. package/dist/tools/prefabeditor/EditorUI.js +1 -1
  10. package/dist/tools/prefabeditor/PrefabEditor.d.ts +1 -0
  11. package/dist/tools/prefabeditor/PrefabEditor.js +13 -2
  12. package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -0
  13. package/dist/tools/prefabeditor/PrefabRoot.js +120 -34
  14. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +3 -7
  15. package/dist/tools/prefabeditor/components/CameraComponent.d.ts +3 -0
  16. package/dist/tools/prefabeditor/components/CameraComponent.js +25 -0
  17. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +2 -2
  18. package/dist/tools/prefabeditor/components/EnvironmentComponent.d.ts +3 -0
  19. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +15 -0
  20. package/dist/tools/prefabeditor/components/GeometryComponent.js +46 -46
  21. package/dist/tools/prefabeditor/components/Input.d.ts +51 -1
  22. package/dist/tools/prefabeditor/components/Input.js +73 -21
  23. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +8 -2
  24. package/dist/tools/prefabeditor/components/MaterialComponent.js +122 -14
  25. package/dist/tools/prefabeditor/components/ModelComponent.js +44 -3
  26. package/dist/tools/prefabeditor/components/PhysicsComponent.js +16 -81
  27. package/dist/tools/prefabeditor/components/SpotLightComponent.js +4 -12
  28. package/dist/tools/prefabeditor/components/TextComponent.js +7 -53
  29. package/dist/tools/prefabeditor/components/TransformComponent.js +18 -8
  30. package/dist/tools/prefabeditor/components/index.js +5 -1
  31. package/dist/tools/prefabeditor/styles.d.ts +5 -2
  32. package/dist/tools/prefabeditor/styles.js +7 -3
  33. package/dist/tools/prefabeditor/utils.d.ts +4 -3
  34. package/dist/tools/prefabeditor/utils.js +53 -5
  35. package/package.json +1 -1
  36. package/src/index.ts +7 -0
  37. package/src/shared/GameCanvas.tsx +0 -3
  38. package/src/tools/assetviewer/page.tsx +77 -45
  39. package/src/tools/prefabeditor/Dropdown.tsx +112 -0
  40. package/src/tools/prefabeditor/EditorContext.tsx +5 -0
  41. package/src/tools/prefabeditor/EditorTree.tsx +234 -101
  42. package/src/tools/prefabeditor/EditorUI.tsx +1 -1
  43. package/src/tools/prefabeditor/PrefabEditor.tsx +17 -4
  44. package/src/tools/prefabeditor/PrefabRoot.tsx +208 -58
  45. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +5 -11
  46. package/src/tools/prefabeditor/components/CameraComponent.tsx +80 -0
  47. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +2 -2
  48. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +47 -0
  49. package/src/tools/prefabeditor/components/GeometryComponent.tsx +69 -63
  50. package/src/tools/prefabeditor/components/Input.tsx +220 -27
  51. package/src/tools/prefabeditor/components/MaterialComponent.tsx +178 -16
  52. package/src/tools/prefabeditor/components/ModelComponent.tsx +51 -4
  53. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +44 -85
  54. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +11 -17
  55. package/src/tools/prefabeditor/components/TextComponent.tsx +58 -57
  56. package/src/tools/prefabeditor/components/TransformComponent.tsx +61 -9
  57. package/src/tools/prefabeditor/components/index.ts +5 -1
  58. package/src/tools/prefabeditor/styles.ts +7 -3
  59. package/src/tools/prefabeditor/utils.ts +55 -4
@@ -1,28 +1,49 @@
1
1
  import { Component } from "./ComponentRegistry";
2
- import { FieldRenderer, FieldDefinition, Input, Label } from "./Input";
2
+ import { FieldGroup, NumberField, SelectField } from "./Input";
3
3
 
4
4
  const GEOMETRY_ARGS: Record<string, {
5
- labels: string[];
6
- defaults: number[];
5
+ fields: Array<{
6
+ name: string;
7
+ label: string;
8
+ defaultValue: number;
9
+ min?: number;
10
+ step?: number;
11
+ }>;
7
12
  }> = {
8
13
  box: {
9
- labels: ["Width", "Height", "Depth"],
10
- defaults: [1, 1, 1],
14
+ fields: [
15
+ { name: 'width', label: 'Width', defaultValue: 1, min: 0.01, step: 0.1 },
16
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
17
+ { name: 'depth', label: 'Depth', defaultValue: 1, min: 0.01, step: 0.1 },
18
+ ],
11
19
  },
12
20
  sphere: {
13
- labels: ["Radius", "Width Segments", "Height Segments"],
14
- defaults: [1, 32, 16],
21
+ fields: [
22
+ { name: 'radius', label: 'Radius', defaultValue: 1, min: 0.01, step: 0.1 },
23
+ { name: 'widthSegments', label: 'Width Segments', defaultValue: 32, min: 3, step: 1 },
24
+ { name: 'heightSegments', label: 'Height Segments', defaultValue: 16, min: 2, step: 1 },
25
+ ],
15
26
  },
16
27
  plane: {
17
- labels: ["Width", "Height"],
18
- defaults: [1, 1],
28
+ fields: [
29
+ { name: 'width', label: 'Width', defaultValue: 1, min: 0.01, step: 0.1 },
30
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
31
+ ],
19
32
  },
20
33
  cylinder: {
21
- labels: ["Radius Top", "Radius Bottom", "Height", "Radial Segments"],
22
- defaults: [1, 1, 1, 32],
34
+ fields: [
35
+ { name: 'radiusTop', label: 'Radius Top', defaultValue: 1, min: 0.01, step: 0.1 },
36
+ { name: 'radiusBottom', label: 'Radius Bottom', defaultValue: 1, min: 0.01, step: 0.1 },
37
+ { name: 'height', label: 'Height', defaultValue: 1, min: 0.01, step: 0.1 },
38
+ { name: 'radialSegments', label: 'Radial Segments', defaultValue: 32, min: 3, step: 1 },
39
+ ],
23
40
  },
24
41
  };
25
42
 
43
+ function getDefaultArgs(geometryType: string) {
44
+ return (GEOMETRY_ARGS[geometryType]?.fields ?? []).map(field => field.defaultValue);
45
+ }
46
+
26
47
  function GeometryComponentEditor({
27
48
  component,
28
49
  onUpdate,
@@ -30,67 +51,52 @@ function GeometryComponentEditor({
30
51
  component: any;
31
52
  onUpdate: (newProps: any) => void;
32
53
  }) {
33
- const { geometryType, args = [] } = component.properties;
34
- const schema = GEOMETRY_ARGS[geometryType];
35
-
36
- const fields: FieldDefinition[] = [
37
- {
38
- name: 'geometryType',
39
- type: 'select',
40
- label: 'Type',
41
- options: [
42
- { value: 'box', label: 'Box' },
43
- { value: 'sphere', label: 'Sphere' },
44
- { value: 'plane', label: 'Plane' },
45
- { value: 'cylinder', label: 'Cylinder' },
46
- ],
47
- },
48
- {
49
- name: 'args',
50
- type: 'custom',
51
- label: '',
52
- render: ({ values, onChangeMultiple }) => {
53
- const currentType = values.geometryType;
54
- const currentSchema = GEOMETRY_ARGS[currentType];
55
- const currentArgs = values.args || currentSchema.defaults;
56
-
57
- return (
58
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
59
- {currentSchema.labels.map((label, i) => (
60
- <Input
61
- key={label}
62
- label={label}
63
- value={currentArgs[i] ?? currentSchema.defaults[i]}
64
- step={0.1}
65
- min={0.01}
66
- onChange={value => {
67
- const next = [...currentArgs];
68
- next[i] = value;
69
- onChangeMultiple({ args: next });
70
- }}
71
- />
72
- ))}
73
- </div>
74
- );
75
- },
76
- },
77
- ];
54
+ const geometryType = component.properties.geometryType ?? 'box';
55
+ const schema = GEOMETRY_ARGS[geometryType] ?? GEOMETRY_ARGS.box;
56
+ const args = component.properties.args ?? getDefaultArgs(geometryType);
78
57
 
79
58
  // Handle geometry type change to reset args
80
59
  const handleChange = (newValues: Record<string, any>) => {
81
60
  if ('geometryType' in newValues && newValues.geometryType !== geometryType) {
82
- onUpdate({ geometryType: newValues.geometryType, args: GEOMETRY_ARGS[newValues.geometryType].defaults });
61
+ onUpdate({ geometryType: newValues.geometryType, args: getDefaultArgs(newValues.geometryType) });
83
62
  } else {
84
63
  onUpdate(newValues);
85
64
  }
86
65
  };
87
66
 
67
+ const updateArg = (index: number, value: number) => {
68
+ const next = [...args];
69
+ next[index] = value;
70
+ onUpdate({ args: next });
71
+ };
72
+
88
73
  return (
89
- <FieldRenderer
90
- fields={fields}
91
- values={component.properties}
92
- onChange={handleChange}
93
- />
74
+ <FieldGroup>
75
+ <SelectField
76
+ name="geometryType"
77
+ label="Type"
78
+ values={component.properties}
79
+ onChange={handleChange}
80
+ options={[
81
+ { value: 'box', label: 'Box' },
82
+ { value: 'sphere', label: 'Sphere' },
83
+ { value: 'plane', label: 'Plane' },
84
+ { value: 'cylinder', label: 'Cylinder' },
85
+ ]}
86
+ />
87
+ {schema.fields.map((field, index) => (
88
+ <NumberField
89
+ key={field.name}
90
+ name={field.name}
91
+ label={field.label}
92
+ values={{ [field.name]: args[index] ?? field.defaultValue }}
93
+ onChange={(next) => updateArg(index, next[field.name])}
94
+ fallback={field.defaultValue}
95
+ min={field.min}
96
+ step={field.step}
97
+ />
98
+ ))}
99
+ </FieldGroup>
94
100
  );
95
101
  }
96
102
 
@@ -120,7 +126,7 @@ const GeometryComponent: Component = {
120
126
  nonComposable: true,
121
127
  defaultProperties: {
122
128
  geometryType: 'box',
123
- args: GEOMETRY_ARGS.box.defaults,
129
+ args: getDefaultArgs('box'),
124
130
  }
125
131
  };
126
132
 
@@ -89,6 +89,30 @@ const styles = {
89
89
  } as React.CSSProperties,
90
90
  };
91
91
 
92
+ function getNumericStep(step: string | number | undefined, fallback: number) {
93
+ if (typeof step === 'number' && Number.isFinite(step) && step > 0) return step;
94
+
95
+ if (typeof step === 'string') {
96
+ const parsed = parseFloat(step);
97
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
98
+ }
99
+
100
+ return fallback;
101
+ }
102
+
103
+ function getStepPrecision(step: number) {
104
+ if (!Number.isFinite(step) || step <= 0) return 3;
105
+
106
+ const stepString = step.toString();
107
+ if (stepString.includes('e-')) {
108
+ const exponent = stepString.split('e-')[1];
109
+ return exponent ? parseInt(exponent, 10) : 3;
110
+ }
111
+
112
+ const decimal = stepString.split('.')[1];
113
+ return decimal?.length ?? 0;
114
+ }
115
+
92
116
  interface InputProps {
93
117
  value: number;
94
118
  onChange: (value: number) => void;
@@ -129,15 +153,12 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
129
153
  } | null>(null);
130
154
 
131
155
  const startScrub = (e: React.PointerEvent) => {
132
- if (!label) return;
133
- e.preventDefault();
134
-
135
156
  dragState.current = {
136
157
  startX: e.clientX,
137
158
  startValue: value
138
159
  };
139
160
 
140
- (e.target as HTMLElement).setPointerCapture(e.pointerId);
161
+ e.currentTarget.setPointerCapture(e.pointerId);
141
162
  document.body.style.cursor = "ew-resize";
142
163
  };
143
164
 
@@ -146,18 +167,20 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
146
167
 
147
168
  const { startX, startValue } = dragState.current;
148
169
  const dx = e.clientX - startX;
170
+ const baseStep = getNumericStep(step, 0.1);
171
+ let scrubStep = baseStep;
172
+ if (e.shiftKey) scrubStep /= 10;
173
+ if (e.altKey) scrubStep *= 10;
149
174
 
150
- let speed = 0.02;
151
- if (e.shiftKey) speed *= 0.1; // fine
152
- if (e.altKey) speed *= 5; // coarse
153
-
154
- let nextValue = startValue + dx * speed;
175
+ const precision = getStepPrecision(scrubStep);
176
+ const deltaSteps = Math.round(dx / 8);
177
+ let nextValue = startValue + deltaSteps * scrubStep;
155
178
 
156
179
  // Apply min/max constraints
157
180
  if (min !== undefined && nextValue < min) nextValue = min;
158
181
  if (max !== undefined && nextValue > max) nextValue = max;
159
182
 
160
- setDraft(nextValue.toFixed(3));
183
+ setDraft(nextValue.toFixed(precision));
161
184
  onChange(nextValue);
162
185
  };
163
186
 
@@ -166,7 +189,7 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
166
189
 
167
190
  dragState.current = null;
168
191
  document.body.style.cursor = "";
169
- (e.target as HTMLElement).releasePointerCapture(e.pointerId);
192
+ e.currentTarget.releasePointerCapture(e.pointerId);
170
193
  };
171
194
 
172
195
  if (label) {
@@ -180,14 +203,10 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
180
203
  style={{
181
204
  ...styles.label,
182
205
  marginBottom: 0,
183
- cursor: 'ew-resize',
184
206
  userSelect: 'none',
185
207
  flex: '0 0 auto',
186
208
  minWidth: 20,
187
209
  }}
188
- onPointerDown={startScrub}
189
- onPointerMove={onScrubMove}
190
- onPointerUp={endScrub}
191
210
  >
192
211
  {label}
193
212
  </span>
@@ -204,7 +223,10 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
204
223
  step={step}
205
224
  min={min}
206
225
  max={max}
207
- style={{ ...styles.input, ...style }}
226
+ style={{ ...styles.input, cursor: 'ew-resize', ...style }}
227
+ onPointerDown={startScrub}
228
+ onPointerMove={onScrubMove}
229
+ onPointerUp={endScrub}
208
230
  />
209
231
  </div>
210
232
  );
@@ -224,7 +246,10 @@ export function Input({ value, onChange, step, min, max, style, label }: InputPr
224
246
  step={step}
225
247
  min={min}
226
248
  max={max}
227
- style={{ ...styles.input, ...style }}
249
+ style={{ ...styles.input, cursor: 'ew-resize', ...style }}
250
+ onPointerDown={startScrub}
251
+ onPointerMove={onScrubMove}
252
+ onPointerUp={endScrub}
228
253
  />
229
254
  );
230
255
  }
@@ -237,12 +262,14 @@ export function Vector3Input({
237
262
  label,
238
263
  value,
239
264
  onChange,
240
- snap
265
+ snap,
266
+ labelExtra
241
267
  }: {
242
268
  label: string;
243
269
  value: [number, number, number];
244
270
  onChange: (v: [number, number, number]) => void;
245
271
  snap?: number;
272
+ labelExtra?: React.ReactNode;
246
273
  }) {
247
274
  const snapValue = (num: number) => {
248
275
  if (!snap) return num;
@@ -274,15 +301,13 @@ export function Vector3Input({
274
301
  };
275
302
 
276
303
  const startScrub = (e: React.PointerEvent, index: number) => {
277
- e.preventDefault();
278
-
279
304
  dragState.current = {
280
305
  index,
281
306
  startX: e.clientX,
282
307
  startValue: value[index]
283
308
  };
284
309
 
285
- (e.target as HTMLElement).setPointerCapture(e.pointerId);
310
+ e.currentTarget.setPointerCapture(e.pointerId);
286
311
  document.body.style.cursor = "ew-resize";
287
312
  };
288
313
 
@@ -315,7 +340,7 @@ export function Vector3Input({
315
340
 
316
341
  dragState.current = null;
317
342
  document.body.style.cursor = "";
318
- (e.target as HTMLElement).releasePointerCapture(e.pointerId);
343
+ e.currentTarget.releasePointerCapture(e.pointerId);
319
344
  };
320
345
 
321
346
  const axes = [
@@ -326,7 +351,10 @@ export function Vector3Input({
326
351
 
327
352
  return (
328
353
  <div style={{ marginBottom: 8 }}>
329
- <label style={{ ...styles.label, marginBottom: 4 }}>{label}</label>
354
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
355
+ <label style={{ ...styles.label, marginBottom: 0 }}>{label}</label>
356
+ {labelExtra}
357
+ </div>
330
358
  <div style={{ display: 'flex', gap: 4 }}>
331
359
  {axes.map(({ key, color, index }) => (
332
360
  <div
@@ -341,7 +369,11 @@ export function Vector3Input({
341
369
  borderRadius: 3,
342
370
  padding: '4px 6px',
343
371
  minHeight: 28,
372
+ cursor: 'ew-resize',
344
373
  }}
374
+ onPointerDown={e => startScrub(e, index)}
375
+ onPointerMove={onScrubMove}
376
+ onPointerUp={endScrub}
345
377
  >
346
378
  <span
347
379
  style={{
@@ -349,12 +381,8 @@ export function Vector3Input({
349
381
  fontWeight: 600,
350
382
  color,
351
383
  width: 12,
352
- cursor: 'ew-resize',
353
384
  userSelect: 'none',
354
385
  }}
355
- onPointerDown={e => startScrub(e, index)}
356
- onPointerMove={onScrubMove}
357
- onPointerUp={endScrub}
358
386
  >
359
387
  {key.toUpperCase()}
360
388
  </span>
@@ -369,6 +397,7 @@ export function Vector3Input({
369
397
  outline: 'none',
370
398
  width: '100%',
371
399
  minWidth: 0,
400
+ cursor: 'inherit',
372
401
  }}
373
402
  type="text"
374
403
  value={draft[index]}
@@ -515,6 +544,170 @@ export function SelectInput({
515
544
  );
516
545
  }
517
546
 
547
+ interface BoundFieldProps {
548
+ name: string;
549
+ values: Record<string, any>;
550
+ onChange: (values: Record<string, any>) => void;
551
+ }
552
+
553
+ interface BoundNumberFieldProps extends BoundFieldProps {
554
+ label: string;
555
+ fallback?: number;
556
+ step?: string | number;
557
+ min?: number;
558
+ max?: number;
559
+ style?: React.CSSProperties;
560
+ }
561
+
562
+ interface BoundStringFieldProps extends BoundFieldProps {
563
+ label: string;
564
+ fallback?: string;
565
+ placeholder?: string;
566
+ }
567
+
568
+ interface BoundColorFieldProps extends BoundFieldProps {
569
+ label: string;
570
+ fallback?: string;
571
+ }
572
+
573
+ interface BoundBooleanFieldProps extends BoundFieldProps {
574
+ label: string;
575
+ fallback?: boolean;
576
+ }
577
+
578
+ interface BoundSelectFieldProps extends BoundFieldProps {
579
+ label: string;
580
+ fallback?: string;
581
+ options: { value: string; label: string }[];
582
+ }
583
+
584
+ interface BoundVector3FieldProps extends BoundFieldProps {
585
+ label: string;
586
+ fallback?: [number, number, number];
587
+ snap?: number;
588
+ labelExtra?: React.ReactNode;
589
+ }
590
+
591
+ function bindFieldChange(name: string, onChange: (values: Record<string, any>) => void) {
592
+ return (value: any) => onChange({ [name]: value });
593
+ }
594
+
595
+ export function FieldGroup({ children }: { children: React.ReactNode }) {
596
+ return <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>{children}</div>;
597
+ }
598
+
599
+ export function NumberField({
600
+ name,
601
+ label,
602
+ values,
603
+ onChange,
604
+ fallback = 0,
605
+ step,
606
+ min,
607
+ max,
608
+ style,
609
+ }: BoundNumberFieldProps) {
610
+ return (
611
+ <Input
612
+ label={label}
613
+ value={values[name] ?? fallback}
614
+ onChange={bindFieldChange(name, onChange)}
615
+ step={step}
616
+ min={min}
617
+ max={max}
618
+ style={style}
619
+ />
620
+ );
621
+ }
622
+
623
+ export function StringField({
624
+ name,
625
+ label,
626
+ values,
627
+ onChange,
628
+ fallback = '',
629
+ placeholder,
630
+ }: BoundStringFieldProps) {
631
+ return (
632
+ <StringInput
633
+ label={label}
634
+ value={values[name] ?? fallback}
635
+ onChange={bindFieldChange(name, onChange)}
636
+ placeholder={placeholder}
637
+ />
638
+ );
639
+ }
640
+
641
+ export function ColorField({
642
+ name,
643
+ label,
644
+ values,
645
+ onChange,
646
+ fallback = '#ffffff',
647
+ }: BoundColorFieldProps) {
648
+ return (
649
+ <ColorInput
650
+ label={label}
651
+ value={values[name] ?? fallback}
652
+ onChange={bindFieldChange(name, onChange)}
653
+ />
654
+ );
655
+ }
656
+
657
+ export function BooleanField({
658
+ name,
659
+ label,
660
+ values,
661
+ onChange,
662
+ fallback = false,
663
+ }: BoundBooleanFieldProps) {
664
+ return (
665
+ <BooleanInput
666
+ label={label}
667
+ value={values[name] ?? fallback}
668
+ onChange={bindFieldChange(name, onChange)}
669
+ />
670
+ );
671
+ }
672
+
673
+ export function SelectField({
674
+ name,
675
+ label,
676
+ values,
677
+ onChange,
678
+ fallback,
679
+ options,
680
+ }: BoundSelectFieldProps) {
681
+ return (
682
+ <SelectInput
683
+ label={label}
684
+ value={values[name] ?? fallback ?? options[0]?.value ?? ''}
685
+ onChange={bindFieldChange(name, onChange)}
686
+ options={options}
687
+ />
688
+ );
689
+ }
690
+
691
+ export function Vector3Field({
692
+ name,
693
+ label,
694
+ values,
695
+ onChange,
696
+ fallback = [0, 0, 0],
697
+ snap,
698
+ labelExtra,
699
+ }: BoundVector3FieldProps) {
700
+ return (
701
+ <Vector3Input
702
+ label={label}
703
+ value={values[name] ?? fallback}
704
+ onChange={bindFieldChange(name, onChange)}
705
+ snap={snap}
706
+ labelExtra={labelExtra}
707
+ />
708
+ );
709
+ }
710
+
518
711
  // ============================================================================
519
712
  // Field Renderer - Schema-driven UI generation
520
713
  // ============================================================================