react-three-game 0.0.100 → 0.0.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +2 -1
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +1 -0
  4. package/dist/plugins/crashcat/CrashcatPhysicsComponent.d.ts +3 -0
  5. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +352 -0
  6. package/dist/plugins/crashcat/CrashcatRuntime.d.ts +27 -0
  7. package/dist/plugins/crashcat/CrashcatRuntime.js +154 -0
  8. package/dist/plugins/crashcat/index.d.ts +2 -0
  9. package/dist/plugins/crashcat/index.js +2 -0
  10. package/dist/plugins/index.d.ts +1 -0
  11. package/dist/plugins/index.js +1 -0
  12. package/dist/tools/assetviewer/page.js +95 -46
  13. package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -0
  14. package/dist/tools/dragdrop/DragDropLoader.js +183 -44
  15. package/dist/tools/dragdrop/index.d.ts +1 -1
  16. package/dist/tools/dragdrop/index.js +1 -1
  17. package/dist/tools/dragdrop/modelLoader.js +2 -0
  18. package/dist/tools/prefabeditor/EditorUI.js +7 -8
  19. package/dist/tools/prefabeditor/PrefabEditor.d.ts +3 -0
  20. package/dist/tools/prefabeditor/PrefabEditor.js +28 -11
  21. package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -0
  22. package/dist/tools/prefabeditor/PrefabRoot.js +51 -35
  23. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +20 -0
  24. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +5 -0
  25. package/dist/tools/prefabeditor/components/ComponentRegistry.js +31 -0
  26. package/dist/tools/prefabeditor/components/DataComponent.js +1 -0
  27. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -0
  28. package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -0
  29. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +1 -0
  30. package/dist/tools/prefabeditor/components/MaterialComponent.js +85 -57
  31. package/dist/tools/prefabeditor/components/ModelComponent.js +45 -3
  32. package/dist/tools/prefabeditor/components/SpriteComponent.js +1 -0
  33. package/dist/tools/prefabeditor/components/TransformComponent.js +1 -0
  34. package/dist/tools/prefabeditor/modelPrefab.d.ts +16 -0
  35. package/dist/tools/prefabeditor/modelPrefab.js +180 -0
  36. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
  37. package/dist/tools/prefabeditor/prefabStore.js +75 -42
  38. package/package.json +27 -2
package/README.md CHANGED
@@ -16,6 +16,7 @@ Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fi
16
16
 
17
17
  * Website: https://prnth.com/react-three-game
18
18
  * Editor: https://prnth.com/react-three-game/editor
19
+ * Starter template: https://github.com/prnthh/react-three-game-starter
19
20
 
20
21
  ## Install
21
22
 
@@ -294,4 +295,4 @@ npm run release
294
295
 
295
296
  ## License
296
297
 
297
- VPL
298
+ PFYL / VPL
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
18
18
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
19
19
  export type { ExportGLBOptions } from './tools/prefabeditor/utils';
20
20
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
21
+ export { decomposeModelToPrefabNodes } from './tools/prefabeditor/modelPrefab';
22
+ export type { DecomposeModelOptions } from './tools/prefabeditor/modelPrefab';
21
23
  export type { PrefabEditorProps, PrefabNode, PrefabEditorRef, } from './tools/prefabeditor/PrefabEditor';
22
24
  export type { PrefabRootProps } from './tools/prefabeditor/PrefabRoot';
23
25
  export type { AssetRuntime, NodeApi, LiveRef } from './tools/prefabeditor/assetRuntime';
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Fiel
19
19
  // Prefab Editor - Utils
20
20
  export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from './tools/prefabeditor/utils';
21
21
  export { createModelNode, createImageNode, } from './tools/prefabeditor/prefab';
22
+ export { decomposeModelToPrefabNodes } from './tools/prefabeditor/modelPrefab';
22
23
  export { useAssetRuntime, useNode, useNodeHandle, useNodeObject } from './tools/prefabeditor/assetRuntime';
