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
@@ -347,55 +347,6 @@ export function NodeInput({ label, value, onChange, placeholder, includeRoot = t
347
347
  setQuery('');
348
348
  }, emptyMessage: "No matching nodes." })] })) : null] }));
349
349
  }
350
- const BUILT_IN_EVENT_OPTIONS = [
351
- 'sensor:enter',
352
- 'sensor:exit',
353
- 'collision:enter',
354
- 'collision:exit',
355
- 'click',
356
- ].map(eventName => ({
357
- value: eventName,
358
- label: eventName,
359
- searchText: eventName.toLowerCase(),
360
- }));
361
- export function EventInput({ label, value, onChange, placeholder, }) {
362
- const prefabState = useOptionalPrefabSnapshot();
363
- const [query, setQuery] = useState('');
364
- const options = useMemo(() => {
365
- var _a;
366
- const authoredEvents = new Map();
367
- Object.values((_a = prefabState === null || prefabState === void 0 ? void 0 : prefabState.nodesById) !== null && _a !== void 0 ? _a : {}).forEach(node => {
368
- var _a;
369
- Object.values((_a = node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
370
- var _a;
371
- Object.entries((_a = component === null || component === void 0 ? void 0 : component.properties) !== null && _a !== void 0 ? _a : {}).forEach(([key, entry]) => {
372
- var _a, _b;
373
- if (typeof entry !== 'string')
374
- return;
375
- if (!(key === 'eventName' || key.endsWith('EventName')))
376
- return;
377
- const eventName = entry.trim();
378
- if (!eventName)
379
- return;
380
- authoredEvents.set(eventName, {
381
- value: eventName,
382
- label: eventName,
383
- description: `${(_a = component === null || component === void 0 ? void 0 : component.type) !== null && _a !== void 0 ? _a : 'Component'} -> ${key}`,
384
- searchText: `${eventName} ${(_b = component === null || component === void 0 ? void 0 : component.type) !== null && _b !== void 0 ? _b : ''} ${key}`.toLowerCase(),
385
- });
386
- });
387
- });
388
- });
389
- const merged = new Map();
390
- BUILT_IN_EVENT_OPTIONS.forEach(option => merged.set(option.value, option));
391
- authoredEvents.forEach((option, key) => merged.set(key, option));
392
- return [...merged.values()].sort((left, right) => left.value.localeCompare(right.value));
393
- }, [prefabState === null || prefabState === void 0 ? void 0 : prefabState.nodesById]);
394
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(StringInput, { label: label, value: value, onChange: onChange, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : 'Event name' }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsx("input", { type: "text", style: Object.assign(Object.assign({}, styles.input), { width: '100%', textAlign: 'left' }), value: query, onChange: e => setQuery(e.target.value), placeholder: "Search built-in and authored events" }), _jsx(SearchSuggestionList, { query: query, options: options, onSelect: (nextValue) => {
395
- onChange(nextValue);
396
- setQuery('');
397
- }, emptyMessage: "No matching events." })] })] }));
398
- }
399
350
  export function BooleanInput({ label, value, onChange }) {
400
351
  return (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between' }, children: [label && _jsx(Label, { children: label }), _jsx("input", { type: "checkbox", style: {
401
352
  height: 16,
@@ -470,10 +421,6 @@ export function NodeField({ name, label, values, onChange, fallback = '', }) {
470
421
  var _a;
471
422
  return (_jsx(NodeInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange) }));
472
423
  }
