react-three-game 0.0.85 → 0.0.86

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 (38) hide show
  1. package/README.md +87 -35
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.js +1 -4
  4. package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
  5. package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
  6. package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
  7. package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
  8. package/dist/tools/prefabeditor/PrefabRoot.js +94 -44
  9. package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
  10. package/dist/tools/prefabeditor/assetRuntime.js +37 -0
  11. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +4 -2
  12. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  13. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  14. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  15. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  16. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  17. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  18. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  19. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  20. package/dist/tools/prefabeditor/components/Input.js +0 -55
  21. package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
  22. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
  23. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
  24. package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
  25. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  26. package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
  27. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/index.js +2 -2
  29. package/dist/tools/prefabeditor/types.d.ts +1 -0
  30. package/dist/tools/prefabeditor/types.js +18 -0
  31. package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
  32. package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
  33. package/package.json +1 -1
  34. package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
  35. package/dist/tools/prefabeditor/GameEvents.js +0 -118
  36. package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
  37. package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
  38. package/dist/tools/prefabeditor/runtime.js +0 -184
@@ -13,10 +13,9 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
13
13
  import { useHelper } from "@react-three/drei";
14
14
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
15
15
  import { BoxHelper, Euler, Matrix4, } from "three";
16
- import { useFrame } from "@react-three/fiber";
17
16
  import { useStore } from "zustand";
18
17
  import { useClickValid } from "./useClickValid";
19
- import { findComponent } from "./types";
18
+ import { findComponent, getNodeUserData } from "./types";
20
19
  import { getComponentDef, getComponentAssetRefs, registerComponent } from "./components/ComponentRegistry";
21
20
  import { builtinComponents } from "./components";
22
21
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
@@ -24,7 +23,7 @@ import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, u
24
23
  import { composeTransform, decompose } from "./utils";
25
24
  import { isPhysicsProps } from "./components/PhysicsComponent";
26
25
  import { createPrefabStore, PrefabStoreProvider, useOptionalPrefabStoreApi, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
27
- import { AssetRuntimeContext, EntityRuntimeScope, createRuntimeEngine } from "./runtime";
26
+ import { AssetRuntimeContext, EntityRuntimeScope } from "./assetRuntime";
28
27
  import { sound as soundManager } from "../../helpers/SoundManager";
29
28
  builtinComponents.forEach(registerComponent);
30
29
  const IDENTITY = new Matrix4();
@@ -45,6 +44,36 @@ function isNodeReady(node, loadedModels) {
45
44
  return true;
46
45
  return Boolean(loadedModels[model.properties.filename]);
47
46
  }
47
+ function emitNativeEvent(type, detail) {
48
+ const trimmedType = type === null || type === void 0 ? void 0 : type.trim();
49
+ if (!trimmedType || typeof window === 'undefined')
50
+ return;
51
+ window.dispatchEvent(new CustomEvent(trimmedType, { detail }));
52
+ }
53
+ function getNodeClickEventName(node) {
54
+ var _a;
55
+ const clickComponents = [
56
+ findComponent(node, 'BufferGeometry'),
57
+ findComponent(node, 'Geometry'),
58
+ ];
59
+ for (const component of clickComponents) {
60
+ if (!((_a = component === null || component === void 0 ? void 0 : component.properties) === null || _a === void 0 ? void 0 : _a.emitClickEvent))
61
+ continue;
62
+ const eventName = component.properties.clickEventName;
63
+ if (typeof eventName === 'string' && eventName.trim()) {
64
+ return eventName.trim();
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function getNodeMetadataProps(node) {
70
+ var _a, _b;
71
+ const nodeName = (_b = (_a = node.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
72
+ return {
73
+ name: nodeName,
74
+ userData: Object.assign(Object.assign({ prefabNodeId: node.id }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(node)),
75
+ };
76
+ }
48
77
  export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSelect, onClick, onObjectRefChange, basePath = "" }, ref) => {
49
78
  var _a;
50
79
  const [models, setModels] = useState({});
@@ -94,29 +123,13 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
94
123
  setInjectedSounds(prev => (Object.assign(Object.assign({}, prev), { [path]: sound })));
95
124
  },
96
125
  }), [getObject]);
97
- const runtimeEngine = useMemo(() => createRuntimeEngine({
98
- store: resolvedStore,
99
- getObject,
100
- getRigidBody,
101
- }), [resolvedStore, getObject, getRigidBody]);
102
126
  const registerRef = useCallback((id, obj) => {
103
127
  objectRefs.current[id] = obj;
104
- runtimeEngine.invalidate();
105
128
  onObjectRefChange === null || onObjectRefChange === void 0 ? void 0 : onObjectRefChange(id, obj);
106
- }, [onObjectRefChange, runtimeEngine]);
129
+ }, [onObjectRefChange]);
107
130
  const registerRigidBodyRef = useCallback((id, rb) => {
108
131
  rigidBodyRefs.current.set(id, rb);
109
- runtimeEngine.invalidate();
110
- }, [runtimeEngine]);
111
- useEffect(() => {
112
- runtimeEngine.setActive(!editMode);
113
- }, [editMode, runtimeEngine]);
114
- useEffect(() => {
115
- return () => runtimeEngine.dispose();
116
- }, [runtimeEngine]);
117
- useFrame((_, dt) => {
118
- runtimeEngine.tick(dt);
119
- });
132
+ }, []);
120
133
  useEffect(() => {
121
134
  if (usesOwnedStore && data) {
122
135
  resolvedStore.getState().replacePrefab(data);
@@ -189,15 +202,32 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
189
202
  getAssetRevision: () => `${Object.keys(availableTextures).sort().join('|')}::${Object.keys(availableModels).sort().join('|')}`,
190
203
  }), [getObject, getRigidBody, registerRigidBodyRef, availableModels, availableTextures, availableSounds]);
