react-three-game 0.0.49 → 0.0.51

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.
package/README.md CHANGED
@@ -163,9 +163,13 @@ Keys: **T**ranslate / **R**otate / **S**cale. Drag tree nodes to reparent. Impor
163
163
  ## Tree Utilities
164
164
 
165
165
  ```typescript
166
- import { findNode, updateNode, deleteNode, cloneNode } from 'react-three-game';
166
+ import { findNode, updateNode, updateNodeById, deleteNode, cloneNode, exportGLBData } from 'react-three-game';
167
167
 
168
- const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true }));
168
+ const node = findNode(root, nodeId);
169
+ const updated = updateNode(root, nodeId, n => ({ ...n, disabled: true })); // or updateNodeById
170
+ const afterDelete = deleteNode(root, nodeId);
171
+ const cloned = cloneNode(node);
172
+ const glbData = await exportGLBData(sceneRoot); // export scene to GLB ArrayBuffer
169
173
  ```
170
174
 
171
175
  ## Development
package/dist/index.d.ts CHANGED
@@ -12,5 +12,9 @@ export type { PrefabRootRef } from './tools/prefabeditor/PrefabRoot';
12
12
  export type { Component } from './tools/prefabeditor/components/ComponentRegistry';
13
13
  export type { FieldDefinition, FieldType } from './tools/prefabeditor/components/Input';
14
14
  export type { Prefab, GameObject, ComponentData } from './tools/prefabeditor/types';
15
+ export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
16
+ export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, PhysicsEventPayload } from './tools/prefabeditor/GameEvents';
17
+ export { entityEvents, useEntityEvent } from './tools/prefabeditor/GameEvents';
18
+ export type { EntityEventType, EntityEventPayload } from './tools/prefabeditor/GameEvents';
15
19
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
16
20
  export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
package/dist/index.js CHANGED
@@ -12,6 +12,10 @@ export { registerComponent } from './tools/prefabeditor/components/ComponentRegi
12
12
  export { FieldRenderer, Input, Label, Vector3Input, ColorInput, StringInput, BooleanInput, SelectInput, } from './tools/prefabeditor/components/Input';
13
13
  // Prefab Editor - Styles & Utils
14
14
  export * from './tools/prefabeditor/utils';
15
+ // Game Events (physics + custom events)
16
+ export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/prefabeditor/GameEvents';
17
+ // Backward compatibility aliases
18
+ export { entityEvents, useEntityEvent } from './tools/prefabeditor/GameEvents';
15
19
  // Asset Tools
16
20
  export { DragDropLoader } from './tools/dragdrop/DragDropLoader';
17
21
  export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
