react-three-game 0.0.69 → 0.0.71

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 (64) hide show
  1. package/dist/helpers/SoundManager.d.ts +2 -0
  2. package/dist/helpers/SoundManager.js +6 -0
  3. package/dist/index.d.ts +20 -13
  4. package/dist/index.js +14 -7
  5. package/dist/shared/GameCanvas.js +0 -2
  6. package/dist/tools/assetviewer/page.d.ts +5 -0
  7. package/dist/tools/assetviewer/page.js +3 -0
  8. package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -2
  9. package/dist/tools/dragdrop/DragDropLoader.js +18 -3
  10. package/dist/tools/dragdrop/index.d.ts +2 -2
  11. package/dist/tools/dragdrop/index.js +1 -1
  12. package/dist/tools/dragdrop/modelLoader.d.ts +10 -0
  13. package/dist/tools/dragdrop/modelLoader.js +60 -0
  14. package/dist/tools/prefabeditor/EditorTree.js +6 -40
  15. package/dist/tools/prefabeditor/EditorTreeMenus.js +2 -20
  16. package/dist/tools/prefabeditor/EditorUI.js +8 -5
  17. package/dist/tools/prefabeditor/InstanceProvider.d.ts +2 -0
  18. package/dist/tools/prefabeditor/InstanceProvider.js +54 -52
  19. package/dist/tools/prefabeditor/PrefabEditor.d.ts +23 -1
  20. package/dist/tools/prefabeditor/PrefabEditor.js +79 -47
  21. package/dist/tools/prefabeditor/PrefabRoot.d.ts +26 -9
  22. package/dist/tools/prefabeditor/PrefabRoot.js +195 -159
  23. package/dist/tools/prefabeditor/RefBridge.d.ts +24 -0
  24. package/dist/tools/prefabeditor/RefBridge.js +44 -0
  25. package/dist/tools/prefabeditor/components/AmbientLightComponent.js +10 -7
  26. package/dist/tools/prefabeditor/components/CameraComponent.js +8 -14
  27. package/dist/tools/prefabeditor/components/ClickComponent.js +12 -7
  28. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +31 -5
  29. package/dist/tools/prefabeditor/components/ComponentRegistry.js +6 -6
  30. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +124 -52
  31. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +5 -3
  32. package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -1
  33. package/dist/tools/prefabeditor/components/Input.d.ts +16 -0
  34. package/dist/tools/prefabeditor/components/Input.js +33 -0
  35. package/dist/tools/prefabeditor/components/MaterialComponent.js +19 -8
  36. package/dist/tools/prefabeditor/components/ModelComponent.js +39 -45
  37. package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +10 -1
  38. package/dist/tools/prefabeditor/components/PhysicsComponent.js +127 -31
  39. package/dist/tools/prefabeditor/components/PointLightComponent.d.ts +3 -0
  40. package/dist/tools/prefabeditor/components/PointLightComponent.js +55 -0
  41. package/dist/tools/prefabeditor/components/SoundComponent.d.ts +3 -0
  42. package/dist/tools/prefabeditor/components/SoundComponent.js +244 -0
  43. package/dist/tools/prefabeditor/components/SpotLightComponent.js +53 -24
  44. package/dist/tools/prefabeditor/components/TransformComponent.js +2 -2
  45. package/dist/tools/prefabeditor/components/index.js +4 -0
  46. package/dist/tools/prefabeditor/components/lightUtils.d.ts +13 -0
  47. package/dist/tools/prefabeditor/components/lightUtils.js +64 -0
  48. package/dist/tools/prefabeditor/prefab.d.ts +37 -0
  49. package/dist/tools/prefabeditor/prefab.js +229 -0
  50. package/dist/tools/prefabeditor/prefabStore.d.ts +4 -16
  51. package/dist/tools/prefabeditor/prefabStore.js +32 -173
  52. package/dist/tools/prefabeditor/{sceneApi.d.ts → scene.d.ts} +15 -1
  53. package/dist/tools/prefabeditor/{sceneApi.js → scene.js} +66 -32
  54. package/dist/tools/prefabeditor/styles.d.ts +1 -0
  55. package/dist/tools/prefabeditor/styles.js +9 -0
  56. package/dist/tools/prefabeditor/types.d.ts +13 -0
  57. package/dist/tools/prefabeditor/types.js +28 -1
  58. package/dist/tools/prefabeditor/useClickValid.d.ts +13 -0
  59. package/dist/tools/prefabeditor/useClickValid.js +21 -0
  60. package/dist/tools/prefabeditor/utils.d.ts +2 -4
  61. package/dist/tools/prefabeditor/utils.js +8 -46
  62. package/package.json +1 -1
  63. package/dist/tools/prefabeditor/EditorContext.d.ts +0 -16
  64. package/dist/tools/prefabeditor/EditorContext.js +0 -9
