react-three-game 0.0.60 → 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 (58) hide show
  1. package/package.json +9 -3
  2. package/.gitattributes +0 -2
  3. package/.github/copilot-instructions.md +0 -83
  4. package/.github/workflows/nextjs.yml +0 -99
  5. package/.gitmodules +0 -3
  6. package/assets/architecture.png +0 -0
  7. package/assets/editor.gif +0 -0
  8. package/assets/favicon.ico +0 -0
  9. package/assets/react-three-game-logo.png +0 -0
  10. package/dist/tools/dragdrop/page.d.ts +0 -1
  11. package/dist/tools/dragdrop/page.js +0 -11
  12. package/dist/tools/prefabeditor/EntityEvents.d.ts +0 -54
  13. package/dist/tools/prefabeditor/EntityEvents.js +0 -85
  14. package/dist/tools/prefabeditor/page.d.ts +0 -1
  15. package/dist/tools/prefabeditor/page.js +0 -5
  16. package/react-three-game-skill/.gitattributes +0 -2
  17. package/react-three-game-skill/README.md +0 -7
  18. package/react-three-game-skill/react-three-game/SKILL.md +0 -514
  19. package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +0 -472
  20. package/react-three-game-skill/react-three-game/rules/LIGHTING.md +0 -6
  21. package/src/helpers/SoundManager.ts +0 -130
  22. package/src/helpers/index.ts +0 -91
  23. package/src/index.ts +0 -59
  24. package/src/shared/ContactShadow.tsx +0 -74
  25. package/src/shared/GameCanvas.tsx +0 -52
  26. package/src/tools/assetviewer/page.tsx +0 -425
  27. package/src/tools/dragdrop/DragDropLoader.tsx +0 -159
  28. package/src/tools/dragdrop/index.ts +0 -4
  29. package/src/tools/dragdrop/modelLoader.ts +0 -204
  30. package/src/tools/dragdrop/page.tsx +0 -45
  31. package/src/tools/prefabeditor/Dropdown.tsx +0 -112
  32. package/src/tools/prefabeditor/EditorContext.tsx +0 -25
  33. package/src/tools/prefabeditor/EditorTree.tsx +0 -452
  34. package/src/tools/prefabeditor/EditorTreeMenus.tsx +0 -307
  35. package/src/tools/prefabeditor/EditorUI.tsx +0 -204
  36. package/src/tools/prefabeditor/EventSystem.tsx +0 -36
  37. package/src/tools/prefabeditor/GameEvents.ts +0 -191
  38. package/src/tools/prefabeditor/InstanceProvider.tsx +0 -466
  39. package/src/tools/prefabeditor/PrefabEditor.tsx +0 -256
  40. package/src/tools/prefabeditor/PrefabRoot.tsx +0 -767
  41. package/src/tools/prefabeditor/components/AmbientLightComponent.tsx +0 -34
  42. package/src/tools/prefabeditor/components/CameraComponent.tsx +0 -117
  43. package/src/tools/prefabeditor/components/ComponentRegistry.ts +0 -40
  44. package/src/tools/prefabeditor/components/DirectionalLightComponent.tsx +0 -210
  45. package/src/tools/prefabeditor/components/EnvironmentComponent.tsx +0 -47
  46. package/src/tools/prefabeditor/components/GeometryComponent.tsx +0 -133
  47. package/src/tools/prefabeditor/components/Input.tsx +0 -820
  48. package/src/tools/prefabeditor/components/MaterialComponent.tsx +0 -431
  49. package/src/tools/prefabeditor/components/ModelComponent.tsx +0 -176
  50. package/src/tools/prefabeditor/components/PhysicsComponent.tsx +0 -188
  51. package/src/tools/prefabeditor/components/SpotLightComponent.tsx +0 -109
  52. package/src/tools/prefabeditor/components/TextComponent.tsx +0 -137
  53. package/src/tools/prefabeditor/components/TransformComponent.tsx +0 -173
  54. package/src/tools/prefabeditor/components/index.ts +0 -26
  55. package/src/tools/prefabeditor/page.tsx +0 -10
  56. package/src/tools/prefabeditor/styles.ts +0 -235
  57. package/src/tools/prefabeditor/types.ts +0 -20
  58. 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
- }