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,159 +0,0 @@
1
- import { ChangeEvent, useRef } from "react";
2
- import type { DragEvent, HTMLAttributes, MouseEvent, ReactNode } from "react";
3
- import type { LoadedModel, LoadedTexture } from "./modelLoader";
4
- import { canParseModelFile, canParseTextureFile, parseModelFromFile, parseTextureFromFile } from "./modelLoader";
5
-
6
- export interface AssetLoadOptions {
7
- onModelLoaded?: (model: LoadedModel, filename: string, file: File) => void | Promise<void>;
8
- onTextureLoaded?: (texture: LoadedTexture, filename: string, file: File) => void | Promise<void>;
9
- onUnhandledFile?: (file: File) => void | Promise<void>;
10
- onFilesLoaded?: (files: File[]) => void | Promise<void>;
11
- onLoadError?: (error: unknown, filename: string, file: File) => void | Promise<void>;
12
- }
13
-
14
- type DivProps = Omit<HTMLAttributes<HTMLDivElement>, "children" | "onDrop" | "onDragOver">;
15
-
16
- export interface DragDropLoaderProps extends AssetLoadOptions, DivProps {
17
- children?: ReactNode;
18
- }
19
-
20
- export interface FilePickerProps extends AssetLoadOptions, DivProps {
21
- accept?: string;
22
- children?: ReactNode;
23
- multiple?: boolean;
24
- }
25
-
26
- const DEFAULT_ACCEPT = ".glb,.gltf,.fbx,.png,.jpg,.jpeg,.webp,.gif,.bmp,.svg";
27
-
28
- function getFiles(fileList?: FileList | null) {
29
- return fileList ? Array.from(fileList) : [];
30
- }
31
-
32
- export async function loadFiles(
33
- files: File[],
34
- { onModelLoaded, onTextureLoaded, onUnhandledFile, onFilesLoaded, onLoadError }: AssetLoadOptions,
35
- ) {
36
- await Promise.all(
37
- files.map(async (file) => {
38
- const shouldParseModel = canParseModelFile(file);
39
- const shouldParseTexture = canParseTextureFile(file);
40
-
41
- if (shouldParseModel) {
42
- const result = await parseModelFromFile(file);
43
-
44
- if (result.success && result.model) {
45
- await onModelLoaded?.(result.model, file.name, file);
46
- return;
47
- }
48
-
49
- if (onLoadError) {
50
- await onLoadError(result.error, file.name, file);
51
- return;
52
- }
53
-
54
- console.error("Model parse error:", result.error);
55
- return;
56
- }
57
-
58
- if (shouldParseTexture) {
59
- const result = await parseTextureFromFile(file);
60
-
61
- if (result.success && result.texture) {
62
- await onTextureLoaded?.(result.texture, file.name, file);
63
- return;
64
- }
65
-
66
- if (onLoadError) {
67
- await onLoadError(result.error, file.name, file);
68
- return;
69
- }
70
-
71
- console.error("Texture parse error:", result.error);
72
- return;
73
- }
74
-
75
- if (onUnhandledFile) {
76
- await onUnhandledFile(file);
77
- }
78
- }),
79
- );
80
-
81
- await onFilesLoaded?.(files);
82
- }
83
-
84
- function reportFileLoadError(error: unknown) {
85
- console.error("File load error:", error);
86
- }
87
-
88
- function createLoadHandlers(options: AssetLoadOptions) {
89
- return {
90
- onFilesLoaded: options.onFilesLoaded,
91
- onModelLoaded: options.onModelLoaded,
92
- onTextureLoaded: options.onTextureLoaded,
93
- onUnhandledFile: options.onUnhandledFile,
94
- onLoadError: options.onLoadError,
95
- } satisfies AssetLoadOptions;
96
- }
97
-
98
- export function DragDropLoader({
99
- children,
100
- ...divProps
101
- }: DragDropLoaderProps) {
102
- const loadOptions = createLoadHandlers(divProps);
103
-
104
- function handleDrop(event: DragEvent<HTMLDivElement>) {
105
- event.preventDefault();
106
- event.stopPropagation();
107
-
108
- void loadFiles(getFiles(event.dataTransfer?.files), loadOptions).catch(reportFileLoadError);
109
- }
110
-
111
- function handleDragOver(event: DragEvent<HTMLDivElement>) {
112
- event.preventDefault();
113
- event.stopPropagation();
114
- }
115
-
116
- return (
117
- <div {...divProps} onDrop={handleDrop} onDragOver={handleDragOver}>
118
- {children}
119
- </div>
120
- );
121
- }
122
-
123
- export function FilePicker({
124
- accept = DEFAULT_ACCEPT,
125
- children,
126
- multiple = true,
127
- ...divProps
128
- }: FilePickerProps) {
129
- const inputRef = useRef<HTMLInputElement>(null);
130
- const { onClick, ...wrapperProps } = divProps;
131
- const loadOptions = createLoadHandlers(divProps);
132
-
133
- function onChange(event: ChangeEvent<HTMLInputElement>) {
134
- void loadFiles(getFiles(event.target.files), loadOptions).catch(reportFileLoadError);
135
- event.target.value = "";
136
- }
137
-
138
- function handleClick(event: MouseEvent<HTMLDivElement>) {
139
- onClick?.(event);
140
-
141
- if (!event.defaultPrevented) {
142
- inputRef.current?.click();
143
- }
144
- }
145
-
146
- return (
147
- <div {...wrapperProps} onClick={handleClick}>
148
- <input
149
- ref={inputRef}
150
- type="file"
151
- accept={accept}
152
- multiple={multiple}
153
- onChange={onChange}
154
- hidden
155
- />
156
- {children ?? "Select Files"}
157
- </div>
158
- );
159
- }
@@ -1,4 +0,0 @@
1
- export { DragDropLoader, FilePicker, loadFiles } from "./DragDropLoader";
2
- export type { AssetLoadOptions, DragDropLoaderProps, FilePickerProps } from "./DragDropLoader";
3
- export { loadModel, loadTexture, parseModelFromFile, parseTextureFromFile } from "./modelLoader";
4
- export type { LoadedModel, LoadedTexture, ModelLoadResult, ProgressCallback, TextureLoadResult } from "./modelLoader";
@@ -1,204 +0,0 @@
1
- import type { Object3D, Texture } from "three";
2
- import { SRGBColorSpace, TextureLoader } from "three";
3
- import { DRACOLoader, FBXLoader, GLTFLoader } from "three/examples/jsm/Addons.js";
4
-
5
- export type LoadedModel = Object3D;
6
- export type LoadedTexture = Texture;
7
-
8
- export type ModelLoadResult = {
9
- success: boolean;
10
- model?: LoadedModel;
11
- error?: unknown;
12
- };
13
-
14
- export type TextureLoadResult = {
15
- success: boolean;
16
- texture?: LoadedTexture;
17
- error?: unknown;
18
- };
19
-
20
- export type ProgressCallback = (filename: string, loaded: number, total: number) => void;
21
-
22
- const dracoLoader = new DRACOLoader();
23
- dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
24
-
25
- const gltfLoader = new GLTFLoader();
26
- gltfLoader.setDRACOLoader(dracoLoader);
27
-
28
- const fbxLoader = new FBXLoader();
29
- const textureLoader = new TextureLoader();
30
-
31
- type ModelFileKind = "gltf" | "fbx";
32
- const TEXTURE_FILE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".svg"] as const;
33
-
34
- function normalizeModelPath(name: string) {
35
- return name.split(/[?#]/, 1)[0].toLowerCase();
36
- }
37
-
38
- function getModelFileKind(name: string): ModelFileKind | null {
39
- const normalizedName = normalizeModelPath(name);
40
-
41
- if (normalizedName.endsWith(".glb") || normalizedName.endsWith(".gltf")) {
42
- return "gltf";
43
- }
44
-
45
- if (normalizedName.endsWith(".fbx")) {
46
- return "fbx";
47
- }
48
-
49
- return null;
50
- }
51
-
52
- export function canParseModelFile(file: File | string) {
53
- const filename = typeof file === "string" ? file : file.name;
54
- return getModelFileKind(filename) !== null;
55
- }
56
-
57
- export function canParseTextureFile(file: File | string) {
58
- const filename = typeof file === "string" ? file : file.name;
59
- const normalizedName = normalizeModelPath(filename);
60
-
61
- return TEXTURE_FILE_EXTENSIONS.some(extension => normalizedName.endsWith(extension));
62
- }
63
-
64
- function parseModelBuffer(arrayBuffer: ArrayBuffer, sourceName: string): Promise<ModelLoadResult> {
65
- const modelFileKind = getModelFileKind(sourceName);
66
-
67
- if (modelFileKind === "gltf") {
68
- return new Promise(resolve => {
69
- gltfLoader.parse(
70
- arrayBuffer,
71
- "",
72
- gltf => {
73
- resolve({ success: true, model: gltf.scene });
74
- },
75
- error => {
76
- resolve({ success: false, error });
77
- },
78
- );
79
- });
80
- }
81
-
82
- if (modelFileKind === "fbx") {
83
- try {
84
- const model = fbxLoader.parse(arrayBuffer, "");
85
- return Promise.resolve({ success: true, model });
86
- } catch (error) {
87
- return Promise.resolve({ success: false, error });
88
- }
89
- }
90
-
91
- return Promise.resolve({ success: false, error: new Error(`Unsupported file format: ${sourceName}`) });
92
- }
93
-
94
- export function parseModelFromFile(file: File): Promise<ModelLoadResult> {
95
- return new Promise(resolve => {
96
- const reader = new FileReader();
97
-
98
- reader.onload = event => {
99
- const arrayBuffer = event.target?.result as ArrayBuffer;
100
-
101
- if (!arrayBuffer) {
102
- resolve({ success: false, error: new Error("Failed to read file") });
103
- return;
104
- }
105
-
106
- void parseModelBuffer(arrayBuffer, file.name).then(resolve);
107
- };
108
-
109
- reader.onerror = () => resolve({ success: false, error: reader.error });
110
- reader.readAsArrayBuffer(file);
111
- });
112
- }
113
-
114
- export function parseTextureFromFile(file: File): Promise<TextureLoadResult> {
115
- return new Promise(resolve => {
116
- const url = URL.createObjectURL(file);
117
-
118
- textureLoader.load(
119
- url,
120
- texture => {
121
- texture.colorSpace = SRGBColorSpace;
122
- resolve({ success: true, texture });
123
- URL.revokeObjectURL(url);
124
- },
125
- undefined,
126
- error => {
127
- resolve({ success: false, error });
128
- URL.revokeObjectURL(url);
129
- },
130
- );
131
- });
132
- }
133
-
134
- export async function loadModel(
135
- filename: string,
136
- onProgress?: ProgressCallback,
137
- ): Promise<ModelLoadResult> {
138
- try {
139
- const fullPath = filename;
140
- const modelFileKind = getModelFileKind(filename);
141
-
142
- if (modelFileKind === "gltf") {
143
- return new Promise(resolve => {
144
- gltfLoader.load(
145
- fullPath,
146
- gltf => resolve({ success: true, model: gltf.scene }),
147
- progressEvent => {
148
- if (!onProgress) {
149
- return;
150
- }
151
-
152
- const total = progressEvent.total || progressEvent.loaded;
153
- onProgress(filename, progressEvent.loaded, total);
154
- },
155
- error => resolve({ success: false, error }),
156
- );
157
- });
158
- }
159
-
160
- if (modelFileKind === "fbx") {
161
- return new Promise(resolve => {
162
- fbxLoader.load(
163
- fullPath,
164
- model => resolve({ success: true, model }),
165
- progressEvent => {
166
- if (!onProgress) {
167
- return;
168
- }
169
-
170
- const total = progressEvent.total || progressEvent.loaded;
171
- onProgress(filename, progressEvent.loaded, total);
172
- },
173
- error => resolve({ success: false, error }),
174
- );
175
- });
176
- }
177
-
178
- return { success: false, error: new Error(`Unsupported file format: ${filename}`) };
179
- } catch (error) {
180
- return { success: false, error };
181
- }
182
- }
183
-
184
- export async function loadTexture(filename: string): Promise<TextureLoadResult> {
185
- try {
186
- if (!canParseTextureFile(filename)) {
187
- return { success: false, error: new Error(`Unsupported file format: ${filename}`) };
188
- }
189
-
190
- return await new Promise(resolve => {
191
- textureLoader.load(
192
- filename,
193
- texture => {
194
- texture.colorSpace = SRGBColorSpace;
195
- resolve({ success: true, texture });
196
- },
197
- undefined,
198
- error => resolve({ success: false, error }),
199
- );
200
- });
201
- } catch (error) {
202
- return { success: false, error };
203
- }
204
- }
@@ -1,45 +0,0 @@
1
- "use client";
2
-
3
- import { Physics, RigidBody } from "@react-three/rapier";
4
- import { OrbitControls } from "@react-three/drei";
5
- import { useState } from "react";
6
- import { DragDropLoader } from "./index";
7
- import GameCanvas from "../../shared/GameCanvas";
8
-
9
- export default function Home() {
10
- const [models, setModels] = useState<any[]>([]);
11
-
12
- return (
13
- <DragDropLoader
14
- onModelLoaded={model => setModels(prev => [...prev, model])}
15
- className="w-full items-center justify-items-center min-h-screen"
16
- style={{ height: "100vh" }}
17
- >
18
- <div className="w-full items-center justify-items-center min-h-screen" style={{ height: "100vh" }}>
19
- <GameCanvas>
20
- <Physics>
21
- <RigidBody>
22
- <mesh castShadow>
23
- <boxGeometry args={[1, 1, 1]} />
24
- <meshStandardMaterial color="orange" />
25
- </mesh>
26
- </RigidBody>
27
- <RigidBody type="fixed">
28
- <mesh position={[0, -2, 0]} scale={[10, 0.1, 10]} receiveShadow>
29
- <boxGeometry />
30
- <meshStandardMaterial color="gray" />
31
- </mesh>
32
- </RigidBody>
33
- {/* Render loaded models */}
34
- {models.map((model, idx) => (
35
- <primitive object={model} key={idx} position={[0, 0, 0]} />
36
- ))}
37
- <ambientLight intensity={0.5} />
38
- <pointLight position={[10, 10, 10]} castShadow intensity={1000} />
39
- <OrbitControls />
40
- </Physics>
41
- </GameCanvas>
42
- </div>
43
- </DragDropLoader>
44
- );
45
- }
@@ -1,112 +0,0 @@
1
- import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
- import { createPortal } from 'react-dom';
3
-
4
- type Placement = 'bottom-start' | 'bottom-end' | 'left-start' | 'right-start';
5
-
6
- export function Dropdown({
7
- trigger,
8
- children,
9
- placement = 'bottom-end',
10
- offset = 6,
11
- zIndex = 1000,
12
- }: {
13
- trigger: (props: { ref: React.RefObject<HTMLButtonElement | null>; isOpen: boolean; toggle: () => void; close: () => void; }) => ReactNode;
14
- children: ReactNode | ((close: () => void) => ReactNode);
15
- placement?: Placement;
16
- offset?: number;
17
- zIndex?: number;
18
- }) {
19
- const [isOpen, setIsOpen] = useState(false);
20
- const [position, setPosition] = useState<{ left: number; top: number } | null>(null);
21
- const triggerRef = useRef<HTMLButtonElement>(null);
22
- const panelRef = useRef<HTMLDivElement>(null);
23
-
24
- const close = () => setIsOpen(false);
25
- const toggle = () => setIsOpen(prev => !prev);
26
-
27
- useLayoutEffect(() => {
28
- if (!isOpen || !triggerRef.current || !panelRef.current || typeof window === 'undefined') return;
29
-
30
- const updatePosition = () => {
31
- const triggerRect = triggerRef.current?.getBoundingClientRect();
32
- const panelRect = panelRef.current?.getBoundingClientRect();
33
- if (!triggerRect || !panelRect) return;
34
-
35
- let left = triggerRect.left;
36
- let top = triggerRect.bottom + offset;
37
-
38
- if (placement === 'bottom-end') {
39
- left = triggerRect.right - panelRect.width;
40
- top = triggerRect.bottom + offset;
41
- } else if (placement === 'bottom-start') {
42
- left = triggerRect.left;
43
- top = triggerRect.bottom + offset;
44
- } else if (placement === 'left-start') {
45
- left = triggerRect.left - panelRect.width - offset;
46
- top = triggerRect.top;
47
- } else if (placement === 'right-start') {
48
- left = triggerRect.right + offset;
49
- top = triggerRect.top;
50
- }
51
-
52
- left = Math.max(8, Math.min(left, window.innerWidth - panelRect.width - 8));
53
- top = Math.max(8, Math.min(top, window.innerHeight - panelRect.height - 8));
54
-
55
- setPosition({ left, top });
56
- };
57
-
58
- updatePosition();
59
- window.addEventListener('resize', updatePosition);
60
- window.addEventListener('scroll', updatePosition, true);
61
-
62
- return () => {
63
- window.removeEventListener('resize', updatePosition);
64
- window.removeEventListener('scroll', updatePosition, true);
65
- };
66
- }, [isOpen, placement, offset]);
67
-
68
- useEffect(() => {
69
- if (!isOpen) return;
70
-
71
- const handlePointerDown = (event: PointerEvent) => {
72
- const target = event.target as Node | null;
73
- if (!target) return;
74
- if (triggerRef.current?.contains(target)) return;
75
- if (panelRef.current?.contains(target)) return;
76
- close();
77
- };
78
-
79
- const handleKeyDown = (event: KeyboardEvent) => {
80
- if (event.key === 'Escape') close();
81
- };
82
-
83
- document.addEventListener('pointerdown', handlePointerDown);
84
- document.addEventListener('keydown', handleKeyDown);
85
-
86
- return () => {
87
- document.removeEventListener('pointerdown', handlePointerDown);
88
- document.removeEventListener('keydown', handleKeyDown);
89
- };
90
- }, [isOpen]);
91
-
92
- return (
93
- <>
94
- {trigger({ ref: triggerRef, isOpen, toggle, close })}
95
- {isOpen && typeof document !== 'undefined' && createPortal(
96
- <div
97
- ref={panelRef}
98
- onMouseLeave={close}
99
- style={{
100
- position: 'fixed',
101
- left: position?.left ?? -9999,
102
- top: position?.top ?? -9999,
103
- zIndex,
104
- }}
105
- >
106
- {typeof children === 'function' ? children(close) : children}
107
- </div>,
108
- document.body
109
- )}
110
- </>
111
- );
112
- }
@@ -1,25 +0,0 @@
1
- import { createContext, useContext } from "react";
2
-
3
- interface EditorContextType {
4
- transformMode: "translate" | "rotate" | "scale";
5
- setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
6
- snapResolution: number;
7
- setSnapResolution: (resolution: number) => void;
8
- positionSnap: number;
9
- setPositionSnap: (resolution: number) => void;
10
- rotationSnap: number;
11
- setRotationSnap: (resolution: number) => void;
12
- onFocusNode?: (nodeId: string) => void;
13
- onScreenshot?: () => void;
14
- onExportGLB?: () => void;
15
- }
16
-
17
- export const EditorContext = createContext<EditorContextType | null>(null);
18
-
19
- export function useEditorContext() {
20
- const context = useContext(EditorContext);
21
- if (!context) {
22
- throw new Error("useEditorContext must be used within EditorContext.Provider");
23
- }
24
- return context;
25
- }