@@ -0,0 +1,244 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { useThree } from '@react-three/fiber';
4
+ import { SoundPicker } from '../../assetviewer/page';
5
+ import { sound as soundManager } from '../../../helpers/SoundManager';
6
+ import { gameEvents } from '../GameEvents';
7
+ import { useSceneRuntime } from '../PrefabRoot';
8
+ import { BooleanField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField, StringField } from './Input';
9
+ import { colors } from '../styles';
10
+ import { AudioListener } from 'three';
11
+ const CLIP_MODE_OPTIONS = [
12
+ { value: 'single', label: 'Single Clip' },
13
+ { value: 'random', label: 'Random Clip' },
14
+ { value: 'sequence', label: 'Sequence' },
15
+ ];
16
+ let sharedAudioListener = null;
17
+ function getSharedAudioListener() {
18
+ if (!sharedAudioListener) {
19
+ sharedAudioListener = new AudioListener();
20
+ }
21
+ return sharedAudioListener;
22
+ }
23
+ function normalizeClips(clips) {
24
+ return (clips !== null && clips !== void 0 ? clips : []).map(clip => clip.trim()).filter(Boolean);
25
+ }
26
+ function clampRange(min, max, fallbackMin, fallbackMax) {
27
+ const safeMin = Number.isFinite(min) ? Number(min) : fallbackMin;
28
+ const safeMax = Number.isFinite(max) ? Number(max) : fallbackMax;
29
+ return safeMin <= safeMax ? [safeMin, safeMax] : [safeMax, safeMin];
30
+ }
31
+ function sampleRange(min, max) {
32
+ return min + Math.random() * (max - min);
33
+ }
34
+ function getPitchValue(properties) {
35
+ if (properties.randomizePitch) {
36
+ const [pitchFloor, pitchCeiling] = clampRange(properties.minPitch, properties.maxPitch, 0.96, 1.04);
37
+ return sampleRange(pitchFloor, pitchCeiling);
38
+ }
39
+ return Number.isFinite(properties.pitch) ? Number(properties.pitch) : 1;
40
+ }
41
+ function getVolumeValue(properties) {
42
+ if (properties.randomizeVolume) {
43
+ const [volumeFloor, volumeCeiling] = clampRange(properties.minVolume, properties.maxVolume, 0.9, 1);
44
+ return sampleRange(volumeFloor, volumeCeiling);
45
+ }
46
+ return Number.isFinite(properties.volume) ? Number(properties.volume) : 1;
47
+ }
48
+ function resolveClipPaths({ clips, clipMode }) {
49
+ const normalizedClips = normalizeClips(clips);
50
+ if (normalizedClips.length > 0) {
51
+ return { paths: normalizedClips, mode: clipMode !== null && clipMode !== void 0 ? clipMode : 'random' };
52
+ }
53
+ return { paths: [], mode: 'single' };
54
+ }
55
+ function pickClip(paths, mode, sequenceIndexRef) {
56
+ if (paths.length <= 1 || mode === 'single') {
57
+ return paths[0];
58
+ }
59
+ if (mode === 'sequence') {
60
+ const clip = paths[sequenceIndexRef.current % paths.length];
61
+ sequenceIndexRef.current += 1;
62
+ return clip;
63
+ }
64
+ return paths[Math.floor(Math.random() * paths.length)];
65
+ }
66
+ function SoundComponentEditor({ component, onUpdate, basePath = '' }) {
67
+ const clips = Array.isArray(component.properties.clips)
68
+ ? component.properties.clips.map((clip) => typeof clip === 'string' ? clip : '')
69
+ : [];
70
+ const randomizePitch = Boolean(component.properties.randomizePitch);
71
+ const randomizeVolume = Boolean(component.properties.randomizeVolume);
72
+ const positional = Boolean(component.properties.positional);
73
+ const setClips = (nextClips) => {
74
+ onUpdate({ clips: nextClips });
75
+ };
76
+ const addClip = () => {
77
+ setClips([...clips, '']);
78
+ };
79
+ const updateClip = (index, nextPath) => {
80
+ const nextClips = [...clips];
81
+ nextClips[index] = nextPath;
82
+ setClips(nextClips);
83
+ };
84
+ const removeClip = (index) => {
85
+ setClips(clips.filter((_, clipIndex) => clipIndex !== index));
86
+ };
87
+ return (_jsxs(FieldGroup, { children: [_jsx(StringField, { name: "eventName", label: "Listen Event", values: component.properties, onChange: onUpdate, placeholder: "click" }), _jsx(FieldRenderer, { fields: [
88
+ {
89
+ name: 'clipMode',
90
+ label: 'Clip Mode',
91
+ type: 'select',
92
+ options: CLIP_MODE_OPTIONS.map(option => ({ value: option.value, label: option.label })),
93
+ },
94
+ ], values: component.properties, onChange: onUpdate }), _jsx(ListEditor, { label: "Clips", items: clips, onAdd: addClip, emptyMessage: "No clips added.", addButtonTitle: "Add clip", addDisabledTitle: "Add clip", renderItem: (clip, index) => (_jsxs("div", { style: {
95
+ display: 'flex',
96
+ gap: 6,
97
+ alignItems: 'end',
98
+ padding: 8,
99
+ border: `1px solid ${colors.border}`,
100
+ borderRadius: 4,
101
+ background: colors.bgSurface,
102
+ }, children: [_jsx("div", { style: { flex: 1, minWidth: 0 }, children: _jsx(SoundPicker, { value: clip || undefined, onChange: (nextPath) => updateClip(index, nextPath !== null && nextPath !== void 0 ? nextPath : ''), basePath: basePath }) }), _jsx("button", { type: "button", onClick: () => removeClip(index), style: {
103
+ height: 24,
104
+ width: 28,
105
+ borderRadius: 3,
106
+ border: `1px solid ${colors.border}`,
107
+ background: colors.bgInput,
108
+ color: colors.text,
109
+ cursor: 'pointer',
110
+ padding: 0,
111
+ flexShrink: 0,
112
+ }, title: "Remove clip", children: "\u00D7" })] }, `${clip}-${index}`)) }), _jsx(BooleanField, { name: "positional", label: "Positional", values: component.properties, onChange: onUpdate, fallback: false }), positional ? (_jsxs(_Fragment, { children: [_jsx(NumberField, { name: "refDistance", label: "Ref Distance", values: component.properties, onChange: onUpdate, fallback: 1, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "maxDistance", label: "Max Distance", values: component.properties, onChange: onUpdate, fallback: 24, min: 0.01, step: 0.1 }), _jsx(NumberField, { name: "rolloffFactor", label: "Rolloff", values: component.properties, onChange: onUpdate, fallback: 1, min: 0, step: 0.1 }), _jsx(SelectField, { name: "distanceModel", label: "Distance Model", values: component.properties, onChange: onUpdate, fallback: "inverse", options: [
113
+ { value: 'inverse', label: 'Inverse' },
114
+ { value: 'linear', label: 'Linear' },
115
+ { value: 'exponential', label: 'Exponential' },
116
+ ] })] })) : 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
+ }
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
+ function SoundComponentView({ properties, editMode, nodeId, children }) {
127
+ const { getSound } = useSceneRuntime();
128
+ const { camera } = useThree();
129
+ const { eventName, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = 'inverse' } = properties;
130
+ const sequenceIndexRef = useRef(0);
131
+ const listenerRef = useRef(null);
132
+ const positionalAudioRef = useRef(null);
133
+ const { paths, mode } = resolveClipPaths(properties);
134
+ if (!listenerRef.current) {
135
+ listenerRef.current = getSharedAudioListener();
136
+ }
137
+ useEffect(() => {
138
+ var _a;
139
+ if (!positional) {
140
+ return;
141
+ }
142
+ const listener = listenerRef.current;
143
+ if (!listener) {
144
+ return;
145
+ }
146
+ if (listener.parent !== camera) {
147
+ (_a = listener.parent) === null || _a === void 0 ? void 0 : _a.remove(listener);
148
+ camera.add(listener);
149
+ }
150
+ return () => {
151
+ if (listener.parent === camera) {
152
+ camera.remove(listener);
153
+ }
154
+ };
155
+ }, [camera, positional]);
156
+ useEffect(() => {
157
+ const audio = positionalAudioRef.current;
158
+ if (!audio) {
159
+ return;
160
+ }
161
+ audio.setRefDistance(refDistance);
162
+ audio.setMaxDistance(maxDistance);
163
+ audio.setRolloffFactor(rolloffFactor);
164
+ audio.setDistanceModel(distanceModel);
165
+ }, [distanceModel, maxDistance, refDistance, rolloffFactor]);
166
+ useEffect(() => {
167
+ if (editMode || paths.length === 0 || !eventName) {
168
+ return;
169
+ }
170
+ return gameEvents.on(eventName, (payload) => {
171
+ if (!payloadMatchesNode(nodeId, payload)) {
172
+ return;
173
+ }
174
+ const clip = pickClip(paths, mode, sequenceIndexRef);
175
+ if (!clip)
176
+ return;
177
+ const pitch = getPitchValue(properties);
178
+ const volume = getVolumeValue(properties);
179
+ if (!positional) {
180
+ const loadedBuffer = getSound(clip);
181
+ if (loadedBuffer && !soundManager.hasBuffer(clip)) {
182
+ soundManager.setBuffer(clip, loadedBuffer);
183
+ }
184
+ if (soundManager.hasBuffer(clip)) {
185
+ soundManager.playSync(clip, { pitch, volume });
186
+ return;
187
+ }
188
+ void soundManager.play(clip, { pitch, volume });
189
+ return;
190
+ }
191
+ const audio = positionalAudioRef.current;
192
+ const listener = listenerRef.current;
193
+ const buffer = getSound(clip);
194
+ if (!audio || !listener || !buffer) {
195
+ return;
196
+ }
197
+ void listener.context.resume();
198
+ if (audio.isPlaying) {
199
+ audio.stop();
200
+ }
201
+ audio.setBuffer(buffer);
202
+ audio.setLoop(false);
203
+ audio.setPlaybackRate(pitch);
204
+ audio.setVolume(volume);
205
+ audio.play();
206
+ });
207
+ }, [editMode, eventName, getSound, mode, nodeId, paths, positional, properties]);
208
+ return (_jsxs(_Fragment, { children: [positional && listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
209
+ }
210
+ const SoundComponent = {
211
+ name: 'Sound',
212
+ Editor: SoundComponentEditor,
213
+ View: SoundComponentView,
214
+ defaultProperties: {
215
+ eventName: '',
216
+ clips: [],
217
+ clipMode: 'single',
218
+ positional: false,
219
+ refDistance: 1,
220
+ maxDistance: 24,
221
+ rolloffFactor: 1,
222
+ distanceModel: 'inverse',
223
+ pitch: 1,
224
+ randomizePitch: false,
225
+ minPitch: 0.96,
226
+ maxPitch: 1.04,
227
+ volume: 1,
228
+ randomizeVolume: false,
229
+ minVolume: 0.9,
230
+ maxVolume: 1,
231
+ },
232
+ getAssetRefs: (properties) => {
233
+ const refs = [];
234
+ if (Array.isArray(properties.clips)) {
235
+ properties.clips.forEach((clip) => {
236
+ if (typeof clip === 'string' && clip.trim().length > 0) {
237
+ refs.push({ type: 'sound', path: clip });
238
+ }
239
+ });
240
+ }
241
+ return refs;
242
+ },
243
+ };
244
+ export default SoundComponent;
@@ -1,56 +1,85 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useRef, useEffect, useMemo, useState } from "react";
3
- import { BooleanField, ColorField, FieldGroup, Label, NumberField } from "./Input";
2
+ import { useHelper } from "@react-three/drei";
3
+ import { useRef, useEffect } from "react";
4
+ import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
4
5
  import { SpotLightHelper } from "three";
6
+ import { useSceneRuntime } from "../PrefabRoot";
5
7
  import { useFrame } from "@react-three/fiber";
6
8
  import { TexturePicker } from "../../assetviewer/page";
9
+ import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
7
10
  const spotLightDefaults = {
8
11
  color: '#ffffff',
9
- intensity: 200,
10
- angle: 0.5,
11
- penumbra: 0.5,
12
- distance: 100,
13
- castShadow: true,
12
+ intensity: 1,
13
+ angle: Math.PI / 3,
14
+ penumbra: 0,
15
+ distance: 0,
16
+ decay: 2,
17
+ castShadow: false,
18
+ shadowMapSize: 512,
19
+ shadowBias: 0,
20
+ shadowNormalBias: 0,
21
+ shadowAutoUpdate: true,
22
+ shadowCameraNear: 0.5,
23
+ shadowCameraFar: 500,
24
+ targetOffset: [0, -5, 0],
25
+ map: undefined,
14
26
  };
15
27
  function SpotLightComponentEditor({ component, onUpdate, basePath = "" }) {
16
- const values = Object.assign(Object.assign({}, spotLightDefaults), component.properties);
17
- return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 200 }), _jsx(NumberField, { name: "angle", label: "Angle", values: values, onChange: onUpdate, min: 0, max: Math.PI, step: 0.05, fallback: 0.5 }), _jsx(NumberField, { name: "penumbra", label: "Penumbra", values: values, onChange: onUpdate, min: 0, max: 1, step: 0.05, fallback: 0.5 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 100 }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: true }), _jsxs("div", { children: [_jsx(Label, { children: "Texture Map" }), _jsx(TexturePicker, { value: values.map, onChange: (map) => onUpdate({ map }), basePath: basePath })] })] }));
28
+ const values = mergeWithDefaults(spotLightDefaults, component.properties);
29
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "angle", label: "Angle", values: values, onChange: onUpdate, min: 0, max: Math.PI / 2, step: 0.05, fallback: Math.PI / 3 }), _jsx(NumberField, { name: "penumbra", label: "Penumbra", values: values, onChange: onUpdate, min: 0, max: 1, step: 0.05, fallback: 0 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 0 }), _jsx(NumberField, { name: "decay", label: "Decay", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 2 }), _jsx(Vector3Input, { label: "Target Offset", value: values.targetOffset, onChange: targetOffset => onUpdate({ targetOffset }), snap: 0.5 }), _jsxs("div", { children: [_jsx(Label, { children: "Texture Map" }), _jsx(TexturePicker, { value: values.map, onChange: (map) => onUpdate({ map }), basePath: basePath })] })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 })] })) : null] })] }));
18
30
  }