191
204
  const content = (_jsx("group", { ref: rootRef, children: _jsx(GameInstanceProvider, { models: availableModels, selectedId: selectedId, editMode: editMode, onSelect: editMode ? onSelect : undefined, registerRef: registerRef, children: _jsx(StoreRootNode, { selectedId: selectedId, onSelect: editMode ? onSelect : undefined, onClick: onClick, registerRef: registerRef, loadedModels: availableModels, editMode: editMode, parentMatrix: IDENTITY }) }) }));
205
+ const runtimeContent = _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
192
206
  if (!shouldProvideStoreContext) {
193
- return _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content });
207
+ return runtimeContent;
194
208
  }
195
- return _jsx(PrefabStoreProvider, { store: resolvedStore, children: _jsx(AssetRuntimeContext.Provider, { value: assetRuntime, children: content }) });
209
+ return _jsx(PrefabStoreProvider, { store: resolvedStore, children: runtimeContent });
196
210
  });
197
211
  function StoreRootNode(props) {
198
212
  const rootId = usePrefabRootId();
199
213
  return _jsx(GameObjectRenderer, Object.assign({}, props, { nodeId: rootId }));
200
214
  }
215
+ function emitNodePointerEvent(eventName, event, nodeId, node, fallbackObject) {
216
+ var _a;
217
+ if (!eventName)
218
+ return;
219
+ emitNativeEvent(eventName, {
220
+ nodeId,
221
+ node,
222
+ object: (_a = event.object) !== null && _a !== void 0 ? _a : fallbackObject,
223
+ button: event.button,
224
+ altKey: event.nativeEvent.altKey,
225
+ ctrlKey: event.nativeEvent.ctrlKey,
226
+ metaKey: event.nativeEvent.metaKey,
227
+ shiftKey: event.nativeEvent.shiftKey,
228
+ r3fEvent: event,
229
+ });
230
+ }
201
231
  export function GameObjectRenderer(props) {
202
232
  var _a, _b;
203
233
  const node = usePrefabNode(props.nodeId);
@@ -221,20 +251,19 @@ export function GameObjectRenderer(props) {
221
251
  : _jsx(StandardNode, Object.assign({}, props), key);
222
252
  }
223
253
  function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onClick }) {
224
- var _a, _b, _c;
254
+ var _a, _b;
225
255
  const gameObject = usePrefabNode(nodeId);
226
256
  if (!gameObject)
227
257
  return null;
228
258
  const localTransform = getNodeTransformProps(gameObject);
229
259
  const isLocked = Boolean(gameObject.locked);
230
- const clickComponent = findComponent(gameObject, "Click");
231
- const clickable = Boolean(clickComponent);
232
- const clickEventName = (_a = clickComponent === null || clickComponent === void 0 ? void 0 : clickComponent.properties) === null || _a === void 0 ? void 0 : _a.eventName;
260
+ const metadataProps = getNodeMetadataProps(gameObject);
261
+ const groupProps = Object.assign(Object.assign({}, metadataProps), { position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
233
262
  const physicsData = findComponent(gameObject, "Physics");
234
263
  const physicsProps = isPhysicsProps(physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties)
235
264
  ? physicsData === null || physicsData === void 0 ? void 0 : physicsData.properties
236
265
  : undefined;
237
- const modelUrl = (_c = (_b = findComponent(gameObject, "Model")) === null || _b === void 0 ? void 0 : _b.properties) === null || _c === void 0 ? void 0 : _c.filename;
266
+ const modelUrl = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
238
267
  const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl, physicsProps), [gameObject, modelUrl, parentMatrix, physicsProps]);
239
268
  const groupRef = useRef(null);
240
269
  const handleGroupRef = useCallback((object) => {
@@ -243,22 +272,27 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
243
272
  registerRef(nodeId, object);
244
273
  }
245
274
  }, [editMode, nodeId, registerRef]);
