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 +6 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/tools/prefabeditor/EntityEvents.d.ts +54 -0
- package/dist/tools/prefabeditor/EntityEvents.js +85 -0
- package/dist/tools/prefabeditor/GameEvents.d.ts +126 -0
- package/dist/tools/prefabeditor/GameEvents.js +119 -0
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -0
- package/dist/tools/prefabeditor/PrefabRoot.js +11 -6
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +9 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +27 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +59 -3
- package/package.json +1 -1
- package/react-three-game-skill/react-three-game/SKILL.md +138 -80
- package/react-three-game-skill/react-three-game/rules/ADVANCED_PHYSICS.md +454 -0
- package/src/index.ts +7 -0
- package/src/tools/prefabeditor/GameEvents.ts +191 -0
- package/src/tools/prefabeditor/PrefabRoot.tsx +17 -1
- package/src/tools/prefabeditor/components/MaterialComponent.tsx +37 -8
- package/src/tools/prefabeditor/components/PhysicsComponent.tsx +69 -3
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
|
|
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
|
-
|
|
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;
|