react-three-game 0.0.59 → 0.0.61

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 (67) hide show
  1. package/dist/tools/dragdrop/DragDropLoader.d.ts +8 -8
  2. package/dist/tools/dragdrop/DragDropLoader.js +33 -15
  3. package/dist/tools/dragdrop/index.d.ts +3 -3
  4. package/dist/tools/dragdrop/index.js +1 -1
  5. package/dist/tools/dragdrop/modelLoader.d.ts +10 -1
  6. package/dist/tools/dragdrop/modelLoader.js +39 -0
  7. package/dist/tools/prefabeditor/PrefabEditor.js +17 -26
  8. package/dist/tools/prefabeditor/PrefabRoot.d.ts +1 -1
  9. package/dist/tools/prefabeditor/PrefabRoot.js +2 -8
  10. package/package.json +9 -3
  11. package/.gitattributes +0 -2
  12. package/.github/copilot-instructions.md +0 -83
  13. package/.github/workflows/nextjs.yml +0 -99
  14. package/.gitmodules +0 -3
  15. package/assets/architecture.png +0 -0
  16. package/assets/editor.gif +0 -0
  17. package/assets/favicon.ico +0 -0
  18. package/assets/react-three-game-logo.png +0 -0
  19. package/dist/tools/dragdrop/page.d.ts +0 -1
  20. package/dist/tools/dragdrop/page.js +0 -11
  21. package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
  22. package/dist/tools/prefabeditor/EntityEvents.js +0 -85
  23. package/dist/tools/prefabeditor/page.d.ts +0 -1
  24. package/dist/tools/prefabeditor/page.js +0 -5
  25. package/react-three-game-skill/.gitattributes +0 -2
  26. package/react-three-game-skill/README.md +0 -7
  27. package/react-three-game-skill/react-three-game/SKILL.md +0 -514
  28. package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
  29. package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
  30. package/src/helpers/SoundManager.ts +0 -130
  31. package/src/helpers/index.ts +0 -91
  32. package/src/index.ts +0 -59
  33. package/src/shared/ContactShadow.tsx +0 -74
  34. package/src/shared/GameCanvas.tsx +0 -52
  35. package/src/tools/assetviewer/page.tsx +0 -425
  36. package/src/tools/dragdrop/DragDropLoader.tsx +0 -136
  37. package/src/tools/dragdrop/index.ts +0 -4
  38. package/src/tools/dragdrop/modelLoader.ts +0 -145
  39. package/src/tools/dragdrop/page.tsx +0 -45
  40. package/src/tools/prefabeditor/Dropdown.tsx +0 -112
  41. package/src/tools/prefabeditor/EditorContext.tsx +0 -25
  42. package/src/tools/prefabeditor/EditorTree.tsx +0 -452
  43. package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
  44. package/src/tools/prefabeditor/EditorUI.tsx +0 -204
  45. package/src/tools/prefabeditor/EventSystem.tsx +0 -36
  46. package/src/tools/prefabeditor/GameEvents.ts +0 -191
  47. package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
  48. package/src/tools/prefabeditor/PrefabEditor.tsx +0 -262
  49. package/src/tools/prefabeditor/PrefabRoot.tsx +0 -773
  50. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
  51. package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
  52. package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
  53. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
  54. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
  55. package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
  56. package/src/tools/prefabeditor/components/Input.tsx +0 -820
  57. package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
  58. package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
  59. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
  60. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
  61. package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
  62. package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
  63. package/src/tools/prefabeditor/components/index.ts +0 -26
  64. package/src/tools/prefabeditor/page.tsx +0 -10
  65. package/src/tools/prefabeditor/styles.ts +0 -235
  66. package/src/tools/prefabeditor/types.ts +0 -20
  67. package/src/tools/prefabeditor/utils.ts +0 -312