19
- function SpotLightView({ properties, children, editMode, isSelected, loadedTextures }) {
20
- const merged = Object.assign(Object.assign({}, spotLightDefaults), properties);
31
+ function SpotLightView({ properties, children, editMode, isSelected }) {
32
+ var _a;
33
+ const { getTexture } = useSceneRuntime();
34
+ const merged = mergeWithDefaults(spotLightDefaults, properties);
21
35
  const color = merged.color;
22
36
  const intensity = merged.intensity;
23
37
  const angle = merged.angle;
24
38
  const penumbra = merged.penumbra;
25
39
  const distance = merged.distance;
40
+ const decay = merged.decay;
26
41
  const castShadow = merged.castShadow;
27
- const textureMap = merged.map && loadedTextures ? loadedTextures[merged.map] : undefined;
42
+ const shadowMapSize = merged.shadowMapSize;
43
+ const shadowBias = merged.shadowBias;
44
+ const shadowNormalBias = merged.shadowNormalBias;
45
+ const shadowAutoUpdate = merged.shadowAutoUpdate;
46
+ const shadowCameraNear = merged.shadowCameraNear;
47
+ const shadowCameraFar = merged.shadowCameraFar;
48
+ const targetOffset = merged.targetOffset;
49
+ const textureMap = merged.map ? (_a = getTexture(merged.map)) !== null && _a !== void 0 ? _a : undefined : undefined;
28
50
  const spotLightRef = useRef(null);
29
51
  const targetRef = useRef(null);
30
- const [spotLight, setSpotLight] = useState(null);
31
- const spotLightHelper = useMemo(() => spotLight ? new SpotLightHelper(spotLight, color) : null, [spotLight, color]);
32
- useEffect(() => {
33
- return () => {
34
- spotLightHelper === null || spotLightHelper === void 0 ? void 0 : spotLightHelper.dispose();
35
- };
36
- }, [spotLightHelper]);
52
+ useHelper(editMode && isSelected ? spotLightRef : null, SpotLightHelper, color);
37
53
  useEffect(() => {
38
54
  if (spotLightRef.current && targetRef.current) {
39
55
  spotLightRef.current.target = targetRef.current;
40
- setSpotLight(spotLightRef.current);
41
56
  }
42
57
  }, []);
58
+ useEffect(() => {
59
+ var _a;
60
+ const shadow = (_a = spotLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
61
+ if (!shadow)
62
+ return;
63
+ shadow.needsUpdate = true;
64
+ shadow.camera.updateProjectionMatrix();
65
+ }, [castShadow, shadowMapSize, shadowBias, shadowNormalBias, shadowAutoUpdate, shadowCameraNear, shadowCameraFar]);
43
66
  useFrame(() => {
44
- if (spotLightHelper && editMode && isSelected) {
45
- spotLightHelper.update();
67
+ var _a;
68
+ if ((_a = spotLightRef.current) === null || _a === void 0 ? void 0 : _a.target) {
69
+ spotLightRef.current.target.updateMatrixWorld();
46
70
  }
47
71
  });
48
- return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, map: textureMap, castShadow: castShadow, "shadow-mapSize-width": 1024, "shadow-mapSize-height": 1024, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), editMode && isSelected && spotLightHelper && (_jsx("primitive", { object: spotLightHelper })), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] })), children] }));
72
+ return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, decay: decay, map: textureMap, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] })), children] }));
49
73
  }
