react-three-game 0.0.101 → 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.
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,49 @@ 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
+ var _a;
143
+ let cancelled = false;
144
+ (_a = textureRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
145
+ textureRef.current = null;
146
+ setTexture(null);
147
+ const loader = new TextureLoader();
148
+ loader.load(url, loadedTexture => {
149
+ if (cancelled) {
150
+ loadedTexture.dispose();
151
+ return;
152
+ }
153
+ textureRef.current = loadedTexture;
154
+ setTexture(loadedTexture);
155
+ }, undefined, () => {
156
+ var _a;
157
+ if (!cancelled) {
158
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
159
+ }
160
+ });
161
+ return () => {
162
+ var _a;
163
+ cancelled = true;
164
+ (_a = textureRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
165
+ textureRef.current = null;
166
+ };
167
+ }, [url]);
168
+ if (!texture)
169
+ return null;
133
170
  return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
134
171
  }
135
172
  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, useEffect, 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,54 @@ 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 cloneConfiguredTexture(texture, options) {
61
+ if (!texture)
62
+ return undefined;
63
+ const nextTexture = texture.clone();
64
+ configureTexture(nextTexture, options);
65
+ return nextTexture;
66
+ }
67
+ function useConfiguredTexture(texture, options) {
68
+ var _a, _b, _c, _d;
69
+ const configuredTexture = useMemo(() => cloneConfiguredTexture(texture, options), [texture]);
70
+ useEffect(() => {
71
+ return () => configuredTexture === null || configuredTexture === void 0 ? void 0 : configuredTexture.dispose();
72
+ }, [configuredTexture]);
73
+ useLayoutEffect(() => {
74
+ configureTexture(configuredTexture, options);
75
+ }, [
76
+ configuredTexture,
77
+ options.colorSpace,
78
+ options.repeat,
79
+ (_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0],
80
+ (_b = options.repeatCount) === null || _b === void 0 ? void 0 : _b[1],
81
+ (_c = options.offset) === null || _c === void 0 ? void 0 : _c[0],
82
+ (_d = options.offset) === null || _d === void 0 ? void 0 : _d[1],
83
+ options.generateMipmaps,
84
+ options.minFilter,
85
+ options.magFilter,
86
+ ]);
87
+ return configuredTexture;
88
+ }
41
89
  export function useMaterialOverrides() {
42
90
  return useContext(MaterialOverridesContext);
43
91
  }
@@ -52,13 +100,14 @@ extend({
52
100
  SpriteNodeMaterial,
53
101
  });
54
102
  function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
55
- var _a;
103
+ var _a, _b, _c, _d;
56
104
  const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
57
105
  const hasTexture = !!component.properties.texture;
58
106
  const hasRepeat = component.properties.repeat;
59
107
  const animateOffset = component.properties.animateOffset;
60
108
  const isStandardMaterial = materialType === 'standard';
61
109
  const isSpriteMaterial = materialType === 'sprite';
110
+ 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
111
  const fields = [
63
112
  {
64
113
  name: 'attach',
@@ -151,12 +200,12 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
151
200
  type: 'select',
152
201
  label: 'Min Filter',
153
202
  options: [
203
+ { value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
204
+ { value: 'LinearFilter', label: 'Linear' },
205
+ { value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
154
206
  { value: 'NearestFilter', label: 'Nearest' },
155
207
  { value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
156
208
  { 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
209
  ],
161
210
  },
162
211
  {
@@ -164,17 +213,17 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
164
213
  type: 'select',
165
214
  label: 'Mag Filter',
166
215
  options: [
167
- { value: 'NearestFilter', label: 'Nearest' },
168
216
  { value: 'LinearFilter', label: 'Linear (Default)' },
217
+ { value: 'NearestFilter', label: 'Nearest' },
169
218
  ],
170
219
  },
171
220
  ] : []),
172
221
  ];
173
- return (_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }));
222
+ return (_jsx(FieldRenderer, { fields: fields, values: editorValues, onChange: onUpdate }));
174
223
  }
175
224
  // View for Material component
176
225
  function MaterialComponentView({ properties: rawProps }) {
177
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
226
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
178
227
  const { getTexture } = useAssetRuntime();
179
228
  const properties = rawProps;
180
229
  const materialSource = properties !== null && properties !== void 0 ? properties : {};
@@ -198,80 +247,16 @@ function MaterialComponentView({ properties: rawProps }) {
198
247
  const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
199
248
  const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
200
249
  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,
250
+ const textureConfig = {
229
251
  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],
252
+ repeatCount,
253
+ offset,
234
254
  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
- ]);
255
+ minFilter: resolvedMinFilter,
256
+ magFilter: resolvedMagFilter,
257
+ };
258
+ const finalTexture = useConfiguredTexture(texture, Object.assign(Object.assign({}, textureConfig), { colorSpace: SRGBColorSpace }));
259
+ const finalNormalMap = useConfiguredTexture(normalMapTexture, Object.assign(Object.assign({}, textureConfig), { colorSpace: NoColorSpace }));
275
260
  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
261
  useFrame((_, delta) => {
277
262
  var _a, _b;
@@ -294,17 +279,23 @@ function MaterialComponentView({ properties: rawProps }) {
294
279
  materialType,
295
280
  textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
296
281
  normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
282
+ repeat ? 'repeat' : 'clamp',
283
+ (_l = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0]) !== null && _l !== void 0 ? _l : 1,
284
+ (_m = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]) !== null && _m !== void 0 ? _m : 1,
285
+ generateMipmaps ? 'mips' : 'no-mips',
286
+ minFilter,
287
+ magFilter,
297
288
  ].join('|');
298
289
  if (materialType === 'basic') {
299
290
  return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
300
291
  }
301
292
  if (materialType === 'sprite') {
302
293
  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 => {
294
+ 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
295
  material.needsUpdate = true;
305
296
  } }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
306
297
  }
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));
298
+ 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
299
  }
309
300
  const MaterialComponent = {
310
301
  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.102",
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",