246
- const editClickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
275
+ const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
247
276
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
248
- onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
277
+ onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
249
278
  });
279
+ const renderedInstances = instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id)));
250
280
  if (editMode) {
251
- return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale }, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, clickEventName: clickEventName, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id)))] }));
281
+ return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), renderedInstances] }));
252
282
  }
253
- return (_jsx(_Fragment, { children: instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, clickable: clickable, clickEventName: clickEventName, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, locked: isLocked, physics: instance.physics }, instance.id))) }));
283
+ return _jsx(_Fragment, { children: renderedInstances });
254
284
  }
255
285
  function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, }) {
256
286
  var _a, _b;
257
287
  const gameObject = usePrefabNode(nodeId);
258
288
  const childIds = usePrefabChildIds(nodeId);
289
+ if (!gameObject)
290
+ return null;
259
291
  const isSelected = selectedId === nodeId;
260
- const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
292
+ const isLocked = Boolean(gameObject.locked);
261
293
  const stillInstanced = useInstanceCheck(nodeId);
294
+ const clickEventName = getNodeClickEventName(gameObject);
295
+ const metadataProps = getNodeMetadataProps(gameObject);
262
296
  const groupRef = useRef(null);
263
297
  const helperRef = useRef(null);
264
298
  const handleGroupRef = useCallback((object) => {
@@ -268,27 +302,43 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, registerRef, load
268
302
  const handleHelperRef = useCallback((object) => {
269
303
  helperRef.current = object;
270
304
  }, []);
271
- const clickHandlers = useClickValid(!!editMode && !isLocked, (e) => {
305
+ const handleEditGroupRef = useCallback((object) => {
306
+ handleGroupRef(object);
307
+ handleHelperRef(object);
308
+ }, [handleGroupRef, handleHelperRef]);
309
+ const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
272
310
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
273
- if (gameObject)
274
- onClick === null || onClick === void 0 ? void 0 : onClick(e, gameObject);
311
+ onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
275
312
  });
313
+ const primaryClickHandlers = !editMode && (clickEventName || onClick)
314
+ ? {
315
+ onClick: (event) => {
316
+ event.stopPropagation();
317
+ emitNodePointerEvent(clickEventName, event, nodeId, gameObject, groupRef.current);
318
+ onClick === null || onClick === void 0 ? void 0 : onClick(event, gameObject);
319
+ },
320
+ }
321
+ : undefined;
276
322
  useHelper(editMode && isSelected ? helperRef : null, BoxHelper, "cyan");
277
- if (!gameObject)
278
- return null;
279
323
  const world = parentMatrix.clone().multiply(compose(gameObject));
280
324
  const physics = findComponent(gameObject, "Physics");
281
325
  const ready = isNodeReady(gameObject, loadedModels);
282
326
  const hasPhysics = physics && ready && !stillInstanced;
283
327
  const transform = getNodeTransformProps(gameObject);
328
+ const transformProps = {
329
+ position: transform.position,
330
+ rotation: transform.rotation,
331
+ scale: transform.scale,
332
+ };
333
+ const groupProps = Object.assign(Object.assign({}, metadataProps), transformProps);
284
334
  const physicsDef = hasPhysics ? getComponentDef(physics.type) : null;
285
335
  const isInstanced = (_b = (_a = findComponent(gameObject, "Model")) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.instanced;
286
336
  const physicsKey = `physics_${nodeId}_${isInstanced ? 'instanced' : 'standard'}`;
287
337
  const renderCtx = { loadedModels, editMode, registerRef };
288
338
  const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode });
289
- const inner = (_jsx("group", Object.assign({}, clickHandlers, { children: renderCompositionNode(gameObject, renderCtx, childNodes) })));
339
+ const inner = renderCompositionNode(gameObject, renderCtx, primaryClickHandlers, childNodes);
290
340
  const physicsInner = editMode ? _jsx("group", { visible: false, children: inner }) : inner;
291
- return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsx("group", { ref: handleGroupRef, 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: handleHelperRef, 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, children: physicsInner }, physicsKey)) : null] })) : 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, children: inner }, physicsKey)) : (_jsx("group", { ref: handleGroupRef, position: transform.position, rotation: transform.rotation, scale: transform.scale, children: inner })) }));
341
+ return (_jsx(EntityRuntimeScope, { nodeId: nodeId, editMode: editMode, isSelected: isSelected, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("group", Object.assign({ ref: handleEditGroupRef }, groupProps, editClickHandlers, { children: [_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }), inner] })), hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: physicsInner }), physicsKey)) : null] })) : hasPhysics && (physicsDef === null || physicsDef === void 0 ? void 0 : physicsDef.View) ? (_jsx(physicsDef.View, Object.assign({ properties: physics.properties }, transformProps, { children: _jsx("group", Object.assign({ ref: handleGroupRef }, metadataProps, { children: inner })) }), physicsKey)) : (_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, { children: inner }))) }));
292
342
  }