50
74
  const SpotLightComponent = {
51
75
  name: 'SpotLight',
52
76
  Editor: SpotLightComponentEditor,
53
77
  View: SpotLightView,
54
- defaultProperties: spotLightDefaults
78
+ defaultProperties: {},
79
+ getAssetRefs: (properties) => {
80
+ if (properties.map)
81
+ return [{ type: 'texture', path: properties.map }];
82
+ return [];
83
+ },
55
84
  };
56
85
  export default SpotLightComponent;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Label, Vector3Input } from "./Input";
3
- import { useEditorContext } from "../EditorContext";
3
+ import { useEditorContext } from "../PrefabEditor";
4
4
  import { colors } from "../styles";
5
5
  const buttonStyle = {
6
6
  padding: '4px 8px',
@@ -48,7 +48,7 @@ function TransformComponentEditor({ component, onUpdate }) {
48
48
  const TransformComponent = {
49
49
  name: 'Transform',
50
50
  Editor: TransformComponentEditor,
51
- nonComposable: true,
51
+ isWrapper: true,
52
52
  defaultProperties: {
53
53
  position: [0, 0, 0],
54
54
  rotation: [0, 0, 0],
@@ -3,6 +3,7 @@ import TransformComponent from './TransformComponent';
3
3
  import MaterialComponent from './MaterialComponent';
4
4
  import PhysicsComponent from './PhysicsComponent';
5
5
  import SpotLightComponent from './SpotLightComponent';
6
+ import PointLightComponent from './PointLightComponent';
6
7
  import DirectionalLightComponent from './DirectionalLightComponent';
7
8
  import AmbientLightComponent from './AmbientLightComponent';
8
9
  import ModelComponent from './ModelComponent';
@@ -10,12 +11,14 @@ import TextComponent from './TextComponent';
10
11
  import EnvironmentComponent from './EnvironmentComponent';
11
12
  import CameraComponent from './CameraComponent';
12
13
  import ClickComponent from './ClickComponent';
14
+ import SoundComponent from './SoundComponent';
13
15
  export default [
14
16
  GeometryComponent,
15
17
  TransformComponent,
16
18
  MaterialComponent,
17
19
  PhysicsComponent,
18
20
  SpotLightComponent,
21
+ PointLightComponent,
19
22
  DirectionalLightComponent,
20
23
  AmbientLightComponent,
21
24
  ModelComponent,
@@ -23,4 +26,5 @@ export default [
23
26
  EnvironmentComponent,
24
27
  CameraComponent,
25
28
  ClickComponent,
29
+ SoundComponent,
26
30
  ];
@@ -0,0 +1,13 @@
1
+ import { type ReactNode } from 'react';
2
+ export declare function mergeWithDefaults<T extends Record<string, any>>(defaults: T, properties?: Record<string, any> | null): T;
3
+ export declare function LightSection({ title, children }: {
4
+ title: string;
5
+ children: ReactNode;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ShadowBiasField({ name, label, values, onChange, fallback, }: {
8
+ name: string;
9
+ label: string;
10
+ values: Record<string, any>;
11
+ onChange: (values: Record<string, any>) => void;
12
+ fallback?: number;
13
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { colors } from '../styles';
4
+ import { FieldGroup, FieldRow, NumberInput } from './Input';
5
+ export function mergeWithDefaults(defaults, properties) {
6
+ const merged = Object.assign({}, defaults);
7
+ if (!properties) {
8
+ return merged;
9
+ }
10
+ for (const [key, value] of Object.entries(properties)) {
11
+ if (value !== undefined) {
12
+ merged[key] = value;
13
+ }
14
+ }
15
+ return merged;
16
+ }
17
+ export function LightSection({ title, children }) {
18
+ return (_jsxs("div", { style: {
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ gap: 8,
22
+ padding: '8px 10px',
23
+ border: `1px solid ${colors.border}`,
24
+ borderRadius: 6,
25
+ background: colors.bgSurface,
26
+ }, children: [_jsx("div", { style: {
27
+ fontSize: 10,
28
+ textTransform: 'uppercase',
29
+ letterSpacing: '0.08em',
30
+ color: colors.textMuted,
31
+ fontWeight: 600,
32
+ }, children: title }), _jsx(FieldGroup, { children: children })] }));
33
+ }
34
+ const shadowBiasSteps = [0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001];
35
+ function getBiasStep(value) {
36
+ var _a;
37
+ const absValue = Math.abs(value);
38
+ if (absValue === 0) {
39
+ return 0.001;
40
+ }
41
+ return (_a = shadowBiasSteps.find(step => absValue >= step)) !== null && _a !== void 0 ? _a : shadowBiasSteps[shadowBiasSteps.length - 1];
42
+ }
43
+ function formatBiasStep(step) {
44
+ return step.toLocaleString('en-US', {
45
+ minimumFractionDigits: 0,
46
+ maximumFractionDigits: 6,
47
+ useGrouping: false,
48
+ });
49
+ }
50
+ export function ShadowBiasField({ name, label, values, onChange, fallback = 0, }) {
51
+ var _a;
52
+ const value = (_a = values[name]) !== null && _a !== void 0 ? _a : fallback;
53
+ const [step, setStep] = useState(() => getBiasStep(value));
54
+ return (_jsx(FieldRow, { label: label, children: _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx(NumberInput, { value: value, onChange: nextValue => onChange({ [name]: nextValue }), step: step, min: -0.1, max: 0.1, style: { width: 92 } }), _jsx("select", { value: step.toString(), onChange: event => setStep(Number(event.target.value)), style: {
55
+ width: 78,
56
+ backgroundColor: colors.bgInput,
57
+ border: `1px solid ${colors.border}`,
58
+ color: colors.text,
59
+ borderRadius: 3,
60
+ fontSize: 11,
61
+ padding: '3px 6px',
62
+ fontFamily: 'monospace',
63
+ }, title: "Bias scrub step", children: shadowBiasSteps.map(option => (_jsx("option", { value: option, children: formatBiasStep(option) }, option))) })] }) }));
64
+ }
@@ -0,0 +1,37 @@
1
+ import type { ComponentData, GameObject, Prefab } from './types';
2
+ export type PrefabNodeRecord = Omit<GameObject, 'children'>;
3
+ export type PrefabAssetRefCounts = Record<string, number>;
4
+ export interface PrefabState {
5
+ prefabId?: string;
6
+ prefabName?: string;
7
+ rootId: string;
8
+ nodesById: Record<string, PrefabNodeRecord>;
9
+ childIdsById: Record<string, string[]>;
10
+ parentIdById: Record<string, string | null>;
11
+ revision: number;
12
+ assetManifestKey: string;
13
+ assetRefCounts: PrefabAssetRefCounts;
14
+ }
15
+ export declare function createDefaultComponentProperties(type: string): Record<string, any>;
16
+ export declare function createComponentData(type: string, properties?: Record<string, any>): ComponentData;
17
+ export declare function createNode(name: string, components?: Record<string, {
18
+ type: string;
19
+ properties?: Record<string, any>;
20
+ }>, options?: {
21
+ id?: string;
22
+ children?: GameObject[];
23
+ }): GameObject;
24
+ export declare function createEmptyNode(name?: string): GameObject;
25
+ export declare function createEmptyPrefab(): Prefab;
26
+ export declare function createModelNode(filename: string, name?: string): GameObject;
27
+ export declare function createImageNode(texturePath: string, name?: string): GameObject;
28
+ export declare function normalizePrefab(prefab: Prefab, revision?: number): PrefabState;
29
+ export declare function createPrefabPatch(state: PrefabState, patch: Partial<PrefabState>, nextAssetRefCounts?: PrefabAssetRefCounts): Partial<PrefabState>;
30
+ export declare function denormalizePrefab(state: Pick<PrefabState, 'prefabId' | 'prefabName' | 'rootId' | 'nodesById' | 'childIdsById'>): Prefab;
31
+ export declare function collectSubtreeIds(id: string, childIdsById: Record<string, string[]>): string[];
32
+ export declare function insertSubtree(node: GameObject, parentId: string | null, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): void;
33
+ export declare function cloneSubtree(id: string, parentId: string | null, source: Pick<PrefabState, 'nodesById' | 'childIdsById'>, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): string | null;
34
+ export declare function isDescendant(id: string, potentialAncestorId: string, parentIdById: Record<string, string | null>): boolean;
35
+ export declare function updateAssetRefsForNodeChange(assetRefCounts: PrefabAssetRefCounts, currentNode: PrefabNodeRecord, nextNode: PrefabNodeRecord): PrefabAssetRefCounts;
36
+ export declare function collectSubtreeAssetRefs(node: GameObject): string[];
37
+ export declare function collectAssetRefsForIds(ids: string[], nodesById: Record<string, PrefabNodeRecord>): string[];