react-three-game 0.0.1 → 0.0.3

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 (54) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/copilot-instructions.md +207 -0
  3. package/LICENSE +661 -0
  4. package/README.md +664 -0
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.js +4 -1
  7. package/dist/shared/GameCanvas.d.ts +6 -0
  8. package/dist/shared/GameCanvas.js +48 -0
  9. package/dist/shared/extend-three.d.ts +1 -0
  10. package/dist/shared/extend-three.js +13 -0
  11. package/dist/tools/assetviewer/page.d.ts +21 -0
  12. package/dist/tools/assetviewer/page.js +153 -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 +53 -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/EditorTree.d.ts +10 -0
  20. package/dist/tools/prefabeditor/EditorTree.js +182 -0
  21. package/dist/tools/prefabeditor/EditorUI.d.ts +11 -0
  22. package/dist/tools/prefabeditor/EditorUI.js +96 -0
  23. package/dist/tools/prefabeditor/EventSystem.d.ts +7 -0
  24. package/dist/tools/prefabeditor/EventSystem.js +23 -0
  25. package/dist/tools/prefabeditor/InstanceProvider.d.ts +30 -0
  26. package/dist/tools/prefabeditor/InstanceProvider.js +172 -0
  27. package/dist/tools/prefabeditor/PrefabEditor.d.ts +4 -0
  28. package/dist/tools/prefabeditor/PrefabEditor.js +89 -0
  29. package/dist/tools/prefabeditor/PrefabRoot.d.ts +12 -0
  30. package/dist/tools/prefabeditor/PrefabRoot.js +273 -0
  31. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +13 -0
  32. package/dist/tools/prefabeditor/components/ComponentRegistry.js +13 -0
  33. package/dist/tools/prefabeditor/components/GeometryComponent.d.ts +3 -0
  34. package/dist/tools/prefabeditor/components/GeometryComponent.js +28 -0
  35. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +3 -0
  36. package/dist/tools/prefabeditor/components/MaterialComponent.js +66 -0
  37. package/dist/tools/prefabeditor/components/ModelComponent.d.ts +3 -0
  38. package/dist/tools/prefabeditor/components/ModelComponent.js +39 -0
  39. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +3 -0
  40. package/dist/tools/prefabeditor/components/PhysicsComponent.js +19 -0
  41. package/dist/tools/prefabeditor/components/SpotLightComponent.d.ts +3 -0
  42. package/dist/tools/prefabeditor/components/SpotLightComponent.js +19 -0
  43. package/dist/tools/prefabeditor/components/TransformComponent.d.ts +8 -0
  44. package/dist/tools/prefabeditor/components/TransformComponent.js +22 -0
  45. package/dist/tools/prefabeditor/components/index.d.ts +2 -0
  46. package/dist/tools/prefabeditor/components/index.js +14 -0
  47. package/dist/tools/prefabeditor/page.d.ts +1 -0
  48. package/dist/tools/prefabeditor/page.js +5 -0
  49. package/dist/tools/prefabeditor/types.d.ts +29 -0
  50. package/dist/tools/prefabeditor/types.js +1 -0
  51. package/package.json +16 -4
  52. package/tsconfig.json +2 -1
  53. package/dist/GameCanvas.d.ts +0 -6
  54. package/dist/GameCanvas.js +0 -5