293
343
  function isRendererHandledComponent(componentType) {
294
344
  return componentType === "Transform"
@@ -383,11 +433,11 @@ function getNodeTransformProps(node) {
383
433
  scale: (_d = t === null || t === void 0 ? void 0 : t.scale) !== null && _d !== void 0 ? _d : [1, 1, 1],
384
434
  };
385
435
  }
386
- function renderCompositionNode(gameObject, ctx, childNodes) {
387
- const primaryContent = renderNodePrimaryContent(gameObject, ctx);
436
+ function renderCompositionNode(gameObject, ctx, primaryClickHandlers, childNodes) {
437
+ const primaryContent = renderNodePrimaryContent(gameObject, ctx, primaryClickHandlers);
388
438
  return applyNodeComposition(gameObject, _jsxs(_Fragment, { children: [primaryContent, childNodes] }));
389
439
  }
390
- function renderNodePrimaryContent(gameObject, ctx) {
440
+ function renderNodePrimaryContent(gameObject, ctx, primaryClickHandlers) {
391
441
  var _a, _b, _c;
392
442
  const geometry = (_a = findComponent(gameObject, "BufferGeometry")) !== null && _a !== void 0 ? _a : findComponent(gameObject, "Geometry");
393
443
  const material = findComponent(gameObject, "Material");
@@ -400,7 +450,7 @@ function renderNodePrimaryContent(gameObject, ctx) {
400
450
  const meshCastShadow = meshVisible && geometryProperties.castShadow !== false;
401
451
  const meshReceiveShadow = meshVisible && geometryProperties.receiveShadow !== false;
402
452
  if ((geometry === null || geometry === void 0 ? void 0 : geometry.type) && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
403
- return (_jsxs("mesh", { visible: meshVisible, castShadow: meshCastShadow, receiveShadow: meshReceiveShadow, children: [_jsx(geometryDef.View, { properties: geometry.properties }), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: material.properties }, "material"))] }));
453
+ return (_jsxs("mesh", Object.assign({ visible: meshVisible, castShadow: meshCastShadow, receiveShadow: meshReceiveShadow }, primaryClickHandlers, { children: [_jsx(geometryDef.View, { properties: geometry.properties }), material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: material.properties }, "material"))] })));
404
454
  }
