react-three-game 0.0.107 → 0.0.109

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 (49) hide show
  1. package/README.md +12 -5
  2. package/dist/editor.d.ts +22 -0
  3. package/dist/editor.js +15 -0
  4. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +75 -47
  5. package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
  6. package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
  7. package/dist/plugins/crashcat/CrashcatRuntime.js +19 -15
  8. package/dist/plugins/crashcat/index.d.ts +1 -0
  9. package/dist/plugins/crashcat/index.js +1 -0
  10. package/dist/tools/assetviewer/page.js +4 -4
  11. package/dist/tools/prefabeditor/EditorContext.d.ts +36 -0
  12. package/dist/tools/prefabeditor/EditorContext.js +17 -0
  13. package/dist/tools/prefabeditor/EditorTree.js +6 -3
  14. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
  15. package/dist/tools/prefabeditor/EditorTreeMenus.js +18 -6
  16. package/dist/tools/prefabeditor/EditorUI.js +1 -1
  17. package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
  18. package/dist/tools/prefabeditor/PrefabEditor.d.ts +5 -37
  19. package/dist/tools/prefabeditor/PrefabEditor.js +41 -43
  20. package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -27
  21. package/dist/tools/prefabeditor/PrefabRoot.js +133 -78
  22. package/dist/tools/prefabeditor/SceneContext.d.ts +28 -0
  23. package/dist/tools/prefabeditor/SceneContext.js +14 -0
  24. package/dist/tools/prefabeditor/assetRuntime.d.ts +4 -0
  25. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +16 -1
  26. package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
  27. package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
  28. package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
  29. package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
  30. package/dist/tools/prefabeditor/components/TransformComponent.js +1 -1
  31. package/dist/tools/prefabeditor/components/index.d.ts +1 -0
  32. package/dist/tools/prefabeditor/components/index.js +10 -0
  33. package/dist/tools/prefabeditor/components/runtime.d.ts +4 -0
  34. package/dist/tools/prefabeditor/components/runtime.js +372 -0
  35. package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
  36. package/dist/tools/prefabeditor/modelPrefab.js +44 -11
  37. package/dist/tools/prefabeditor/prefab.d.ts +5 -4
  38. package/dist/tools/prefabeditor/prefab.js +47 -29
  39. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -1
  40. package/dist/tools/prefabeditor/prefabStore.js +5 -1
  41. package/dist/tools/prefabeditor/runtimeUtils.d.ts +10 -0
  42. package/dist/tools/prefabeditor/runtimeUtils.js +30 -0
  43. package/dist/tools/prefabeditor/utils.d.ts +7 -8
  44. package/dist/tools/prefabeditor/utils.js +63 -36
  45. package/dist/viewer.d.ts +22 -0
  46. package/dist/viewer.js +14 -0
  47. package/package.json +22 -20
  48. package/dist/index.d.ts +0 -40
  49. package/dist/index.js +0 -32
@@ -1,5 +1,6 @@
1
1
  // biome-ignore assist/source/organizeImports: <in order of display in the editor>
2
2
  import TransformComponent from "./TransformComponent";
3
+ import PrefabRefComponent from "./PrefabRefComponent";
3
4
  import GeometryComponent from "./GeometryComponent";
4
5
  import BufferGeometryComponent from "./BufferGeometryComponent";
5
6
  import ModelComponent from "./ModelComponent";
@@ -14,6 +15,7 @@ import EnvironmentComponent from "./EnvironmentComponent";
14
15
  import CameraComponent from "./CameraComponent";
15
16
  import SoundComponent from "./SoundComponent";
16
17
  import DataComponent from "./DataComponent";
18
+ import { registerComponent } from "./ComponentRegistry";
17
19
  // this controls the order of components in the editor, and also which components are available to add