@@ -0,0 +1,54 @@
1
+ import type { RapierRigidBody } from '@react-three/rapier';
2
+ export type EntityEventType = 'sensor:enter' | 'sensor:exit' | 'collision:enter' | 'collision:exit';
3
+ export interface EntityEventPayload {
4
+ sourceEntityId: string;
5
+ targetEntityId: string | null;
6
+ targetRigidBody: RapierRigidBody | null | undefined;
7
+ }
8
+ type EventHandler = (payload: EntityEventPayload) => void;
9
+ /**
10
+ * Entity event system for physics interactions.
11
+ *
12
+ * Events:
13
+ * - sensor:enter - Fired when something enters a sensor collider
14
+ * - sensor:exit - Fired when something exits a sensor collider
15
+ * - collision:enter - Fired when a collision starts
16
+ * - collision:exit - Fired when a collision ends
17
+ */
18
+ export declare const entityEvents: {
19
+ /**
20
+ * Emit an event to all subscribers
21
+ */
22
+ emit(type: EntityEventType, payload: EntityEventPayload): void;
23
+ /**
24
+ * Subscribe to an event type
25
+ * @returns Unsubscribe function
26
+ */
27
+ on(type: EntityEventType, handler: EventHandler): () => void;
28
+ /**
29
+ * Unsubscribe from an event type
30
+ */
31
+ off(type: EntityEventType, handler: EventHandler): void;
32
+ /**
33
+ * Remove all subscribers (useful for cleanup)
34
+ */
35
+ clear(): void;
36
+ };
37
+ /**
38
+ * React hook to subscribe to entity events.
39
+ * Automatically cleans up on unmount.
40
+ *
41
+ * @example
42
+ * useEntityEvent('sensor:enter', (payload) => {
43
+ * if (payload.sourceEntityId === 'trigger-zone') {
44
+ * console.log('Player entered trigger zone!');
45
+ * }
46
+ * });
47
+ */
48
+ export declare function useEntityEvent(type: EntityEventType, handler: EventHandler, deps?: any[]): void;
49
+ /**
50
+ * Helper to extract entity ID from Rapier collision data.
51
+ * Entity IDs are stored in RigidBody userData.
52
+ */
53
+ export declare function getEntityIdFromRigidBody(rigidBody: RapierRigidBody | null | undefined): string | null;
54
+ export {};
@@ -0,0 +1,85 @@
1
+ import { useEffect, useCallback } from 'react';
2
+ // Internal subscriber storage
3
+ const subscribers = new Map();
4
+ /**
5
+ * Entity event system for physics interactions.
6
+ *
7
+ * Events:
8
+ * - sensor:enter - Fired when something enters a sensor collider
9
+ * - sensor:exit - Fired when something exits a sensor collider
10
+ * - collision:enter - Fired when a collision starts
11
+ * - collision:exit - Fired when a collision ends
12
+ */
13
+ export const entityEvents = {
14
+ /**
15
+ * Emit an event to all subscribers
16
+ */
17
+ emit(type, payload) {
18
+ const handlers = subscribers.get(type);
19
+ if (handlers) {
20
+ handlers.forEach(handler => {
21
+ try {
22
+ handler(payload);
23
+ }
24
+ catch (e) {
25
+ console.error(`Error in entityEvents handler for ${type}:`, e);
26
+ }
27
+ });
28
+ }
29
+ },
30
+ /**
31
+ * Subscribe to an event type
32
+ * @returns Unsubscribe function
33
+ */
34
+ on(type, handler) {
35
+ if (!subscribers.has(type)) {
36
+ subscribers.set(type, new Set());
37
+ }
38
+ subscribers.get(type).add(handler);
39
+ return () => {
40
+ var _a;
41
+ (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
42
+ };
43
+ },
44
+ /**
45
+ * Unsubscribe from an event type
46
+ */
47
+ off(type, handler) {
48
+ var _a;
49
+ (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
50
+ },
51
+ /**
52
+ * Remove all subscribers (useful for cleanup)
53
+ */
54
+ clear() {
55
+ subscribers.clear();
56
+ }
57
+ };
58
+ /**
59
+ * React hook to subscribe to entity events.
60
+ * Automatically cleans up on unmount.
61
+ *
62
+ * @example
63
+ * useEntityEvent('sensor:enter', (payload) => {
64
+ * if (payload.sourceEntityId === 'trigger-zone') {
65
+ * console.log('Player entered trigger zone!');
66
+ * }
67
+ * });
68
+ */
69
+ export function useEntityEvent(type, handler, deps = []) {
70
+ const stableHandler = useCallback(handler, deps);
71
+ useEffect(() => {
72
+ return entityEvents.on(type, stableHandler);
73
+ }, [type, stableHandler]);
74
+ }
75
+ /**
76
+ * Helper to extract entity ID from Rapier collision data.
77
+ * Entity IDs are stored in RigidBody userData.
78
+ */
79
+ export function getEntityIdFromRigidBody(rigidBody) {
80
+ var _a;
81
+ if (!rigidBody)
82
+ return null;
83
+ const userData = rigidBody.userData;
84
+ return (_a = userData === null || userData === void 0 ? void 0 : userData.entityId) !== null && _a !== void 0 ? _a : null;
85
+ }
@@ -0,0 +1,126 @@
1
+ import type { RapierRigidBody } from '@react-three/rapier';
2
+ /** Physics event types (built-in) */
3
+ export type PhysicsEventType = 'sensor:enter' | 'sensor:exit' | 'collision:enter' | 'collision:exit';
4
+ /** Payload for physics events */
5
+ export interface PhysicsEventPayload {
6
+ sourceEntityId: string;
7
+ targetEntityId: string | null;
8
+ targetRigidBody: RapierRigidBody | null | undefined;
9
+ }
10
+ /**
11
+ * Register your custom event types here by extending this interface:
12
+ *
13
+ * declare module 'react-three-game' {
14
+ * interface GameEventMap {
15
+ * 'player:death': { playerId: string; cause: string };
16
+ * 'score:change': { delta: number; total: number };
17
+ * }
18
+ * }
19
+ */
20
+ export interface GameEventMap {
21
+ 'sensor:enter': PhysicsEventPayload;
22
+ 'sensor:exit': PhysicsEventPayload;
23
+ 'collision:enter': PhysicsEventPayload;
24
+ 'collision:exit': PhysicsEventPayload;
25
+ }
26
+ /** All registered event types */
27
+ export type GameEventType = keyof GameEventMap | (string & {});
28
+ /** Get payload type for an event, or fallback to generic */
29
+ export type GameEventPayload<T extends string> = T extends keyof GameEventMap ? GameEventMap[T] : Record<string, unknown>;
30
+ type EventHandler<T = unknown> = (payload: T) => void;
31
+ /**
32
+ * Game event system for all game interactions.
33
+ *
34
+ * Built-in physics events:
35
+ * - sensor:enter - Something entered a sensor collider
36
+ * - sensor:exit - Something exited a sensor collider
37
+ * - collision:enter - A collision started
38
+ * - collision:exit - A collision ended
39
+ *
40
+ * Custom events:
41
+ * - Emit any event type with any payload
42
+ * - Extend GameEventMap interface for type safety
43
+ *
44
+ * @example
45
+ * // Physics events (typed)
46
+ * gameEvents.emit('sensor:enter', { sourceEntityId: 'zone', targetEntityId: 'player', targetRigidBody: rb });
47
+ *
48
+ * // Custom events
49
+ * gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
50
+ * gameEvents.emit('level:complete', { levelId: 3, time: 45.2 });
51
+ */
52
+ export declare const gameEvents: {
53
+ /**
54
+ * Emit an event to all subscribers
55
+ */
56
+ emit<T extends string>(type: T, payload: GameEventPayload<T>): void;
57
+ /**
58
+ * Subscribe to an event type
59
+ * @returns Unsubscribe function
60
+ */
61
+ on<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): () => void;
62
+ /**
63
+ * Unsubscribe from an event type
64
+ */
65
+ off<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): void;
66
+ /**
67
+ * Remove all subscribers (useful for cleanup/reset)
68
+ */
69
+ clear(): void;
70
+ /**
71
+ * Check if an event type has any subscribers
72
+ */
73
+ hasListeners(type: string): boolean;
74
+ };
75
+ /**
76
+ * React hook to subscribe to game events.
77
+ * Automatically cleans up on unmount.
78
+ *
79
+ * @example
80
+ * // Physics event
81
+ * useGameEvent('sensor:enter', (payload) => {
82
+ * if (payload.sourceEntityId === 'coin') collectCoin();
83
+ * }, []);
84
+ *
85
+ * // Custom event
86
+ * useGameEvent('player:death', (payload) => {
87
+ * showGameOver(payload.cause);
88
+ * }, []);
89
+ */
90
+ export declare function useGameEvent<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>, deps?: unknown[]): void;
91
+ /**
92
+ * Helper to extract entity ID from Rapier collision data.
93
+ * Entity IDs are stored in RigidBody userData.
94
+ */
95
+ export declare function getEntityIdFromRigidBody(rigidBody: RapierRigidBody | null | undefined): string | null;
96
+ /** @deprecated Use gameEvents instead */
97
+ export declare const entityEvents: {
98
+ /**
99
+ * Emit an event to all subscribers
100
+ */
101
+ emit<T extends string>(type: T, payload: GameEventPayload<T>): void;
102
+ /**
103
+ * Subscribe to an event type
104
+ * @returns Unsubscribe function
105
+ */
106
+ on<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): () => void;
107
+ /**
108
+ * Unsubscribe from an event type
109
+ */
110
+ off<T extends string>(type: T, handler: EventHandler<GameEventPayload<T>>): void;
111
+ /**
112
+ * Remove all subscribers (useful for cleanup/reset)
113
+ */
114
+ clear(): void;
115
+ /**
116
+ * Check if an event type has any subscribers
117
+ */
118
+ hasListeners(type: string): boolean;
119
+ };
120
+ /** @deprecated Use useGameEvent instead */
121
+ export declare const useEntityEvent: typeof useGameEvent;
122
+ /** @deprecated Use GameEventType instead */
123
+ export type EntityEventType = PhysicsEventType;
124
+ /** @deprecated Use PhysicsEventPayload instead */
125
+ export type EntityEventPayload = PhysicsEventPayload;
126
+ export {};
@@ -0,0 +1,119 @@
1
+ import { useEffect, useCallback } from 'react';
2
+ // Internal subscriber storage
3
+ const subscribers = new Map();
4
+ /**
5
+ * Game event system for all game interactions.
6
+ *
7
+ * Built-in physics events:
8
+ * - sensor:enter - Something entered a sensor collider
9
+ * - sensor:exit - Something exited a sensor collider
10
+ * - collision:enter - A collision started
11
+ * - collision:exit - A collision ended
12
+ *
13
+ * Custom events:
14
+ * - Emit any event type with any payload
15
+ * - Extend GameEventMap interface for type safety
16
+ *
17
+ * @example
18
+ * // Physics events (typed)
19
+ * gameEvents.emit('sensor:enter', { sourceEntityId: 'zone', targetEntityId: 'player', targetRigidBody: rb });
20
+ *
21
+ * // Custom events
22
+ * gameEvents.emit('player:death', { playerId: 'p1', cause: 'lava' });
23
+ * gameEvents.emit('level:complete', { levelId: 3, time: 45.2 });
24
+ */
25
+ export const gameEvents = {
26
+ /**
27
+ * Emit an event to all subscribers
28
+ */
29
+ emit(type, payload) {
30
+ const handlers = subscribers.get(type);
31
+ if (handlers) {
32
+ handlers.forEach(handler => {
33
+ try {
34
+ handler(payload);
35
+ }
36
+ catch (e) {
37
+ console.error(`Error in gameEvents handler for ${type}:`, e);
38
+ }
39
+ });
40
+ }
41
+ },
42
+ /**
43
+ * Subscribe to an event type
44
+ * @returns Unsubscribe function
45
+ */
46
+ on(type, handler) {
47
+ if (!subscribers.has(type)) {
48
+ subscribers.set(type, new Set());
49
+ }
50
+ subscribers.get(type).add(handler);
51
+ return () => {
52
+ var _a;
53
+ (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
54
+ };
55
+ },
56
+ /**
57
+ * Unsubscribe from an event type
58
+ */
59
+ off(type, handler) {
60
+ var _a;
61
+ (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.delete(handler);
62
+ },
63
+ /**
64
+ * Remove all subscribers (useful for cleanup/reset)
65
+ */
66
+ clear() {
67
+ subscribers.clear();
68
+ },
69
+ /**
70
+ * Check if an event type has any subscribers
71
+ */
72
+ hasListeners(type) {
73
+ var _a, _b;
74
+ return ((_b = (_a = subscribers.get(type)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
75
+ }
76
+ };
77
+ /**
78
+ * React hook to subscribe to game events.
79
+ * Automatically cleans up on unmount.
80
+ *
81
+ * @example
82
+ * // Physics event
83
+ * useGameEvent('sensor:enter', (payload) => {
84
+ * if (payload.sourceEntityId === 'coin') collectCoin();
85
+ * }, []);
86
+ *
87
+ * // Custom event
88
+ * useGameEvent('player:death', (payload) => {
89
+ * showGameOver(payload.cause);
90
+ * }, []);
91
+ */
92
+ export function useGameEvent(type, handler, deps = []) {
93
+ // eslint-disable-next-line react-hooks/exhaustive-deps
94
+ const stableHandler = useCallback(handler, deps);
95
+ useEffect(() => {
96
+ return gameEvents.on(type, stableHandler);
97
+ }, [type, stableHandler]);
98
+ }
99
+ // ============================================================================
100
+ // Helpers
101
+ // ============================================================================
102
+ /**
103
+ * Helper to extract entity ID from Rapier collision data.
104
+ * Entity IDs are stored in RigidBody userData.
105
+ */
106
+ export function getEntityIdFromRigidBody(rigidBody) {
107
+ var _a;
108
+ if (!rigidBody)
109
+ return null;
110
+ const userData = rigidBody.userData;
111
+ return (_a = userData === null || userData === void 0 ? void 0 : userData.entityId) !== null && _a !== void 0 ? _a : null;
112
+ }
113
+ // ============================================================================
114
+ // Backward Compatibility Aliases
115
+ // ============================================================================
116
+ /** @deprecated Use gameEvents instead */
117
+ export const entityEvents = gameEvents;
118
+ /** @deprecated Use useGameEvent instead */
119
+ export const useEntityEvent = useGameEvent;
@@ -1,8 +1,10 @@
1
1
  import { Group, Matrix4, Object3D, Texture } from "three";
2
2
  import { ThreeEvent } from "@react-three/fiber";
3
3
  import { Prefab, GameObject as GameObjectType } from "./types";
4
+ import type { RapierRigidBody } from "@react-three/rapier";
4
5
  export interface PrefabRootRef {
5
6
  root: Group | null;
7
+ rigidBodyRefs: Map<string, RapierRigidBody | null>;
6
8
  }
7
9
  export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
8
10
  editMode?: boolean;
@@ -20,6 +22,7 @@ interface RendererProps {
20
22
  onSelect?: (id: string) => void;
21
23
  onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
22
24
  registerRef: (id: string, obj: Object3D | null) => void;
25
+ registerRigidBodyRef: (id: string, rb: RapierRigidBody | null) => void;
23
26
  loadedModels: Record<string, Object3D>;
24
27
  loadedTextures: Record<string, Texture>;
25
28
  editMode?: boolean;
@@ -30,16 +30,21 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
30
30
  const [textures, setTextures] = useState({});
31
31
  const loading = useRef(new Set());
32
32
  const objectRefs = useRef({});
33
+ const rigidBodyRefs = useRef(new Map());
33
34
  const [selectedObject, setSelectedObject] = useState(null);
34
35
  const rootRef = useRef(null);
35
36
  useImperativeHandle(ref, () => ({
36
- root: rootRef.current
37
+ root: rootRef.current,
38
+ rigidBodyRefs: rigidBodyRefs.current
37
39
  }), []);
38
40
  const registerRef = useCallback((id, obj) => {
39
41
  objectRefs.current[id] = obj;
40
42
  if (id === selectedId)
41
43
  setSelectedObject(obj);
42
44
  }, [selectedId]);
45
+ const registerRigidBodyRef = useCallback((id, rb) => {
46
+ rigidBodyRefs.current.set(id, rb);
47
+ }, []);
43
48
  useEffect(() => {
44
49
  const originalError = console.error;
45
50
  console.error = (...args) => {
@@ -103,7 +108,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, onPrefabChange, selected
103
108
  });
104
109
  });
105
110
  }, [data, models, textures]);
106
- return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
111
+ return (_jsxs("group", { ref: rootRef, children: [_jsx(GameInstanceProvider, { models: models, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(GameObjectRenderer, { gameObject: data.root, selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: models, loadedTextures: textures, editMode: editMode, parentMatrix: IDENTITY }) }), editMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { makeDefault: true }), selectedObject && (_jsx(TransformControls, { object: selectedObject, mode: transformMode, space: "local", onObjectChange: onTransformChange, translationSnap: snapResolution > 0 ? snapResolution : undefined, rotationSnap: snapResolution > 0 ? snapResolution : undefined, scaleSnap: snapResolution > 0 ? snapResolution : undefined }, `transform-${snapResolution}`))] }))] }));
107
112
  });