405
455
  if ((model === null || model === void 0 ? void 0 : model.type) && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View) && !((_c = model.properties) === null || _c === void 0 ? void 0 : _c.instanced) && isNodeReady(gameObject, ctx.loadedModels)) {
406
456
  return _jsx(modelDef.View, { properties: model.properties });
@@ -1,7 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
2
  import type { Object3D, Texture } from "three";
3
- import type { PrefabStoreApi } from "./prefabStore";
4
- import { type EntityComponent, type Scene } from "./scene";
5
3
  export interface AssetRuntime {
6
4
  registerRigidBodyRef: (id: string, rb: any) => void;
7
5
  getModel: (path: string) => Object3D | null;
@@ -36,26 +34,3 @@ export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, child
36
34
  isSelected?: boolean;
37
35
  children: ReactNode;
38
36
  }): import("react/jsx-runtime").JSX.Element;
39
- /** Runtime behaviour produced by `Component.create(ctx)`. All methods optional. */
40
- export interface ComponentInstance {
41
- start?(): void;
42
- update?(dt: number): void;
43
- destroy?(): void;
44
- }
45
- export interface ComponentRuntimeContext<TProperties = Record<string, any>> {
46
- scene: Scene;
47
- component: EntityComponent<TProperties>;
48
- object: Object3D;
49
- rigidBody: any;
50
- }
51
- export interface RuntimeEngine {
52
- tick: (dt: number) => void;
53
- setActive: (active: boolean) => void;
54
- invalidate: () => void;
55
- dispose: () => void;
56
- }
57
- export declare function createRuntimeEngine({ store, getObject, getRigidBody, }: {
58
- store: PrefabStoreApi;
59
- getObject: (id: string) => Object3D | null;
60
- getRigidBody: (id: string) => any;
61
- }): RuntimeEngine;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ export const AssetRuntimeContext = createContext(null);
4
+ const EntityRuntimeContext = createContext(null);
5
+ export function useAssetRuntime() {
6
+ const ctx = useContext(AssetRuntimeContext);
7
+ if (!ctx)
8
+ throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
9
+ return ctx;
10
+ }
11
+ export function useEntityRuntime() {
12
+ const ctx = useContext(EntityRuntimeContext);
13
+ if (!ctx)
14
+ throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
15
+ return ctx;
16
+ }
17
+ export function useEntityObjectRef() {
18
+ const { getObject } = useEntityRuntime();
19
+ return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
20
+ }
21
+ export function useEntityRigidBodyRef() {
22
+ const { getRigidBody } = useEntityRuntime();
23
+ return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
24
+ }
25
+ export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
26
+ const asset = useContext(AssetRuntimeContext);
27
+ if (!asset)
28
+ throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
29
+ const value = useMemo(() => ({
30
+ nodeId,
31
+ editMode,
32
+ isSelected,
33
+ getObject: () => asset.getObject(nodeId),
34
+ getRigidBody: () => asset.getRigidBody(nodeId),
35
+ }), [asset, editMode, isSelected, nodeId]);
36
+ return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
37
+ }
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { BooleanField, FieldGroup } from "./Input";
2
+ import { BooleanField, FieldGroup, StringField } from "./Input";
3
3
  const DEFAULT_TRIANGLE_POSITIONS = [
4
4
  0, 0, 0,
5
5
  1, 0, 0,
@@ -63,7 +63,7 @@ function BufferArrayField({ label, value, fallback, onChange, rows = 4, }) {
63
63
  function BufferGeometryComponentEditor({ component, onUpdate, }) {
64
64
  var _a;
65
65
  const properties = (_a = component.properties) !== null && _a !== void 0 ? _a : {};
66
- return (_jsxs(FieldGroup, { children: [_jsx(BufferArrayField, { label: "Positions", value: properties.positions, fallback: DEFAULT_TRIANGLE_POSITIONS, rows: 5, onChange: (positions) => onUpdate({ positions }) }), _jsx(BufferArrayField, { label: "Indices", value: properties.indices, fallback: DEFAULT_TRIANGLE_INDICES, onChange: (indices) => onUpdate({ indices }) }), _jsx(BufferArrayField, { label: "Normals", value: properties.normals, fallback: [], onChange: (normals) => onUpdate({ normals }) }), _jsx(BufferArrayField, { label: "UVs", value: properties.uvs, fallback: DEFAULT_TRIANGLE_UVS, onChange: (uvs) => onUpdate({ uvs }) }), _jsx(BooleanField, { name: "computeVertexNormals", label: "Compute Normals", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "visible", label: "Visible", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: properties, onChange: onUpdate, fallback: true })] }));
66
+ return (_jsxs(FieldGroup, { children: [_jsx(BufferArrayField, { label: "Positions", value: properties.positions, fallback: DEFAULT_TRIANGLE_POSITIONS, rows: 5, onChange: (positions) => onUpdate({ positions }) }), _jsx(BufferArrayField, { label: "Indices", value: properties.indices, fallback: DEFAULT_TRIANGLE_INDICES, onChange: (indices) => onUpdate({ indices }) }), _jsx(BufferArrayField, { label: "Normals", value: properties.normals, fallback: [], onChange: (normals) => onUpdate({ normals }) }), _jsx(BufferArrayField, { label: "UVs", value: properties.uvs, fallback: DEFAULT_TRIANGLE_UVS, onChange: (uvs) => onUpdate({ uvs }) }), _jsx(BooleanField, { name: "computeVertexNormals", label: "Compute Normals", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "visible", label: "Visible", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: properties, onChange: onUpdate, fallback: true }), _jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: properties, onChange: onUpdate, fallback: false }), properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: properties, onChange: onUpdate, placeholder: "entity:click" })) : null] }));
67
67
  }
