react-three-game 0.0.101 → 0.0.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -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";
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Canvas, useLoader } from "@react-three/fiber";
2
+ import { Canvas } from "@react-three/fiber";
3
3
  import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
4
4
  import { Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
5
5
  import { createPortal } from 'react-dom';
@@ -124,12 +124,44 @@ export function TextureListViewer({ files, selected, onSelect, basePath = "" })
124
124
  }
125
125
  function TextureCard({ file, onSelect, basePath = "" }) {
126
126
  const [isHovered, setIsHovered] = useState(false);
127
+ const [error, setError] = useState(false);
127
128
  const { ref, isInView } = useInView();
128
129
  const fullPath = resolvePrefabAssetPath(basePath, file);
129
- return (_jsxs("div", { ref: ref, style: Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: file.split('/').pop() })] }));
130
+ const fileName = file.split('/').pop();
131
+ if (error) {
132
+ return (_jsxs("div", { ref: ref, style: Object.assign(Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), { backgroundColor: assetViewerColors.errorBg }), onClick: () => onSelect(file), title: `Could not load ${file}`, children: [_jsx("div", { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }), _jsx("div", { style: styles.bottomLabel, children: fileName })] }));
133
+ }
134
+ return (_jsxs("div", { ref: ref, style: Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: fileName })] }));
130
135
  }