108
113
  export function GameObjectRenderer(props) {
109
114
  var _a, _b, _c;
@@ -160,7 +165,7 @@ function InstancedNode({ gameObject, parentMatrix = IDENTITY, editMode, register
160
165
  }
161
166
  return (_jsx(GameInstance, { id: gameObject.id, modelUrl: (_k = (_j = (_h = gameObject.components) === null || _h === void 0 ? void 0 : _h.model) === null || _j === void 0 ? void 0 : _j.properties) === null || _k === void 0 ? void 0 : _k.filename, position: worldPosition, rotation: worldRotation, scale: worldScale, physics: physicsProps }));
162
167
  }
163
- function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
168
+ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef, registerRigidBodyRef, loadedModels, loadedTextures, editMode, parentMatrix = IDENTITY, }) {
164
169
  var _a, _b, _c, _d, _e, _f;
165
170
  const groupRef = useRef(null);
166
171
  const helperRef = useRef(null);
@@ -193,12 +198,12 @@ function StandardNode({ gameObject, selectedId, onSelect, onClick, registerRef,
193
198
  const physicsDef = hasPhysics ? getComponent("Physics") : null;
194
199
  const isInstanced = (_e = (_d = (_c = gameObject.components) === null || _c === void 0 ? void 0 : _c.model) === null || _d === void 0 ? void 0 : _d.properties) === null || _e === void 0 ? void 0 : _e.instanced;
195
200
  const physicsKey = `physics_${gameObject.id}_${isInstanced ? 'instanced' : 'standard'}`;
196
- const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
201
+ const inner = (_jsxs("group", { onPointerDown: editMode ? onDown : undefined, onPointerMove: editMode ? () => (clickValid.current = false) : undefined, onPointerUp: editMode ? onUp : undefined, children: [renderCoreNode(gameObject, { loadedModels, loadedTextures, editMode, registerRef }, parentMatrix), (_f = gameObject.children) === null || _f === void 0 ? void 0 : _f.map(child => (_jsx(GameObjectRenderer, { gameObject: child, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, registerRigidBodyRef: registerRigidBodyRef, loadedModels: loadedModels, loadedTextures: loadedTextures, editMode: editMode, parentMatrix: world }, child.id)))] }));
197
202
  if (editMode) {
198
- return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey)) : null] }));
203
+ return (_jsxs(_Fragment, { children: [_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) }), _jsx("group", { ref: helperRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner }), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: gameObject.id, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey)) : null] }));
199
204
  }