68
68
  function BufferGeometryComponentView({ properties }) {
69
69
  const positions = normalizeNumberArray(properties.positions, DEFAULT_TRIANGLE_POSITIONS);
@@ -91,6 +91,8 @@ const BufferGeometryComponent = {
91
91
  normals: [],
92
92
  uvs: DEFAULT_TRIANGLE_UVS,
93
93
  computeVertexNormals: true,
94
+ emitClickEvent: false,
95
+ clickEventName: '',
94
96
  },
95
97
  };
96
98
  export default BufferGeometryComponent;
@@ -3,7 +3,7 @@ import { OrthographicCamera, PerspectiveCamera, useHelper } from '@react-three/d
3
3
  import { useRef } from 'react';
4
4
  import { CameraHelper } from 'three';
5
5
  import { useFrame, useThree } from '@react-three/fiber';
6
- import { useEntityRuntime } from '../runtime';
6
+ import { useEntityRuntime } from '../assetRuntime';
7
7
  import { FieldGroup, NumberField, SelectField } from './Input';
8
8
  const CAMERA_PROJECTION_OPTIONS = [
9
9
  { value: 'perspective', label: 'Perspective' },
@@ -1,6 +1,5 @@
1
1
  import { FC } from "react";
2
2
  import { ComponentData, GameObject } from "../types";
3
- import type { ComponentInstance, ComponentRuntimeContext } from "../runtime";
4
3
  export type AssetRef = {
5
4
  type: "model" | "texture" | "sound";
6
5
  path: string;
@@ -28,8 +27,6 @@ export interface Component {
28
27
  }>;
29
28
  defaultProperties: any;
30
29
  View?: FC<ComponentViewProps>;
31
- /** Optional runtime factory for the non-React game loop. */
32
- create?: (ctx: ComponentRuntimeContext) => ComponentInstance | void;
33
30
  /** Declare which asset paths this component references (for asset loading). */
34
31
  getAssetRefs?: (properties: Record<string, any>) => AssetRef[];
35
32
  }
@@ -0,0 +1,3 @@
1
+ import { Component } from "./ComponentRegistry";
2
+ declare const DataComponent: Component;
3
+ export default DataComponent;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { colors } from "../styles";
4
+ const RESERVED_USER_DATA_KEYS = new Set([
5
+ 'prefabNodeId',
6
+ 'prefabNodeName',
7
+ ]);
8
+ const inputStyle = {
9
+ width: '100%',
10
+ backgroundColor: colors.bgInput,
11
+ border: `1px solid ${colors.border}`,
12
+ padding: '6px 8px',
13
+ fontSize: '11px',
14
+ color: colors.text,
15
+ fontFamily: 'monospace',
16
+ outline: 'none',
17
+ borderRadius: 3,
18
+ boxSizing: 'border-box',
19
+ };
20
+ function isRecord(value) {
21
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
22
+ }
23
+ function normalizeData(value) {
24
+ if (!isRecord(value))
25
+ return {};
26
+ return Object.entries(value).reduce((result, [key, entry]) => {
27
+ if (!key.trim() || entry === undefined) {
28
+ return result;
29
+ }
30
+ result[key] = entry;
31
+ return result;
32
+ }, {});
33
+ }
34
+ function formatData(value) {
35
+ return JSON.stringify(normalizeData(value), null, 2);
36
+ }
37
+ function parseData(raw) {
38
+ const trimmed = raw.trim();
39
+ if (!trimmed) {
40
+ return { ok: true, value: {} };
41
+ }
42
+ try {
43
+ const parsed = JSON.parse(trimmed);
44
+ if (!isRecord(parsed)) {
45
+ return { ok: false, error: 'Data must be a JSON object' };
46
+ }
47
+ const nextData = normalizeData(parsed);
48
+ for (const key of Object.keys(nextData)) {
49
+ if (RESERVED_USER_DATA_KEYS.has(key)) {
50
+ return { ok: false, error: `Reserved key: ${key}` };
51
+ }
52
+ }
53
+ return { ok: true, value: nextData };
54
+ }
55
+ catch (_a) {
56
+ return { ok: false, error: 'Data must be valid JSON' };
57
+ }
58
+ }
59
+ function DataComponentEditor({ component, onUpdate }) {
60
+ var _a;
61
+ const [draft, setDraft] = useState(() => { var _a; return formatData((_a = component.properties) === null || _a === void 0 ? void 0 : _a.data); });
62
+ const [error, setError] = useState(null);
63
+ useEffect(() => {
64
+ var _a;
65
+ setDraft(formatData((_a = component.properties) === null || _a === void 0 ? void 0 : _a.data));
66
+ setError(null);
67
+ }, [(_a = component.properties) === null || _a === void 0 ? void 0 : _a.data]);
68
+ const commitDraft = () => {
69
+ const parsed = parseData(draft);
70
+ if (!parsed.ok) {
71
+ setError(parsed.error);
72
+ return;
73
+ }
74
+ setError(null);
75
+ setDraft(formatData(parsed.value));
76
+ onUpdate({ data: parsed.value });
77
+ };
78
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsx("textarea", { rows: 10, spellCheck: false, value: draft, onChange: (event) => setDraft(event.target.value), onBlur: commitDraft, style: Object.assign(Object.assign({}, inputStyle), { resize: 'vertical', minHeight: 140 }) }), error ? (_jsx("div", { style: { fontSize: 10, color: colors.accent, fontFamily: 'monospace' }, children: error })) : null, _jsx("div", { style: { fontSize: 10, color: colors.textMuted, lineHeight: 1.4 }, children: "Enter a JSON object. Keys map directly onto `object.userData`." })] }));
79
+ }
80
+ const DataComponent = {
81
+ name: 'Data',
82
+ Editor: DataComponentEditor,
83
+ defaultProperties: {
84
+ data: {},
85
+ },
86
+ };
87
+ export default DataComponent;
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect, useState } from "react";
4
4
  import { useFrame } from "@react-three/fiber";