131
- function TextureSphere({ url }) {
132
- const texture = useLoader(TextureLoader, url);
136
+ function TextureSphere({ url, onError }) {
137
+ const [texture, setTexture] = useState(null);
138
+ const textureRef = useRef(null);
139
+ const onErrorRef = useRef(onError);
140
+ onErrorRef.current = onError;
141
+ useEffect(() => {
142
+ let cancelled = false;
143
+ textureRef.current = null;
144
+ setTexture(null);
145
+ const loader = new TextureLoader();
146
+ loader.load(url, loadedTexture => {
147
+ if (cancelled) {
148
+ return;
149
+ }
150
+ textureRef.current = loadedTexture;
151
+ setTexture(loadedTexture);
152
+ }, undefined, () => {
153
+ var _a;
154
+ if (!cancelled) {
155
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
156
+ }
157
+ });
158
+ return () => {
159
+ cancelled = true;
160
+ textureRef.current = null;
161
+ };
162
+ }, [url]);
163
+ if (!texture)
164
+ return null;
133
165
  return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
134
166
  }
135
167
  export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
13
- import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
13
+ import { createContext, useContext, useLayoutEffect, useMemo, useRef } from 'react';
14
14
  import { extend } from '@react-three/fiber';
15
15
  import { useFrame } from '@react-three/fiber';
16
16
  import { assetRef, assetRefs } from './ComponentRegistry';
@@ -38,6 +38,50 @@ const MAG_FILTER_MAP = {
38
38
  NearestFilter,
39
39
  LinearFilter,
40
40
  };
41
+ function configureTexture(texture, options) {
42
+ var _a, _b, _c, _d, _e, _f, _g, _h;
43
+ if (!texture)
44
+ return;
45
+ if (options.repeat) {
46
+ texture.wrapS = texture.wrapT = RepeatWrapping;
47
+ texture.repeat.set((_b = (_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, (_d = (_c = options.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1);
48
+ }
49
+ else {
50
+ texture.wrapS = texture.wrapT = ClampToEdgeWrapping;
51
+ texture.repeat.set(1, 1);
52
+ }
53
+ texture.offset.set((_f = (_e = options.offset) === null || _e === void 0 ? void 0 : _e[0]) !== null && _f !== void 0 ? _f : 0, (_h = (_g = options.offset) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : 0);
54
+ texture.colorSpace = options.colorSpace;
55
+ texture.generateMipmaps = options.generateMipmaps;
56
+ texture.minFilter = options.minFilter;
57
+ texture.magFilter = options.magFilter;
58
+ texture.needsUpdate = true;
59
+ }
60
+ function useConfiguredTexture(texture, options) {
61
+ const { colorSpace, repeat, repeatCount, offset, generateMipmaps, minFilter, magFilter, } = options;
62
+ const configuredTexture = useMemo(() => texture === null || texture === void 0 ? void 0 : texture.clone(), [texture]);
63
+ useLayoutEffect(() => {
64
+ configureTexture(configuredTexture, {
65
+ colorSpace,
66
+ repeat,
67
+ repeatCount,
68
+ offset,
69
+ generateMipmaps,
70
+ minFilter,
71
+ magFilter,
72
+ });
73
+ }, [
74
+ configuredTexture,
75
+ colorSpace,
76
+ repeat,
77
+ repeatCount,
78
+ offset,
79
+ generateMipmaps,
80
+ minFilter,
81
+ magFilter,
82
+ ]);
83
+ return configuredTexture;
84
+ }
41
85
  export function useMaterialOverrides() {
42
86
  return useContext(MaterialOverridesContext);
43
87
  }
@@ -52,13 +96,14 @@ extend({
52
96
  SpriteNodeMaterial,
53
97
  });
54
98
  function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
55
- var _a;
99
+ var _a, _b, _c, _d;
56
100
  const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
57
101
  const hasTexture = !!component.properties.texture;
58
102
  const hasRepeat = component.properties.repeat;
59
103
  const animateOffset = component.properties.animateOffset;
60
104
  const isStandardMaterial = materialType === 'standard';
61
105
  const isSpriteMaterial = materialType === 'sprite';
106
+ const editorValues = Object.assign(Object.assign({}, component.properties), { generateMipmaps: (_b = component.properties.generateMipmaps) !== null && _b !== void 0 ? _b : true, minFilter: (_c = component.properties.minFilter) !== null && _c !== void 0 ? _c : 'LinearMipmapLinearFilter', magFilter: (_d = component.properties.magFilter) !== null && _d !== void 0 ? _d : 'LinearFilter' });
62
107
  const fields = [
63
108
  {
64
109
  name: 'attach',
@@ -151,12 +196,12 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
151
196
  type: 'select',
152
197
  label: 'Min Filter',
153
198
  options: [
199
+ { value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
200
+ { value: 'LinearFilter', label: 'Linear' },
201
+ { value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
154
202
  { value: 'NearestFilter', label: 'Nearest' },
155
203
  { value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
156
204
  { value: 'NearestMipmapLinearFilter', label: 'Nearest Mipmap Linear' },
157
- { value: 'LinearFilter', label: 'Linear' },
158
- { value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
159
- { value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
160
205
  ],
161
206
  },
162
207
  {
@@ -164,17 +209,17 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
164
209
  type: 'select',
165
210
  label: 'Mag Filter',
166
211
  options: [
167
- { value: 'NearestFilter', label: 'Nearest' },
168
212
  { value: 'LinearFilter', label: 'Linear (Default)' },
213
+ { value: 'NearestFilter', label: 'Nearest' },
169
214
  ],
170
215
  },
171
216
  ] : []),
172
217
  ];
173
- return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }));
218
+ return (_jsx(FieldRenderer, { fields: fields, values: editorValues, onChange: onUpdate }));
174
219
  }
175
220
  // View for Material component
176
221
  function MaterialComponentView({ properties: rawProps }) {
177
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
222
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
178
223
  const { getTexture } = useAssetRuntime();
179
224
  const properties = rawProps;
180
225
  const materialSource = properties !== null && properties !== void 0 ? properties : {};
@@ -198,80 +243,16 @@ function MaterialComponentView({ properties: rawProps }) {
198
243
  const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
199
244
  const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
200
245
  const animatedOffsetRef = useRef([(_g = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _g !== void 0 ? _g : 0, (_h = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _h !== void 0 ? _h : 0]);
201
- const finalTexture = useMemo(() => {
202
- return texture ? texture.clone() : undefined;
203
- }, [texture]);
204
- useEffect(() => {
205
- return () => finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.dispose();
206
- }, [finalTexture]);
207
- useEffect(() => {
208
- var _a, _b;
209
- if (!finalTexture)
210
- return;
211
- if (repeat) {
212
- finalTexture.wrapS = finalTexture.wrapT = RepeatWrapping;
213
- if (repeatCount) {
214
- finalTexture.repeat.set(repeatCount[0], repeatCount[1]);
215
- }
216
- }
217
- else {
218
- finalTexture.wrapS = finalTexture.wrapT = ClampToEdgeWrapping;
219
- finalTexture.repeat.set(1, 1);
220
- }
221
- finalTexture.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
222
- finalTexture.colorSpace = SRGBColorSpace;
223
- finalTexture.generateMipmaps = generateMipmaps;
224
- finalTexture.minFilter = resolvedMinFilter;
225
- finalTexture.magFilter = resolvedMagFilter;
226
- finalTexture.needsUpdate = true;
227
- }, [
228
- finalTexture,
246
+ const textureConfig = {
229
247
  repeat,
230
- repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
231
- repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
232
- offset === null || offset === void 0 ? void 0 : offset[0],
233
- offset === null || offset === void 0 ? void 0 : offset[1],
248
+ repeatCount,
249
+ offset,
234
250
  generateMipmaps,
235
- resolvedMinFilter,
236
- resolvedMagFilter
237
- ]);
238
- const finalNormalMap = useMemo(() => {
239
- return normalMapTexture ? normalMapTexture.clone() : undefined;
240
- }, [normalMapTexture]);
241
- useEffect(() => {
242
- return () => finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.dispose();
243
- }, [finalNormalMap]);
244
- useEffect(() => {
245
- var _a, _b;
246
- if (!finalNormalMap)
247
- return;
248
- if (repeat) {
249
- finalNormalMap.wrapS = finalNormalMap.wrapT = RepeatWrapping;
250
- if (repeatCount) {
251
- finalNormalMap.repeat.set(repeatCount[0], repeatCount[1]);
252
- }
253
- }
254
- else {
255
- finalNormalMap.wrapS = finalNormalMap.wrapT = ClampToEdgeWrapping;
256
- finalNormalMap.repeat.set(1, 1);
257
- }
258
- finalNormalMap.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
259
- finalNormalMap.colorSpace = NoColorSpace;
260
- finalNormalMap.generateMipmaps = generateMipmaps;
261
- finalNormalMap.minFilter = resolvedMinFilter;
262
- finalNormalMap.magFilter = resolvedMagFilter;
263
- finalNormalMap.needsUpdate = true;
264
- }, [
265
- finalNormalMap,
266
- repeat,
267
- repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
268
- repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
269
- offset === null || offset === void 0 ? void 0 : offset[0],
270
- offset === null || offset === void 0 ? void 0 : offset[1],
271
- generateMipmaps,
272
- resolvedMinFilter,
273
- resolvedMagFilter
274
- ]);
251
+ minFilter: resolvedMinFilter,
252
+ magFilter: resolvedMagFilter,
253
+ };
254
+ const finalTexture = useConfiguredTexture(texture, Object.assign(Object.assign({}, textureConfig), { colorSpace: SRGBColorSpace }));
255
+ const finalNormalMap = useConfiguredTexture(normalMapTexture, Object.assign(Object.assign({}, textureConfig), { colorSpace: NoColorSpace }));
275
256
  animatedOffsetRef.current = [(_j = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _j !== void 0 ? _j : 0, (_k = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _k !== void 0 ? _k : 0];
276
257
  useFrame((_, delta) => {
277
258
  var _a, _b;
@@ -294,17 +275,23 @@ function MaterialComponentView({ properties: rawProps }) {
294
275
  materialType,
295
276
  textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
296
277
  normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
278
+ repeat ? 'repeat' : 'clamp',
279
+ (_l = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0]) !== null && _l !== void 0 ? _l : 1,
280
+ (_m = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]) !== null && _m !== void 0 ? _m : 1,
281
+ generateMipmaps ? 'mips' : 'no-mips',
282
+ minFilter,
283
+ magFilter,
297
284
  ].join('|');
298
285
  if (materialType === 'basic') {
299
286
  return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
300
287
  }
301
288
  if (materialType === 'sprite') {
302
289
  const spriteTransparent = materialSource.transparent !== false;
303
- return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (_l = materialSource.color) !== null && _l !== void 0 ? _l : '#ffffff', opacity: (_m = materialSource.opacity) !== null && _m !== void 0 ? _m : 1, transparent: spriteTransparent, alphaTest: (_o = materialSource.alphaTest) !== null && _o !== void 0 ? _o : 0, depthTest: (_p = materialSource.depthTest) !== null && _p !== void 0 ? _p : false, depthWrite: (_q = materialSource.depthWrite) !== null && _q !== void 0 ? _q : false, toneMapped: (_r = materialSource.toneMapped) !== null && _r !== void 0 ? _r : true, onUpdate: material => {
290
+ return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (_o = materialSource.color) !== null && _o !== void 0 ? _o : '#ffffff', opacity: (_p = materialSource.opacity) !== null && _p !== void 0 ? _p : 1, transparent: spriteTransparent, alphaTest: (_q = materialSource.alphaTest) !== null && _q !== void 0 ? _q : 0, depthTest: (_r = materialSource.depthTest) !== null && _r !== void 0 ? _r : false, depthWrite: (_s = materialSource.depthWrite) !== null && _s !== void 0 ? _s : false, toneMapped: (_t = materialSource.toneMapped) !== null && _t !== void 0 ? _t : true, onUpdate: material => {
304
291
  material.needsUpdate = true;
305
292
  } }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
306
293
  }
307
- return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(_s = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _s !== void 0 ? _s : 1, (_t = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _t !== void 0 ? _t : 1] : undefined }), materialKey));
294
+ return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(_u = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _u !== void 0 ? _u : 1, (_v = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _v !== void 0 ? _v : 1] : undefined }), materialKey));
308
295
  }
309
296
  const MaterialComponent = {
310
297
  name: 'Material',
package/package.json CHANGED
@@ -1,10 +1,28 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.101",
3
+ "version": "0.0.103",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./plugins": {
15
+ "types": "./dist/plugins/index.d.ts",
16
+ "import": "./dist/plugins/index.js",
17
+ "default": "./dist/plugins/index.js"
18
+ },
19
+ "./plugins/crashcat": {
20
+ "types": "./dist/plugins/crashcat/index.d.ts",
21
+ "import": "./dist/plugins/crashcat/index.js",
22
+ "default": "./dist/plugins/crashcat/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
8
26
  "files": [
9
27
  "dist",
10
28
  "README.md",
@@ -30,7 +48,13 @@
30
48
  "react": ">=18.0.0",
31
49
  "react-dom": ">=18.0.0",
32
50
  "three": ">=0.182.0",
33
- "three-text": ">=0.4.4"
51
+ "three-text": ">=0.4.4",
52
+ "crashcat": ">=0.0.4"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "crashcat": {
56
+ "optional": true
57
+ }
34
58
  },
35
59
  "devDependencies": {
36
60
  "@react-three/drei": "^10.7.7",
@@ -39,6 +63,7 @@
39
63
  "@types/react-dom": "^19.2.3",
40
64
  "@types/three": "^0.182.0",
41
65
  "concurrently": "^9.2.1",
66
+ "crashcat": "^0.0.4",
42
67
  "react": "^19.2.4",
43
68
  "react-dom": "^19.2.4",
44
69
  "three": "^0.184.0",