473
- export function EventField({ name, label, values, onChange, fallback = '', placeholder, }) {
474
- var _a;
475
- return (_jsx(EventInput, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), placeholder: placeholder }));
476
- }
477
424
  export function Vector3Field({ name, label, values, onChange, fallback = [0, 0, 0], snap, labelExtra, }) {
478
425
  var _a;
479
426
  return (_jsx(Vector3Input, { label: label, value: (_a = values[name]) !== null && _a !== void 0 ? _a : fallback, onChange: bindFieldChange(name, onChange), snap: snap, labelExtra: labelExtra }));
@@ -500,8 +447,6 @@ export function FieldRenderer({ fields, values, onChange }) {
500
447
  return (_jsx(SelectInput, { label: field.label, value: (_b = value !== null && value !== void 0 ? value : (_a = field.options[0]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '', onChange: v => updateField(field.name, v), options: field.options }, field.name));
501
448
  case 'node':
502
449
  return (_jsx(NodeInput, { label: field.label, value: value !== null && value !== void 0 ? value : '', onChange: v => updateField(field.name, v), placeholder: field.placeholder, includeRoot: field.includeRoot }, field.name));
503
- case 'event':
504
- return (_jsx(EventInput, { label: field.label, value: value !== null && value !== void 0 ? value : '', onChange: v => updateField(field.name, v), placeholder: field.placeholder }, field.name));
505
450
  case 'custom':
506
451
  return (_jsxs("div", { children: [field.label && _jsx(Label, { children: field.label }), field.render({
507
452
  value,
@@ -14,7 +14,7 @@ import { createContext, useContext, useMemo, useRef } from 'react';
14
14
  import { extend } from '@react-three/fiber';
15
15
  import { useFrame } from '@react-three/fiber';
16
16
  import { FieldRenderer, Label, NumberInput } from './Input';
17
- import { useAssetRuntime } from '../runtime';
17
+ import { useAssetRuntime } from '../assetRuntime';
18
18
  import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
19
19
  import { TexturePicker } from '../../assetviewer/page';
20
20
  import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ModelPicker } from '../../assetviewer/page';
3
3
  import { useContext, useMemo } from 'react';
4
- import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput } from './Input';
5
- import { useAssetRuntime } from '../runtime';
4
+ import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
5
+ import { useAssetRuntime } from '../assetRuntime';
6
6
  import { EditorContext } from '../PrefabEditor';
7
7
  import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
8
8
  import { colors } from '../styles';
@@ -61,7 +61,7 @@ function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
61
61
  const editorContext = useContext(EditorContext);
62
62
  const positionSnap = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.positionSnap) !== null && _a !== void 0 ? _a : 0.5;
63
63
  const repeatAxes = getRepeatAxesFromModelProperties(component.properties);
64
- return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: component.properties.filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
64
+ return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: component.properties.filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), !component.properties.instanced ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: component.properties, onChange: onUpdate, placeholder: "entity:click" })) : null] })) : null, component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
65
65
  }
66
66
  // View for Model component