200
205
  if (hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View)) {
201
- return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, children: inner }, physicsKey));
206
+ return (_jsx(physicsDef.View, { properties: physics.properties, position: transform.position, rotation: transform.rotation, scale: transform.scale, editMode: editMode, nodeId: gameObject.id, registerRigidBodyRef: registerRigidBodyRef, children: inner }, physicsKey));
202
207
  }
203
208
  return (_jsx("group", { ref: groupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, onPointerDown: onDown, onPointerMove: () => (clickValid.current = false), onPointerUp: onUp, children: inner }));
204
209
  }
@@ -1,3 +1,12 @@
1
1
  import { Component } from './ComponentRegistry';
2
+ import { MeshStandardMaterialProperties } from 'three';
3
+ export interface MaterialProps extends Omit<MeshStandardMaterialProperties, 'args'> {
4
+ texture?: string;
5
+ repeat?: boolean;
6
+ repeatCount?: [number, number];
7
+ generateMipmaps?: boolean;
8
+ minFilter?: string;
9
+ magFilter?: string;
10
+ }
2
11
  declare const MaterialComponent: Component;
3
12
  export default MaterialComponent;
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
13
  import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
3
14
  import { useEffect, useState } from 'react';
@@ -25,6 +36,13 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
25
36
  const fields = [
26
37
  { name: 'color', type: 'color', label: 'Color' },
27
38
  { name: 'wireframe', type: 'boolean', label: 'Wireframe' },
39
+ { name: 'transparent', type: 'boolean', label: 'Transparent' },
40
+ { name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
41
+ { name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
42
+ { name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
43
+ { name: 'transmission', type: 'number', label: 'Transmission', min: 0, max: 1, step: 0.01 },
44
+ { name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
45
+ { name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
28
46
  {
29
47
  name: 'texture',
30
48
  type: 'custom',
@@ -80,6 +98,9 @@ function MaterialComponentView({ properties, loadedTextures }) {
80
98
  const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
81
99
  const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
82
100
  const texture = textureName && loadedTextures ? loadedTextures[textureName] : undefined;
101
+ // Destructure all material props and separate custom texture handling props
102
+ const _b = properties || {}, { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map } = _b, // Filter out map since we set it explicitly
103
+ materialProps = __rest(_b, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map"]);
83
104
  const minFilterMap = {
84
105
  NearestFilter,
85
106
  LinearFilter,
@@ -116,8 +137,7 @@ function MaterialComponentView({ properties, loadedTextures }) {
116
137
  if (!properties) {
117
138
  return _jsx("meshStandardMaterial", { color: "red", wireframe: true });
118
139
  }
119
- const { color, wireframe = false } = properties;
120
- return (_jsx("meshStandardMaterial", { color: color, wireframe: wireframe, map: finalTexture, transparent: !!finalTexture }, (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
140
+ return (_jsx("meshStandardMaterial", Object.assign({ map: finalTexture }, materialProps), (_a = finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.uuid) !== null && _a !== void 0 ? _a : 'no-texture'));
121
141
  }
122
142
  const MaterialComponent = {
123
143
  name: 'Material',
@@ -126,7 +146,11 @@ const MaterialComponent = {
126
146
  nonComposable: true,
127
147
  defaultProperties: {
128
148
  color: '#ffffff',
129
- wireframe: false
149
+ wireframe: false,
150
+ transparent: false,
151
+ opacity: 1,
152
+ metalness: 0,
153
+ roughness: 1
130
154
  }
131
155
  };
132
156
  export default MaterialComponent;