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.
- package/README.md +87 -35
- package/dist/index.d.ts +3 -7
- package/dist/index.js +1 -4
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +0 -4
- package/dist/tools/prefabeditor/InstanceProvider.js +13 -44
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +7 -2
- package/dist/tools/prefabeditor/PrefabEditor.js +13 -24
- package/dist/tools/prefabeditor/PrefabRoot.js +94 -44
- package/dist/tools/prefabeditor/{runtime.d.ts → assetRuntime.d.ts} +0 -25
- package/dist/tools/prefabeditor/assetRuntime.js +37 -0
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +4 -2
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +0 -3
- package/dist/tools/prefabeditor/components/DataComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/DataComponent.js +87 -0
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
- package/dist/tools/prefabeditor/components/GeometryComponent.js +4 -2
- package/dist/tools/prefabeditor/components/Input.d.ts +2 -13
- package/dist/tools/prefabeditor/components/Input.js +0 -55
- package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +3 -3
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +4 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +69 -132
- package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SoundComponent.js +17 -17
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.js +2 -2
- package/dist/tools/prefabeditor/types.d.ts +1 -0
- package/dist/tools/prefabeditor/types.js +18 -0
- package/dist/tools/prefabeditor/usePointerEvents.d.ts +27 -0
- package/dist/tools/prefabeditor/usePointerEvents.js +52 -0
- package/package.json +1 -1
- package/dist/tools/prefabeditor/GameEvents.d.ts +0 -128
- package/dist/tools/prefabeditor/GameEvents.js +0 -118
- package/dist/tools/prefabeditor/components/ClickComponent.d.ts +0 -3
- package/dist/tools/prefabeditor/components/ClickComponent.js +0 -52
- 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 '../
|
|
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 '../
|
|
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 {
|
|
15
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
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(
|
|
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
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
if (!nodeId
|
|
156
|
+
const dispatchPhysicsEvent = useCallback((eventType, payload) => {
|
|
157
|
+
var _a, _b, _c;
|
|
158
|
+
if (!nodeId)
|
|
233
159
|
return;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
}, [
|
|
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 (!
|
|
176
|
+
if (!emitSensorExitEvent)
|
|
242
177
|
return;
|
|
243
|
-
|
|
244
|
-
|
|
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 (!
|
|
181
|
+
if (!emitCollisionEnterEvent)
|
|
251
182
|
return;
|
|
252
|
-
|
|
253
|
-
|
|
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 (!
|
|
186
|
+
if (!emitCollisionExitEvent)
|
|
260
187
|
return;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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 '../
|
|
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 {
|
|
7
|
-
import {
|
|
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(
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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 "../
|
|
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
|
+
}
|