react-three-game 0.0.41 → 0.0.42

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 (63) hide show
  1. package/README.md +0 -17
  2. package/dist/helpers/SoundManager.d.ts +35 -0
  3. package/dist/helpers/SoundManager.js +93 -0
  4. package/dist/helpers/index.d.ts +35 -0
  5. package/dist/helpers/index.js +44 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.js +14 -0
  8. package/dist/index.umd.js +4347 -163
  9. package/dist/shared/GameCanvas.d.ts +9 -0
  10. package/dist/shared/GameCanvas.js +47 -0
  11. package/dist/tools/assetviewer/page.d.ts +35 -0
  12. package/dist/tools/assetviewer/page.js +166 -0
  13. package/dist/tools/dragdrop/DragDropLoader.d.ts +9 -0
  14. package/dist/tools/dragdrop/DragDropLoader.js +78 -0
  15. package/dist/tools/dragdrop/modelLoader.d.ts +7 -0
  16. package/dist/tools/dragdrop/modelLoader.js +52 -0
  17. package/dist/tools/dragdrop/page.d.ts +1 -0
  18. package/dist/tools/dragdrop/page.js +11 -0
  19. package/dist/tools/prefabeditor/EditorContext.d.ts +11 -0
  20. package/dist/tools/prefabeditor/EditorContext.js +9 -0
  21. package/dist/tools/prefabeditor/EditorTree.d.ts +12 -0
  22. package/dist/tools/prefabeditor/EditorTree.js +150 -0
  23. package/dist/tools/prefabeditor/EditorUI.d.ts +14 -0
  24. package/dist/tools/prefabeditor/EditorUI.js +71 -0
  25. package/dist/tools/prefabeditor/EventSystem.d.ts +7 -0
  26. package/dist/tools/prefabeditor/EventSystem.js +23 -0
  27. package/dist/tools/prefabeditor/InstanceProvider.d.ts +30 -0
  28. package/dist/tools/prefabeditor/InstanceProvider.js +254 -0
  29. package/dist/tools/prefabeditor/PrefabEditor.d.ts +16 -0
  30. package/dist/tools/prefabeditor/PrefabEditor.js +140 -0
  31. package/dist/tools/prefabeditor/PrefabRoot.d.ts +28 -0
  32. package/dist/tools/prefabeditor/PrefabRoot.js +293 -0
  33. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +18 -0
  34. package/dist/tools/prefabeditor/components/ComponentRegistry.js +13 -0
  35. package/dist/tools/prefabeditor/components/DirectionalLightComponent.d.ts +3 -0
  36. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +78 -0
  37. package/dist/tools/prefabeditor/components/GeometryComponent.d.ts +3 -0
  38. package/dist/tools/prefabeditor/components/GeometryComponent.js +66 -0
  39. package/dist/tools/prefabeditor/components/Input.d.ts +20 -0
  40. package/dist/tools/prefabeditor/components/Input.js +129 -0
  41. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
  42. package/dist/tools/prefabeditor/components/MaterialComponent.js +100 -0
  43. package/dist/tools/prefabeditor/components/ModelComponent.d.ts +3 -0
  44. package/dist/tools/prefabeditor/components/ModelComponent.js +57 -0
  45. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -0
  46. package/dist/tools/prefabeditor/components/PhysicsComponent.js +33 -0
  47. package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +3 -0
  48. package/dist/tools/prefabeditor/components/SpotLightComponent.js +49 -0
  49. package/dist/tools/prefabeditor/components/TransformComponent.d.ts +3 -0
  50. package/dist/tools/prefabeditor/components/TransformComponent.js +42 -0
  51. package/dist/tools/prefabeditor/components/index.d.ts +2 -0
  52. package/dist/tools/prefabeditor/components/index.js +16 -0
  53. package/dist/tools/prefabeditor/page.d.ts +1 -0
  54. package/dist/tools/prefabeditor/page.js +5 -0
  55. package/dist/tools/prefabeditor/styles.d.ts +1809 -0
  56. package/dist/tools/prefabeditor/styles.js +167 -0
  57. package/dist/tools/prefabeditor/types.d.ts +19 -0
  58. package/dist/tools/prefabeditor/types.js +1 -0
  59. package/dist/tools/prefabeditor/utils.d.ts +26 -0
  60. package/dist/tools/prefabeditor/utils.js +131 -0
  61. package/package.json +2 -4
  62. package/.claude/settings.local.json +0 -9
  63. package/vite.config.ts +0 -34