@@ -1,188 +0,0 @@
1
- import { RigidBody, RapierRigidBody, useRapier } from "@react-three/rapier";
2
- import type { RigidBodyOptions, CollisionPayload, IntersectionEnterPayload, IntersectionExitPayload } from "@react-three/rapier";
3
- import type { ReactNode } from 'react';
4
- import { useRef, useEffect, useCallback } from 'react';
5
- import { Component } from "./ComponentRegistry";
6
- import { BooleanField, FieldGroup, NumberField, SelectField } from "./Input";
7
- import { ComponentData } from "../types";
8
- import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
9
-
10
- export type PhysicsProps = RigidBodyOptions & {
11
- activeCollisionTypes?: 'all' | undefined;
12
- };
13
-
14
- function PhysicsComponentEditor({ component, onUpdate }: { component: ComponentData; onUpdate: (newComp: any) => void }) {
15
- return (
16
- <FieldGroup>
17
- <SelectField
18
- name="type"
19
- label="Type"
20
- values={component.properties}
21
- onChange={onUpdate}
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
- <SelectField
30
- name="colliders"
31
- label="Collider"
32
- values={component.properties}
33
- onChange={onUpdate}
34
- options={[
35
- { value: 'hull', label: 'Hull (convex)' },
36
- { value: 'trimesh', label: 'Trimesh (exact)' },
37
- { value: 'cuboid', label: 'Cuboid (box)' },
38
- { value: 'ball', label: 'Ball (sphere)' },
39
- ]}
40
- />
41
- <NumberField name="mass" label="Mass" values={component.properties} onChange={onUpdate} fallback={1} step={0.1} min={0} />
42
- <NumberField name="restitution" label="Restitution (Bounciness)" values={component.properties} onChange={onUpdate} fallback={0} min={0} max={1} step={0.1} />
43
- <NumberField name="friction" label="Friction" values={component.properties} onChange={onUpdate} fallback={0.5} min={0} step={0.1} />
44
- <NumberField name="linearDamping" label="Linear Damping" values={component.properties} onChange={onUpdate} fallback={0} min={0} step={0.1} />
45
- <NumberField name="angularDamping" label="Angular Damping" values={component.properties} onChange={onUpdate} fallback={0} min={0} step={0.1} />
46
- <NumberField name="gravityScale" label="Gravity Scale" values={component.properties} onChange={onUpdate} fallback={1} step={0.1} />
47
- <BooleanField name="sensor" label="Sensor (Trigger Only)" values={component.properties} onChange={onUpdate} fallback={false} />
48
- <SelectField
49
- name="activeCollisionTypes"
50
- label="Collision Detection"
51
- values={component.properties}
52
- onChange={onUpdate}
53
- options={[
54
- { value: '', label: 'Default (Dynamic only)' },
55
- { value: 'all', label: 'All (includes kinematic & fixed)' },
56
- ]}
57
- />
58
- </FieldGroup>
59
- );
60
- }
61
-
62
- interface PhysicsViewProps {
63
- properties: PhysicsProps;
64
- editMode?: boolean;
65
- children?: ReactNode;
66
- position?: [number, number, number];
67
- rotation?: [number, number, number];
68
- scale?: [number, number, number];
69
- nodeId?: string;
70
- registerRigidBodyRef?: (id: string, rb: RapierRigidBody | null) => void;
71
- }
72
-
73
- function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }: PhysicsViewProps) {
74
- const { type, colliders, sensor, activeCollisionTypes, ...otherProps } = properties;
75
- const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
76
- const rigidBodyRef = useRef<RapierRigidBody>(null);
77
-
78
- // Try to get rapier context - will be null if not inside <Physics>
79
- let rapier: any = null;
80
- try {
81
- const rapierContext = useRapier();
82
- rapier = rapierContext.rapier;
83
- } catch (e) {
84
- // Not inside Physics context - that's ok, just won't have rapier features
85
- }
86
-
87
- // Register RigidBody ref when it's available
88
- useEffect(() => {
89
- if (nodeId && registerRigidBodyRef && rigidBodyRef.current) {
90
- registerRigidBodyRef(nodeId, rigidBodyRef.current);
91
- }
92
- return () => {
93
- if (nodeId && registerRigidBodyRef) {
94
- registerRigidBodyRef(nodeId, null);
95
- }
96
- };
97
- }, [nodeId, registerRigidBodyRef]);
98
-
99
- // Configure active collision types for kinematic/sensor bodies
100
- useEffect(() => {
101
- if (activeCollisionTypes === 'all' && rigidBodyRef.current && rapier) {
102
- const rb = rigidBodyRef.current;
103
- // Apply to all colliders on this rigid body
104
- for (let i = 0; i < rb.numColliders(); i++) {
105
- const collider = rb.collider(i);
106
- collider.setActiveCollisionTypes(
107
- rapier.ActiveCollisionTypes.DEFAULT |
108
- rapier.ActiveCollisionTypes.KINEMATIC_FIXED |
109
- rapier.ActiveCollisionTypes.KINEMATIC_KINEMATIC
110
- );
111
- }
112
- }
113
- }, [activeCollisionTypes, rapier, type, colliders]);
114
-
115
- // Event handlers for physics interactions
116
- const handleIntersectionEnter = useCallback((payload: IntersectionEnterPayload) => {
117
- if (!nodeId) return;
118
- gameEvents.emit('sensor:enter', {
119
- sourceEntityId: nodeId,
120
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
121
- targetRigidBody: payload.other.rigidBody,
122
- });
123
- }, [nodeId]);
124
-
125
- const handleIntersectionExit = useCallback((payload: IntersectionExitPayload) => {
126
- if (!nodeId) return;
127
- gameEvents.emit('sensor:exit', {
128
- sourceEntityId: nodeId,
129
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
130
- targetRigidBody: payload.other.rigidBody,
131
- });
132
- }, [nodeId]);
133
-
134
- const handleCollisionEnter = useCallback((payload: CollisionPayload) => {
135
- if (!nodeId) return;
136
- gameEvents.emit('collision:enter', {
137
- sourceEntityId: nodeId,
138
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
139
- targetRigidBody: payload.other.rigidBody,
140
- });
141
- }, [nodeId]);
142
-
143
- const handleCollisionExit = useCallback((payload: CollisionPayload) => {
144
- if (!nodeId) return;
145
- gameEvents.emit('collision:exit', {
146
- sourceEntityId: nodeId,
147
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
148
- targetRigidBody: payload.other.rigidBody,
149
- });
150
- }, [nodeId]);
151
-
152
- // In edit mode, include position/rotation in key to force remount when transform changes
153
- // This ensures the RigidBody debug visualization updates even when physics is paused
154
- const rbKey = editMode
155
- ? `${type || 'dynamic'}_${colliderType}_${position?.join(',')}_${rotation?.join(',')}`
156
- : `${type || 'dynamic'}_${colliderType}`;
157
-
158
- return (
159
- <RigidBody
160
- key={rbKey}
161
- ref={rigidBodyRef}
162
- type={type}
163
- colliders={colliderType as any}
164
- position={position}
165
- rotation={rotation}
166
- scale={scale}
167
- sensor={sensor}
168
- userData={{ entityId: nodeId }}
169
- onIntersectionEnter={handleIntersectionEnter}
170
- onIntersectionExit={handleIntersectionExit}
171
- onCollisionEnter={handleCollisionEnter}
172
- onCollisionExit={handleCollisionExit}
173
- {...otherProps}
174
- >
175
- {children}
176
- </RigidBody>
177
- );
178
- }
179
-
180
- const PhysicsComponent: Component = {
181
- name: 'Physics',
182
- Editor: PhysicsComponentEditor,
183
- View: PhysicsComponentView,
184
- nonComposable: true,
185
- defaultProperties: { type: 'dynamic', colliders: 'hull' }
186
- };
187
-
188
- export default PhysicsComponent;
@@ -1,109 +0,0 @@
1
- import { Component } from "./ComponentRegistry";
2
- import { useRef, useEffect, useMemo, useState } from "react";
3
- import { BooleanField, ColorField, FieldGroup, NumberField } from "./Input";
4
- import { SpotLight, SpotLightHelper } from "three";
5
- import { useFrame } from "@react-three/fiber";
6
-
7
- const spotLightDefaults = {
8
- color: '#ffffff',
9
- intensity: 1,
10
- angle: Math.PI / 6,
11
- penumbra: 0.5,
12
- distance: 100,
13
- castShadow: true,
14
- };
15
-
16
- function SpotLightComponentEditor({ component, onUpdate }: { component: any; onUpdate: (newComp: any) => void }) {
17
- const values = { ...spotLightDefaults, ...component.properties };
18
-
19
- return (
20
- <FieldGroup>
21
- <ColorField name="color" label="Color" values={values} onChange={onUpdate} />
22
- <NumberField name="intensity" label="Intensity" values={values} onChange={onUpdate} min={0} step={0.1} fallback={1} />
23
- <NumberField name="angle" label="Angle" values={values} onChange={onUpdate} min={0} max={Math.PI} step={0.05} fallback={Math.PI / 6} />
24
- <NumberField name="penumbra" label="Penumbra" values={values} onChange={onUpdate} min={0} max={1} step={0.05} fallback={0.5} />
25
- <NumberField name="distance" label="Distance" values={values} onChange={onUpdate} min={0} step={1} fallback={100} />
26
- <BooleanField name="castShadow" label="Cast Shadow" values={values} onChange={onUpdate} fallback={true} />
27
- </FieldGroup>
28
- );
29
- }
30
-
31
- function SpotLightView({ properties, editMode, isSelected }: { properties: any; editMode?: boolean; isSelected?: boolean }) {
32
- const merged = { ...spotLightDefaults, ...properties };
33
- const color = merged.color;
34
- const intensity = merged.intensity;
35
- const angle = merged.angle;
36
- const penumbra = merged.penumbra;
37
- const distance = merged.distance;
38
- const castShadow = merged.castShadow;
39
-
40
- const spotLightRef = useRef<SpotLight>(null);
41
- const targetRef = useRef<any>(null);
42
- const [spotLight, setSpotLight] = useState<SpotLight | null>(null);
43
- const spotLightHelper = useMemo(
44
- () => spotLight ? new SpotLightHelper(spotLight, color) : null,
45
- [spotLight, color]
46
- );
47
-
48
- useEffect(() => {
49
- return () => {
50
- spotLightHelper?.dispose();
51
- };
52
- }, [spotLightHelper]);
53
-
54
- useEffect(() => {
55
- if (spotLightRef.current && targetRef.current) {
56
- spotLightRef.current.target = targetRef.current;
57
- setSpotLight(spotLightRef.current);
58
- }
59
- }, []);
60
-
61
- useFrame(() => {
62
- if (spotLightHelper && editMode && isSelected) {
63
- spotLightHelper.update();
64
- }
65
- });
66
-
67
- return (
68
- <>
69
- <spotLight
70
- ref={spotLightRef}
71
- color={color}
72
- intensity={intensity}
73
- angle={angle}
74
- penumbra={penumbra}
75
- distance={distance}
76
- castShadow={castShadow}
77
- shadow-mapSize-width={1024}
78
- shadow-mapSize-height={1024}
79
- shadow-bias={-0.0001}
80
- shadow-normalBias={0.02}
81
- />
82
- {editMode && isSelected && spotLightHelper && (
83
- <primitive object={spotLightHelper} />
84
- )}
85
- <object3D ref={targetRef} position={[0, -5, 0]} />
86
- {editMode && (
87
- <>
88
- <mesh>
89
- <sphereGeometry args={[0.2, 8, 6]} />
90
- <meshBasicMaterial color={color} wireframe />
91
- </mesh>
92
- <mesh position={[0, -5, 0]}>
93
- <sphereGeometry args={[0.15, 8, 6]} />
94
- <meshBasicMaterial color={color} wireframe opacity={0.5} transparent />
95
- </mesh>
96
- </>
97
- )}
98
- </>
99
- );
100
- }
101
-
102
- const SpotLightComponent: Component = {
103
- name: 'SpotLight',
104
- Editor: SpotLightComponentEditor,
105
- View: SpotLightView,
106
- defaultProperties: spotLightDefaults
107
- };
108
-
109
- export default SpotLightComponent;
@@ -1,137 +0,0 @@
1
- import { Component } from "./ComponentRegistry";
2
- import { ColorField, FieldGroup, NumberField, SelectField, StringField } from "./Input";
3
- import { Text } from 'three-text/three/react';
4
- import { useRef, useState, useCallback } from 'react';
5
- import { BufferGeometry, Mesh } from "three";
6
-
7
- // Initialize HarfBuzz path for font shaping
8
- Text.setHarfBuzzPath('/fonts/hb.wasm');
9
-
10
- function TextComponentEditor({
11
- component,
12
- onUpdate,
13
- }: {
14
- component: any;
15
- onUpdate: (newProps: any) => void;
16
- }) {
17
- return (
18
- <FieldGroup>
19
- <StringField
20
- name="text"
21
- label="Text"
22
- values={component.properties}
23
- onChange={onUpdate}
24
- placeholder="Enter text..."
25
- />
26
- <ColorField
27
- name="color"
28
- label="Color"
29
- values={component.properties}
30
- onChange={onUpdate}
31
- />
32
- <StringField
33
- name="font"
34
- label="Font"
35
- values={component.properties}
36
- onChange={onUpdate}
37
- placeholder="/fonts/NotoSans-Regular.ttf"
38
- />
39
- <NumberField
40
- name="size"
41
- label="Size"
42
- values={component.properties}
43
- onChange={onUpdate}
44
- min={0.01}
45
- step={0.1}
46
- />
47
- <NumberField
48
- name="depth"
49
- label="Depth"
50
- values={component.properties}
51
- onChange={onUpdate}
52
- min={0}
53
- step={0.1}
54
- />
55
- <NumberField
56
- name="width"
57
- label="Width"
58
- values={component.properties}
59
- onChange={onUpdate}
60
- min={0}
61
- step={0.5}
62
- />
63
- <SelectField
64
- name="align"
65
- label="Align"
66
- values={component.properties}
67
- onChange={onUpdate}
68
- options={[
69
- { value: 'left', label: 'Left' },
70
- { value: 'center', label: 'Center' },
71
- { value: 'right', label: 'Right' },
72
- ]}
73
- />
74
- </FieldGroup>
75
- );
76
- }
77
-
78
- function TextComponentView({ properties }: { properties: any }) {
79
- const { text = '', font, size, depth, width, align, color } = properties;
80
- const textContent = String(text || '');
81
- const meshRef = useRef<Mesh>(null);
82
- const [offset, setOffset] = useState<[number, number, number]>([0, 0, 0]);
83
-
84
- const handleLoad = useCallback((_geometry: BufferGeometry, info: any) => {
85
- if (info?.planeBounds) {
86
- const bounds = info.planeBounds;
87
- // Calculate X offset based on alignment
88
- let centerX = 0;
89
- if (align === 'center') {
90
- centerX = -(bounds.min.x + bounds.max.x) / 2;
91
- } else if (align === 'right') {
92
- centerX = -bounds.max.x;
93
- } else {
94
- // left alignment
95
- centerX = -bounds.min.x;
96
- }
97
- const centerY = -(bounds.min.y + bounds.max.y) / 2;
98
- setOffset([centerX, centerY, 0]);
99
- }
100
- }, [align]);
101
-
102
- if (!textContent) return null;
103
-
104
- return (
105
- <group position={offset}>
106
- <Text
107
- ref={meshRef}
108
- font={font}
109
- size={size}
110
- depth={depth}
111
- layout={{ align, width }}
112
- color={color}
113
- onLoad={handleLoad}
114
- >
115
- {textContent}
116
- </Text>
117
- </group>
118
- );
119
- }
120
-
121
- const TextComponent: Component = {
122
- name: 'Text',
123
- Editor: TextComponentEditor,
124
- View: TextComponentView,
125
- nonComposable: true,
126
- defaultProperties: {
127
- text: 'Hello World',
128
- color: '#888888',
129
- font: '/fonts/NotoSans-Regular.ttf',
130
- size: 0.5,
131
- depth: 0,
132
- width: 5,
133
- align: 'center',
134
- }
135
- };
136
-
137
- export default TextComponent;
@@ -1,173 +0,0 @@
1
- import { Component } from "./ComponentRegistry";
2
- import { Label, Vector3Field, Vector3Input } from "./Input";
3
- import { useEditorContext } from "../EditorContext";
4
- import { colors } from "../styles";
5
-
6
- const buttonStyle = {
7
- padding: '4px 8px',
8
- background: colors.bgSurface,
9
- color: colors.text,
10
- border: `1px solid ${colors.border}`,
11
- borderRadius: 3,
12
- cursor: 'pointer',
13
- font: 'inherit',
14
- fontSize: 11,
15
- flex: 1,
16
- };
17
-
18
- function TransformModeSelector({
19
- transformMode,
20
- setTransformMode,
21
- snapResolution,
22
- setSnapResolution
23
- }: {
24
- transformMode: "translate" | "rotate" | "scale";
25
- setTransformMode: (m: "translate" | "rotate" | "scale") => void;
26
- snapResolution: number;
27
- setSnapResolution: (v: number) => void;
28
- }) {
29
- return (
30
- <div style={{ marginBottom: 8 }}>
31
- <Label>Transform Mode {snapResolution > 0 && `(Snap: ${snapResolution})`}</Label>
32
- <div style={{ display: 'flex', gap: 6 }}>
33
- {["translate", "rotate", "scale"].map(mode => {
34
- const isActive = transformMode === mode;
35
- return (
36
- <button
37
- key={mode}
38
- onClick={() => setTransformMode(mode as any)}
39
- style={{
40
- ...buttonStyle,
41
- background: isActive ? colors.accentBg : colors.bgSurface,
42
- borderColor: isActive ? colors.accentBorder : colors.border,
43
- color: isActive ? colors.accent : colors.text,
44
- }}
45
- onPointerEnter={(e) => {
46
- if (!isActive) e.currentTarget.style.background = colors.bgHover;
47
- }}
48
- onPointerLeave={(e) => {
49
- if (!isActive) e.currentTarget.style.background = colors.bgSurface;
50
- }}
51
- >
52
- {mode}
53
- </button>
54
- );
55
- })}
56
- </div>
57
- <div style={{ marginTop: 6 }}>
58
- <button
59
- onClick={() => setSnapResolution(snapResolution > 0 ? 0 : 0.1)}
60
- style={{
61
- ...buttonStyle,
62
- background: snapResolution > 0 ? colors.accentBg : colors.bgSurface,
63
- borderColor: snapResolution > 0 ? colors.accentBorder : colors.border,
64
- color: snapResolution > 0 ? colors.accent : colors.text,
65
- width: '100%',
66
- }}
67
- onPointerEnter={(e) => {
68
- if (snapResolution === 0) e.currentTarget.style.background = colors.bgHover;
69
- }}
70
- onPointerLeave={(e) => {
71
- if (snapResolution === 0) e.currentTarget.style.background = colors.bgSurface;
72
- }}
73
- >
74
- Snap: {snapResolution > 0 ? `ON (${snapResolution})` : 'OFF'}
75
- </button>
76
- </div>
77
- </div>
78
- );
79
- }
80
-
81
- const snapLockBtnStyle: React.CSSProperties = {
82
- background: 'none',
83
- border: 'none',
84
- cursor: 'pointer',
85
- padding: '0 2px',
86
- fontSize: 12,
87
- lineHeight: 1,
88
- color: colors.textMuted,
89
- };
90
-
91
- function SnapLockButton({ locked, onToggle, title }: { locked: boolean; onToggle: () => void; title: string }) {
92
- return (
93
- <button style={snapLockBtnStyle} onClick={onToggle} title={title}>
94
- {locked ? '🔒' : '🔓'}
95
- </button>
96
- );
97
- }
98
-
99
- function TransformComponentEditor({ component, onUpdate }: {
100
- component: any;
101
- onUpdate: (newComp: any) => void;
102
- }) {
103
- const {
104
- transformMode,
105
- setTransformMode,
106
- snapResolution,
107
- setSnapResolution,
108
- positionSnap,
109
- setPositionSnap,
110
- rotationSnap,
111
- setRotationSnap
112
- } = useEditorContext();
113
-
114
- const positionSnapped = positionSnap > 0;
115
- const rotationSnapped = rotationSnap > 0;
116
-
117
- return (
118
- <div style={{ display: 'flex', flexDirection: 'column' }}>
119
- <TransformModeSelector
120
- transformMode={transformMode}
121
- setTransformMode={setTransformMode}
122
- snapResolution={snapResolution}
123
- setSnapResolution={setSnapResolution}
124
- />
125
- <Vector3Input
126
- label="Position"
127
- value={component.properties.position ?? [0, 0, 0]}
128
- onChange={v => onUpdate({ position: v })}
129
- snap={positionSnap}
130
- labelExtra={
131
- <SnapLockButton
132
- locked={positionSnapped}
133
- onToggle={() => setPositionSnap(positionSnapped ? 0 : 0.5)}
134
- title={positionSnapped ? `Snap ON (0.5) — click to disable` : `Snap OFF — click to enable (0.5)`}
135
- />
136
- }
137
- />
138
- <Vector3Input
139
- label="Rotation"
140
- value={component.properties.rotation ?? [0, 0, 0]}
141
- onChange={v => onUpdate({ rotation: v })}
142
- snap={rotationSnap}
143
- labelExtra={
144
- <SnapLockButton
145
- locked={rotationSnapped}
146
- onToggle={() => setRotationSnap(rotationSnapped ? 0 : Math.PI / 4)}
147
- title={rotationSnapped ? `Snap ON (π/4) — click to disable` : `Snap OFF — click to enable (π/4)`}
148
- />
149
- }
150
- />
151
- <Vector3Field
152
- name="scale"
153
- label="Scale"
154
- values={component.properties}
155
- onChange={onUpdate}
156
- fallback={[1, 1, 1]}
157
- />
158
- </div>
159
- );
160
- }
161
-
162
- const TransformComponent: Component = {
163
- name: 'Transform',
164
- Editor: TransformComponentEditor,
165
- nonComposable: true,
166
- defaultProperties: {
167
- position: [0, 0, 0],
168
- rotation: [0, 0, 0],
169
- scale: [1, 1, 1]
170
- }
171
- };
172
-
173
- export default TransformComponent;
@@ -1,26 +0,0 @@
1
- import GeometryComponent from './GeometryComponent';
2
- import TransformComponent from './TransformComponent';
3
- import MaterialComponent from './MaterialComponent';
4
- import PhysicsComponent from './PhysicsComponent';
5
- import SpotLightComponent from './SpotLightComponent';
6
- import DirectionalLightComponent from './DirectionalLightComponent';
7
- import AmbientLightComponent from './AmbientLightComponent';
8
- import ModelComponent from './ModelComponent';
9
- import TextComponent from './TextComponent';
10
- import EnvironmentComponent from './EnvironmentComponent';
11
- import CameraComponent from './CameraComponent';
12
-
13
- export default [
14
- GeometryComponent,
15
- TransformComponent,
16
- MaterialComponent,
17
- PhysicsComponent,
18
- SpotLightComponent,
19
- DirectionalLightComponent,
20
- AmbientLightComponent,
21
- ModelComponent,
22
- TextComponent,
23
- EnvironmentComponent,
24
- CameraComponent,
25
- ];
26
-
@@ -1,10 +0,0 @@
1
- import PrefabEditor from "./PrefabEditor";
2
-
3
-
4
- export default function PrefabEditorPage() {
5
- return <div style={{ width: '100%', height: '100%' }}>
6
- <PrefabEditor>
7
- <directionalLight position={[5, 10, 7.5]} intensity={1} castShadow />
8
- </PrefabEditor>
9
- </div>
10
- }