67
67
  function ModelComponentView({ properties, children }) {
@@ -8,9 +8,13 @@ export type PhysicsProps = Omit<RigidBodyOptions, 'colliders'> & {
8
8
  angularVelocity?: [number, number, number];
9
9
  capsuleRadius?: number;
10
10
  capsuleHalfHeight?: number;
11
+ emitSensorEnterEvent?: boolean;
11
12
  sensorEnterEventName?: string;
13
+ emitSensorExitEvent?: boolean;
12
14
  sensorExitEventName?: string;
15
+ emitCollisionEnterEvent?: boolean;
13
16
  collisionEnterEventName?: string;
17
+ emitCollisionExitEvent?: boolean;
14
18
  collisionExitEventName?: string;
15
19
  };
16
20
  export declare function isPhysicsProps(v: any): v is PhysicsProps;
@@ -11,10 +11,11 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
14
- import { useRef, useEffect, useCallback } from 'react';
15
- import { useAssetRuntime, useEntityRuntime } from "../runtime";
16
- import { BooleanField, EventInput, FieldGroup, ListEditor, NumberField, SelectField, SelectInput, Vector3Field } from "./Input";
17
- import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
14
+ import { useCallback, useEffect, useRef } from 'react';
15
+ import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
16
+ import { usePrefabNode } from "../prefabStore";
17
+ import { BooleanField, FieldGroup, NumberField, SelectField, StringField, Vector3Field } from "./Input";
18
+ import { getNodeUserData } from "../types";
18
19
  import { colors } from "../styles";
19
20
  export function isPhysicsProps(v) {
20
21
  return (v === null || v === void 0 ? void 0 : v.type) === "fixed" || (v === null || v === void 0 ? void 0 : v.type) === "dynamic" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicPosition" || (v === null || v === void 0 ? void 0 : v.type) === "kinematicVelocity";
@@ -22,90 +23,6 @@ export function isPhysicsProps(v) {
22
23
  const enabledAxesFallback = [true, true, true];
23
24
  const capsuleRadiusFallback = 0.35;
24
25
  const capsuleHalfHeightFallback = 0.45;
25
- const PHYSICS_EVENT_OPTIONS = [
26
- {
27
- key: 'sensorEnterEventName',
28
- label: 'Sensor Enter',
29
- defaultName: 'sensor:enter',
30
- requiresSensor: true,
31
- },
32
- {
33
- key: 'sensorExitEventName',
34
- label: 'Sensor Exit',
35
- defaultName: 'sensor:exit',
36
- requiresSensor: true,
37
- },
38
- {
39
- key: 'collisionEnterEventName',
40
- label: 'Collision Enter',
41
- defaultName: 'collision:enter',
42
- requiresSensor: false,
43
- },
44
- {
45
- key: 'collisionExitEventName',
46
- label: 'Collision Exit',
47
- defaultName: 'collision:exit',
48
- requiresSensor: false,
49
- },
50
- ];
51
- function getPhysicsEventOption(key) {
52
- return PHYSICS_EVENT_OPTIONS.find(option => option.key === key);
53
- }
54
- function getConfiguredPhysicsEvents(values) {
55
- return PHYSICS_EVENT_OPTIONS.filter(option => typeof values[option.key] === 'string' && values[option.key].trim().length > 0);
56
- }
57
- function getAvailablePhysicsEvents(values, currentKey) {
58
- const configuredKeys = new Set(getConfiguredPhysicsEvents(values).map(option => option.key));
59
- return PHYSICS_EVENT_OPTIONS
60
- .filter(option => option.key === currentKey || !configuredKeys.has(option.key))
61
- .map(option => ({ value: option.key, label: option.label }));
62
- }
63
- function PhysicsEventBindingsEditor({ values, onChange, }) {
64
- const configuredEvents = getConfiguredPhysicsEvents(values);
65
- const nextEventOptions = getAvailablePhysicsEvents(values);
66
- const addEvent = (eventKey) => {
67
- if (!eventKey)
68
- return;
69
- const option = getPhysicsEventOption(eventKey);
70
- if (!option)
71
- return;
72
- onChange(Object.assign({ [option.key]: option.defaultName }, (option.requiresSensor ? { sensor: true } : null)));
73
- };
74
- const updateEventKey = (currentKey, nextKey) => {
75
- const nextOption = getPhysicsEventOption(nextKey);
76
- if (!nextOption)
77
- return;
78
- onChange(Object.assign({ [currentKey]: undefined, [nextOption.key]: values[currentKey] || nextOption.defaultName }, (nextOption.requiresSensor ? { sensor: true } : null)));
79
- };
80
- const updateEventName = (key, eventName) => {
81
- onChange({ [key]: eventName });
82
- };
83
- const removeEvent = (key) => {
84
- onChange({ [key]: undefined });
85
- };
86
- return (_jsx(ListEditor, { label: "Events", items: configuredEvents, onAdd: addEvent, addOptions: nextEventOptions, canAdd: nextEventOptions.length > 0, emptyMessage: "No physics events configured.", addButtonTitle: "Add physics event", addDisabledTitle: "All physics events already added", renderItem: (option) => {
87
- var _a;
88
- return (_jsxs("div", { style: {
89
- display: 'flex',
90
- flexDirection: 'column',
91
- gap: 6,
92
- padding: 8,
93
- border: `1px solid ${colors.border}`,
94
- borderRadius: 4,
95
- background: colors.bgSurface,
96
- }, children: [_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'end' }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SelectInput, { label: "Type", value: option.key, onChange: (nextKey) => updateEventKey(option.key, nextKey), options: getAvailablePhysicsEvents(values, option.key) }) }), _jsx("button", { type: "button", onClick: () => removeEvent(option.key), style: {
97
- height: 24,
98
- width: 28,
99
- borderRadius: 3,
100
- border: `1px solid ${colors.border}`,
101
- background: colors.bgInput,
102
- color: colors.text,
103
- cursor: 'pointer',
104
- padding: 0,
105
- flexShrink: 0,
106
- }, title: "Remove physics event", children: "\u00D7" })] }), _jsx(EventInput, { label: "Event Name", value: (_a = values[option.key]) !== null && _a !== void 0 ? _a : option.defaultName, onChange: (eventName) => updateEventName(option.key, eventName), placeholder: option.defaultName })] }, option.key));
107
- } }));
108
- }
109
26
  function LockedAxisField({ label, name, values, onChange, }) {
110
27
  const enabledAxes = Array.isArray(values[name])
111
28
  ? values[name]
@@ -158,15 +75,29 @@ function PhysicsComponentEditor({ component, onUpdate }) {
158
75
  { value: 'cuboid', label: 'Cuboid (box)' },
159
76
  { value: 'ball', label: 'Ball (sphere)' },
160
77
  { value: 'capsule', label: 'Capsule' },
161
- ] }), component.properties.colliders === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null, _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", name: "enabledTranslations", values: component.properties, onChange: onUpdate }), _jsx(LockedAxisField, { label: "Lock Rotations", name: "enabledRotations", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(PhysicsEventBindingsEditor, { values: component.properties, onChange: onUpdate }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
78
+ ] }), component.properties.colliders === 'capsule' ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "capsuleRadius", label: "Capsule Radius", values: component.properties, onChange: onUpdate, fallback: capsuleRadiusFallback, min: 0.01, step: 0.01 }), _jsx(NumberField, { name: "capsuleHalfHeight", label: "Capsule Half Height", values: component.properties, onChange: onUpdate, fallback: capsuleHalfHeightFallback, min: 0.01, step: 0.01 })] })) : null, _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", name: "enabledTranslations", values: component.properties, onChange: onUpdate }), _jsx(LockedAxisField, { label: "Lock Rotations", name: "enabledRotations", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(BooleanField, { name: "emitCollisionEnterEvent", label: "Emit Collision Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionEnterEvent ? (_jsx(StringField, { name: "collisionEnterEventName", label: "Collision Enter Event", values: component.properties, onChange: onUpdate, placeholder: "target:hit" })) : null, _jsx(BooleanField, { name: "emitCollisionExitEvent", label: "Emit Collision Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitCollisionExitEvent ? (_jsx(StringField, { name: "collisionExitEventName", label: "Collision Exit Event", values: component.properties, onChange: onUpdate, placeholder: "target:reset" })) : null, _jsx(BooleanField, { name: "emitSensorEnterEvent", label: "Emit Sensor Enter", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorEnterEvent ? (_jsx(StringField, { name: "sensorEnterEventName", label: "Sensor Enter Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:enter" })) : null, _jsx(BooleanField, { name: "emitSensorExitEvent", label: "Emit Sensor Exit", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitSensorExitEvent ? (_jsx(StringField, { name: "sensorExitEventName", label: "Sensor Exit Event", values: component.properties, onChange: onUpdate, placeholder: "sensor:exit" })) : null, _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
162
79
  { value: '', label: 'Default (Dynamic only)' },
163
80
  { value: 'all', label: 'All (includes kinematic & fixed)' },
164
81
  ] })] }));