@@ -0,0 +1,9 @@
1
+ import { CanvasProps } from "@react-three/fiber";
2
+ import { WebGPURendererParameters } from "three/src/renderers/webgpu/WebGPURenderer.Nodes.js";
3
+ interface GameCanvasProps extends Omit<CanvasProps, 'children'> {
4
+ loader?: boolean;
5
+ children: React.ReactNode;
6
+ glConfig?: WebGPURendererParameters;
7
+ }
8
+ export default function GameCanvas({ loader, children, glConfig, ...props }: GameCanvasProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,47 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __rest = (this && this.__rest) || function (s, e) {
11
+ var t = {};
12
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
13
+ t[p] = s[p];
14
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
15
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
16
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
17
+ t[p[i]] = s[p[i]];
18
+ }
19
+ return t;
20
+ };
21
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
22
+ import { Canvas, extend } from "@react-three/fiber";
23
+ import { WebGPURenderer, MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial, PCFShadowMap } from "three/webgpu";
24
+ import { Suspense, useState } from "react";
25
+ import { Loader } from "@react-three/drei";
26
+ // generic version
27
+ // extend(THREE as any)
28
+ extend({
29
+ MeshBasicNodeMaterial: MeshBasicNodeMaterial,
30
+ MeshStandardNodeMaterial: MeshStandardNodeMaterial,
31
+ SpriteNodeMaterial: SpriteNodeMaterial,
32
+ });
33
+ export default function GameCanvas(_a) {
34
+ var { loader = false, children, glConfig } = _a, props = __rest(_a, ["loader", "children", "glConfig"]);
35
+ const [frameloop, setFrameloop] = useState("never");
36
+ return _jsx(_Fragment, { children: _jsxs(Canvas, Object.assign({ style: { touchAction: 'none' }, shadows: { type: PCFShadowMap, }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
37
+ const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
38
+ // @ts-expect-error futuristic
39
+ shadowMap: true, antialias: true }, glConfig));
40
+ yield renderer.init().then(() => {
41
+ setFrameloop("always");
42
+ });
43
+ return renderer;
44
+ }), camera: {
45
+ position: [0, 1, 5],
46
+ } }, props, { children: [_jsx(Suspense, { children: children }), loader ? _jsx(Loader, {}) : null] })) });
47
+ }
@@ -0,0 +1,35 @@
1
+ interface TextureListViewerProps {
2
+ files: string[];
3
+ selected?: string;
4
+ onSelect: (file: string) => void;
5
+ basePath?: string;
6
+ }
7
+ export declare function TextureListViewer({ files, selected, onSelect, basePath }: TextureListViewerProps): import("react/jsx-runtime").JSX.Element;
8
+ interface ModelListViewerProps {
9
+ files: string[];
10
+ selected?: string;
11
+ onSelect: (file: string) => void;
12
+ basePath?: string;
13
+ }
14
+ export declare function ModelListViewer({ files, selected, onSelect, basePath }: ModelListViewerProps): import("react/jsx-runtime").JSX.Element;
15
+ interface SoundListViewerProps {
16
+ files: string[];
17
+ selected?: string;
18
+ onSelect: (file: string) => void;
19
+ basePath?: string;
20
+ }
21
+ export declare function SoundListViewer({ files, selected, onSelect, basePath }: SoundListViewerProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function SingleTextureViewer({ file, basePath }: {
23
+ file?: string;
24
+ basePath?: string;
25
+ }): import("react/jsx-runtime").JSX.Element | null;
26
+ export declare function SingleModelViewer({ file, basePath }: {
27
+ file?: string;
28
+ basePath?: string;
29
+ }): import("react/jsx-runtime").JSX.Element | null;
30
+ export declare function SingleSoundViewer({ file, basePath }: {
31
+ file?: string;
32
+ basePath?: string;
33
+ }): import("react/jsx-runtime").JSX.Element | null;
34
+ export declare function SharedCanvas(): import("react/jsx-runtime").JSX.Element;
35
+ export {};
@@ -0,0 +1,166 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Canvas, useLoader } from "@react-three/fiber";
3
+ import { OrbitControls, Stage, View, PerspectiveCamera } from "@react-three/drei";
4
+ import { Suspense, useEffect, useState, useRef } from "react";
5
+ import { TextureLoader } from "three";
6
+ import { loadModel } from "../dragdrop/modelLoader";
7
+ // view models and textures in manifest, onselect callback
8
+ const styles = {
9
+ errorIcon: { color: '#fca5a5', fontSize: 12 }, // text-red-400 text-xs
10
+ flexFillRelative: { flex: 1, position: 'relative' },
11
+ bottomLabel: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' },
12
+ iconLarge: { fontSize: 20 }
13
+ };
14
+ function getItemsInPath(files, currentPath) {
15
+ // Remove the leading category folder (e.g., /textures/, /models/, /sounds/)
16
+ const filesWithoutCategory = files.map(file => {
17
+ const parts = file.split('/').filter(Boolean);
18
+ return parts.length > 1 ? '/' + parts.slice(1).join('/') : '';
19
+ }).filter(Boolean);
20
+ const prefix = currentPath ? `/${currentPath}/` : '/';
21
+ const relevantFiles = filesWithoutCategory.filter(file => file.startsWith(prefix));
22
+ const folders = new Set();
23
+ const filesInCurrentPath = [];
24
+ relevantFiles.forEach((file, index) => {
25
+ const relativePath = file.slice(prefix.length);
26
+ const parts = relativePath.split('/').filter(Boolean);
27
+ if (parts.length > 1) {
28
+ folders.add(parts[0]);
29
+ }
30
+ else if (parts[0]) {
31
+ // Return the original file path
32
+ filesInCurrentPath.push(files[filesWithoutCategory.indexOf(file)]);
33
+ }
34
+ });
35
+ return { folders: Array.from(folders), filesInCurrentPath };
36
+ }
37
+ function FolderTile({ name, onClick }) {
38
+ return (_jsxs("div", { onClick: onClick, style: {
39
+ maxWidth: 60,
40
+ aspectRatio: '1 / 1',
41
+ backgroundColor: '#1f2937', /* gray-800 */
42
+ cursor: 'pointer',
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ alignItems: 'center',
46
+ justifyContent: 'center'
47
+ }, children: [_jsx("div", { style: { fontSize: 24 }, children: "\uD83D\uDCC1" }), _jsx("div", { style: { fontSize: 10, textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', width: '100%', padding: '0 4px', marginTop: 4 }, children: name })] }));
48
+ }
49
+ function useInView() {
50
+ const [isInView, setIsInView] = useState(false);
51
+ const ref = useRef(null);
52
+ useEffect(() => {
53
+ const observer = new IntersectionObserver(([entry]) => {
54
+ setIsInView(entry.isIntersecting);
55
+ }, { rootMargin: '100px' });
56
+ if (ref.current) {
57
+ observer.observe(ref.current);
58
+ }
59
+ return () => {
60
+ if (ref.current) {
61
+ observer.unobserve(ref.current);
62
+ }
63
+ };
64
+ }, []);
65
+ return { ref, isInView };
66
+ }
67
+ function AssetListViewer({ files, selected, onSelect, renderCard }) {
68
+ const [currentPath, setCurrentPath] = useState('');
69
+ const { folders, filesInCurrentPath } = getItemsInPath(files, currentPath);
70
+ return (_jsxs("div", { children: [currentPath && (_jsx("button", { onClick: () => {
71
+ const pathParts = currentPath.split('/').filter(Boolean);
72
+ pathParts.pop();
73
+ setCurrentPath(pathParts.join('/'));
74
+ }, style: { marginBottom: 4, padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 12, cursor: 'pointer', border: 'none' }, children: "\u2190 Back" })), _jsxs("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4 }, children: [folders.map((folder) => (_jsx(FolderTile, { name: folder, onClick: () => setCurrentPath(currentPath ? `${currentPath}/${folder}` : folder) }, folder))), filesInCurrentPath.map((file) => (_jsx("div", { children: renderCard(file, onSelect) }, file)))] })] }));
75
+ }
76
+ export function TextureListViewer({ files, selected, onSelect, basePath = "" }) {
77
+ return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(TextureCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
78
+ }
79
+ function TextureCard({ file, onSelect, basePath = "" }) {
80
+ const [error, setError] = useState(false);
81
+ const [isHovered, setIsHovered] = useState(false);
82
+ const { ref, isInView } = useInView();
83
+ const fullPath = basePath ? `/${basePath}${file}` : file;
84
+ if (error) {
85
+ return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
86
+ }
87
+ return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
88
+ }
89
+ function TextureSphere({ url, onError }) {
90
+ const texture = useLoader(TextureLoader, url, undefined, (error) => {
91
+ console.error('Failed to load texture:', url, error);
92
+ onError === null || onError === void 0 ? void 0 : onError();
93
+ });
94
+ return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
95
+ }
96
+ export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
97
+ return (_jsxs(_Fragment, { children: [_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(ModelCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }), _jsx(SharedCanvas, {})] }));
98
+ }
99
+ function ModelCard({ file, onSelect, basePath = "" }) {
100
+ const [error, setError] = useState(false);
101
+ const { ref, isInView } = useInView();
102
+ const fullPath = basePath ? `/${basePath}${file}` : file;
103
+ if (error) {
104
+ return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
105
+ }
106
+ return (_jsxs("div", { ref: ref, style: { maxWidth: 60, aspectRatio: '1 / 1', backgroundColor: '#111827', cursor: 'pointer', display: 'flex', flexDirection: 'column' }, onClick: () => onSelect(file), children: [_jsx("div", { style: styles.flexFillRelative, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 1, 3], fov: 50 }), _jsxs(Suspense, { fallback: null, children: [_jsx(Stage, { intensity: 0.5, environment: "city", children: _jsx(ModelPreview, { url: fullPath, onError: () => setError(true) }) }), _jsx(OrbitControls, { enableZoom: false })] })] })) : null }), _jsx("div", { style: { backgroundColor: 'rgba(0,0,0,0.6)', fontSize: 10, padding: '0 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center' }, children: file.split('/').pop() })] }));
107
+ }
108
+ function ModelPreview({ url, onError }) {
109
+ const [model, setModel] = useState(null);
110
+ const onErrorRef = useRef(onError);
111
+ onErrorRef.current = onError;
112
+ useEffect(() => {
113
+ let cancelled = false;
114
+ setModel(null);
115
+ loadModel(url).then((result) => {
116
+ var _a;
117
+ if (cancelled)
118
+ return;
119
+ if (result.success && result.model) {
120
+ setModel(result.model);
121
+ }
122
+ else {
123
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
124
+ }
125
+ });
126
+ return () => { cancelled = true; };
127
+ }, [url]);
128
+ if (!model)
129
+ return null;
130
+ return _jsx("primitive", { object: model });
131
+ }
132
+ export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
133
+ return (_jsx(AssetListViewer, { files: files, selected: selected, onSelect: onSelect, renderCard: (file, onSelectHandler) => (_jsx(SoundCard, { file: file, basePath: basePath, onSelect: onSelectHandler })) }));
134
+ }
135
+ function SoundCard({ file, onSelect, basePath = "" }) {
136
+ const fileName = file.split('/').pop() || '';
137
+ const fullPath = basePath ? `/${basePath}${file}` : file;
138
+ return (_jsxs("div", { onClick: () => onSelect(file), style: { aspectRatio: '1 / 1', backgroundColor: '#374151', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }, children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { fontSize: 12, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
139
+ }
140
+ // Single Asset Viewer Components - display only one selected asset
141
+ export function SingleTextureViewer({ file, basePath = "" }) {
142
+ if (!file)
143
+ return null;
144
+ return (_jsxs(_Fragment, { children: [_jsx(TextureCard, { file: file, basePath: basePath, onSelect: () => { } }), _jsx(SharedCanvas, {})] }));
145
+ }
146
+ export function SingleModelViewer({ file, basePath = "" }) {
147
+ if (!file)
148
+ return null;
149
+ return (_jsxs(_Fragment, { children: [_jsx(ModelCard, { file: file, basePath: basePath, onSelect: () => { } }), _jsx(SharedCanvas, {})] }));
150
+ }
151
+ export function SingleSoundViewer({ file, basePath = "" }) {
152
+ if (!file)
153
+ return null;
154
+ return _jsx(SoundCard, { file: file, basePath: basePath, onSelect: () => { } });
155
+ }
156
+ // Shared Canvas Component - can be used independently in any viewer
157
+ export function SharedCanvas() {
158
+ return (_jsx(Canvas, { shadows: true, dpr: [1, 1.5], camera: { position: [0, 0, 3], fov: 45, near: 0.1, far: 1000 }, style: {
159
+ position: 'absolute',
160
+ top: 0,
161
+ left: 0,
162
+ width: '100vw',
163
+ height: '100vh',
164
+ pointerEvents: 'none',
165
+ }, eventSource: typeof document !== 'undefined' ? document.getElementById('root') || undefined : undefined, eventPrefix: "client", children: _jsx(View.Port, {}) }));
166
+ }
@@ -0,0 +1,9 @@
1
+ interface DragDropLoaderProps {
2
+ onModelLoaded: (model: any, filename: string) => void;
3
+ }
4
+ export declare function DragDropLoader({ onModelLoaded }: DragDropLoaderProps): null;
5
+ interface FilePickerProps {
6
+ onModelLoaded: (model: any, filename: string) => void;
7
+ }
8
+ export declare function FilePicker({ onModelLoaded }: FilePickerProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // DragDropLoader.tsx
3
+ import { useEffect } from "react";
4
+ import { DRACOLoader, FBXLoader, GLTFLoader } from "three/examples/jsm/Addons.js";
5
+ // Shared file handling logic
6
+ function handleFiles(files, onModelLoaded) {
7
+ files.forEach((file) => {
8
+ if (file.name.endsWith(".glb") || file.name.endsWith(".gltf")) {
9
+ loadGLTFFile(file, onModelLoaded);
10
+ }
11
+ else if (file.name.endsWith(".fbx")) {
12
+ loadFBXFile(file, onModelLoaded);
13
+ }
14
+ });
15
+ }
16
+ function loadGLTFFile(file, onModelLoaded) {
17
+ const reader = new FileReader();
18
+ reader.onload = (event) => {
19
+ var _a;
20
+ const arrayBuffer = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
21
+ if (arrayBuffer) {
22
+ const loader = new GLTFLoader();
23
+ const dracoLoader = new DRACOLoader();
24
+ dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
25
+ loader.setDRACOLoader(dracoLoader);
26
+ loader.parse(arrayBuffer, "", (gltf) => {
27
+ onModelLoaded(gltf.scene, file.name);
28
+ }, (error) => {
29
+ console.error("GLTFLoader parse error", error);
30
+ });
31
+ }
32
+ };
33
+ reader.readAsArrayBuffer(file);
34
+ }
35
+ function loadFBXFile(file, onModelLoaded) {
36
+ const reader = new FileReader();
37
+ reader.onload = (event) => {
38
+ var _a;
39
+ const arrayBuffer = (_a = event.target) === null || _a === void 0 ? void 0 : _a.result;
40
+ if (arrayBuffer) {
41
+ const loader = new FBXLoader();
42
+ const model = loader.parse(arrayBuffer, "");
43
+ onModelLoaded(model, file.name);
44
+ }
45
+ };
46
+ reader.readAsArrayBuffer(file);
47
+ }
48
+ export function DragDropLoader({ onModelLoaded }) {
49
+ useEffect(() => {
50
+ function handleDrop(e) {
51
+ var _a;
52
+ e.preventDefault();
53
+ e.stopPropagation();
54
+ const files = ((_a = e.dataTransfer) === null || _a === void 0 ? void 0 : _a.files) ? Array.from(e.dataTransfer.files) : [];
55
+ handleFiles(files, onModelLoaded);
56
+ }
57
+ function handleDragOver(e) {
58
+ e.preventDefault();
59
+ e.stopPropagation();
60
+ }
61
+ window.addEventListener("drop", handleDrop);
62
+ window.addEventListener("dragover", handleDragOver);
63
+ return () => {
64
+ window.removeEventListener("drop", handleDrop);
65
+ window.removeEventListener("dragover", handleDragOver);
66
+ };
67
+ }, [onModelLoaded]);
68
+ return null;
69
+ }
70
+ export function FilePicker({ onModelLoaded }) {
71
+ function onChange(e) {
72
+ const files = e.target.files ? Array.from(e.target.files) : [];
73
+ handleFiles(files, onModelLoaded);
74
+ }
75
+ // Ref for the hidden input
76
+ const inputId = "file-picker-input";
77
+ return (_jsxs(_Fragment, { children: [_jsx("input", { id: inputId, type: "file", accept: ".glb,.gltf,.fbx", multiple: true, onChange: onChange, className: "hidden" }), _jsx("button", { className: "px-3 py-1 bg-blue-500/20 hover:bg-blue-500/30 border border-blue-400/40 hover:border-blue-400/60 text-blue-200 hover:text-blue-100 text-xs font-medium transition-all", type: "button", onClick: () => { var _a; return (_a = document.getElementById(inputId)) === null || _a === void 0 ? void 0 : _a.click(); }, children: "Select Files" })] }));
78
+ }
@@ -0,0 +1,7 @@
1
+ export type ModelLoadResult = {
2
+ success: boolean;
3
+ model?: any;
4
+ error?: any;
5
+ };
6
+ export type ProgressCallback = (filename: string, loaded: number, total: number) => void;
7
+ export declare function loadModel(filename: string, onProgress?: ProgressCallback): Promise<ModelLoadResult>;
@@ -0,0 +1,52 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { GLTFLoader, FBXLoader, DRACOLoader } from "three/examples/jsm/Addons.js";
11
+ // Singleton loader instances
12
+ const dracoLoader = new DRACOLoader();
13
+ dracoLoader.setDecoderPath("https://www.gstatic.com/draco/v1/decoders/");
14
+ const gltfLoader = new GLTFLoader();
15
+ gltfLoader.setDRACOLoader(dracoLoader);
16
+ const fbxLoader = new FBXLoader();
17
+ export function loadModel(filename, onProgress) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ try {
20
+ // Use filename directly (should already include leading /)
21
+ const fullPath = filename;
22
+ if (filename.endsWith('.glb') || filename.endsWith('.gltf')) {
23
+ return new Promise((resolve) => {
24
+ gltfLoader.load(fullPath, (gltf) => resolve({ success: true, model: gltf.scene }), (progressEvent) => {
25
+ if (onProgress) {
26
+ // Use loaded as total if total is not available
27
+ const total = progressEvent.total || progressEvent.loaded;
28
+ onProgress(filename, progressEvent.loaded, total);
29
+ }
30
+ }, (error) => resolve({ success: false, error }));
31
+ });
32
+ }
33
+ else if (filename.endsWith('.fbx')) {
34
+ return new Promise((resolve) => {
35
+ fbxLoader.load(fullPath, (model) => resolve({ success: true, model }), (progressEvent) => {
36
+ if (onProgress) {
37
+ // Use loaded as total if total is not available
38
+ const total = progressEvent.total || progressEvent.loaded;
39
+ onProgress(filename, progressEvent.loaded, total);
40
+ }
41
+ }, (error) => resolve({ success: false, error }));
42
+ });
43
+ }
44
+ else {
45
+ return { success: false, error: new Error(`Unsupported file format: ${filename}`) };
46
+ }
47
+ }
48
+ catch (error) {
49
+ return { success: false, error };
50
+ }
51
+ });
52
+ }
@@ -0,0 +1 @@
1
+ export default function Home(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { Physics, RigidBody } from "@react-three/rapier";
4
+ import { OrbitControls } from "@react-three/drei";
5
+ import { useState } from "react";
6
+ import { DragDropLoader } from "./DragDropLoader";
7
+ import GameCanvas from "../../shared/GameCanvas";
8
+ export default function Home() {
9
+ const [models, setModels] = useState([]);
10
+ return (_jsxs(_Fragment, { children: [_jsx(DragDropLoader, { onModelLoaded: model => setModels(prev => [...prev, model]) }), _jsx("div", { className: "w-full items-center justify-items-center min-h-screen", style: { height: "100vh" }, children: _jsx(GameCanvas, { children: _jsxs(Physics, { children: [_jsx(RigidBody, { children: _jsxs("mesh", { castShadow: true, children: [_jsx("boxGeometry", { args: [1, 1, 1] }), _jsx("meshStandardMaterial", { color: "orange" })] }) }), _jsx(RigidBody, { type: "fixed", children: _jsxs("mesh", { position: [0, -2, 0], scale: [10, 0.1, 10], receiveShadow: true, children: [_jsx("boxGeometry", {}), _jsx("meshStandardMaterial", { color: "gray" })] }) }), models.map((model, idx) => (_jsx("primitive", { object: model, position: [0, 0, 0] }, idx))), _jsx("ambientLight", { intensity: 0.5 }), _jsx("pointLight", { position: [10, 10, 10], castShadow: true, intensity: 1000 }), _jsx(OrbitControls, {})] }) }) })] }));
11
+ }
@@ -0,0 +1,11 @@
1
+ interface EditorContextType {
2
+ transformMode: "translate" | "rotate" | "scale";
3
+ setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
4
+ snapResolution: number;
5
+ setSnapResolution: (resolution: number) => void;
6
+ onScreenshot?: () => void;
7
+ onExportGLB?: () => void;
8
+ }
9
+ export declare const EditorContext: import("react").Context<EditorContextType | null>;
10
+ export declare function useEditorContext(): EditorContextType;
11
+ export {};
@@ -0,0 +1,9 @@
1
+ import { createContext, useContext } from "react";
2
+ export const EditorContext = createContext(null);
3
+ export function useEditorContext() {
4
+ const context = useContext(EditorContext);
5
+ if (!context) {
6
+ throw new Error("useEditorContext must be used within EditorContext.Provider");
7
+ }
8
+ return context;
9
+ }
@@ -0,0 +1,12 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ import { Prefab } from "./types";
3
+ export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }: {
4
+ prefabData?: Prefab;
5
+ setPrefabData?: Dispatch<SetStateAction<Prefab>>;
6
+ selectedId: string | null;
7
+ setSelectedId: Dispatch<SetStateAction<string | null>>;
8
+ onUndo?: () => void;
9
+ onRedo?: () => void;
10
+ canUndo?: boolean;
11
+ canRedo?: boolean;
12
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,150 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
11
+ import { useState } from 'react';
12
+ import { getComponent } from './components/ComponentRegistry';
13
+ import { base, tree, menu } from './styles';
14
+ import { findNode, findParent, deleteNode, cloneNode, updateNodeById, loadJson, saveJson, regenerateIds } from './utils';
15
+ import { useEditorContext } from './EditorContext';
16
+ export default function EditorTree({ prefabData, setPrefabData, selectedId, setSelectedId, onUndo, onRedo, canUndo, canRedo }) {
17
+ const [contextMenu, setContextMenu] = useState(null);
18
+ const [draggedId, setDraggedId] = useState(null);
19
+ const [collapsedIds, setCollapsedIds] = useState(new Set());
20
+ const [collapsed, setCollapsed] = useState(false);
21
+ const [fileMenuOpen, setFileMenuOpen] = useState(false);
22
+ if (!prefabData || !setPrefabData)
23
+ return null;
24
+ const handleContextMenu = (e, nodeId) => {
25
+ e.preventDefault();
26
+ e.stopPropagation();
27
+ setContextMenu({ x: e.clientX, y: e.clientY, nodeId });
28
+ };
29
+ const toggleCollapse = (e, id) => {
30
+ e.stopPropagation();
31
+ setCollapsedIds(prev => {
32
+ const next = new Set(prev);
33
+ next.has(id) ? next.delete(id) : next.add(id);
34
+ return next;
35
+ });
36
+ };
37
+ const handleAddChild = (parentId) => {
38
+ setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, parentId, parent => {
39
+ var _a, _b;
40
+ return (Object.assign(Object.assign({}, parent), { children: [...((_a = parent.children) !== null && _a !== void 0 ? _a : []), {
41
+ id: crypto.randomUUID(),
42
+ name: "New Node",
43
+ components: {
44
+ transform: {
45
+ type: "Transform",
46
+ properties: Object.assign({}, (_b = getComponent('Transform')) === null || _b === void 0 ? void 0 : _b.defaultProperties)
47
+ }
48
+ }
49
+ }] }));
50
+ }) })));
51
+ setContextMenu(null);
52
+ };
53
+ const handleDuplicate = (nodeId) => {
54
+ if (nodeId === prefabData.root.id)
55
+ return;
56
+ setPrefabData(prev => {
57
+ const node = findNode(prev.root, nodeId);
58
+ const parent = findParent(prev.root, nodeId);
59
+ if (!node || !parent)
60
+ return prev;
61
+ return Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, parent.id, p => {
62
+ var _a;
63
+ return (Object.assign(Object.assign({}, p), { children: [...((_a = p.children) !== null && _a !== void 0 ? _a : []), cloneNode(node)] }));
64
+ }) });
65
+ });
66
+ setContextMenu(null);
67
+ };
68
+ const handleDelete = (nodeId) => {
69
+ if (nodeId === prefabData.root.id)
70
+ return;
71
+ setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: deleteNode(prev.root, nodeId) })));
72
+ if (selectedId === nodeId)
73
+ setSelectedId(null);
74
+ setContextMenu(null);
75
+ };
76
+ const handleDragStart = (e, id) => {
77
+ if (id === prefabData.root.id)
78
+ return e.preventDefault();
79
+ e.dataTransfer.effectAllowed = "move";
80
+ setDraggedId(id);
81
+ };
82
+ const handleDragOver = (e, targetId) => {
83
+ if (!draggedId || draggedId === targetId)
84
+ return;
85
+ const draggedNode = findNode(prefabData.root, draggedId);
86
+ if (draggedNode && findNode(draggedNode, targetId))
87
+ return;
88
+ e.preventDefault();
89
+ };
90
+ const handleDrop = (e, targetId) => {
91
+ if (!draggedId || draggedId === targetId)
92
+ return;
93
+ e.preventDefault();
94
+ setPrefabData(prev => {
95
+ const draggedNode = findNode(prev.root, draggedId);
96
+ const oldParent = findParent(prev.root, draggedId);
97
+ if (!draggedNode || !oldParent || findNode(draggedNode, targetId))
98
+ return prev;
99
+ let root = updateNodeById(prev.root, oldParent.id, p => (Object.assign(Object.assign({}, p), { children: p.children.filter(c => c.id !== draggedId) })));
100
+ root = updateNodeById(root, targetId, t => {
101
+ var _a;
102
+ return (Object.assign(Object.assign({}, t), { children: [...((_a = t.children) !== null && _a !== void 0 ? _a : []), draggedNode] }));
103
+ });
104
+ return Object.assign(Object.assign({}, prev), { root });
105
+ });
106
+ setDraggedId(null);
107
+ };
108
+ const renderNode = (node, depth = 0) => {
109
+ var _a;
110
+ if (!node)
111
+ return null;
112
+ const isSelected = node.id === selectedId;
113
+ const isCollapsed = collapsedIds.has(node.id);
114
+ const hasChildren = node.children && node.children.length > 0;
115
+ const isRoot = node.id === prefabData.root.id;
116
+ return (_jsxs("div", { children: [_jsxs("div", { style: Object.assign(Object.assign(Object.assign({}, tree.row), (isSelected ? tree.selected : {})), { paddingLeft: `${depth * 12 + 6}px` }), draggable: !isRoot, onClick: (e) => { e.stopPropagation(); setSelectedId(node.id); }, onContextMenu: (e) => handleContextMenu(e, node.id), onDragStart: (e) => handleDragStart(e, node.id), onDragEnd: () => setDraggedId(null), onDragOver: (e) => handleDragOver(e, node.id), onDrop: (e) => handleDrop(e, node.id), children: [_jsx("span", { style: {
117
+ width: 12,
118
+ opacity: 0.6,
119
+ marginRight: 4,
120
+ cursor: 'pointer',
121
+ visibility: hasChildren ? 'visible' : 'hidden'
122
+ }, onClick: (e) => hasChildren && toggleCollapse(e, node.id), children: isCollapsed ? '▶' : '▼' }), !isRoot && _jsx("span", { style: { marginRight: 4, opacity: 0.4 }, children: "\u22EE\u22EE" }), _jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis' }, children: (_a = node.name) !== null && _a !== void 0 ? _a : node.id })] }), !isCollapsed && node.children && node.children.map(child => renderNode(child, depth + 1))] }, node.id));
123
+ };
124
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), onClick: () => { setContextMenu(null); setFileMenuOpen(false); }, children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Scene" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsxs("div", { style: { position: 'relative' }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => { e.stopPropagation(); setFileMenuOpen(!fileMenuOpen); }, title: "File", children: "\u22EE" }), fileMenuOpen && (_jsx(FileMenu, { prefabData: prefabData, setPrefabData: setPrefabData, onClose: () => setFileMenuOpen(false) }))] })] }))] }), !collapsed && _jsx("div", { style: tree.scroll, children: renderNode(prefabData.root) })] }), contextMenu && (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: contextMenu.y, left: contextMenu.x }), onClick: (e) => e.stopPropagation(), onPointerLeave: () => setContextMenu(null), children: [_jsx("button", { style: menu.item, onClick: () => handleAddChild(contextMenu.nodeId), children: "Add Child" }), contextMenu.nodeId !== prefabData.root.id && (_jsxs(_Fragment, { children: [_jsx("button", { style: menu.item, onClick: () => handleDuplicate(contextMenu.nodeId), children: "Duplicate" }), _jsx("button", { style: Object.assign(Object.assign({}, menu.item), menu.danger), onClick: () => handleDelete(contextMenu.nodeId), children: "Delete" })] }))] }))] }));
125
+ }
126
+ function FileMenu({ prefabData, setPrefabData, onClose }) {
127
+ const { onScreenshot, onExportGLB } = useEditorContext();
128
+ const handleLoad = () => __awaiter(this, void 0, void 0, function* () {
129
+ const loadedPrefab = yield loadJson();
130
+ if (!loadedPrefab)
131
+ return;
132
+ setPrefabData(loadedPrefab);
133
+ onClose();
134
+ });
135
+ const handleSave = () => {
136
+ saveJson(prefabData, "prefab");
137
+ onClose();
138
+ };
139
+ const handleLoadIntoScene = () => __awaiter(this, void 0, void 0, function* () {
140
+ const loadedPrefab = yield loadJson();
141
+ if (!loadedPrefab)
142
+ return;
143
+ setPrefabData(prev => (Object.assign(Object.assign({}, prev), { root: updateNodeById(prev.root, prev.root.id, root => {
144
+ var _a;
145
+ return (Object.assign(Object.assign({}, root), { children: [...((_a = root.children) !== null && _a !== void 0 ? _a : []), regenerateIds(loadedPrefab.root)] }));
146
+ }) })));
147
+ onClose();
148
+ });
149
+ return (_jsxs("div", { style: Object.assign(Object.assign({}, menu.container), { top: 28, right: 0 }), onClick: (e) => e.stopPropagation(), children: [_jsx("button", { style: menu.item, onClick: handleLoad, children: "\uD83D\uDCE5 Load Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleSave, children: "\uD83D\uDCBE Save Prefab JSON" }), _jsx("button", { style: menu.item, onClick: handleLoadIntoScene, children: "\uD83D\uDCC2 Load into Scene" }), _jsx("button", { style: menu.item, onClick: () => { onScreenshot === null || onScreenshot === void 0 ? void 0 : onScreenshot(); onClose(); }, children: "\uD83D\uDCF8 Screenshot" }), _jsx("button", { style: menu.item, onClick: () => { onExportGLB === null || onExportGLB === void 0 ? void 0 : onExportGLB(); onClose(); }, children: "\uD83D\uDCE6 Export GLB" })] }));
150
+ }
@@ -0,0 +1,14 @@
1
+ import { Dispatch, SetStateAction } from 'react';
2
+ import { Prefab } from "./types";
3
+ declare function EditorUI({ prefabData, setPrefabData, selectedId, setSelectedId, basePath, onUndo, onRedo, canUndo, canRedo }: {
4
+ prefabData?: Prefab;
5
+ setPrefabData?: Dispatch<SetStateAction<Prefab>>;
6
+ selectedId: string | null;
7
+ setSelectedId: Dispatch<SetStateAction<string | null>>;
8
+ basePath?: string;
9
+ onUndo?: () => void;
10
+ onRedo?: () => void;
11
+ canUndo?: boolean;
12
+ canRedo?: boolean;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export default EditorUI;