react-three-game 0.0.85 → 0.0.87

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 +5 -7
  3. package/dist/index.js +2 -4
  4. package/dist/tools/prefabeditor/GameEvents.d.ts +36 -117
  5. package/dist/tools/prefabeditor/GameEvents.js +44 -96
  6. package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
  7. package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
  8. package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
  9. package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
  10. package/dist/tools/prefabeditor/PrefabRoot.js +93 -44
  11. package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
  12. package/dist/tools/prefabeditor/assetRuntime.js +37 -0
  13. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +4 -2
  14. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
  15. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
  16. package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
  17. package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
  18. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
  19. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
  20. package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
  21. package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
  22. package/dist/tools/prefabeditor/components/Input.js +0 -55
  23. package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
  24. package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
  25. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
  26. package/dist/tools/prefabeditor/components/PhysicsComponent.js +64 -130
  27. package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
  28. package/dist/tools/prefabeditor/components/SoundComponent.js +18 -11
  29. package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
  30. package/dist/tools/prefabeditor/components/index.js +2 -2
  31. package/dist/tools/prefabeditor/types.d.ts +1 -0
  32. package/dist/tools/prefabeditor/types.js +18 -0
  33. package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
  34. package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
  35. package/package.json +1 -1
  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
@@ -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[];
@@ -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,12 @@ 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";
14
+ import { useCallback, useEffect, useRef } from 'react';
15
+ import { useAssetRuntime, useEntityRuntime } from "../assetRuntime";
17
16
  import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
17
+ import { usePrefabNode } from "../prefabStore";
18
+ import { BooleanField, FieldGroup, NumberField, SelectField, StringField, Vector3Field } from "./Input";
19
+ import { getNodeUserData } from "../types";
18
20
  import { colors } from "../styles";
19
21
  export function isPhysicsProps(v) {
20
22
  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 +24,6 @@ export function isPhysicsProps(v) {
22
24
  const enabledAxesFallback = [true, true, true];
23
25
  const capsuleRadiusFallback = 0.35;
24
26
  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
27
  function LockedAxisField({ label, name, values, onChange, }) {
110
28
  const enabledAxes = Array.isArray(values[name])
111
29
  ? values[name]
@@ -158,15 +76,19 @@ function PhysicsComponentEditor({ component, onUpdate }) {
158
76
  { value: 'cuboid', label: 'Cuboid (box)' },
159
77
  { value: 'ball', label: 'Ball (sphere)' },
160
78
  { 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: [
79
+ ] }), 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
80
  { value: '', label: 'Default (Dynamic only)' },
163
81
  { value: 'all', label: 'All (includes kinematic & fixed)' },
164
82
  ] })] }));
165
83
  }
166
84
  function PhysicsComponentView({ properties, children, position, rotation, scale }) {
85
+ var _a, _b, _c;
167
86
  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"]);
87
+ const { editMode, nodeId, getObject } = useEntityRuntime();
88
+ const gameObject = usePrefabNode(nodeId);
89
+ 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 : '';
90
+ 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));
91
+ 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
92
  const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
171
93
  const usesManualCapsuleCollider = colliderType === 'capsule';
172
94
  const rigidBodyRef = useRef(null);
@@ -175,6 +97,12 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
175
97
  const rbKey = editMode