165
82
  }
83
+ function emitNativeEvent(type, detail) {
84
+ const trimmedType = type === null || type === void 0 ? void 0 : type.trim();
85
+ if (!trimmedType || typeof window === 'undefined')
86
+ return;
87
+ window.dispatchEvent(new CustomEvent(trimmedType, { detail }));
88
+ }
89
+ function getEntityIdFromRigidBody(rigidBody) {
90
+ const userData = rigidBody === null || rigidBody === void 0 ? void 0 : rigidBody.userData;
91
+ return typeof (userData === null || userData === void 0 ? void 0 : userData.entityId) === 'string' ? userData.entityId : null;
92
+ }
166
93
  function PhysicsComponentView({ properties, children, position, rotation, scale }) {
94
+ var _a, _b, _c;
167
95
  const { registerRigidBodyRef } = useAssetRuntime();
168
- const { editMode, nodeId } = useEntityRuntime();
169
- const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, sensorEnterEventName, sensorExitEventName, collisionEnterEventName, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "capsuleRadius", "capsuleHalfHeight", "sensorEnterEventName", "sensorExitEventName", "collisionEnterEventName", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
96
+ const { editMode, nodeId, getObject } = useEntityRuntime();
97
+ const gameObject = usePrefabNode(nodeId);
98
+ const nodeName = (_b = (_a = gameObject === null || gameObject === void 0 ? void 0 : gameObject.name) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
99
+ const userData = Object.assign(Object.assign({ prefabNodeId: (_c = gameObject === null || gameObject === void 0 ? void 0 : gameObject.id) !== null && _c !== void 0 ? _c : nodeId }, (nodeName ? { prefabNodeName: nodeName } : {})), getNodeUserData(gameObject));
100
+ const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], capsuleRadius = capsuleRadiusFallback, capsuleHalfHeight = capsuleHalfHeightFallback, emitSensorEnterEvent = false, sensorEnterEventName, emitSensorExitEvent = false, sensorExitEventName, emitCollisionEnterEvent = false, collisionEnterEventName, emitCollisionExitEvent = false, collisionExitEventName, enabledTranslations = enabledAxesFallback, enabledRotations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "capsuleRadius", "capsuleHalfHeight", "emitSensorEnterEvent", "sensorEnterEventName", "emitSensorExitEvent", "sensorExitEventName", "emitCollisionEnterEvent", "collisionEnterEventName", "emitCollisionExitEvent", "collisionExitEventName", "enabledTranslations", "enabledRotations"]);
170
101
  const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
171
102
  const usesManualCapsuleCollider = colliderType === 'capsule';
172
103
  const rigidBodyRef = useRef(null);
@@ -175,6 +106,12 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
175
106
  const rbKey = editMode