23
24
  export { useScene } from './tools/prefabeditor/PrefabRoot';
24
25
  export { MaterialOverridesProvider, useMaterialOverrides } from './tools/prefabeditor/components/MaterialComponent';
@@ -0,0 +1,3 @@
1
+ import { type Component } from "../../tools/prefabeditor/components/ComponentRegistry";
2
+ declare const CrashcatPhysicsComponent: Component;
3
+ export default CrashcatPhysicsComponent;
@@ -0,0 +1,352 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import { useEffect, useMemo, useRef } from "react";
5
+ import { BooleanField, FieldRenderer, StringField, Vector3Field, } from "../../tools/prefabeditor/components/Input";
6
+ import { useAssetRuntime, useNode } from "../../tools/prefabeditor/assetRuntime";
7
+ import { usePrefabStoreApi } from "../../tools/prefabeditor/prefabStore";
8
+ import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
9
+ import { box, capsule, convexHull, MotionQuality, MotionType, rigidBody, sphere, triangleMesh, } from "crashcat";
10
+ import { Matrix4, Quaternion, Vector3 } from "three";
11
+ import { useCrashcat } from "./CrashcatRuntime";
12
+ const MAX_PHYSICS_DELTA = 1 / 30;
13
+ const crashcatPhysicsFields = [
14
+ {
15
+ name: "type",
16
+ type: "select",
17
+ label: "Motion Type",
18
+ options: [
19
+ { value: "fixed", label: "Fixed" },
20
+ { value: "dynamic", label: "Dynamic" },
21
+ { value: "kinematicPosition", label: "Kinematic Position" },
22
+ { value: "kinematicVelocity", label: "Kinematic Velocity" },
23
+ ],
24
+ },
25
+ {
26
+ name: "colliders",
27
+ type: "select",
28
+ label: "Collider",
29
+ options: [
30
+ { value: "cuboid", label: "Cuboid" },
31
+ { value: "ball", label: "Ball" },
32
+ { value: "capsule", label: "Capsule" },
33
+ { value: "hull", label: "Hull" },
34
+ { value: "trimesh", label: "Tri Mesh" },
35
+ ],
36
+ },
37
+ { name: "friction", type: "number", label: "Friction", step: 0.05 },
38
+ { name: "restitution", type: "number", label: "Restitution", step: 0.05 },
39
+ { name: "capsuleRadius", type: "number", label: "Capsule Radius", step: 0.05 },
40
+ { name: "capsuleHalfHeight", type: "number", label: "Capsule Half Height", step: 0.05 },
41
+ ];
42
+ function CrashcatPhysicsEditor({ component, onUpdate }) {
43
+ return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [_jsx(FieldRenderer, { fields: crashcatPhysicsFields, values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor", values: component.properties, onChange: onUpdate, fallback: false }), _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(StringField, { name: "collisionEnterEventName", label: "Collision Enter", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "collisionExitEventName", label: "Collision Exit", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "sensorEnterEventName", label: "Sensor Enter", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "sensorExitEventName", label: "Sensor Exit", values: component.properties, onChange: onUpdate, fallback: "" })] }));
44
+ }
45
+ const inverseWorldMatrix = new Matrix4();
46
+ const childToLocalMatrix = new Matrix4();
47
+ const scratchVertex = new Vector3();
48
+ const scratchScale = new Vector3();
49
+ const scratchPosition = new Vector3();
50
+ const scratchBoundsSize = new Vector3();
51
+ const worldQuaternion = new Quaternion();
52
+ const parentWorldQuaternion = new Quaternion();
53
+ const localQuaternion = new Quaternion();
54
+ function collectGeometryData(object) {
55
+ const positions = [];
56
+ const indices = [];
57
+ let vertexOffset = 0;
58
+ inverseWorldMatrix.copy(object.matrixWorld).invert();
59
+ object.traverse((child) => {
60
+ var _a;
61
+ const geometry = child.geometry;
62
+ const positionAttribute = (_a = geometry === null || geometry === void 0 ? void 0 : geometry.attributes) === null || _a === void 0 ? void 0 : _a.position;
63
+ if (!positionAttribute)
64
+ return;
65
+ childToLocalMatrix.multiplyMatrices(inverseWorldMatrix, child.matrixWorld);
66
+ for (let i = 0; i < positionAttribute.count; i += 1) {
67
+ scratchVertex
68
+ .set(positionAttribute.getX(i), positionAttribute.getY(i), positionAttribute.getZ(i))
69
+ .applyMatrix4(childToLocalMatrix);
70
+ positions.push(scratchVertex.x, scratchVertex.y, scratchVertex.z);
71
+ }
72
+ if (geometry.index) {
73
+ for (let i = 0; i < geometry.index.count; i += 1) {
74
+ indices.push(vertexOffset + geometry.index.getX(i));
75
+ }
76
+ }
77
+ else {
78
+ for (let i = 0; i < positionAttribute.count; i += 1) {
79
+ indices.push(vertexOffset + i);
80
+ }
81
+ }
82
+ vertexOffset += positionAttribute.count;
83
+ });
84
+ if (positions.length === 0 || indices.length < 3)
85
+ return null;
86
+ return { positions, indices };
87
+ }
88
+ function createShapeForObject(object, physics) {
89
+ var _a, _b;
90
+ object.updateWorldMatrix(true, true);
91
+ if (physics.colliders === "trimesh") {
92
+ const geometry = collectGeometryData(object);
93
+ return geometry ? triangleMesh.create(geometry) : null;
94
+ }
95
+ if (physics.colliders === "hull") {
96
+ const geometry = collectGeometryData(object);
97
+ return geometry ? convexHull.create({ positions: geometry.positions }) : null;
98
+ }
99
+ if (physics.colliders === "capsule") {
100
+ return capsule.create({
101
+ radius: Math.max((_a = physics.capsuleRadius) !== null && _a !== void 0 ? _a : 0.35, 0.01),
102
+ halfHeightOfCylinder: Math.max((_b = physics.capsuleHalfHeight) !== null && _b !== void 0 ? _b : 0.45, 0.01),
103
+ });
104
+ }
105
+ object.getWorldScale(scratchScale);
106
+ const geometry = collectGeometryData(object);
107
+ if (!geometry)
108
+ return null;
109
+ if (physics.colliders === "ball") {
110
+ let maxRadiusSq = 0;
111
+ for (let i = 0; i < geometry.positions.length; i += 3) {
112
+ const x = geometry.positions[i] * scratchScale.x;
113
+ const y = geometry.positions[i + 1] * scratchScale.y;
114
+ const z = geometry.positions[i + 2] * scratchScale.z;
115
+ maxRadiusSq = Math.max(maxRadiusSq, x * x + y * y + z * z);
116
+ }
117
+ return sphere.create({ radius: Math.max(Math.sqrt(maxRadiusSq), 0.01) });
118
+ }
119
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
120
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
121
+ for (let i = 0; i < geometry.positions.length; i += 3) {
122
+ const x = geometry.positions[i] * scratchScale.x;
123
+ const y = geometry.positions[i + 1] * scratchScale.y;
124
+ const z = geometry.positions[i + 2] * scratchScale.z;
125
+ if (x < minX)
126
+ minX = x;
127
+ if (x > maxX)
128
+ maxX = x;
129
+ if (y < minY)
130
+ minY = y;
131
+ if (y > maxY)
132
+ maxY = y;
133
+ if (z < minZ)
134
+ minZ = z;
135
+ if (z > maxZ)
136
+ maxZ = z;
137
+ }
138
+ scratchBoundsSize.set(maxX - minX, maxY - minY, maxZ - minZ);
139
+ return box.create({
140
+ halfExtents: [
141
+ Math.max(scratchBoundsSize.x * 0.5, 0.01),
142
+ Math.max(scratchBoundsSize.y * 0.5, 0.01),
143
+ Math.max(scratchBoundsSize.z * 0.5, 0.01),
144
+ ],
145
+ });
146
+ }
147
+ function toMotionType(physics) {
148
+ if (physics.type === "dynamic")
149
+ return MotionType.DYNAMIC;
150
+ if (physics.type === "kinematicPosition" || physics.type === "kinematicVelocity")
151
+ return MotionType.KINEMATIC;
152
+ return MotionType.STATIC;
153
+ }
154
+ function toMotionQuality(physics) {
155
+ return physics.type === "kinematicPosition" ? MotionQuality.LINEAR_CAST : undefined;
156
+ }
157
+ function setObjectWorldTransform(object, position, quaternion) {
158
+ if (!object.parent) {
159
+ object.position.set(position[0], position[1], position[2]);
160
+ object.quaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
161
+ object.updateMatrixWorld(true);
162
+ return;
163
+ }
164
+ scratchPosition.set(position[0], position[1], position[2]);
165
+ object.parent.worldToLocal(scratchPosition);
166
+ object.position.copy(scratchPosition);
167
+ object.parent.getWorldQuaternion(parentWorldQuaternion);
168
+ worldQuaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
169
+ localQuaternion.copy(parentWorldQuaternion).invert().multiply(worldQuaternion);
170
+ object.quaternion.copy(localQuaternion);
171
+ object.updateMatrixWorld(true);
172
+ }
173
+ function syncObjectToBody(world, body, object, position, quaternion, delta) {
174
+ object.getWorldPosition(scratchPosition);
175
+ object.getWorldQuaternion(worldQuaternion);
176
+ position.splice(0, 3, scratchPosition.x, scratchPosition.y, scratchPosition.z);
177
+ quaternion.splice(0, 4, worldQuaternion.x, worldQuaternion.y, worldQuaternion.z, worldQuaternion.w);
178
+ if (delta === undefined) {
179
+ rigidBody.setPosition(world, body, position, false);
180
+ rigidBody.setQuaternion(world, body, quaternion, false);
181
+ }
182
+ else {
183
+ rigidBody.moveKinematic(body, position, quaternion, delta);
184
+ }
185
+ }
186
+ function bodyTransformChanged(body, lastPosition, lastQuaternion) {
187
+ const position = body.position;
188
+ const quaternion = body.quaternion;
189
+ return !lastPosition
190
+ || position[0] !== lastPosition[0]
191
+ || position[1] !== lastPosition[1]
192
+ || position[2] !== lastPosition[2]
193
+ || quaternion[0] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[0])
194
+ || quaternion[1] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[1])
195
+ || quaternion[2] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[2])
196
+ || quaternion[3] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[3]);
197
+ }
198
+ function getRegisteredBody(api, nodeId, body) {
199
+ return api && body && api.getBody(nodeId) === body ? body : null;
200
+ }
201
+ function CrashcatPhysicsView({ properties, children }) {
202
+ const { nodeId, getObject } = useNode();
203
+ const scene = useScene();
204
+ const store = usePrefabStoreApi();
205
+ const api = useCrashcat();
206
+ const { getAssetRevision } = useAssetRuntime();
207
+ const revision = getAssetRevision();
208
+ const bodyRef = useRef(null);
209
+ const motionTypeRef = useRef(MotionType.STATIC);
210
+ const syncPositionRef = useRef([0, 0, 0]);
211
+ const syncQuaternionRef = useRef([0, 0, 0, 1]);
212
+ const lastPositionRef = useRef(null);
213
+ const lastQuaternionRef = useRef(null);
214
+ const { angularVelocity, capsuleHalfHeight, capsuleRadius, colliders, collisionEnterEventName, collisionExitEventName, friction, linearVelocity, restitution, sensor, sensorEnterEventName, sensorExitEventName, type, } = properties;
215
+ const physics = useMemo(() => ({
216
+ angularVelocity,
217
+ capsuleHalfHeight,
218
+ capsuleRadius,
219
+ colliders,
220
+ collisionEnterEventName,
221
+ collisionExitEventName,
222
+ friction,
223
+ linearVelocity,
224
+ restitution,
225
+ sensor,
226
+ sensorEnterEventName,
227
+ sensorExitEventName,
228
+ type,
229
+ }), [
230
+ angularVelocity,
231
+ capsuleHalfHeight,
232
+ capsuleRadius,
233
+ colliders,
234
+ collisionEnterEventName,
235
+ collisionExitEventName,
236
+ friction,
237
+ linearVelocity,
238
+ restitution,
239
+ sensor,
240
+ sensorEnterEventName,
241
+ sensorExitEventName,
242
+ type,
243
+ ]);
244
+ useEffect(() => {
245
+ // Rebuild mesh-derived colliders when referenced assets finish loading.
246
+ void revision;
247
+ if (!api)
248
+ return;
249
+ const object = getObject();
250
+ if (!object)
251
+ return;
252
+ const shape = createShapeForObject(object, physics);
253
+ if (!shape)
254
+ return;
255
+ object.updateWorldMatrix(true, true);
256
+ object.getWorldPosition(scratchPosition);
257
+ const wq = new Quaternion();
258
+ object.getWorldQuaternion(wq);
259
+ const motionType = toMotionType(physics);
260
+ const motionQuality = toMotionQuality(physics);
261
+ const isKinematic = motionType === MotionType.KINEMATIC;
262
+ const isStatic = motionType === MotionType.STATIC;
263
+ const body = rigidBody.create(api.world, {
264
+ shape,
265
+ motionType,
266
+ motionQuality,
267
+ objectLayer: isStatic ? api.staticObjectLayer : api.movingObjectLayer,
268
+ position: [scratchPosition.x, scratchPosition.y, scratchPosition.z],
269
+ quaternion: [wq.x, wq.y, wq.z, wq.w],
270
+ sensor: Boolean(physics.sensor),
271
+ collideKinematicVsNonDynamic: isKinematic,
272
+ friction: physics.friction,
273
+ restitution: physics.restitution,
274
+ userData: { nodeId },
275
+ });
276
+ if (physics.linearVelocity) {
277
+ rigidBody.setLinearVelocity(api.world, body, physics.linearVelocity);
278
+ }
279
+ if (physics.angularVelocity) {
280
+ rigidBody.setAngularVelocity(api.world, body, physics.angularVelocity);
281
+ }
282
+ bodyRef.current = body;
283
+ motionTypeRef.current = motionType;
284
+ lastPositionRef.current = null;
285
+ lastQuaternionRef.current = null;
286
+ api.register(nodeId, body, {
287
+ motionType,
288
+ sensor: Boolean(physics.sensor),
289
+ events: {
290
+ collisionEnter: physics.collisionEnterEventName,
291
+ collisionExit: physics.collisionExitEventName,
292
+ sensorEnter: physics.sensorEnterEventName,
293
+ sensorExit: physics.sensorExitEventName,
294
+ },
295
+ });
296
+ return () => {
297
+ bodyRef.current = null;
298
+ api.unregister(nodeId);
299
+ };
300
+ }, [
301
+ api,
302
+ getObject,
303
+ nodeId,
304
+ physics,
305
+ revision,
306
+ ]);
307
+ useEffect(() => {
308
+ const syncEditBody = () => {
309
+ const body = getRegisteredBody(api, nodeId, bodyRef.current);
310
+ const object = getObject();
311
+ if (!api || !body || !object || scene.mode !== PrefabEditorMode.Edit)
312
+ return;
313
+ syncObjectToBody(api.world, body, object, syncPositionRef.current, syncQuaternionRef.current);
314
+ };
315
+ syncEditBody();
316
+ return store.subscribe(syncEditBody);
317
+ }, [api, getObject, nodeId, scene.mode, store]);
318
+ useFrame((_, delta) => {
319
+ const body = getRegisteredBody(api, nodeId, bodyRef.current);
320
+ const object = getObject();
321
+ if (!api || !body || !object || scene.mode !== PrefabEditorMode.Play || motionTypeRef.current !== MotionType.KINEMATIC)
322
+ return;
323
+ syncObjectToBody(api.world, body, object, syncPositionRef.current, syncQuaternionRef.current, Math.min(delta, MAX_PHYSICS_DELTA));
324
+ }, -2);
325
+ useFrame(() => {
326
+ const body = getRegisteredBody(api, nodeId, bodyRef.current);
327
+ const object = getObject();
328
+ if (!api || !body || !object || motionTypeRef.current !== MotionType.DYNAMIC || scene.mode !== PrefabEditorMode.Play)
329
+ return;
330
+ if (bodyTransformChanged(body, lastPositionRef.current, lastQuaternionRef.current)) {
331
+ setObjectWorldTransform(object, body.position, body.quaternion);
332
+ lastPositionRef.current = [body.position[0], body.position[1], body.position[2]];
333
+ lastQuaternionRef.current = [body.quaternion[0], body.quaternion[1], body.quaternion[2], body.quaternion[3]];
334
+ }
335
+ if (body.position[1] < -40) {
336
+ bodyRef.current = null;
337
+ api.unregister(nodeId);
338
+ }
339
+ });
340
+ return _jsx(_Fragment, { children: children });
341
+ }
342
+ const CrashcatPhysicsComponent = {
343
+ name: "CrashcatPhysics",
344
+ Editor: CrashcatPhysicsEditor,
345
+ View: CrashcatPhysicsView,
346
+ defaultProperties: {
347
+ type: "fixed",
348
+ colliders: "cuboid",
349
+ sensor: false,
350
+ },
351
+ };
352
+ export default CrashcatPhysicsComponent;
@@ -0,0 +1,27 @@
1
+ import { MotionType, type Filter, type RigidBody, type World } from "crashcat";
2
+ export type CrashcatEventConfig = {
3
+ collisionEnter?: string;
4
+ collisionExit?: string;
5
+ sensorEnter?: string;
6
+ sensorExit?: string;
7
+ };
8
+ export type BodyMeta = {
9
+ nodeId: string;
10
+ motionType: MotionType;
11
+ sensor: boolean;
12
+ events?: CrashcatEventConfig;
13
+ };
14
+ export interface CrashcatApi {
15
+ world: World;
16
+ queryFilter: Filter;
17
+ staticObjectLayer: number;
18
+ movingObjectLayer: number;
19
+ register: (nodeId: string, body: RigidBody, meta: Omit<BodyMeta, "nodeId">) => void;
20
+ unregister: (nodeId: string) => void;
21
+ getBody: (nodeId: string) => RigidBody | null;
22
+ }
23
+ export declare function useCrashcat(): CrashcatApi | null;
24
+ export declare function CrashcatRuntime({ debug, children }: {
25
+ debug?: boolean;
26
+ children?: React.ReactNode;
27
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,154 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useFrame } from "@react-three/fiber";
4
+ import { addBroadphaseLayer, addObjectLayer, createWorld, createWorldSettings, enableCollision, filter, registerAll, rigidBody, updateWorld, } from "crashcat";
5
+ import { debugRenderer } from "crashcat/three";
6
+ import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
7
+ import { gameEvents } from "../../tools/prefabeditor/GameEvents";
8
+ import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
9
+ const SLEEP_TIME_BEFORE_REST = 0.1;
10
+ const SLEEP_POINT_VELOCITY_THRESHOLD = 0.06;
11
+ const MAX_PHYSICS_DELTA = 1 / 30;
12
+ let didRegisterCrashcat = false;
13
+ function ensureCrashcatRegistered() {
14
+ if (didRegisterCrashcat)
15
+ return;
16
+ registerAll();
17
+ didRegisterCrashcat = true;
18
+ }
19
+ const crashcatListeners = new Set();
20
+ let crashcatApi = null;
21
+ export function useCrashcat() {
22
+ return useSyncExternalStore((listener) => (crashcatListeners.add(listener), () => crashcatListeners.delete(listener)), () => crashcatApi, () => crashcatApi);
23
+ }
24
+ function setCrashcatApi(api) {
25
+ crashcatApi = api;
26
+ crashcatListeners.forEach((listener) => listener());
27
+ }
28
+ function emitConfiguredEvent(eventName, sourceNodeId, targetNodeId, collisionNormal) {
29
+ const trimmed = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
30
+ if (!trimmed)
31
+ return;
32
+ gameEvents.emit(trimmed, Object.assign({ sourceEntityId: sourceNodeId, sourceNodeId, targetEntityId: targetNodeId, targetNodeId }, (collisionNormal ? { collisionNormal } : {})));
33
+ }
34
+ function createDebugState() {
35
+ const options = debugRenderer.createDefaultOptions();
36
+ options.bodies.wireframe = true;
37
+ options.bodies.color = debugRenderer.BodyColorMode.MOTION_TYPE;
38
+ options.bodies.showAngularVelocity = false;
39
+ options.bodies.showLinearVelocity = false;
40
+ options.contacts.enabled = false;
41
+ options.contactConstraints.enabled = false;
42
+ return debugRenderer.init(options);
43
+ }
44
+ function getBodyMeta(bodyById, body) {
45
+ var _a;
46
+ const registered = bodyById.get(Number(body.id));
47
+ if (registered)
48
+ return registered;
49
+ const nodeId = (_a = body.userData) === null || _a === void 0 ? void 0 : _a.nodeId;
50
+ if (typeof nodeId !== "string")
51
+ return null;
52
+ return {
53
+ nodeId,
54
+ motionType: body.motionType,
55
+ sensor: body.sensor,
56
+ };
57
+ }
58
+ export function CrashcatRuntime({ debug = false, children }) {
59
+ const { mode } = useScene();
60
+ const bodiesRef = useRef(new Map());
61
+ const bodyByIdRef = useRef(new Map());
62
+ const apiRef = useRef(null);
63
+ const [debugState] = useState(() => debug ? createDebugState() : null);
64
+ const listener = useMemo(() => ({
65
+ onContactAdded: (bodyA, bodyB, manifold) => {
66
+ var _a, _b;
67
+ const metaA = getBodyMeta(bodyByIdRef.current, bodyA);
68
+ const metaB = getBodyMeta(bodyByIdRef.current, bodyB);
69
+ const n = manifold === null || manifold === void 0 ? void 0 : manifold.worldSpaceNormal;
70
+ const nA = n ? [n[0], n[1], n[2]] : undefined;
71
+ const nB = n ? [-n[0], -n[1], -n[2]] : undefined;
72
+ if (metaA === null || metaA === void 0 ? void 0 : metaA.events)
73
+ emitConfiguredEvent(metaA.sensor ? metaA.events.sensorEnter : metaA.events.collisionEnter, metaA.nodeId, (_a = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _a !== void 0 ? _a : null, nA);
74
+ if (metaB === null || metaB === void 0 ? void 0 : metaB.events)
75
+ emitConfiguredEvent(metaB.sensor ? metaB.events.sensorEnter : metaB.events.collisionEnter, metaB.nodeId, (_b = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _b !== void 0 ? _b : null, nB);
76
+ },
77
+ onContactRemoved: (idA, idB) => {
78
+ var _a, _b;
79
+ const metaA = bodyByIdRef.current.get(Number(idA));
80
+ const metaB = bodyByIdRef.current.get(Number(idB));
81
+ if (metaA === null || metaA === void 0 ? void 0 : metaA.events)
82
+ emitConfiguredEvent(metaA.sensor ? metaA.events.sensorExit : metaA.events.collisionExit, metaA.nodeId, (_a = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _a !== void 0 ? _a : null);
83
+ if (metaB === null || metaB === void 0 ? void 0 : metaB.events)
84
+ emitConfiguredEvent(metaB.sensor ? metaB.events.sensorExit : metaB.events.collisionExit, metaB.nodeId, (_b = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _b !== void 0 ? _b : null);
85
+ },
86
+ }), []);
87
+ useEffect(() => {
88
+ ensureCrashcatRegistered();
89
+ const settings = createWorldSettings();
90
+ settings.narrowphase.collideWithBackfaces = true;
91
+ settings.sleeping.timeBeforeSleep = SLEEP_TIME_BEFORE_REST;
92
+ settings.sleeping.pointVelocitySleepThreshold = SLEEP_POINT_VELOCITY_THRESHOLD;
93
+ const movingBroadphase = addBroadphaseLayer(settings);
94
+ const staticBroadphase = addBroadphaseLayer(settings);
95
+ const movingObjectLayer = addObjectLayer(settings, movingBroadphase);
96
+ const staticObjectLayer = addObjectLayer(settings, staticBroadphase);
97
+ enableCollision(settings, movingObjectLayer, staticObjectLayer);
98
+ enableCollision(settings, movingObjectLayer, movingObjectLayer);
99
+ const world = createWorld(settings);
100
+ const queryFilter = filter.forWorld(world);
101
+ const bodies = bodiesRef.current;
102
+ const bodyById = bodyByIdRef.current;
103
+ const unregister = (nodeId) => {
104
+ const entry = bodies.get(nodeId);
105
+ if (!entry)
106
+ return;
107
+ bodyById.delete(Number(entry.body.id));
108
+ rigidBody.remove(world, entry.body);
109
+ bodies.delete(nodeId);
110
+ };
111
+ const runtimeApi = {
112
+ world,
113
+ queryFilter,
114
+ staticObjectLayer,
115
+ movingObjectLayer,
116
+ register: (nodeId, body, meta) => {
117
+ unregister(nodeId);
118
+ const full = Object.assign({ nodeId }, meta);
119
+ bodies.set(nodeId, { body, meta: full });
120
+ bodyById.set(Number(body.id), full);
121
+ },
122
+ unregister,
123
+ getBody: (nodeId) => { var _a, _b; return (_b = (_a = bodies.get(nodeId)) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : null; },
124
+ };
125
+ apiRef.current = runtimeApi;
126
+ setCrashcatApi(runtimeApi);
127
+ return () => {
128
+ for (const entry of bodies.values()) {
129
+ rigidBody.remove(world, entry.body);
130
+ }
131
+ apiRef.current = null;
132
+ if (crashcatApi === runtimeApi)
133
+ setCrashcatApi(null);
134
+ bodies.clear();
135
+ bodyById.clear();
136
+ if (debugState)
137
+ debugRenderer.dispose(debugState);
138
+ };
139
+ }, [debugState]);
140
+ useFrame((_, delta) => {
141
+ const runtimeApi = apiRef.current;
142
+ if (!runtimeApi)
143
+ return;
144
+ const { world } = runtimeApi;
145
+ const stepDelta = Math.min(delta, MAX_PHYSICS_DELTA);
146
+ if (mode === PrefabEditorMode.Play)
147
+ updateWorld(world, listener, stepDelta);
148
+ if (debugState)
149
+ debugRenderer.update(debugState, world);
150
+ }, -1);
151
+ return (_jsxs(_Fragment, { children: [children, debugState && mode === PrefabEditorMode.Edit
152
+ ? _jsx("primitive", { object: debugState.object3d })
153
+ : null] }));
154
+ }
@@ -0,0 +1,2 @@
1
+ export { CrashcatRuntime, useCrashcat, type BodyMeta, type CrashcatApi, type CrashcatEventConfig, } from "./CrashcatRuntime";
2
+ export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
@@ -0,0 +1,2 @@
1
+ export { CrashcatRuntime, useCrashcat, } from "./CrashcatRuntime";
2
+ export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
@@ -0,0 +1 @@
1
+ export * from "./crashcat";
@@ -0,0 +1 @@
1
+ export * from "./crashcat";