176
98
  ? `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
177
99
  : `${type || 'dynamic'}_${colliderType}_${capsuleRadius}_${capsuleHalfHeight}`;
100
+ const handleRigidBodyRef = useCallback((rigidBody) => {
101
+ rigidBodyRef.current = rigidBody;
102
+ if (!nodeId)
103
+ return;
104
+ registerRigidBodyRef(nodeId, rigidBody);
105
+ }, [nodeId, registerRigidBodyRef]);
178
106
  // Try to get rapier context - will be null if not inside <Physics>
179
107
  let rapier = null;
180
108
  try {
@@ -184,17 +112,6 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
184
112
  catch (e) {
185
113
  // Not inside Physics context - that's ok, just won't have rapier features
186
114
  }
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
115
  // Configure active collision types for kinematic/sensor bodies
199
116
  useEffect(() => {
200
117
  if (activeCollisionTypes === 'all' && rigidBodyRef.current && rapier) {
@@ -227,44 +144,53 @@ function PhysicsComponentView({ properties, children, position, rotation, scale
227
144
  z: angularVelocity[2],
228
145
  }, true);
229
146
  }, [rbKey, angularVelocityKey]);
230
- // Event handlers for physics interactions
231
- const handleIntersectionEnter = useCallback((payload) => {
232
- if (!nodeId || !sensorEnterEventName)
147
+ const dispatchPhysicsEvent = useCallback((eventType, payload) => {
148
+ var _a, _b, _c;
149
+ if (!nodeId)
150
+ return;
151
+ const trimmedEventType = eventType === null || eventType === void 0 ? void 0 : eventType.trim();
152
+ if (!trimmedEventType)
233
153
  return;
234
- gameEvents.emit(sensorEnterEventName, {
154
+ const targetEntityId = getEntityIdFromRigidBody(payload.other.rigidBody);
155
+ gameEvents.emit(trimmedEventType, {
235
156
  sourceEntityId: nodeId,
236
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
237
- targetRigidBody: payload.other.rigidBody,
157
+ sourceNodeId: nodeId,
158
+ sourceObject: getObject(),
159
+ sourceRigidBody: rigidBodyRef.current,
160
+ targetEntityId,
161
+ targetNodeId: targetEntityId,
162
+ targetObject: (_b = (_a = payload.other.rigidBodyObject) !== null && _a !== void 0 ? _a : payload.other.colliderObject) !== null && _b !== void 0 ? _b : null,
163
+ targetRigidBody: (_c = payload.other.rigidBody) !== null && _c !== void 0 ? _c : null,
164
+ rapierEvent: payload,
238
165
  });
239
- }, [nodeId, sensorEnterEventName]);
166
+ }, [getObject, nodeId]);
167
+ const handleIntersectionEnter = useCallback((payload) => {
168
+ if (!emitSensorEnterEvent)
169
+ return;
170
+ dispatchPhysicsEvent(sensorEnterEventName, payload);
171
+ }, [dispatchPhysicsEvent, emitSensorEnterEvent, sensorEnterEventName]);
240
172
  const handleIntersectionExit = useCallback((payload) => {
241
- if (!nodeId || !sensorExitEventName)
173
+ if (!emitSensorExitEvent)
242
174
  return;
243
- gameEvents.emit(sensorExitEventName, {
244
- sourceEntityId: nodeId,
245
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
246
- targetRigidBody: payload.other.rigidBody,
247
- });
248
- }, [nodeId, sensorExitEventName]);
175
+ dispatchPhysicsEvent(sensorExitEventName, payload);
176
+ }, [dispatchPhysicsEvent, emitSensorExitEvent, sensorExitEventName]);
249
177
  const handleCollisionEnter = useCallback((payload) => {
250
- if (!nodeId || !collisionEnterEventName)
178
+ if (!emitCollisionEnterEvent)
251
179
  return;
252
- gameEvents.emit(collisionEnterEventName, {
253
- sourceEntityId: nodeId,
254
- targetEntityId: getEntityIdFromRigidBody(payload.other.rigidBody),
255
- targetRigidBody: payload.other.rigidBody,
256
- });
257
- }, [collisionEnterEventName, nodeId]);
180
+ dispatchPhysicsEvent(collisionEnterEventName, payload);
181
+ }, [collisionEnterEventName, dispatchPhysicsEvent, emitCollisionEnterEvent]);
258
182
  const handleCollisionExit = useCallback((payload) => {
259
- if (!nodeId || !collisionExitEventName)
183
+ if (!emitCollisionExitEvent)
260
184
  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));
185
+ dispatchPhysicsEvent(collisionExitEventName, payload);
186
+ }, [collisionExitEventName, dispatchPhysicsEvent, emitCollisionExitEvent]);
187
+ const rigidBodyProps = Object.assign({ ref: handleRigidBodyRef, type, colliders: usesManualCapsuleCollider ? false : colliderType, position,
188
+ rotation,
189
+ scale,
190
+ sensor,
191
+ enabledTranslations,
192
+ 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);
193
+ return (_jsxs(RigidBody, Object.assign({}, rigidBodyProps, { children: [usesManualCapsuleCollider ? _jsx(CapsuleCollider, { args: [capsuleHalfHeight, capsuleRadius], sensor: sensor }) : null, children] }), rbKey));
268
194
  }
269
195
  const PhysicsComponent = {
270
196
  name: 'Physics',
@@ -279,6 +205,14 @@ const PhysicsComponent = {
279
205
  angularVelocity: [0, 0, 0],
280
206
  enabledTranslations: [true, true, true],
281
207
  enabledRotations: [true, true, true],
208
+ emitSensorEnterEvent: false,
209
+ sensorEnterEventName: '',
210
+ emitSensorExitEvent: false,
211
+ sensorExitEventName: '',
212
+ emitCollisionEnterEvent: false,
213
+ collisionEnterEventName: '',
214
+ emitCollisionExitEvent: false,
215
+ collisionExitEventName: '',
282
216
  }
283
217
  };
284
218
  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 = {