react-three-game 0.0.107 → 0.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +12 -5
  2. package/dist/editor.d.ts +22 -0
  3. package/dist/editor.js +15 -0
  4. package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +75 -47
  5. package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
  6. package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
  7. package/dist/plugins/crashcat/CrashcatRuntime.js +19 -15
  8. package/dist/plugins/crashcat/index.d.ts +1 -0
  9. package/dist/plugins/crashcat/index.js +1 -0
  10. package/dist/tools/assetviewer/page.js +4 -4
  11. package/dist/tools/prefabeditor/EditorContext.d.ts +36 -0
  12. package/dist/tools/prefabeditor/EditorContext.js +17 -0
  13. package/dist/tools/prefabeditor/EditorTree.js +6 -3
  14. package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
  15. package/dist/tools/prefabeditor/EditorTreeMenus.js +18 -6
  16. package/dist/tools/prefabeditor/EditorUI.js +1 -1
  17. package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
  18. package/dist/tools/prefabeditor/PrefabEditor.d.ts +5 -37
  19. package/dist/tools/prefabeditor/PrefabEditor.js +41 -43
  20. package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -27
  21. package/dist/tools/prefabeditor/PrefabRoot.js +133 -78
  22. package/dist/tools/prefabeditor/SceneContext.d.ts +28 -0
  23. package/dist/tools/prefabeditor/SceneContext.js +14 -0
  24. package/dist/tools/prefabeditor/assetRuntime.d.ts +4 -0
  25. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +16 -1
  26. package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
  27. package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
  28. package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
  29. package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
  30. package/dist/tools/prefabeditor/components/TransformComponent.js +1 -1
  31. package/dist/tools/prefabeditor/components/index.d.ts +1 -0
  32. package/dist/tools/prefabeditor/components/index.js +10 -0
  33. package/dist/tools/prefabeditor/components/runtime.d.ts +4 -0
  34. package/dist/tools/prefabeditor/components/runtime.js +372 -0
  35. package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
  36. package/dist/tools/prefabeditor/modelPrefab.js +44 -11
  37. package/dist/tools/prefabeditor/prefab.d.ts +5 -4
  38. package/dist/tools/prefabeditor/prefab.js +47 -29
  39. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -1
  40. package/dist/tools/prefabeditor/prefabStore.js +5 -1
  41. package/dist/tools/prefabeditor/runtimeUtils.d.ts +10 -0
  42. package/dist/tools/prefabeditor/runtimeUtils.js +30 -0
  43. package/dist/tools/prefabeditor/utils.d.ts +7 -8
  44. package/dist/tools/prefabeditor/utils.js +63 -36
  45. package/dist/viewer.d.ts +22 -0
  46. package/dist/viewer.js +14 -0
  47. package/package.json +22 -20
  48. package/dist/index.d.ts +0 -40
  49. package/dist/index.js +0 -32
package/README.md CHANGED
@@ -29,7 +29,7 @@ npm install react-three-game @react-three/drei @react-three/fiber three
29
29
  Here is a minimal example that renders a prefab inside a normal R3F app:
30
30
 
31
31
  ```tsx
32
- import { GameCanvas, PrefabRoot, ground } from "react-three-game";
32
+ import { GameCanvas, PrefabRoot, ground } from "react-three-game/viewer";
33
33
 
34
34
  const prefab = {
35
35
  id: "starter-scene",
@@ -80,7 +80,7 @@ This example renders a simple authored prefab with a ground plane and mesh conte
80
80
  In addition to the runtime renderer, there is a visual editor for authoring prefabs.
81
81
 
82
82
  ```tsx
83
- import { PrefabEditor } from "react-three-game";
83
+ import { PrefabEditor } from "react-three-game/editor";
84
84
 