@@ -0,0 +1,273 @@
1
+ "use client";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { MapControls, TransformControls } from "@react-three/drei";
13
+ import { useState, useRef, useEffect, forwardRef, useCallback } from "react";
14
+ import { Vector3, Euler, Quaternion, SRGBColorSpace, TextureLoader, Matrix4 } from "three";
15
+ import { getComponent } from "./components/ComponentRegistry";
16
+ import { loadModel } from "../dragdrop/modelLoader";
17
+ import { GameInstance, GameInstanceProvider } from "./InstanceProvider";
18
+ // register all components
19
+ import { registerComponent } from './components/ComponentRegistry';
20
+ import components from './components/';
21
+ components.forEach(registerComponent);
22
+ function updatePrefabNode(root, id, update) {
23
+ if (root.id === id) {
24
+ return update(root);
25
+ }
26
+ if (root.children) {
27
+ return Object.assign(Object.assign({}, root), { children: root.children.map(child => updatePrefabNode(child, id, update)) });
28
+ }
29
+ return root;
30
+ }
31
+ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selectedId, onSelect, transformMode, setTransformMode }, ref) => {
32
+ const [loadedModels, setLoadedModels] = useState({});
33
+ const [loadedTextures, setLoadedTextures] = useState({});
34
+ // const [prefabRoot, setPrefabRoot] = useState<Prefab>(data); // Removed local state
35
+ const loadingRefs = useRef(new Set());
36
+ const objectRefs = useRef({});
37
+ const [selectedObject, setSelectedObject] = useState(null);
38
+ const registerRef = useCallback((id, obj) => {
39
+ objectRefs.current[id] = obj;
40
+ if (id === selectedId) {
41
+ setSelectedObject(obj);
42
+ }
43
+ }, [selectedId]);
44
+ useEffect(() => {
45
+ if (selectedId) {
46
+ setSelectedObject(objectRefs.current[selectedId] || null);
47
+ }
48
+ else {
49
+ setSelectedObject(null);
50
+ }
51
+ }, [selectedId]);
52
+ // const [transformMode, setTransformMode] = useState<"translate" | "rotate" | "scale">("translate"); // Removed local state
53
+ const updateNode = (updater) => {
54
+ if (!selectedId || !onPrefabChange)
55
+ return;
56
+ const newRoot = updatePrefabNode(data.root, selectedId, updater);
57
+ onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
58
+ };
59
+ const onTransformChange = () => {
60
+ if (!selectedId || !onPrefabChange)
61
+ return;
62
+ const obj = objectRefs.current[selectedId];
63
+ if (!obj)
64
+ return;
65
+ // 1. Get world matrix from the actual Three object
66
+ const worldMatrix = obj.matrixWorld.clone();
67
+ // 2. Compute parent world matrix from the prefab tree
68
+ const parentWorld = computeParentWorldMatrix(data.root, selectedId);
69
+ const parentInv = parentWorld.clone().invert();
70
+ // 3. Convert world -> local
71
+ const localMatrix = new Matrix4().multiplyMatrices(parentInv, worldMatrix);
72
+ const lp = new Vector3();
73
+ const lq = new Quaternion();
74
+ const ls = new Vector3();
75
+ localMatrix.decompose(lp, lq, ls);
76
+ const le = new Euler().setFromQuaternion(lq);
77
+ // 4. Write back LOCAL transform into the prefab node
78
+ const newRoot = updatePrefabNode(data.root, selectedId, (node) => {
79
+ var _a, _b, _c;
80
+ return (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node === null || node === void 0 ? void 0 : node.components), { transform: {
81
+ type: "Transform",
82
+ properties: {
83
+ position: [lp.x, lp.y, lp.z],
84
+ rotation: [le.x, le.y, le.z],
85
+ scale: [ls.x, ls.y, ls.z],
86
+ },
87
+ }, geometry: (_a = node.components) === null || _a === void 0 ? void 0 : _a.geometry, material: (_b = node.components) === null || _b === void 0 ? void 0 : _b.material, model: (_c = node.components) === null || _c === void 0 ? void 0 : _c.model }) }));
88
+ });
89
+ onPrefabChange(Object.assign(Object.assign({}, data), { root: newRoot }));
90
+ };
91
+ useEffect(() => {
92
+ const loadAssets = () => __awaiter(void 0, void 0, void 0, function* () {
93
+ const modelsToLoad = new Set();
94
+ const texturesToLoad = new Set();
95
+ const traverse = (node) => {
96
+ var _a, _b, _c, _d, _e, _f, _g;
97
+ if (!node)
98
+ return;
99
+ if ((_c = (_b = (_a = node.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename) {
100
+ modelsToLoad.add(node.components.model.properties.filename);
101
+ }
102
+ if ((_f = (_e = (_d = node.components) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.properties) === null || _f === void 0 ? void 0 : _f.texture) {
103
+ texturesToLoad.add(node.components.material.properties.texture);
104
+ }
105
+ (_g = node.children) === null || _g === void 0 ? void 0 : _g.forEach(traverse);
106
+ };
107
+ traverse(data.root);
108
+ for (const filename of modelsToLoad) {
109
+ if (!loadedModels[filename] && !loadingRefs.current.has(filename)) {
110
+ loadingRefs.current.add(filename);
111
+ const result = yield loadModel(filename, "");
112
+ if (result.success && result.model) {
113
+ setLoadedModels(prev => (Object.assign(Object.assign({}, prev), { [filename]: result.model })));
114
+ }
115
+ }
116
+ }
117
+ const textureLoader = new TextureLoader();
118
+ for (const filename of texturesToLoad) {
119
+ if (!loadedTextures[filename] && !loadingRefs.current.has(filename)) {
120
+ loadingRefs.current.add(filename);
121
+ textureLoader.load(filename, (texture) => {
122
+ texture.colorSpace = SRGBColorSpace;
123
+ setLoadedTextures(prev => (Object.assign(Object.assign({}, prev), { [filename]: texture })));
124
+ });
125
+ }
126
+ }
127
+ });
128
+ loadAssets();
129
+ }, [data, loadedModels, loadedTextures]);
130
+ return _jsxs("group", { ref: ref, children: [_jsx(GameInstanceProvider, { models: loadedModels, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: new Matrix4() }) }), editMode && _jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedId && selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange }))] })] });
131
+ });
132
+ function GameObjectRenderer({ gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = new Matrix4(), }) {
133
+ var _a, _b, _c, _d;
134
+ // Early return if gameObject is null or undefined
135
+ if (!gameObject)
136
+ return null;
137
+ // Build a small context object to avoid long param lists
138
+ const ctx = { gameObject, selectedId, onSelect, registerRef, loadedModels, loadedTextures, editMode };
139
+ // --- 1. Transform (local + world) ---
140
+ const transformProps = getNodeTransformProps(gameObject);
141
+ const localMatrix = new Matrix4().compose(new Vector3(...transformProps.position), new Quaternion().setFromEuler(new Euler(...transformProps.rotation)), new Vector3(...transformProps.scale));
142
+ const worldMatrix = parentMatrix.clone().multiply(localMatrix);
143
+ // preserve click/drag detection from previous implementation
144
+ const clickValid = useRef(false);
145
+ const handlePointerDown = (e) => {
146
+ e.stopPropagation();
147
+ clickValid.current = true;
148
+ };
149
+ const handlePointerMove = () => {
150
+ if (clickValid.current)
151
+ clickValid.current = false;
152
+ };
153
+ const handlePointerUp = (e) => {
154
+ if (clickValid.current) {
155
+ e.stopPropagation();
156
+ onSelect === null || onSelect === void 0 ? void 0 : onSelect(gameObject.id);
157
+ }
158
+ clickValid.current = false;
159
+ };
160
+ if (!gameObject.enabled || !gameObject.visible)
161
+ return null;
162
+ // --- 2. If instanced, short-circuit to a tiny clean branch ---
163
+ const isInstanced = !!((_c = (_b = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.instanced);
164
+ if (isInstanced) {
165
+ return renderInstancedNode(gameObject, worldMatrix, ctx);
166
+ }
167
+ // --- 3. Core content decided by component registry ---
168
+ const core = renderCoreNode(gameObject, ctx, parentMatrix);
169
+ // --- 4. Wrap with physics if needed ---
170
+ const physicsWrapped = wrapPhysicsIfNeeded(gameObject, core, ctx);
171
+ // --- 5. Render children (always relative transforms) ---
172
+ const children = ((_d = gameObject.children) !== null && _d !== void 0 ? _d : []).map((child) => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: worldMatrix }, child.id)));
173
+ // --- 6. Final group wrapper ---
174
+ return (_jsxs("group", { ref: (el) => registerRef(gameObject.id, el), position: transformProps.position, rotation: transformProps.rotation, scale: transformProps.scale, onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, children: [physicsWrapped, children] }));
175
+ }
176
+ // Helper: render an instanced GameInstance (terminal node)
177
+ function renderInstancedNode(gameObject, worldMatrix, ctx) {
178
+ var _a, _b, _c, _d;
179
+ const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
180
+ const wp = new Vector3();
181
+ const wq = new Quaternion();
182
+ const ws = new Vector3();
183
+ worldMatrix.decompose(wp, wq, ws);
184
+ const we = new Euler().setFromQuaternion(wq);
185
+ const modelUrl = (_d = (_c = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.model) === null || _c === void 0 ? void 0 : _c.properties) === null || _d === void 0 ? void 0 : _d.filename;
186
+ return (_jsx(GameInstance, { id: gameObject.id, modelUrl: modelUrl, position: [wp.x, wp.y, wp.z], rotation: [we.x, we.y, we.z], scale: [ws.x, ws.y, ws.z], physics: ctx.editMode ? undefined : physics === null || physics === void 0 ? void 0 : physics.properties }));
187
+ }
188
+ // Helper: render main model/geometry content for a non-instanced node
189
+ function renderCoreNode(gameObject, ctx, parentMatrix) {
190
+ var _a, _b, _c;
191
+ const geometry = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.geometry;
192
+ const material = (_b = gameObject.components) === null || _b === void 0 ? void 0 : _b.material;
193
+ const modelComp = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model;
194
+ const geometryDef = geometry ? getComponent('Geometry') : undefined;
195
+ const materialDef = material ? getComponent('Material') : undefined;
196
+ const isModelAvailable = !!(modelComp && modelComp.properties && modelComp.properties.filename && ctx.loadedModels[modelComp.properties.filename]);
197
+ // Generic component views (exclude geometry/material/model)
198
+ const contextProps = {
199
+ loadedModels: ctx.loadedModels,
200
+ loadedTextures: ctx.loadedTextures,
201
+ isSelected: ctx.selectedId === gameObject.id,
202
+ editMode: ctx.editMode,
203
+ parentMatrix,
204
+ registerRef: ctx.registerRef,
205
+ };
206
+ const allComponentViews = gameObject.components
207
+ ? Object.entries(gameObject.components)
208
+ .filter(([key]) => key !== 'geometry' && key !== 'material' && key !== 'model')
209
+ .map(([key, comp]) => {
210
+ const def = getComponent(key);
211
+ if (!def || !def.View || !comp)
212
+ return null;
213
+ return _jsx(def.View, Object.assign({ properties: comp.properties }, contextProps), key);
214
+ })
215
+ : null;
216
+ // If we have a model (non-instanced) render it as a primitive with material override
217
+ if (isModelAvailable) {
218
+ const modelObj = ctx.loadedModels[modelComp.properties.filename].clone();
219
+ return (_jsxs("primitive", { object: modelObj, children: [material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), allComponentViews] }));
220
+ }
221
+ // Otherwise, if geometry present, render a mesh
222
+ if (geometry && geometryDef && geometryDef.View) {
223
+ return (_jsxs("mesh", { children: [_jsx(geometryDef.View, Object.assign({ properties: geometry.properties }, contextProps), "geometry"), material && materialDef && materialDef.View && (_jsx(materialDef.View, { properties: material.properties, loadedTextures: ctx.loadedTextures, isSelected: ctx.selectedId === gameObject.id, editMode: ctx.editMode, parentMatrix: parentMatrix, registerRef: ctx.registerRef }, "material")), allComponentViews] }));
224
+ }
225
+ // Default: render other component views (no geometry/model)
226
+ return _jsx(_Fragment, { children: allComponentViews });
227
+ }
228
+ // Helper: wrap core content with physics component when necessary
229
+ function wrapPhysicsIfNeeded(gameObject, content, ctx) {
230
+ var _a;
231
+ const physics = (_a = gameObject.components) === null || _a === void 0 ? void 0 : _a.physics;
232
+ if (!physics)
233
+ return content;
234
+ const physicsDef = getComponent('Physics');
235
+ if (!physicsDef || !physicsDef.View)
236
+ return content;
237
+ return (_jsx(physicsDef.View, { properties: Object.assign(Object.assign({}, physics.properties), { id: gameObject.id }), registerRef: ctx.registerRef, editMode: ctx.editMode, children: content }));
238
+ }
239
+ export default PrefabRoot;
240
+ function getNodeTransformProps(node) {
241
+ var _a, _b, _c, _d, _e;
242
+ const t = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.transform) === null || _b === void 0 ? void 0 : _b.properties;
243
+ return {
244
+ position: (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0],
245
+ rotation: (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0],
246
+ scale: (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1],
247
+ };
248
+ }
249
+ function computeParentWorldMatrix(root, targetId) {
250
+ var _a;
251
+ const identity = new Matrix4();
252
+ function traverse(node, parentWorld) {
253
+ if (node.id === targetId) {
254
+ // parentWorld is what we want
255
+ return parentWorld.clone();
256
+ }
257
+ const { position, rotation, scale } = getNodeTransformProps(node);
258
+ const localPos = new Vector3(...position);
259
+ const localRot = new Euler(...rotation);
260
+ const localScale = new Vector3(...scale);
261
+ const localMat = new Matrix4().compose(localPos, new Quaternion().setFromEuler(localRot), localScale);
262
+ const worldMat = parentWorld.clone().multiply(localMat);
263
+ if (node.children) {
264
+ for (const child of node.children) {
265
+ const res = traverse(child, worldMat);
266
+ if (res)
267
+ return res;
268
+ }
269
+ }
270
+ return null;
271
+ }
272
+ return (_a = traverse(root, identity)) !== null && _a !== void 0 ? _a : identity;
273
+ }
@@ -0,0 +1,13 @@
1
+ import { FC } from "react";
2
+ export interface Component {
3
+ name: string;
4
+ Editor: FC<{
5
+ component: any;
6
+ onUpdate: (newComp: any) => void;
7
+ }>;
8
+ defaultProperties: any;
9
+ View?: FC<any>;
10
+ }
11
+ export declare function registerComponent(component: Component): void;
12
+ export declare function getComponent(name: string): Component | undefined;
13
+ export declare function getAllComponents(): Record<string, Component>;
@@ -0,0 +1,13 @@
1
+ const REGISTRY = {};
2
+ export function registerComponent(component) {
3
+ if (REGISTRY[component.name]) {
4
+ throw new Error(`Component with name ${component.name} already registered.`);
5
+ }
6
+ REGISTRY[component.name] = component;
7
+ }
8
+ export function getComponent(name) {
9
+ return REGISTRY[name];
10
+ }
11
+ export function getAllComponents() {
12
+ return Object.assign({}, REGISTRY);
13
+ }
@@ -0,0 +1,3 @@
1
+ import { Component } from "./ComponentRegistry";
2
+ declare const GeometryComponent: Component;
3
+ export default GeometryComponent;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function GeometryComponentEditor({ component, onUpdate }) {
3
+ return _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Type" }), _jsxs("select", { className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.geometryType, onChange: e => onUpdate({ geometryType: e.target.value }), children: [_jsx("option", { value: "box", children: "Box" }), _jsx("option", { value: "sphere", children: "Sphere" }), _jsx("option", { value: "plane", children: "Plane" })] })] });
4
+ }
5
+ // View for Geometry component
6
+ function GeometryComponentView({ properties, children }) {
7
+ const { geometryType, args = [] } = properties;
8
+ // Only return the geometry node, do not wrap in mesh or group
9
+ switch (geometryType) {
10
+ case "box":
11
+ return _jsx("boxGeometry", { args: args });
12
+ case "sphere":
13
+ return _jsx("sphereGeometry", { args: args });
14
+ case "plane":
15
+ return _jsx("planeGeometry", { args: args });
16
+ default:
17
+ return _jsx("boxGeometry", { args: [1, 1, 1] });
18
+ }
19
+ }
20
+ const GeometryComponent = {
21
+ name: 'Geometry',
22
+ Editor: GeometryComponentEditor,
23
+ View: GeometryComponentView,
24
+ defaultProperties: {
25
+ geometryType: 'box'
26
+ }
27
+ };
28
+ export default GeometryComponent;
@@ -0,0 +1,3 @@
1
+ import { Component } from './ComponentRegistry';
2
+ declare const MaterialComponent: Component;
3
+ export default MaterialComponent;
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextureListViewer } from '../../assetviewer/page';
3
+ import { useEffect, useState } from 'react';
4
+ function MaterialComponentEditor({ component, onUpdate }) {
5
+ var _a, _b, _c, _d;
6
+ const [textureFiles, setTextureFiles] = useState([]);
7
+ useEffect(() => {
8
+ fetch('/textures/manifest.json')
9
+ .then(r => r.json())
10
+ .then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
11
+ .catch(console.error);
12
+ }, []);
13
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.wireframe || false, onChange: e => onUpdate({ 'wireframe': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Wireframe" })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Texture" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(TextureListViewer, { files: textureFiles, selected: component.properties.texture || undefined, onSelect: (file) => onUpdate({ 'texture': file }) }) })] }), component.properties.texture && (_jsxs("div", { className: "border-t border-cyan-500/20 pt-1 mt-1", children: [_jsxs("div", { className: "flex items-center gap-1 mb-1", children: [_jsx("input", { type: "checkbox", className: "w-3 h-3", checked: component.properties.repeat || false, onChange: e => onUpdate({ 'repeat': e.target.checked }) }), _jsx("label", { className: "text-[9px] text-cyan-400/60", children: "Repeat Texture" })] }), component.properties.repeat && (_jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Repeat (X, Y)" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "number", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, onChange: e => {
14
+ var _a, _b;
15
+ const y = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : 1;
16
+ onUpdate({ 'repeatCount': [parseFloat(e.target.value), y] });
17
+ } }), _jsx("input", { type: "number", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: (_d = (_c = component.properties.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1, onChange: e => {
18
+ var _a, _b;
19
+ const x = (_b = (_a = component.properties.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1;
20
+ onUpdate({ 'repeatCount': [x, parseFloat(e.target.value)] });
21
+ } })] })] }))] }))] }));
22
+ }
23
+ ;
24
+ import { useMemo } from 'react';
25
+ import { DoubleSide, RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace } from 'three';
26
+ // View for Material component
27
+ function MaterialComponentView({ properties, loadedTextures, isSelected }) {
28
+ var _a;
29
+ const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
30
+ const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
31
+ const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
32
+ const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
33
+ const finalTexture = useMemo(() => {
34
+ if (!texture)
35
+ return undefined;
36
+ const t = texture.clone();
37
+ if (repeat) {
38
+ t.wrapS = t.wrapT = RepeatWrapping;
39
+ if (repeatCount)
40
+ t.repeat.set(repeatCount[0], repeatCount[1]);
41
+ }
42
+ else {
43
+ t.wrapS = t.wrapT = ClampToEdgeWrapping;
44
+ t.repeat.set(1, 1);
45
+ }
46
+ t.colorSpace = SRGBColorSpace;
47
+ t.needsUpdate = true;
48
+ return t;
49
+ }, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]]);
50
+ if (!properties) {
51
+ return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
52
+ }
53
+ const { color, wireframe = false } = properties;
54
+ const displayColor = isSelected ? "cyan" : color;
55
+ return _jsx("meshStandardMaterial", { color: displayColor, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture, side: DoubleSide }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture');
56
+ }
57
+ const MaterialComponent = {
58
+ name: 'Material',
59
+ Editor: MaterialComponentEditor,
60
+ View: MaterialComponentView,
61
+ defaultProperties: {
62
+ color: '#ffffff',
63
+ wireframe: false
64
+ }
65
+ };
66
+ export default MaterialComponent;
@@ -0,0 +1,3 @@
1
+ import { Component } from './ComponentRegistry';
2
+ declare const ModelComponent: Component;
3
+ export default ModelComponent;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ModelListViewer } from '../../assetviewer/page';
3
+ import { useEffect, useState } from 'react';
4
+ function ModelComponentEditor({ component, onUpdate }) {
5
+ const [modelFiles, setModelFiles] = useState([]);
6
+ useEffect(() => {
7
+ fetch('/models/manifest.json')
8
+ .then(r => r.json())
9
+ .then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
10
+ .catch(console.error);
11
+ }, []);
12
+ const handleModelSelect = (file) => {
13
+ // Remove leading slash for prefab compatibility
14
+ const filename = file.startsWith('/') ? file.slice(1) : file;
15
+ onUpdate({ 'filename': filename });
16
+ };
17
+ return _jsxs("div", { children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Model" }), _jsx("div", { className: "max-h-32 overflow-y-auto", children: _jsx(ModelListViewer, { files: modelFiles, selected: component.properties.filename ? `/${component.properties.filename}` : undefined, onSelect: handleModelSelect }) })] }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("input", { type: "checkbox", id: "instanced-checkbox", checked: component.properties.instanced || false, onChange: e => onUpdate({ 'instanced': e.target.checked }), className: "w-3 h-3" }), _jsx("label", { htmlFor: "instanced-checkbox", className: "text-[9px] text-cyan-400/60", children: "Instanced" })] })] });
18
+ }
19
+ // View for Model component
20
+ function ModelComponentView({ properties, loadedModels, children }) {
21
+ // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
22
+ if (!properties.filename || properties.instanced)
23
+ return children || null;
24
+ if (loadedModels && loadedModels[properties.filename]) {
25
+ return _jsxs(_Fragment, { children: [_jsx("primitive", { object: loadedModels[properties.filename].clone() }), children] });
26
+ }
27
+ // Optionally, render a placeholder if model is not loaded
28
+ return children || null;
29
+ }
30
+ const ModelComponent = {
31
+ name: 'Model',
32
+ Editor: ModelComponentEditor,
33
+ View: ModelComponentView,
34
+ defaultProperties: {
35
+ filename: '',
36
+ instanced: false
37
+ }
38
+ };
39
+ export default ModelComponent;
@@ -0,0 +1,3 @@
1
+ import { Component } from "./ComponentRegistry";
2
+ declare const PhysicsComponent: Component;
3
+ export default PhysicsComponent;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function PhysicsComponentEditor({ component, onUpdate }) {
3
+ return _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Type" }), _jsxs("select", { className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.type, onChange: e => onUpdate({ type: e.target.value }), children: [_jsx("option", { value: "dynamic", children: "Dynamic" }), _jsx("option", { value: "fixed", children: "Fixed" })] })] });
4
+ }
5
+ import { RigidBody } from "@react-three/rapier";
6
+ function PhysicsComponentView({ properties, children, registerRef, transform, editMode }) {
7
+ if (editMode)
8
+ return children;
9
+ return (_jsx(RigidBody, { ref: el => registerRef && registerRef(properties.id, el), position: transform === null || transform === void 0 ? void 0 : transform.position, rotation: transform === null || transform === void 0 ? void 0 : transform.rotation, scale: transform === null || transform === void 0 ? void 0 : transform.scale, type: properties.type, colliders: "cuboid", children: children }));
10
+ }
11
+ const PhysicsComponent = {
12
+ name: 'Physics',
13
+ Editor: PhysicsComponentEditor,
14
+ View: PhysicsComponentView,
15
+ defaultProperties: {
16
+ type: 'dynamic'
17
+ }
18
+ };
19
+ export default PhysicsComponent;
@@ -0,0 +1,3 @@
1
+ import { Component } from "./ComponentRegistry";
2
+ declare const SpotLightComponent: Component;
3
+ export default SpotLightComponent;
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function SpotLightComponentEditor({ component, onUpdate }) {
3
+ return _jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Color" }), _jsxs("div", { className: "flex gap-0.5", children: [_jsx("input", { type: "color", className: "h-5 w-5 bg-transparent border-none cursor-pointer", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) }), _jsx("input", { type: "text", className: "flex-1 bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.color, onChange: e => onUpdate({ 'color': e.target.value }) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: "Intensity" }), _jsx("input", { type: "number", step: "0.1", className: "w-full bg-black/40 border border-cyan-500/30 px-1 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", value: component.properties.intensity, onChange: e => onUpdate({ 'intensity': parseFloat(e.target.value) }) })] })] });
4
+ }
5
+ // The view component for SpotLight
6
+ function SpotLightView({ properties }) {
7
+ // You can expand this with more spotlight properties as needed
8
+ return _jsx("spotLight", { color: properties.color, intensity: properties.intensity });
9
+ }
10
+ const SpotLightComponent = {
11
+ name: 'SpotLight',
12
+ Editor: SpotLightComponentEditor,
13
+ View: SpotLightView,
14
+ defaultProperties: {
15
+ color: '#ffffff',
16
+ intensity: 1.0
17
+ }
18
+ };
19
+ export default SpotLightComponent;
@@ -0,0 +1,8 @@
1
+ import { Component } from "./ComponentRegistry";
2
+ declare const TransformComponent: Component;
3
+ export default TransformComponent;
4
+ export declare function Vector3Input({ label, value, onChange }: {
5
+ label: string;
6
+ value: [number, number, number];
7
+ onChange: (v: [number, number, number]) => void;
8
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ function TransformComponentEditor({ component, onUpdate }) {
3
+ return _jsxs("div", { className: "flex flex-col", children: [_jsx(Vector3Input, { label: "Position", value: component.properties.position, onChange: v => onUpdate({ position: v }) }), _jsx(Vector3Input, { label: "Rotation", value: component.properties.rotation, onChange: v => onUpdate({ rotation: v }) }), _jsx(Vector3Input, { label: "Scale", value: component.properties.scale, onChange: v => onUpdate({ scale: v }) })] });
4
+ }
5
+ const TransformComponent = {
6
+ name: 'Transform',
7
+ Editor: TransformComponentEditor,
8
+ defaultProperties: {
9
+ position: [0, 0, 0],
10
+ rotation: [0, 0, 0],
11
+ scale: [1, 1, 1]
12
+ }
13
+ };
14
+ export default TransformComponent;
15
+ export function Vector3Input({ label, value, onChange }) {
16
+ const handleChange = (index, val) => {
17
+ const newValue = [...value];
18
+ newValue[index] = parseFloat(val) || 0;
19
+ onChange(newValue);
20
+ };
21
+ return _jsxs("div", { className: "mb-1", children: [_jsx("label", { className: "block text-[9px] text-cyan-400/60 uppercase tracking-wider mb-0.5", children: label }), _jsxs("div", { className: "flex gap-0.5", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-red-400/80 font-mono", children: "X" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[0], onChange: e => handleChange(0, e.target.value) })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-green-400/80 font-mono", children: "Y" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[1], onChange: e => handleChange(1, e.target.value) })] }), _jsxs("div", { className: "relative flex-1", children: [_jsx("span", { className: "absolute left-0.5 top-0 text-[8px] text-blue-400/80 font-mono", children: "Z" }), _jsx("input", { className: "w-full bg-black/40 border border-cyan-500/30 pl-3 pr-0.5 py-0.5 text-[10px] text-cyan-300 font-mono focus:outline-none focus:border-cyan-400/50", type: "number", step: "0.1", value: value[2], onChange: e => handleChange(2, e.target.value) })] })] })] });
22
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("./ComponentRegistry").Component[];
2
+ export default _default;
@@ -0,0 +1,14 @@
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 ModelComponent from './ModelComponent';
7
+ export default [
8
+ GeometryComponent,
9
+ TransformComponent,
10
+ MaterialComponent,
11
+ PhysicsComponent,
12
+ SpotLightComponent,
13
+ ModelComponent
14
+ ];
@@ -0,0 +1 @@
1
+ export default function PrefabEditorPage(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import PrefabEditor from "./PrefabEditor";
3
+ export default function PrefabEditorPage() {
4
+ return _jsx("div", { className: "w-screen h-screen", children: _jsx(PrefabEditor, { children: _jsx("directionalLight", { position: [5, 10, 7.5], intensity: 1, castShadow: true }) }) });
5
+ }
@@ -0,0 +1,29 @@
1
+ export interface Prefab {
2
+ id: string;
3
+ name: string;
4
+ description?: string;
5
+ author?: string;
6
+ version?: string;
7
+ assets?: string[] | {
8
+ [assetName: string]: string;
9
+ };
10
+ onStart?: (target: any) => void;
11
+ root: GameObject;
12
+ }
13
+ export interface GameObject {
14
+ id: string;
15
+ enabled: boolean;
16
+ visible: boolean;
17
+ ref?: any;
18
+ children?: GameObject[];
19
+ components?: {
20
+ [uuid: string]: Component | undefined;
21
+ };
22
+ }
23
+ interface Component {
24
+ type: string;
25
+ properties: {
26
+ [key: string]: any;
27
+ };
28
+ }
29
+ export {};
@@ -0,0 +1 @@
1
+ export {};