176
107
  ? `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
177
108
  : `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}`;
109
+ const handleRigidBodyRef = useCallback((rigidBody) => {
110
+ rigidBodyRef.current = rigidBody;
111
+ if (!nodeId)
112
+ return;
113
+ registerRigidBodyRef(nodeId, rigidBody);
114
+ }, [nodeId, registerRigidBodyRef]);
178
115
  // Try to get rapier context - will be null if not inside <Physics>
179
116
  let rapier = null;
180
117
  try {
@@ -184,17 +121,6 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
184
121
  catch (e) {
185
122
  // Not inside Physics context - that's ok, just won't have rapier features
186
123
  }
187
- // Register RigidBody ref when it's available
188
- useEffect(() => {
189
- if (nodeId && rigidBodyRef.current) {
190
- registerRigidBodyRef(nodeId, rigidBodyRef.current);
191
- }
192
- return () => {
193
- if (nodeId) {
194
- registerRigidBodyRef(nodeId, null);
195
- }
196
- };
197
- }, [nodeId, registerRigidBodyRef]);
198
124
  // Configure active collision types for kinematic/sensor bodies
199
125
  useEffect(() => {
200
126
  if (activeCollisionTypes === 'all' && rigidBodyRef.current && rapier) {
@@ -227,44 +153,47 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
227
153
  z: angularVelocity[2],
228
154
  }, true);
229
155
  }, [rbKey, angularVelocityKey]);
230
- // Event handlers for physics interactions
231
- const handleIntersectionEnter = useCallback((payload) => {
232
- if (!nodeId || !sensorEnterEventName)
156
+ const dispatchPhysicsEvent = useCallback((eventType, payload) => {
157
+ var _a, _b, _c;
158
+ if (!nodeId)
233
159
  return;
234
- gameEvents.emit(sensorEnterEventName, {
235
- sourceEntityId: nodeId,
236
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
237
- targetRigidBody: payload.other.rigidBody,
160
+ emitNativeEvent(eventType, {
161
+ sourceNodeId: nodeId,
162
+ sourceObject: getObject(),
163
+ sourceRigidBody: rigidBodyRef.current,
164
+ targetNodeId: getEntityIdFromRigidBody(payload.other.rigidBody),
165
+ targetObject: (_b = (_a = payload.other.rigidBodyObject) !== null && _a !== void 0 ? _a : payload.other.colliderObject) !== null && _b !== void 0 ? _b : null,
166
+ targetRigidBody: (_c = payload.other.rigidBody) !== null && _c !== void 0 ? _c : null,
167
+ rapierEvent: payload,
238
168
  });
239
- }, [nodeId, sensorEnterEventName]);
169
+ }, [getObject, nodeId]);
170
+ const handleIntersectionEnter = useCallback((payload) => {
171
+ if (!emitSensorEnterEvent)
172
+ return;
173
+ dispatchPhysicsEvent(sensorEnterEventName, payload);
174
+ }, [dispatchPhysicsEvent, emitSensorEnterEvent, sensorEnterEventName]);
240
175
  const handleIntersectionExit = useCallback((payload) => {
241
- if (!nodeId || !sensorExitEventName)
176
+ if (!emitSensorExitEvent)
242
177
  return;
243
- gameEvents.emit(sensorExitEventName, {
244
- sourceEntityId: nodeId,
245
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
246
- targetRigidBody: payload.other.rigidBody,
247
- });
248
- }, [nodeId, sensorExitEventName]);
178
+ dispatchPhysicsEvent(sensorExitEventName, payload);
179
+ }, [dispatchPhysicsEvent, emitSensorExitEvent, sensorExitEventName]);
249
180
  const handleCollisionEnter = useCallback((payload) => {
250
- if (!nodeId || !collisionEnterEventName)
181
+ if (!emitCollisionEnterEvent)
251
182
  return;
252
- gameEvents.emit(collisionEnterEventName, {
253
- sourceEntityId: nodeId,
254
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
255
- targetRigidBody: payload.other.rigidBody,
256
- });
257
- }, [collisionEnterEventName, nodeId]);
183
+ dispatchPhysicsEvent(collisionEnterEventName, payload);
184
+ }, [collisionEnterEventName, dispatchPhysicsEvent, emitCollisionEnterEvent]);
258
185
  const handleCollisionExit = useCallback((payload) => {
259
- if (!nodeId || !collisionExitEventName)
186
+ if (!emitCollisionExitEvent)
260
187
  return;
261
- gameEvents.emit(collisionExitEventName, {
262
- sourceEntityId: nodeId,
263
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
264
- targetRigidBody: payload.other.rigidBody,
265
- });
266
- }, [collisionExitEventName, nodeId]);
267
- return (_jsxs(RigidBody, Object.assign({ ref: rigidBodyRef, type: type, colliders: usesManualCapsuleCollider ? false : colliderType, position: position, rotation: rotation, scale: scale, sensor: sensor, enabledTranslations: enabledTranslations, enabledRotations: enabledRotations, userData: { entityId: nodeId }, onIntersectionEnter: handleIntersectionEnter, onIntersectionExit: handleIntersectionExit, onCollisionEnter: handleCollisionEnter, onCollisionExit: handleCollisionExit }, otherProps, { children: [usesManualCapsuleCollider ? _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor }) : null, children] }), rbKey));
188
+ dispatchPhysicsEvent(collisionExitEventName, payload);
189
+ }, [collisionExitEventName, dispatchPhysicsEvent, emitCollisionExitEvent]);
190
+ const rigidBodyProps = Object.assign({ ref: handleRigidBodyRef, type, colliders: usesManualCapsuleCollider ? false : colliderType, position,
191
+ rotation,
192
+ scale,
193
+ sensor,
194
+ enabledTranslations,
195
+ enabledRotations, name: nodeName, userData: Object.assign({ entityId: nodeId }, userData), onIntersectionEnter: emitSensorEnterEvent ? handleIntersectionEnter : undefined, onIntersectionExit: emitSensorExitEvent ? handleIntersectionExit : undefined, onCollisionEnter: emitCollisionEnterEvent ? handleCollisionEnter : undefined, onCollisionExit: emitCollisionExitEvent ? handleCollisionExit : undefined }, otherProps);
196
+ return (_jsxs(RigidBody, Object.assign({}, rigidBodyProps, { children: [usesManualCapsuleCollider ? _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor }) : null, children] }), rbKey));
268
197
  }
269
198
  const PhysicsComponent = {
270
199
  name: 'Physics',
@@ -279,6 +208,14 @@ const PhysicsComponent = {
279
208
  angularVelocity: [0, 0, 0],
280
209
  enabledTranslations: [true, true, true],
281
210
  enabledRotations: [true, true, true],
211
+ emitSensorEnterEvent: false,
212
+ sensorEnterEventName: '',
213
+ emitSensorExitEvent: false,
214
+ sensorExitEventName: '',
215
+ emitCollisionEnterEvent: false,
216
+ collisionEnterEventName: '',
217
+ emitCollisionExitEvent: false,
218
+ collisionExitEventName: '',
282
219
  }
283
220
  };
284
221
  export default PhysicsComponent;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useRef } from 'react';
3
3
  import { useHelper } from '@react-three/drei';
4
4
  import { PointLightHelper } from 'three';
5
- import { useEntityRuntime } from '../runtime';
5
+ import { useEntityRuntime } from '../assetRuntime';
6
6
  import { BooleanField, ColorField, NumberField } from './Input';
7
7
  import { LightSection, ShadowBiasField, mergeWithDefaults } from './lightUtils';
8
8
  const pointLightDefaults = {
@@ -3,9 +3,8 @@ import { useEffect, useRef } from 'react';
3
3
  import { useThree } from '@react-three/fiber';
4
4
  import { SoundPicker } from '../../assetviewer/page';
5
5
  import { sound as soundManager } from '../../../helpers/SoundManager';
6
- import { gameEvents } from '../GameEvents';
7
- import { useAssetRuntime, useEntityRuntime } from '../runtime';
8
- import { BooleanField, EventField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField } from './Input';
6
+ import { useAssetRuntime, useEntityRuntime } from '../assetRuntime';
7
+ import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
9
8
  import { colors } from '../styles';
10
9
  import { AudioListener } from 'three';
11
10
  const CLIP_MODE_OPTIONS = [
@@ -84,7 +83,7 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
84
83
  const removeClip = (index) => {
85
84
  setClips(clips.filter((_, clipIndex) => clipIndex !== index));
86
85
  };
87
- return (_jsxs(FieldGroup, { children: [_jsx(EventField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "click" }), _jsx(FieldRenderer, { fields: [
86
+ return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "player:footstep" }), _jsx(FieldRenderer, { fields: [
88
87
  {
89
88
  name: 'clipMode',
90
89
  label: 'Clip Mode',
@@ -115,14 +114,6 @@ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
115
114
  { value: 'exponential', label: 'Exponential' },
116
115
  ] })] })) : null, _jsx(BooleanField, { name: "randomizePitch", label: "Random Pitch", values: component.properties, onChange: onUpdate, fallback: false }), randomizePitch ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minPitch", label: "Min Pitch", values: component.properties, onChange: onUpdate, fallback: 0.96, step: 0.01, min: 0.1 }), _jsx(NumberField, { name: "maxPitch", label: "Max Pitch", values: component.properties, onChange: onUpdate, fallback: 1.04, step: 0.01, min: 0.1 })] })) : (_jsx(NumberField, { name: "pitch", label: "Pitch", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0.1 })), _jsx(BooleanField, { name: "randomizeVolume", label: "Random Volume", values: component.properties, onChange: onUpdate, fallback: false }), randomizeVolume ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "minVolume", label: "Min Volume", values: component.properties, onChange: onUpdate, fallback: 0.9, step: 0.01, min: 0 }), _jsx(NumberField, { name: "maxVolume", label: "Max Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 })] })) : (_jsx(NumberField, { name: "volume", label: "Volume", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.01, min: 0 }))] }));
117
116
  }
118
- function payloadMatchesNode(nodeId, payload) {
119
- if (!nodeId || !payload || typeof payload !== 'object')
120
- return true;
121
- const eventPayload = payload;
122
- const ids = [eventPayload.sourceEntityId, eventPayload.targetEntityId, eventPayload.instanceEntityId];
123
- const hasEntityIds = ids.some(id => typeof id === 'string');
124
- return hasEntityIds ? ids.includes(nodeId) : true;
125
- }
126
117
  function SoundComponentView({ properties, children }) {
127
118
  const { getSound } = useAssetRuntime();
128
119
  const { editMode, nodeId } = useEntityRuntime();
@@ -165,12 +156,17 @@ function SoundComponentView({ properties, children }) {
165
156
  audio.setDistanceModel(distanceModel);
166
157
  }, [distanceModel, maxDistance, refDistance, rolloffFactor]);
167
158
  useEffect(() => {
168
- if (editMode || paths.length === 0 || !eventName) {
159
+ if (editMode || paths.length === 0 || !eventName || typeof window === 'undefined') {
169
160
  return;
170
161
  }
171
- return gameEvents.on(eventName, (payload) => {
172
- if (!payloadMatchesNode(nodeId, payload)) {
173
- return;
162
+ const handleEvent = (event) => {
163
+ const customEvent = event;
164
+ const detail = customEvent.detail;
165
+ if (nodeId && detail && typeof detail === 'object') {
166
+ const relatedNodeIds = [detail.nodeId, detail.sourceNodeId, detail.targetNodeId].filter((value) => typeof value === 'string');
167
+ if (relatedNodeIds.length > 0 && !relatedNodeIds.includes(nodeId)) {
168
+ return;
169
+ }
174
170
  }
175
171
  const clip = pickClip(paths, mode, sequenceIndexRef);
176
172
  if (!clip)
@@ -204,7 +200,11 @@ function SoundComponentView({ properties, children }) {
204
200
  audio.setPlaybackRate(pitch);
205
201
  audio.setVolume(volume);
206
202
  audio.play();
207
- });
203
+ };
204
+ window.addEventListener(eventName, handleEvent);
205
+ return () => {
206
+ window.removeEventListener(eventName, handleEvent);
207
+ };
208
208
  }, [editMode, eventName, getSound, mode, nodeId, paths, positional, properties]);
209
209
  return (_jsxs(_Fragment, { children: [positional && listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
210
210
  }
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
3
3
  import { useRef, useEffect } from "react";
4
4
  import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
5
5
  import { SpotLightHelper } from "three";
6
- import { useAssetRuntime, useEntityRuntime } from "../runtime";
6
+ import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
7
7
  import { useFrame } from "@react-three/fiber";
8
8
  import { TexturePicker } from "../../assetviewer/page";
9
9
  import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
@@ -11,8 +11,8 @@ import DirectionalLightComponent from './DirectionalLightComponent';
11
11
  import AmbientLightComponent from './AmbientLightComponent';
12
12
  import EnvironmentComponent from './EnvironmentComponent';
13
13
  import CameraComponent from './CameraComponent';
14
- import ClickComponent from './ClickComponent';
15
14
  import SoundComponent from './SoundComponent';
15
+ import DataComponent from './DataComponent';
16
16
  export const builtinComponents = [
17
17
  TransformComponent,
18
18
  GeometryComponent,
@@ -27,6 +27,6 @@ export const builtinComponents = [
27
27
  AmbientLightComponent,
28
28
  EnvironmentComponent,
29
29
  CameraComponent,
30
- ClickComponent,
31
30
  SoundComponent,
31
+ DataComponent,
32
32
  ];
@@ -29,4 +29,5 @@ export declare function findComponent(node: ComponentHost | null | undefined, na
29
29
  export declare function findComponentEntry(node: ComponentHost | null | undefined, name: string): [string, ComponentData] | undefined;
30
30
  /** Check if a node has a component of the given type. */
31
31
  export declare function hasComponent(node: ComponentHost | null | undefined, typeName: string): boolean;
32
+ export declare function getNodeUserData(node: GameObject | null | undefined): Record<string, unknown>;
32
33
  export {};
@@ -1,3 +1,7 @@
1
+ const RESERVED_USER_DATA_KEYS = new Set([
2
+ 'prefabNodeId',
3
+ 'prefabNodeName',
4
+ ]);
1
5
  /** Find a component on a node by type name or key (e.g. "Model", "transform"). */
2
6
  export function findComponent(node, name) {
3
7
  var _a;
@@ -26,3 +30,17 @@ export function findComponentEntry(node, name) {
26
30
  export function hasComponent(node, typeName) {
27
31
  return findComponentEntry(node, typeName) !== undefined;
28
32
  }
33
+ export function getNodeUserData(node) {
34
+ var _a, _b;
35
+ const data = (_b = (_a = findComponent(node, 'Data')) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.data;
36
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
37
+ return {};
38
+ }
39
+ return Object.entries(data).reduce((result, [key, value]) => {
40
+ if (!key.trim() || RESERVED_USER_DATA_KEYS.has(key) || value === undefined) {
41
+ return result;
42
+ }
43
+ result[key] = value;
44
+ return result;
45
+ }, {});
46
+ }
@@ -0,0 +1,27 @@
1
+ import type { ThreeEvent } from "@react-three/fiber";
2
+ export type PointerHandler<T> = (event: ThreeEvent<PointerEvent>, entity: T) => void;
3
+ export interface PointerEventHandlers<T> {
4
+ onClick?: PointerHandler<T>;
5
+ onPointerDown?: PointerHandler<T>;
6
+ onPointerUp?: PointerHandler<T>;
7
+ onPointerMove?: PointerHandler<T>;
8
+ onPointerEnter?: PointerHandler<T>;
9
+ onPointerLeave?: PointerHandler<T>;
10
+ onPointerOver?: PointerHandler<T>;
11
+ onPointerOut?: PointerHandler<T>;
12
+ }
13
+ export interface UsePointerEventsOptions<T> extends PointerEventHandlers<T> {
14
+ enabled: boolean;
15
+ entity: T | null | undefined;
16
+ }
17
+ export declare function hasPointerEventHandlers<T>(handlers: PointerEventHandlers<T>): boolean;
18
+ export declare function usePointerEvents<T>({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }: UsePointerEventsOptions<T>): {
19
+ onClick: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
20
+ onPointerDown: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
21
+ onPointerMove: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
22
+ onPointerUp: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
23
+ onPointerEnter: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
24
+ onPointerLeave: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
25
+ onPointerOver: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
26
+ onPointerOut: ((event: ThreeEvent<PointerEvent>) => void) | undefined;
27
+ };
@@ -0,0 +1,52 @@
1
+ export function hasPointerEventHandlers(handlers) {
2
+ return Boolean(handlers.onClick
3
+ || handlers.onPointerDown
4
+ || handlers.onPointerUp
5
+ || handlers.onPointerMove
6
+ || handlers.onPointerEnter
7
+ || handlers.onPointerLeave
8
+ || handlers.onPointerOver
9
+ || handlers.onPointerOut);
10
+ }
11
+ export function usePointerEvents({ enabled, entity, onClick, onPointerDown, onPointerUp, onPointerMove, onPointerEnter, onPointerLeave, onPointerOver, onPointerOut, }) {
12
+ if (!enabled) {
13
+ return {
14
+ onClick: undefined,
15
+ onPointerDown: undefined,
16
+ onPointerMove: undefined,
17
+ onPointerUp: undefined,
18
+ onPointerEnter: undefined,
19
+ onPointerLeave: undefined,
20
+ onPointerOver: undefined,
21
+ onPointerOut: undefined,
22
+ };
23
+ }
24
+ const forward = (handler) => {
25
+ if (!handler)
26
+ return undefined;
27
+ return (event) => {
28
+ event.stopPropagation();
29
+ if (!entity)
30
+ return;
31
+ handler(event, entity);
32
+ };
33
+ };
34
+ const forwardMove = onPointerMove
35
+ ? (event) => {
36
+ event.stopPropagation();
37
+ if (!entity)
38
+ return;
39
+ onPointerMove(event, entity);
40
+ }
41
+ : undefined;
42
+ return {
43
+ onClick: forward(onClick),
44
+ onPointerDown: forward(onPointerDown),
45
+ onPointerMove: forwardMove,
46
+ onPointerUp: forward(onPointerUp),
47
+ onPointerEnter: forward(onPointerEnter),
48
+ onPointerLeave: forward(onPointerLeave),
49
+ onPointerOver: forward(onPointerOver),
50
+ onPointerOut: forward(onPointerOut),
51
+ };
52
+ }