85
85
  export default function App() {
86
86
  return <PrefabEditor initialPrefab={prefab} onChange={console.log} />;
@@ -110,6 +110,13 @@ That means authored content stays as a prefab, and the same prefab can be:
110
110
 
111
111
  Custom component `View`s use normal React Three Fiber composition with `children`.
112
112
 
113
+ When you import or decompose a `.glb` or `.gltf` model, mesh names can opt into imported Crashcat colliders:
114
+
115
+ * `MeshName_col` keeps the mesh visible and adds a fixed `CrashcatPhysics` `trimesh` collider.
116
+ * `MeshName_colonly` adds the same collider but hides the decomposed mesh render.
117
+
118
+ This mirrors the common Blender authoring workflow: export helper collision meshes in the GLB, then edit the generated `CrashcatPhysics` properties if that body needs a different motion type or collider shape.
119
+
113
120
  For agent-authored custom meshes, use `BufferGeometry` with flat numeric arrays:
114
121
 
115
122
  ```json
@@ -157,7 +164,7 @@ Use the editor or root ref for scene-native object access, and the `Scene` mutat
157
164
 
158
165
  ```tsx
159
166
  import { useEffect, useRef } from "react";
160
- import { PrefabEditor, type PrefabEditorRef } from "react-three-game";
167
+ import { PrefabEditor, type PrefabEditorRef } from "react-three-game/editor";
161
168
 
162
169
  function RaiseBall() {
163
170
  const editorRef = useRef<PrefabEditorRef>(null);
@@ -192,7 +199,7 @@ ball?.rotateY(0.5);
192
199
  For runtime integrations that need to react to authored scene changes, subscribe through the prefab store:
193
200
 
194
201
  ```tsx
195
- import { usePrefabStoreApi } from "react-three-game";
202
+ import { usePrefabStoreApi } from "react-three-game/editor";
196
203
 
197
204
  const store = usePrefabStoreApi();
198
205
  const stop = store.subscribe(
@@ -207,7 +214,7 @@ For runtime-owned imperative state, register node-local handles instead of reach
207
214
 
208
215
  ```tsx
209
216
  import { useEffect } from "react";
210
- import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game";
217
+ import { useAssetRuntime, useNode, useNodeHandle } from "react-three-game/viewer";
211
218
 
212
219
  function SpinnerView({ children }: { children?: React.ReactNode }) {
213
220
  const { nodeId } = useNode();
@@ -0,0 +1,22 @@
1
+ import { registerBuiltinComponents } from "./tools/prefabeditor/components";
2
+ import "./viewer";
3
+ export { registerBuiltinComponents };
4
+ export * from "./viewer";
5
+ export { default as PrefabEditor } from "./tools/prefabeditor/PrefabEditor";
6
+ export type { PrefabEditorProps, PrefabEditorRef } from "./tools/prefabeditor/PrefabEditor";
7
+ export { useEditorContext, useEditorRef } from "./tools/prefabeditor/EditorContext";
8
+ export type { EditorContextType } from "./tools/prefabeditor/EditorContext";
9
+ export { usePrefabStore, usePrefabStoreApi } from "./tools/prefabeditor/prefabStore";
10
+ export type { PrefabStoreApi, PrefabStoreState } from "./tools/prefabeditor/prefabStore";
11
+ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from "./tools/prefabeditor/components/Input";
12
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from "./tools/prefabeditor/utils";
13
+ export type { ExportGLBOptions } from "./tools/prefabeditor/utils";
14
+ export { decomposeModelToPrefabNodes } from "./tools/prefabeditor/modelPrefab";
15
+ export type { DecomposeModelOptions } from "./tools/prefabeditor/modelPrefab";
16
+ export type { FieldDefinition, FieldType } from "./tools/prefabeditor/components/Input";
17
+ export { MaterialOverridesProvider, useMaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
18
+ export type { MaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
19
+ export { float, positionLocal, sin, time, uniform, vec3, } from "three/tsl";
20
+ export { loadFiles } from "./tools/dragdrop/DragDropLoader";
21
+ export type { AssetLoadOptions } from "./tools/dragdrop/DragDropLoader";
22
+ export { ModelListViewer, SoundListViewer, ModelPicker, SoundPicker, TextureListViewer, TexturePicker, SingleModelViewer, SingleSoundViewer, SingleTextureViewer, SharedCanvas, } from "./tools/assetviewer/page";
package/dist/editor.js ADDED
@@ -0,0 +1,15 @@
1
+ import { registerBuiltinComponents } from "./tools/prefabeditor/components";
2
+ import "./viewer";
3
+ registerBuiltinComponents();
4
+ export { registerBuiltinComponents };
5
+ export * from "./viewer";
6
+ export { default as PrefabEditor } from "./tools/prefabeditor/PrefabEditor";
7
+ export { useEditorContext, useEditorRef } from "./tools/prefabeditor/EditorContext";
8
+ export { usePrefabStore, usePrefabStoreApi } from "./tools/prefabeditor/prefabStore";
9
+ export { FieldRenderer, FieldGroup, ListEditor, Label, Vector3Input, Vector3Field, NumberField, ColorInput, ColorField, StringInput, StringField, BooleanInput, BooleanField, SelectInput, SelectField, } from "./tools/prefabeditor/components/Input";
10
+ export { loadJson, saveJson, exportGLB, exportGLBData, regenerateIds, computeParentWorldMatrix, } from "./tools/prefabeditor/utils";
11
+ export { decomposeModelToPrefabNodes } from "./tools/prefabeditor/modelPrefab";
12
+ export { MaterialOverridesProvider, useMaterialOverrides } from "./tools/prefabeditor/components/MaterialComponent";
13
+ export { float, positionLocal, sin, time, uniform, vec3, } from "three/tsl";
14
+ export { loadFiles } from "./tools/dragdrop/DragDropLoader";
15
+ export { ModelListViewer, SoundListViewer, ModelPicker, SoundPicker, TextureListViewer, TexturePicker, SingleModelViewer, SingleSoundViewer, SingleTextureViewer, SharedCanvas, } from "./tools/assetviewer/page";
@@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef } from "react";
5
5
  import { BooleanField, FieldRenderer, StringField, Vector3Field, } from "../../tools/prefabeditor/components/Input";
6
6
  import { useAssetRuntime, useNode } from "../../tools/prefabeditor/assetRuntime";
7
7
  import { usePrefabStoreApi } from "../../tools/prefabeditor/prefabStore";
8
- import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
8
+ import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/SceneContext";
9
9
  import { box, capsule, convexHull, MotionQuality, MotionType, rigidBody, sphere, triangleMesh, } from "crashcat";
10
10
  import { Matrix4, Quaternion, Vector3 } from "three";
11
11
  import { useCrashcat } from "./CrashcatRuntime";
@@ -198,6 +198,49 @@ function bodyTransformChanged(body, lastPosition, lastQuaternion) {
198
198
  function getRegisteredBody(api, nodeId, body) {
199
199
  return api && body && api.getBody(nodeId) === body ? body : null;
200
200
  }
201
+ function createAndRegisterBody(api, nodeId, object, physics) {
202
+ const shape = createShapeForObject(object, physics);
203
+ if (!shape)
204
+ return null;
205
+ object.updateWorldMatrix(true, true);
206
+ object.getWorldPosition(scratchPosition);
207
+ const wq = new Quaternion();
208
+ object.getWorldQuaternion(wq);
209
+ const motionType = toMotionType(physics);
210
+ const motionQuality = toMotionQuality(physics);
211
+ const isKinematic = motionType === MotionType.KINEMATIC;
212
+ const isStatic = motionType === MotionType.STATIC;
213
+ const body = rigidBody.create(api.world, {
214
+ shape,
215
+ motionType,
216
+ motionQuality,
217
+ objectLayer: isStatic ? api.staticObjectLayer : api.movingObjectLayer,
218
+ position: [scratchPosition.x, scratchPosition.y, scratchPosition.z],
219
+ quaternion: [wq.x, wq.y, wq.z, wq.w],
220
+ sensor: Boolean(physics.sensor),
221
+ collideKinematicVsNonDynamic: isKinematic,
222
+ friction: physics.friction,
223
+ restitution: physics.restitution,
224
+ userData: { nodeId },
225
+ });
226
+ if (physics.linearVelocity) {
227
+ rigidBody.setLinearVelocity(api.world, body, physics.linearVelocity);
228
+ }
229
+ if (physics.angularVelocity) {
230
+ rigidBody.setAngularVelocity(api.world, body, physics.angularVelocity);
231
+ }
232
+ api.register(nodeId, body, {
233
+ motionType,
234
+ sensor: Boolean(physics.sensor),
235
+ events: {
236
+ collisionEnter: physics.collisionEnterEventName,
237
+ collisionExit: physics.collisionExitEventName,
238
+ sensorEnter: physics.sensorEnterEventName,
239
+ sensorExit: physics.sensorExitEventName,
240
+ },
241
+ });
242
+ return { body, motionType };
243
+ }
201
244
  function CrashcatPhysicsView({ properties, children }) {
202
245
  const { nodeId, getObject } = useNode();
203
246
  const scene = useScene();
@@ -207,6 +250,7 @@ function CrashcatPhysicsView({ properties, children }) {
207
250
  const revision = getAssetRevision();
208
251
  const bodyRef = useRef(null);
209
252
  const motionTypeRef = useRef(MotionType.STATIC);
253
+ const needsRegistrationRef = useRef(false);
210
254
  const syncPositionRef = useRef([0, 0, 0]);
211
255
  const syncQuaternionRef = useRef([0, 0, 0, 1]);
212
256
  const lastPositionRef = useRef(null);
@@ -241,61 +285,40 @@ function CrashcatPhysicsView({ properties, children }) {
241
285
  sensorExitEventName,
242
286
  type,
243
287
  ]);
244
- useEffect(() => {
245
- // Rebuild mesh-derived colliders when referenced assets finish loading.
246
- void revision;
247
- if (!api)
288
+ const tryRegisterBody = () => {
289
+ if (!api || getRegisteredBody(api, nodeId, bodyRef.current)) {
290
+ needsRegistrationRef.current = false;
248
291
  return;
292
+ }
249
293
  const object = getObject();
250
- if (!object)
294
+ if (!object) {
295
+ needsRegistrationRef.current = true;
251
296
  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
297
  }
279
- if (physics.angularVelocity) {
280
- rigidBody.setAngularVelocity(api.world, body, physics.angularVelocity);
298
+ const registration = createAndRegisterBody(api, nodeId, object, physics);
299
+ if (!registration) {
300
+ needsRegistrationRef.current = true;
301
+ return;
281
302
  }
282
- bodyRef.current = body;
283
- motionTypeRef.current = motionType;
303
+ bodyRef.current = registration.body;
304
+ motionTypeRef.current = registration.motionType;
305
+ needsRegistrationRef.current = false;
284
306
  lastPositionRef.current = null;
285
307
  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
- });
308
+ };
309
+ useEffect(() => {
310
+ // Rebuild mesh-derived colliders when referenced assets finish loading.
311
+ void revision;
312
+ needsRegistrationRef.current = true;
313
+ if (api) {
314
+ api.unregister(nodeId);
315
+ }
316
+ bodyRef.current = null;
317
+ tryRegisterBody();
296
318
  return () => {
297
319
  bodyRef.current = null;
298
- api.unregister(nodeId);
320
+ needsRegistrationRef.current = false;
321
+ api === null || api === void 0 ? void 0 : api.unregister(nodeId);
299
322
  };
300
323
  }, [
301
324
  api,
@@ -304,6 +327,11 @@ function CrashcatPhysicsView({ properties, children }) {
304
327
  physics,
305
328
  revision,
306
329
  ]);
330
+ useFrame(() => {
331
+ if (needsRegistrationRef.current) {
332
+ tryRegisterBody();
333
+ }
334
+ }, -3);
307
335
  useEffect(() => {
308
336
  const syncEditBody = () => {
309
337
  const body = getRegisteredBody(api, nodeId, bodyRef.current);
@@ -0,0 +1,58 @@
1
+ import type { Vec3 } from "mathcat";
2
+ import { rigidBody, type World } from "crashcat";
3
+ import type { Component, NodeInteractionHandlers } from "../../tools/prefabeditor/components/ComponentRegistry";
4
+ export declare enum RagdollBodyPart {
5
+ UpperBody = 0,
6
+ Head = 1,
7
+ UpperLeftArm = 2,
8
+ LowerLeftArm = 3,
9
+ UpperRightArm = 4,
10
+ LowerRightArm = 5,
11
+ Pelvis = 6,
12
+ UpperLeftLeg = 7,
13
+ LowerLeftLeg = 8,
14
+ UpperRightLeg = 9,
15
+ LowerRightLeg = 10
16
+ }
17
+ type ShapeConfig = {
18
+ args: Vec3;
19
+ density: number;
20
+ position: Vec3;
21
+ };
22
+ type JointConfig = {
23
+ bodyA: RagdollBodyPart;
24
+ bodyB: RagdollBodyPart;
25
+ pivotA: Vec3;
26
+ pivotB: Vec3;
27
+ axisA: Vec3;
28
+ axisB: Vec3;
29
+ angle: number;
30
+ twistAngle: number;
31
+ };
32
+ type SkeletonJoint = {
33
+ bodyPart: RagdollBodyPart;
34
+ parentBodyPart: RagdollBodyPart | null;
35
+ };
36
+ export type RagdollSettings = {
37
+ shapes: Map<RagdollBodyPart, ShapeConfig>;
38
+ joints: JointConfig[];
39
+ skeleton: SkeletonJoint[];
40
+ };
41
+ export type CrashcatRagdollProps = {
42
+ position?: [number, number, number];
43
+ scale?: number;
44
+ swingAngle?: number;
45
+ shoulderAngle?: number;
46
+ twistAngle?: number;
47
+ stabilize?: boolean;
48
+ initialLinearVelocity?: [number, number, number];
49
+ initialAngularVelocity?: [number, number, number];
50
+ color?: string;
51
+ clickImpulse?: number;
52
+ nodeInteractionHandlers?: NodeInteractionHandlers;
53
+ };
54
+ export declare function createRagdollSettings(scale?: number, angleA?: number, angleB?: number, twistAngle?: number): RagdollSettings;
55
+ export declare function CrashcatRagdoll({ position, scale, swingAngle, shoulderAngle, twistAngle, stabilize, initialLinearVelocity, initialAngularVelocity, color, clickImpulse, nodeInteractionHandlers, }: CrashcatRagdollProps): import("react/jsx-runtime").JSX.Element;
56
+ declare const CrashcatRagdollComponent: Component;
57
+ export default CrashcatRagdollComponent;
58
+ export declare function createStaticBoxBody(world: World, objectLayer: number, halfExtents: Vec3, position: Vec3): rigidBody.RigidBody;