5
5
  import { CameraHelper } from "three";
6
- import { useEntityRuntime } from "../runtime";
6
+ import { useEntityRuntime } from "../assetRuntime";
7
7
  import { BooleanField, ColorField, NumberField, NumberInput, Vector3Input } from "./Input";
8
8
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
9
9
  import { colors } from "../styles";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Environment } from '@react-three/drei';
3
3
  import { FieldGroup, NumberField } from './Input';
4
- import { useAssetRuntime } from '../runtime';
4
+ import { useAssetRuntime } from '../assetRuntime';
5
5
  function EnvironmentView({ properties, children, }) {
6
6
  const { getAssetRevision } = useAssetRuntime();
7
7
  const { intensity = 1, resolution = 256 } = properties;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { BooleanField, FieldGroup, NumberField, SelectField } from "./Input";
2
+ import { BooleanField, FieldGroup, NumberField, SelectField, StringField } from "./Input";
3
3
  const GEOMETRY_ARGS = {
4
4
  box: {
5
5
  fields: [
@@ -61,7 +61,7 @@ function GeometryComponentEditor({ component, onUpdate, }) {
61
61
  ] }), schema.fields.map((field, index) => {
62
62
  var _a;
63
63
  return (_jsx(NumberField, { name: field.name, label: field.label, values: { [field.name]: (_a = args[index]) !== null && _a !== void 0 ? _a : field.defaultValue }, onChange: (next) => updateArg(index, next[field.name]), fallback: field.defaultValue, min: field.min, step: field.step }, field.name));
64
- }), _jsx(BooleanField, { name: "visible", label: "Visible", values: component.properties, onChange: handleChange, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: component.properties, onChange: handleChange, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: component.properties, onChange: handleChange, fallback: true })] }));
64
+ }), _jsx(BooleanField, { name: "visible", label: "Visible", values: component.properties, onChange: handleChange, fallback: true }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: component.properties, onChange: handleChange, fallback: true }), _jsx(BooleanField, { name: "receiveShadow", label: "Receive Shadow", values: component.properties, onChange: handleChange, fallback: true }), _jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: component.properties, onChange: handleChange, fallback: false }), component.properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: component.properties, onChange: handleChange, placeholder: "cannon:fire" })) : null] }));
65
65
  }