18
20
  export const builtinComponents = [
19
21
  TransformComponent,
@@ -35,4 +37,12 @@ export const builtinComponents = [
35
37
  CameraComponent,
36
38
  SoundComponent,
37
39
  DataComponent,
40
+ PrefabRefComponent,
38
41
  ];
42
+ let didRegisterBuiltinComponents = false;
43
+ export function registerBuiltinComponents() {
44
+ if (didRegisterBuiltinComponents)
45
+ return;
46
+ builtinComponents.forEach(registerComponent);
47
+ didRegisterBuiltinComponents = true;
48
+ }
@@ -0,0 +1,4 @@
1
+ import { type Component } from "./ComponentRegistry";
2
+ declare const runtimeComponents: Component[];
3
+ export declare function registerRuntimeComponents(): void;
4
+ export { runtimeComponents };
@@ -0,0 +1,372 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Environment } from "@react-three/drei";
3
+ import { useFrame, useThree } from "@react-three/fiber";
4
+ import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react";
5
+ import { AudioListener, ClampToEdgeWrapping, DoubleSide, LinearFilter, LinearMipmapLinearFilter, Mesh, NoColorSpace, RepeatWrapping, SRGBColorSpace } from "three";
6
+ import { Text } from "three-text/three/react";
7
+ import { assetRef, assetRefs, getComponentDef, registerComponent } from "./ComponentRegistry";
8
+ import { useAssetRuntime, useNode } from "../assetRuntime";
9
+ import { gameEvents } from "../GameEvents";
10
+ import { withBasePath } from "../runtimeUtils";
11
+ const PrefabRoot = lazy(() => import("../PrefabRoot"));
12
+ const RuntimeText = Text;
13
+ function GeometryView({ properties }) {
14
+ const { geometryType } = properties;
15
+ const args = Array.isArray(properties.args) ? properties.args : [];
16
+ const geometryKey = `${geometryType !== null && geometryType !== void 0 ? geometryType : "box"}:${JSON.stringify(args)}`;
17
+ switch (geometryType) {
18
+ case "sphere":
19
+ return _jsx("sphereGeometry", { args: args }, geometryKey);
20
+ case "plane":
21
+ return _jsx("planeGeometry", { args: args }, geometryKey);
22
+ case "cylinder":
23
+ return _jsx("cylinderGeometry", { args: args }, geometryKey);
24
+ case "box":
25
+ default:
26
+ return _jsx("boxGeometry", { args: (args.length ? args : [1, 1, 1]) }, geometryKey);
27
+ }
28
+ }
29
+ const DEFAULT_TRIANGLE_POSITIONS = [0, 0, 0, 1, 0, 0, 0, 1, 0];
30
+ const DEFAULT_TRIANGLE_INDICES = [0, 1, 2];
31
+ const DEFAULT_TRIANGLE_UVS = [0, 0, 1, 0, 0, 1];
32
+ function numberArray(value, fallback) {
33
+ return Array.isArray(value) && value.every(entry => typeof entry === "number" && Number.isFinite(entry))
34
+ ? value
35
+ : fallback;
36
+ }
37
+ function indexArray(indices) {
38
+ if (indices.length === 0)
39
+ return null;
40
+ return Math.max(...indices) > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
41
+ }
42
+ function BufferGeometryView({ properties }) {
43
+ const positions = numberArray(properties.positions, DEFAULT_TRIANGLE_POSITIONS);
44
+ const indices = numberArray(properties.indices, DEFAULT_TRIANGLE_INDICES);
45
+ const normals = numberArray(properties.normals, []);
46
+ const uvs = numberArray(properties.uvs, DEFAULT_TRIANGLE_UVS);
47
+ const hasNormals = normals.length === positions.length;
48
+ const hasUvs = uvs.length >= (positions.length / 3) * 2;
49
+ const indicesArray = indexArray(indices);
50
+ return (_jsxs("bufferGeometry", { onUpdate: (geometry) => {
51
+ if (properties.computeVertexNormals !== false && !hasNormals)
52
+ geometry.computeVertexNormals();
53
+ geometry.computeBoundingBox();
54
+ geometry.computeBoundingSphere();
55
+ }, children: [_jsx("bufferAttribute", { attach: "attributes-position", args: [new Float32Array(positions), 3] }), indicesArray ? _jsx("bufferAttribute", { attach: "index", args: [indicesArray, 1] }) : null, hasNormals ? _jsx("bufferAttribute", { attach: "attributes-normal", args: [new Float32Array(normals), 3] }) : null, hasUvs ? _jsx("bufferAttribute", { attach: "attributes-uv", args: [new Float32Array(uvs), 2] }) : null] }));
56
+ }
57
+ function ModelView({ properties, children }) {
58
+ const { getModel } = useAssetRuntime();
59
+ const sourceModel = properties.filename ? getModel(properties.filename) : null;
60
+ const clonedModel = useMemo(() => {
61
+ if (!sourceModel || !properties.filename || properties.instanced)
62
+ return null;
63
+ const clone = sourceModel.clone();
64
+ clone.traverse((object) => {
65
+ if (object instanceof Mesh) {
66
+ object.castShadow = true;
67
+ object.receiveShadow = true;
68
+ }
69
+ });
70
+ return clone;
71
+ }, [properties.filename, properties.instanced, sourceModel]);
72
+ return clonedModel ? _jsx("primitive", { object: clonedModel, children: children }) : _jsx(_Fragment, { children: children });
73
+ }
74
+ function configureTexture(texture, properties, colorSpace) {
75
+ var _a, _b, _c, _d;
76
+ if (!texture)
77
+ return;
78
+ const repeat = Boolean(properties.repeat);
79
+ const repeatCount = Array.isArray(properties.repeatCount) ? properties.repeatCount : [1, 1];
80
+ const offset = Array.isArray(properties.offset) ? properties.offset : [0, 0];
81
+ texture.wrapS = texture.wrapT = repeat ? RepeatWrapping : ClampToEdgeWrapping;
82
+ texture.repeat.set(Number((_a = repeatCount[0]) !== null && _a !== void 0 ? _a : 1), Number((_b = repeatCount[1]) !== null && _b !== void 0 ? _b : 1));
83
+ texture.offset.set(Number((_c = offset[0]) !== null && _c !== void 0 ? _c : 0), Number((_d = offset[1]) !== null && _d !== void 0 ? _d : 0));
84
+ texture.colorSpace = colorSpace;
85
+ texture.generateMipmaps = properties.generateMipmaps !== false;
86
+ texture.minFilter = (properties.minFilter === "LinearFilter" ? LinearFilter : LinearMipmapLinearFilter);
87
+ texture.magFilter = LinearFilter;
88
+ texture.needsUpdate = true;
89
+ }
90
+ function useConfiguredTexture(path, properties, colorSpace) {
91
+ const { getTexture } = useAssetRuntime();
92
+ const sourceTexture = typeof path === "string" ? getTexture(path) : null;
93
+ const texture = useMemo(() => sourceTexture === null || sourceTexture === void 0 ? void 0 : sourceTexture.clone(), [sourceTexture]);
94
+ useEffect(() => {
95
+ configureTexture(texture, properties, colorSpace);
96
+ }, [properties, texture, colorSpace]);
97
+ return texture !== null && texture !== void 0 ? texture : null;
98
+ }
99
+ function MaterialView({ properties }) {
100
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
101
+ const materialType = (_a = properties.materialType) !== null && _a !== void 0 ? _a : "standard";
102
+ const colorMap = useConfiguredTexture(properties.texture, properties, SRGBColorSpace);
103
+ const normalMap = useConfiguredTexture(properties.normalMapTexture, properties, NoColorSpace);
104
+ const offsetSpeed = Array.isArray(properties.offsetSpeed) ? properties.offsetSpeed : [0, 0];
105
+ const animateOffset = Boolean(properties.animateOffset);
106
+ useFrame((_state, delta) => {
107
+ var _a, _b;
108
+ if (!animateOffset)
109
+ return;
110
+ for (const texture of [colorMap, normalMap]) {
111
+ if (!texture)
112
+ continue;
113
+ texture.offset.x += Number((_a = offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
114
+ texture.offset.y += Number((_b = offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
115
+ }
116
+ });
117
+ const sharedProps = {
118
+ attach: (_b = properties.attach) !== null && _b !== void 0 ? _b : "material",
119
+ color: (_c = properties.color) !== null && _c !== void 0 ? _c : "#ffffff",
120
+ opacity: (_d = properties.opacity) !== null && _d !== void 0 ? _d : 1,
121
+ transparent: Boolean(properties.transparent),
122
+ wireframe: Boolean(properties.wireframe),
123
+ toneMapped: properties.toneMapped !== false,
124
+ side: DoubleSide,
125
+ map: colorMap,
126
+ };
127
+ if (materialType === "basic")
128
+ return _jsx("meshBasicMaterial", Object.assign({}, sharedProps));
129
+ if (materialType === "sprite") {
130
+ return (_jsx("spriteMaterial", Object.assign({}, sharedProps, { rotation: (_e = properties.rotation) !== null && _e !== void 0 ? _e : 0, sizeAttenuation: properties.sizeAttenuation !== false, depthTest: properties.depthTest !== false, depthWrite: Boolean(properties.depthWrite) })));
131
+ }
132
+ const normalScale = Array.isArray(properties.normalScale) ? properties.normalScale : [1, 1];
133
+ return (_jsx("meshStandardMaterial", Object.assign({}, sharedProps, { metalness: (_f = properties.metalness) !== null && _f !== void 0 ? _f : 0, roughness: (_g = properties.roughness) !== null && _g !== void 0 ? _g : 1, normalMap: normalMap, normalScale: normalMap ? [Number((_h = normalScale[0]) !== null && _h !== void 0 ? _h : 1), Number((_j = normalScale[1]) !== null && _j !== void 0 ? _j : 1)] : undefined })));
134
+ }
135
+ function TextView({ properties, children, basePath = "" }) {
136
+ const { text = "", font, size, depth, width, align, color } = properties;
137
+ const textContent = String(text || "");
138
+ const resolvedFont = typeof font === "string" ? withBasePath(basePath, font) : undefined;
139
+ const [offset, setOffset] = useState([0, 0, 0]);
140
+ useEffect(() => {
141
+ Text.setHarfBuzzPath(withBasePath(basePath, "/fonts/hb.wasm"));
142
+ }, [basePath]);
143
+ if (!textContent)
144
+ return null;
145
+ const textProps = {
146
+ size: size,
147
+ depth: depth,
148
+ layout: { align: align, width: width },
149
+ color: color,
150
+ onLoad: (_geometry, info) => {
151
+ const bounds = info === null || info === void 0 ? void 0 : info.planeBounds;
152
+ if (!bounds)
153
+ return;
154
+ const x = align === "center" ? -(bounds.min.x + bounds.max.x) / 2 : align === "right" ? -bounds.max.x : -bounds.min.x;
155
+ setOffset([x, -(bounds.min.y + bounds.max.y) / 2, 0]);
156
+ },
157
+ };
158
+ if (resolvedFont)
159
+ textProps.font = resolvedFont;
160
+ return (_jsxs("group", { position: offset, children: [_jsx(RuntimeText, Object.assign({}, textProps, { children: textContent })), children] }));
161
+ }
162
+ function merge(defaults, properties) {
163
+ return Object.assign(Object.assign({}, defaults), properties);
164
+ }
165
+ function AmbientLightView({ properties, children }) {
166
+ const props = merge({ color: "#ffffff", intensity: 1 }, properties);
167
+ return _jsxs(_Fragment, { children: [_jsx("ambientLight", { color: props.color, intensity: props.intensity }), children] });
168
+ }
169
+ function PointLightView({ properties, children }) {
170
+ const props = merge({ color: "#ffffff", intensity: 1, distance: 0, decay: 2 }, properties);
171
+ return (_jsx("pointLight", { color: props.color, intensity: props.intensity, distance: props.distance, decay: props.decay, children: children }));
172
+ }
173
+ function DirectionalLightView({ properties, children }) {
174
+ const props = merge({ color: "#ffffff", intensity: 1, castShadow: false }, properties);
175
+ return _jsx("directionalLight", { color: props.color, intensity: props.intensity, castShadow: Boolean(props.castShadow), children: children });
176
+ }
177
+ function SpotLightView({ properties, children }) {
178
+ const props = merge({ color: "#ffffff", intensity: 1, angle: Math.PI / 3, penumbra: 0, distance: 0, decay: 2, castShadow: false }, properties);
179
+ return (_jsx("spotLight", { color: props.color, intensity: props.intensity, angle: props.angle, penumbra: props.penumbra, distance: props.distance, decay: props.decay, castShadow: Boolean(props.castShadow), children: children }));
180
+ }
181
+ function CameraView({ properties, children }) {
182
+ var _a, _b, _c;
183
+ const projection = (_a = properties.projection) !== null && _a !== void 0 ? _a : "perspective";
184
+ if (projection === "orthographic") {
185
+ return _jsx("orthographicCamera", { zoom: (_b = properties.zoom) !== null && _b !== void 0 ? _b : 1, children: children });
186
+ }
187
+ return _jsx("perspectiveCamera", { fov: (_c = properties.fov) !== null && _c !== void 0 ? _c : 50, children: children });
188
+ }
189
+ function EnvironmentView({ properties, children }) {
190
+ var _a, _b;
191
+ const { getAssetRevision } = useAssetRuntime();
192
+ const intensity = (_a = properties.intensity) !== null && _a !== void 0 ? _a : 1;
193
+ const resolution = (_b = properties.resolution) !== null && _b !== void 0 ? _b : 256;
194
+ return (_jsx(Environment, { background: true, environmentIntensity: intensity, resolution: resolution, frames: 1, children: children }, `${getAssetRevision()}::${intensity}::${resolution}`));
195
+ }
196
+ function PrefabRefView({ properties, children, basePath = "" }) {
197
+ const [loadedPrefab, setLoadedPrefab] = useState(null);
198
+ const resolvedUrl = properties.url ? withBasePath(basePath, properties.url) : "";
199
+ useEffect(() => {
200
+ if (!resolvedUrl)
201
+ return undefined;
202
+ let cancelled = false;
203
+ void fetch(resolvedUrl)
204
+ .then(response => response.json())
205
+ .then(data => {
206
+ if (!cancelled)
207
+ setLoadedPrefab(data);
208
+ })
209
+ .catch(error => console.warn("[PrefabRef] Failed to load:", resolvedUrl, error));
210
+ return () => {
211
+ cancelled = true;
212
+ };
213
+ }, [resolvedUrl]);
214
+ return (_jsxs(_Fragment, { children: [loadedPrefab ? (_jsx(Suspense, { fallback: null, children: _jsx(PrefabRoot, { data: loadedPrefab, editMode: false, basePath: basePath }) })) : null, children] }));
215
+ }
216
+ let sharedAudioListener = null;
217
+ function getSharedAudioListener() {
218
+ if (!sharedAudioListener)
219
+ sharedAudioListener = new AudioListener();
220
+ return sharedAudioListener;
221
+ }
222
+ function clampRange(min, max, fallbackMin, fallbackMax) {
223
+ const safeMin = Number.isFinite(min) ? Number(min) : fallbackMin;
224
+ const safeMax = Number.isFinite(max) ? Number(max) : fallbackMax;
225
+ return safeMin <= safeMax ? [safeMin, safeMax] : [safeMax, safeMin];
226
+ }
227
+ function sampleRange(min, max) {
228
+ return min + Math.random() * (max - min);
229
+ }
230
+ function getPitchValue(properties) {
231
+ if (properties.randomizePitch) {
232
+ const [pitchFloor, pitchCeiling] = clampRange(properties.minPitch, properties.maxPitch, 0.96, 1.04);
233
+ return sampleRange(pitchFloor, pitchCeiling);
234
+ }
235
+ return Number.isFinite(properties.pitch) ? Number(properties.pitch) : 1;
236
+ }
237
+ function getVolumeValue(properties) {
238
+ if (properties.randomizeVolume) {
239
+ const [volumeFloor, volumeCeiling] = clampRange(properties.minVolume, properties.maxVolume, 0.9, 1);
240
+ return sampleRange(volumeFloor, volumeCeiling);
241
+ }
242
+ return Number.isFinite(properties.volume) ? Number(properties.volume) : 1;
243
+ }
244
+ function resolveClipPaths({ clips, clipMode }) {
245
+ const paths = (clips !== null && clips !== void 0 ? clips : []).map(clip => clip.trim()).filter(Boolean);
246
+ return { paths, mode: paths.length > 0 ? clipMode !== null && clipMode !== void 0 ? clipMode : "random" : "single" };
247
+ }
248
+ function pickClip(paths, mode, sequenceIndexRef) {
249
+ if (paths.length <= 1 || mode === "single")
250
+ return paths[0];
251
+ if (mode === "sequence") {
252
+ const clip = paths[sequenceIndexRef.current % paths.length];
253
+ sequenceIndexRef.current += 1;
254
+ return clip;
255
+ }
256
+ return paths[Math.floor(Math.random() * paths.length)];
257
+ }
258
+ function payloadMatchesNode(nodeId, payload) {
259
+ if (!nodeId || !payload || typeof payload !== "object")
260
+ return true;
261
+ const eventPayload = payload;
262
+ const relatedNodeIds = [
263
+ eventPayload.nodeId,
264
+ eventPayload.sourceEntityId,
265
+ eventPayload.sourceNodeId,
266
+ eventPayload.targetEntityId,
267
+ eventPayload.targetNodeId,
268
+ eventPayload.instanceEntityId,
269
+ ].filter((value) => typeof value === "string");
270
+ return relatedNodeIds.length > 0 ? relatedNodeIds.includes(nodeId) : true;
271
+ }
272
+ function playBufferedAudio(audio, buffer, properties) {
273
+ void audio.listener.context.resume();
274
+ if (audio.isPlaying)
275
+ audio.stop();
276
+ audio.setBuffer(buffer);
277
+ audio.setLoop(Boolean(properties.loop));
278
+ audio.setPlaybackRate(getPitchValue(properties));
279
+ audio.setVolume(getVolumeValue(properties));
280
+ audio.play();
281
+ }
282
+ function SoundView({ properties, children }) {
283
+ const { getSound } = useAssetRuntime();
284
+ const { editMode, nodeId } = useNode();
285
+ const { camera } = useThree();
286
+ const { eventName, autoplay = false, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = "inverse" } = properties;
287
+ const sequenceIndexRef = useRef(0);
288
+ const listenerRef = useRef(null);
289
+ const positionalAudioRef = useRef(null);
290
+ const { paths, mode } = resolveClipPaths(properties);
291
+ if (!listenerRef.current)
292
+ listenerRef.current = getSharedAudioListener();
293
+ useEffect(() => {
294
+ var _a;
295
+ const listener = listenerRef.current;
296
+ if (!listener)
297
+ return;
298
+ if (listener.parent !== camera) {
299
+ (_a = listener.parent) === null || _a === void 0 ? void 0 : _a.remove(listener);
300
+ camera.add(listener);
301
+ }
302
+ return () => {
303
+ if (listener.parent === camera)
304
+ camera.remove(listener);
305
+ };
306
+ }, [camera]);
307
+ useEffect(() => {
308
+ const audio = positionalAudioRef.current;
309
+ if (!audio)
310
+ return;
311
+ audio.setRefDistance(positional ? refDistance : Math.max(refDistance, 1));
312
+ audio.setMaxDistance(positional ? maxDistance : 1000000);
313
+ audio.setRolloffFactor(positional ? rolloffFactor : 0);
314
+ audio.setDistanceModel(positional ? distanceModel : "inverse");
315
+ }, [distanceModel, maxDistance, positional, refDistance, rolloffFactor]);
316
+ useEffect(() => {
317
+ if (editMode || paths.length === 0 || !eventName)
318
+ return;
319
+ return gameEvents.on(eventName, (payload) => {
320
+ if (!payloadMatchesNode(nodeId, payload))
321
+ return;
322
+ const clip = pickClip(paths, mode, sequenceIndexRef);
323
+ const audio = positionalAudioRef.current;
324
+ const buffer = clip ? getSound(clip) : null;
325
+ if (audio && buffer)
326
+ playBufferedAudio(audio, buffer, properties);
327
+ });
328
+ }, [editMode, eventName, getSound, mode, nodeId, paths, properties]);
329
+ useEffect(() => {
330
+ if (editMode || !autoplay || paths.length === 0)
331
+ return;
332
+ const clip = pickClip(paths, mode, sequenceIndexRef);
333
+ const audio = positionalAudioRef.current;
334
+ const buffer = clip ? getSound(clip) : null;
335
+ if (audio && buffer)
336
+ playBufferedAudio(audio, buffer, properties);
337
+ return () => {
338
+ if (audio === null || audio === void 0 ? void 0 : audio.isPlaying)
339
+ audio.stop();
340
+ };
341
+ }, [autoplay, editMode, getSound, mode, paths, properties]);
342
+ return (_jsxs(_Fragment, { children: [listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
343
+ }
344
+ const runtimeComponents = [
345
+ { name: "Transform", disableSiblingComposition: true, defaultProperties: {} },
346
+ { name: "Geometry", disableSiblingComposition: "geometry", View: GeometryView, defaultProperties: { geometryType: "box", args: [1, 1, 1], emitClickEvent: false, clickEventName: "" } },
347
+ { name: "BufferGeometry", disableSiblingComposition: "geometry", View: BufferGeometryView, defaultProperties: { positions: DEFAULT_TRIANGLE_POSITIONS, indices: DEFAULT_TRIANGLE_INDICES, normals: [], uvs: DEFAULT_TRIANGLE_UVS, groups: [], computeVertexNormals: true, emitClickEvent: false, clickEventName: "" } },
348
+ { name: "Model", View: ModelView, defaultProperties: {}, getAssetRefs: properties => assetRefs(assetRef("model", properties.filename)) },
349
+ { name: "Sprite", disableSiblingComposition: true, defaultProperties: { center: [0.5, 0.5], emitClickEvent: false, clickEventName: "node:click" } },
350
+ { name: "Text", View: TextView, defaultProperties: { text: "Hello World", color: "#ffffff", size: 1, depth: 0.05, align: "center" } },
351
+ { name: "Material", View: MaterialView, defaultProperties: { attach: "material", materialType: "standard", color: "#ffffff", toneMapped: true, wireframe: false, transparent: false, opacity: 1, sizeAttenuation: true, offset: [0, 0], animateOffset: false, offsetSpeed: [0, 0], metalness: 0, roughness: 1 }, getAssetRefs: properties => assetRefs(assetRef("texture", properties.texture), assetRef("texture", properties.normalMapTexture)) },
352
+ { name: "SpotLight", View: SpotLightView, defaultProperties: {}, getAssetRefs: properties => assetRefs(assetRef("texture", properties.map)) },
353
+ { name: "PointLight", View: PointLightView, defaultProperties: {} },
354
+ { name: "DirectionalLight", View: DirectionalLightView, defaultProperties: {} },
355
+ { name: "AmbientLight", View: AmbientLightView, defaultProperties: {} },
356
+ { name: "Environment", disableSiblingComposition: true, View: EnvironmentView, defaultProperties: {} },
357
+ { name: "Camera", disableSiblingComposition: true, View: CameraView, defaultProperties: { projection: "perspective", fov: 50, near: 0.1, zoom: 1, far: 1000 } },
358
+ { name: "Sound", View: SoundView, defaultProperties: { eventName: "", autoplay: false, loop: false, clips: [], clipMode: "single", positional: false, refDistance: 1, maxDistance: 24, rolloffFactor: 1, distanceModel: "inverse", pitch: 1, randomizePitch: false, minPitch: 0.96, maxPitch: 1.04, volume: 1, randomizeVolume: false, minVolume: 0.9, maxVolume: 1 }, getAssetRefs: (properties) => Array.isArray(properties.clips) ? properties.clips.filter((clip) => typeof clip === "string").map(path => ({ type: "sound", path })) : [] },
359
+ { name: "Data", defaultProperties: { values: {} } },
360
+ { name: "PrefabRef", View: PrefabRefView, defaultProperties: { url: "" } },
361
+ ];
362
+ let didRegisterRuntimeComponents = false;
363
+ export function registerRuntimeComponents() {
364
+ if (didRegisterRuntimeComponents)
365
+ return;
366
+ runtimeComponents.forEach(component => {
367
+ if (!getComponentDef(component.name))
368
+ registerComponent(component);
369
+ });
370
+ didRegisterRuntimeComponents = true;
371
+ }
372
+ export { runtimeComponents };
@@ -6,9 +6,12 @@ export interface DecomposeModelOptions {
6
6
  idPrefix?: string;
7
7
  /** Include invisible Three objects in the generated prefab tree. */
8
8
  includeInvisible?: boolean;
9
+ /** Create CrashcatPhysics components from Blender-style mesh name suffixes. */
10
+ inferCollisionMeshes?: boolean;
9
11
  /** Return a serializable texture ref for embedded or externally loaded textures. */
10
12
  getTexturePath?: (texture: Texture, usage: 'map' | 'normalMap') => string | null | undefined;
11
13
  }
14
+ export declare function hasCollisionMeshConventions(object: Object3D, enabled?: boolean): boolean;
12
15
  /**
13
16
  * Converts a live Three object hierarchy into prefab JSON nodes made from
14
17
  * Transform, BufferGeometry, and Material components.
@@ -52,6 +52,27 @@ function serializeGeometry(geometry) {
52
52
  computeVertexNormals: normals.length === 0,
53
53
  };
54
54
  }
55
+ function getCollisionMeshConvention(name, enabled) {
56
+ if (!enabled)
57
+ return null;
58
+ const match = name.match(/^(.*)_(colonly|col)(?:\.\d+)?$/i);
59
+ if (!match)
60
+ return null;
61
+ const [, baseName, suffix] = match;
62
+ return {
63
+ displayName: baseName || name,
64
+ renderMesh: suffix.toLowerCase() === 'col',
65
+ };
66
+ }
67
+ export function hasCollisionMeshConventions(object, enabled = true) {
68
+ let hasConvention = false;
69
+ object.traverse(child => {
70
+ if (hasConvention)
71
+ return;
72
+ hasConvention = child instanceof Mesh && getCollisionMeshConvention(child.name, enabled) != null;
73
+ });
74
+ return hasConvention;
75
+ }
55
76
  function getSideName(side) {
56
77
  if (side === BackSide)
57
78
  return 'BackSide';
@@ -138,17 +159,19 @@ function createTransformComponent(object) {
138
159
  },
139
160
  };
140
161
  }
141
- function createNode(object, idPrefix, children = [], components = {}) {
162
+ function createNode(object, idPrefix, children = [], components = {}, options = {}) {
163
+ var _a, _b;
142
164
  return {
143
165
  id: createId(idPrefix),
144
- name: object.name || object.type,
145
- hidden: object.visible === false ? true : undefined,
166
+ name: ((_a = options.name) !== null && _a !== void 0 ? _a : object.name) || object.type,
167
+ hidden: (_b = options.hidden) !== null && _b !== void 0 ? _b : (object.visible === false ? true : undefined),
146
168
  components: Object.assign({ transform: createTransformComponent(object) }, components),
147
169
  children,
148
170
  };
149
171
  }
150
172
  function decomposeObject(object, options) {
151
- if (!options.includeInvisible && !object.visible)
173
+ const collisionMesh = getCollisionMeshConvention(object.name, options.inferCollisionMeshes);
174
+ if (!options.includeInvisible && !object.visible && !collisionMesh)
152
175
  return null;
153
176
  const childNodes = object.children
154
177
  .map(child => decomposeObject(child, options))
@@ -161,20 +184,30 @@ function decomposeObject(object, options) {
161
184
  result[part.key] = serializeMaterial(part.material, part.attach, options);
162
185
  return result;
163
186
  }, {});
164
- return createNode(object, options.idPrefix, childNodes, Object.assign({ geometry: {
187
+ return createNode(object, options.idPrefix, childNodes, Object.assign(Object.assign({ geometry: {
165
188
  type: 'BufferGeometry',
166
- properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
167
- } }, materialComponents));
189
+ properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: collisionMesh ? collisionMesh.renderMesh : object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
190
+ } }, materialComponents), (collisionMesh ? {
191
+ crashcatPhysics: {
192
+ type: 'CrashcatPhysics',
193
+ properties: {
194
+ type: 'fixed',
195
+ colliders: 'trimesh',
196
+ sensor: false,
197
+ },
198
+ },
199
+ } : null)), collisionMesh ? { name: collisionMesh.displayName, hidden: false } : undefined);
168
200
  }
169
201
  /**
170
202
  * Converts a live Three object hierarchy into prefab JSON nodes made from
171
203
  * Transform, BufferGeometry, and Material components.
172
204
  */
173
205
  export function decomposeModelToPrefabNodes(object, options = {}) {
174
- var _a, _b, _c, _d, _e;
175
- return (_d = decomposeObject(object, {
206
+ var _a, _b, _c, _d, _e, _f;
207
+ return (_e = decomposeObject(object, {
176
208
  idPrefix: (_a = options.idPrefix) !== null && _a !== void 0 ? _a : 'model',
177
209
  includeInvisible: (_b = options.includeInvisible) !== null && _b !== void 0 ? _b : false,
178
- getTexturePath: (_c = options.getTexturePath) !== null && _c !== void 0 ? _c : (() => undefined),
179
- })) !== null && _d !== void 0 ? _d : createNode(object, (_e = options.idPrefix) !== null && _e !== void 0 ? _e : 'model');
210
+ inferCollisionMeshes: (_c = options.inferCollisionMeshes) !== null && _c !== void 0 ? _c : true,
211
+ getTexturePath: (_d = options.getTexturePath) !== null && _d !== void 0 ? _d : (() => undefined),
212
+ })) !== null && _e !== void 0 ? _e : createNode(object, (_f = options.idPrefix) !== null && _f !== void 0 ? _f : 'model');
180
213
  }
@@ -1,5 +1,5 @@
1
- import type { ComponentData, GameObject, Prefab } from './types';
2
- export type PrefabNodeRecord = Omit<GameObject, 'children'>;
1
+ import type { ComponentData, GameObject, Prefab } from "./types";
2
+ export type PrefabNodeRecord = Omit<GameObject, "children">;
3
3
  export type PrefabAssetRefCounts = Record<string, number>;
4
4
  export interface PrefabState {
5
5
  prefabId?: string;
@@ -24,12 +24,13 @@ export declare function createEmptyNode(name?: string): GameObject;
24
24
  export declare function createEmptyPrefab(): Prefab;
25
25
  export declare function createModelNode(filename: string, name?: string): GameObject;
26
26
  export declare function createImageNode(texturePath: string, name?: string): GameObject;
27
+ export declare function createPackedPrefabNode(url: string): GameObject;
27
28
  export declare function normalizePrefab(prefab: Prefab): PrefabState;
28
29
  export declare function createPrefabPatch(state: PrefabState, patch: Partial<PrefabState>, nextAssetRefCounts?: PrefabAssetRefCounts): Partial<PrefabState>;
29
- export declare function denormalizePrefab(state: Pick<PrefabState, 'prefabId' | 'prefabName' | 'rootId' | 'nodesById' | 'childIdsById'>): Prefab;
30
+ export declare function denormalizePrefab(state: Pick<PrefabState, "prefabId" | "prefabName" | "rootId" | "nodesById" | "childIdsById">): Prefab;
30
31
  export declare function collectSubtreeIds(id: string, childIdsById: Record<string, string[]>): string[];
31
32
  export declare function insertSubtree(node: GameObject, parentId: string | null, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): void;
32
- 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;
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;
33
34
  export declare function isDescendant(id: string, potentialAncestorId: string, parentIdById: Record<string, string | null>): boolean;
34
35
  export declare function updateAssetRefsForNodeChange(assetRefCounts: PrefabAssetRefCounts, currentNode: PrefabNodeRecord, nextNode: PrefabNodeRecord): PrefabAssetRefCounts;
35
36
  export declare function collectSubtreeAssetRefs(node: GameObject): string[];