66
66
  // View for Geometry component
67
67
  function GeometryComponentView({ properties, children }) {
@@ -87,6 +87,8 @@ const GeometryComponent = {
87
87
  defaultProperties: {
88
88
  geometryType: 'box',
89
89
  args: getDefaultArgs('box'),
90
+ emitClickEvent: false,
91
+ clickEventName: '',
90
92
  }
91
93
  };
92
94
  export default GeometryComponent;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- export type FieldType = 'vector3' | 'number' | 'string' | 'color' | 'boolean' | 'select' | 'node' | 'event';
2
+ export type FieldType = 'vector3' | 'number' | 'string' | 'color' | 'boolean' | 'select' | 'node';
3
3
  interface BaseFieldDefinition {
4
4
  name: string;
5
5
  label: string;
@@ -36,10 +36,6 @@ interface NodeFieldDefinition extends BaseFieldDefinition {
36
36
  placeholder?: string;
37
37
  includeRoot?: boolean;
38
38
  }
39
- interface EventFieldDefinition extends BaseFieldDefinition {
40
- type: 'event';
41
- placeholder?: string;
42
- }
43
39
  interface CustomFieldDefinition extends BaseFieldDefinition {
44
40
  type: 'custom';
45
41
  render: (props: {
@@ -49,7 +45,7 @@ interface CustomFieldDefinition extends BaseFieldDefinition {
49
45
  onChangeMultiple: (values: Record<string, any>) => void;
50
46
  }) => React.ReactNode;
51
47
  }
52
- export type FieldDefinition = Vector3FieldDefinition | NumberFieldDefinition | StringFieldDefinition | ColorFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | NodeFieldDefinition | EventFieldDefinition | CustomFieldDefinition;
48
+ export type FieldDefinition = Vector3FieldDefinition | NumberFieldDefinition | StringFieldDefinition | ColorFieldDefinition | BooleanFieldDefinition | SelectFieldDefinition | NodeFieldDefinition | CustomFieldDefinition;
53
49
  declare const styles: {
54
50
  input: React.CSSProperties;
55
51
  label: React.CSSProperties;
@@ -95,12 +91,6 @@ export declare function NodeInput({ label, value, onChange, placeholder, include
95
91
  placeholder?: string;
96
92
  includeRoot?: boolean;
97
93
  }): import("react/jsx-runtime").JSX.Element;
98
- export declare function EventInput({ label, value, onChange, placeholder, }: {
99
- label: string;
100
- value: string;
101
- onChange: (value: string) => void;
102
- placeholder?: string;
103
- }): import("react/jsx-runtime").JSX.Element;
104
94
  export declare function BooleanInput({ label, value, onChange }: {
105
95
  label?: string;
106
96
  value: boolean;
@@ -182,7 +172,6 @@ export declare function SelectField({ name, label, values, onChange, fallback, o
182
172
  export declare function NodeField({ name, label, values, onChange, fallback, }: BoundStringFieldProps & {
183
173
  fallback?: string;
184
174
  }): import("react/jsx-runtime").JSX.Element;
185
- export declare function EventField({ name, label, values, onChange, fallback, placeholder, }: BoundStringFieldProps): import("react/jsx-runtime").JSX.Element;
186
175
  export declare function Vector3Field({ name, label, values, onChange, fallback, snap, labelExtra, }: BoundVector3FieldProps): import("react/jsx-runtime").JSX.Element;
187
176
  interface FieldRendererProps {
188
177
  fields: FieldDefinition[];