@lark-apaas/coding-steering 0.1.2 → 0.1.4

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.
@@ -0,0 +1,820 @@
1
+ # React Three Fiber Physics (Rapier)
2
+
3
+ ## Quick Start
4
+
5
+ ```tsx
6
+ import { Canvas } from '@react-three/fiber'
7
+ import { Physics, RigidBody, CuboidCollider } from '@react-three/rapier'
8
+ import { Suspense } from 'react'
9
+
10
+ function Scene() {
11
+ return (
12
+ <Canvas>
13
+ <Suspense fallback={null}>
14
+ <Physics debug>
15
+ {/* Falling box */}
16
+ <RigidBody>
17
+ <mesh>
18
+ <boxGeometry />
19
+ <meshStandardMaterial color="orange" />
20
+ </mesh>
21
+ </RigidBody>
22
+
23
+ {/* Static ground */}
24
+ <CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />
25
+ </Physics>
26
+ </Suspense>
27
+
28
+ <ambientLight />
29
+ <directionalLight position={[5, 5, 5]} />
30
+ </Canvas>
31
+ )
32
+ }
33
+ ```
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install @react-three/rapier
39
+ ```
40
+
41
+ ## Physics Component
42
+
43
+ The root component that creates the physics world.
44
+
45
+ ```tsx
46
+ import { Physics } from '@react-three/rapier'
47
+
48
+ <Canvas>
49
+ <Suspense fallback={null}>
50
+ <Physics
51
+ gravity={[0, -9.81, 0]} // Gravity vector
52
+ debug={false} // Show collider wireframes
53
+ timeStep={1/60} // Fixed timestep (or "vary" for variable)
54
+ paused={false} // Pause simulation
55
+ interpolate={true} // Smooth rendering between physics steps
56
+ colliders="cuboid" // Default collider type for all RigidBodies
57
+ updateLoop="follow" // "follow" (sync with frame) or "independent"
58
+ >
59
+ {/* Physics objects */}
60
+ </Physics>
61
+ </Suspense>
62
+ </Canvas>
63
+ ```
64
+
65
+ ### On-Demand Rendering
66
+
67
+ For performance optimization with static scenes:
68
+
69
+ ```tsx
70
+ <Canvas frameloop="demand">
71
+ <Physics updateLoop="independent">
72
+ {/* Physics only triggers render when bodies are active */}
73
+ </Physics>
74
+ </Canvas>
75
+ ```
76
+
77
+ ## RigidBody
78
+
79
+ Makes objects participate in physics simulation.
80
+
81
+ ### Basic Usage
82
+
83
+ ```tsx
84
+ import { RigidBody } from '@react-three/rapier'
85
+
86
+ // Dynamic body (affected by forces/gravity)
87
+ <RigidBody>
88
+ <mesh>
89
+ <boxGeometry />
90
+ <meshStandardMaterial color="red" />
91
+ </mesh>
92
+ </RigidBody>
93
+
94
+ // Fixed body (immovable)
95
+ <RigidBody type="fixed">
96
+ <mesh>
97
+ <boxGeometry args={[10, 0.5, 10]} />
98
+ <meshStandardMaterial color="gray" />
99
+ </mesh>
100
+ </RigidBody>
101
+
102
+ // Kinematic body (moved programmatically)
103
+ <RigidBody type="kinematicPosition">
104
+ <mesh>
105
+ <sphereGeometry />
106
+ <meshStandardMaterial color="blue" />
107
+ </mesh>
108
+ </RigidBody>
109
+ ```
110
+
111
+ ### RigidBody Types
112
+
113
+ | Type | Description |
114
+ |------|-------------|
115
+ | `dynamic` | Affected by forces, gravity, collisions (default) |
116
+ | `fixed` | Immovable, infinite mass |
117
+ | `kinematicPosition` | Moved via setNextKinematicTranslation |
118
+ | `kinematicVelocity` | Moved via setNextKinematicRotation |
119
+
120
+ ### RigidBody Properties
121
+
122
+ ```tsx
123
+ <RigidBody
124
+ // Transform
125
+ position={[0, 5, 0]}
126
+ rotation={[0, Math.PI / 4, 0]}
127
+ scale={1}
128
+
129
+ // Physics
130
+ type="dynamic"
131
+ mass={1}
132
+ restitution={0.5} // Bounciness (0-1)
133
+ friction={0.5} // Surface friction
134
+ linearDamping={0} // Slows linear velocity
135
+ angularDamping={0} // Slows angular velocity
136
+ gravityScale={1} // Multiplier for gravity
137
+
138
+ // Collider generation
139
+ colliders="cuboid" // "cuboid" | "ball" | "hull" | "trimesh" | false
140
+
141
+ // Constraints
142
+ lockTranslations={false} // Prevent all translation
143
+ lockRotations={false} // Prevent all rotation
144
+ enabledTranslations={[true, true, true]} // Lock specific axes
145
+ enabledRotations={[true, true, true]} // Lock specific axes
146
+
147
+ // Sleeping
148
+ canSleep={true}
149
+ ccd={false} // Continuous collision detection (fast objects)
150
+
151
+ // Naming (for collision events)
152
+ name="player"
153
+ />
154
+ ```
155
+
156
+ ## Colliders
157
+
158
+ ### Automatic Colliders
159
+
160
+ RigidBody auto-generates colliders from child meshes:
161
+
162
+ ```tsx
163
+ // Global default
164
+ <Physics colliders="hull">
165
+ <RigidBody>
166
+ <Torus /> {/* Gets hull collider */}
167
+ </RigidBody>
168
+ </Physics>
169
+
170
+ // Per-body override
171
+ <Physics colliders={false}>
172
+ <RigidBody colliders="cuboid">
173
+ <Box />
174
+ </RigidBody>
175
+
176
+ <RigidBody colliders="ball">
177
+ <Sphere />
178
+ </RigidBody>
179
+ </Physics>
180
+ ```
181
+
182
+ ### Collider Types
183
+
184
+ | Type | Description | Best For |
185
+ |------|-------------|----------|
186
+ | `cuboid` | Box shape | Boxes, crates |
187
+ | `ball` | Sphere shape | Balls, spherical objects |
188
+ | `hull` | Convex hull | Complex convex shapes |
189
+ | `trimesh` | Triangle mesh | Concave/complex static geometry |
190
+
191
+ ### Manual Colliders
192
+
193
+ ```tsx
194
+ import {
195
+ CuboidCollider,
196
+ BallCollider,
197
+ CapsuleCollider,
198
+ CylinderCollider,
199
+ ConeCollider,
200
+ HeightfieldCollider,
201
+ TrimeshCollider,
202
+ ConvexHullCollider
203
+ } from '@react-three/rapier'
204
+
205
+ // Standalone collider (static)
206
+ <CuboidCollider position={[0, -2, 0]} args={[10, 0.5, 10]} />
207
+
208
+ // Inside RigidBody (compound collider)
209
+ <RigidBody position={[0, 5, 0]}>
210
+ <mesh>
211
+ <boxGeometry />
212
+ <meshStandardMaterial />
213
+ </mesh>
214
+
215
+ {/* Additional colliders */}
216
+ <BallCollider args={[0.5]} position={[0, 1, 0]} />
217
+ <CapsuleCollider args={[0.5, 1]} position={[0, -1, 0]} />
218
+ </RigidBody>
219
+
220
+ // Collider args reference
221
+ <CuboidCollider args={[halfWidth, halfHeight, halfDepth]} />
222
+ <BallCollider args={[radius]} />
223
+ <CapsuleCollider args={[halfHeight, radius]} />
224
+ <CylinderCollider args={[halfHeight, radius]} />
225
+ <ConeCollider args={[halfHeight, radius]} />
226
+ ```
227
+
228
+ ### Mesh Colliders
229
+
230
+ For complex shapes:
231
+
232
+ ```tsx
233
+ import { MeshCollider } from '@react-three/rapier'
234
+
235
+ <RigidBody colliders={false}>
236
+ <MeshCollider type="trimesh">
237
+ <mesh geometry={complexGeometry}>
238
+ <meshStandardMaterial />
239
+ </mesh>
240
+ </MeshCollider>
241
+ </RigidBody>
242
+
243
+ // Convex hull for dynamic bodies
244
+ <RigidBody colliders={false}>
245
+ <MeshCollider type="hull">
246
+ <mesh geometry={someGeometry} />
247
+ </MeshCollider>
248
+ </RigidBody>
249
+ ```
250
+
251
+ ## Applying Forces
252
+
253
+ ### Using Refs
254
+
255
+ ```tsx
256
+ import { RigidBody, RapierRigidBody } from '@react-three/rapier'
257
+ import { useRef, useEffect } from 'react'
258
+
259
+ function ForcefulBox() {
260
+ const rigidBody = useRef<RapierRigidBody>(null)
261
+
262
+ useEffect(() => {
263
+ if (rigidBody.current) {
264
+ // One-time impulse (instantaneous)
265
+ rigidBody.current.applyImpulse({ x: 0, y: 10, z: 0 }, true)
266
+
267
+ // Continuous force (apply each frame)
268
+ rigidBody.current.addForce({ x: 0, y: 10, z: 0 }, true)
269
+
270
+ // Torque (rotation)
271
+ rigidBody.current.applyTorqueImpulse({ x: 0, y: 5, z: 0 }, true)
272
+ rigidBody.current.addTorque({ x: 0, y: 5, z: 0 }, true)
273
+ }
274
+ }, [])
275
+
276
+ return (
277
+ <RigidBody ref={rigidBody}>
278
+ <mesh>
279
+ <boxGeometry />
280
+ <meshStandardMaterial color="red" />
281
+ </mesh>
282
+ </RigidBody>
283
+ )
284
+ }
285
+ ```
286
+
287
+ ### In useFrame
288
+
289
+ ```tsx
290
+ import { useFrame } from '@react-three/fiber'
291
+
292
+ function ContinuousForce() {
293
+ const rigidBody = useRef<RapierRigidBody>(null)
294
+
295
+ useFrame(() => {
296
+ if (rigidBody.current) {
297
+ // Apply force every frame
298
+ rigidBody.current.addForce({ x: 0, y: 20, z: 0 }, true)
299
+ }
300
+ })
301
+
302
+ return (
303
+ <RigidBody ref={rigidBody} gravityScale={0.5}>
304
+ <mesh>
305
+ <sphereGeometry />
306
+ <meshStandardMaterial color="blue" />
307
+ </mesh>
308
+ </RigidBody>
309
+ )
310
+ }
311
+ ```
312
+
313
+ ### Getting/Setting Position
314
+
315
+ ```tsx
316
+ import { vec3, quat, euler } from '@react-three/rapier'
317
+
318
+ function PositionControl() {
319
+ const rigidBody = useRef<RapierRigidBody>(null)
320
+
321
+ const teleport = () => {
322
+ if (rigidBody.current) {
323
+ // Get current transform
324
+ const position = vec3(rigidBody.current.translation())
325
+ const rotation = quat(rigidBody.current.rotation())
326
+
327
+ // Set new transform
328
+ rigidBody.current.setTranslation({ x: 0, y: 10, z: 0 }, true)
329
+ rigidBody.current.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true)
330
+
331
+ // Set velocities
332
+ rigidBody.current.setLinvel({ x: 0, y: 0, z: 0 }, true)
333
+ rigidBody.current.setAngvel({ x: 0, y: 0, z: 0 }, true)
334
+ }
335
+ }
336
+
337
+ return (
338
+ <RigidBody ref={rigidBody}>
339
+ <mesh onClick={teleport}>
340
+ <boxGeometry />
341
+ <meshStandardMaterial />
342
+ </mesh>
343
+ </RigidBody>
344
+ )
345
+ }
346
+ ```
347
+
348
+ ## Collision Events
349
+
350
+ ### On RigidBody
351
+
352
+ ```tsx
353
+ <RigidBody
354
+ name="player"
355
+ onCollisionEnter={({ manifold, target, other }) => {
356
+ console.log('Collision with', other.rigidBodyObject?.name)
357
+ console.log('Contact point', manifold.solverContactPoint(0))
358
+ }}
359
+ onCollisionExit={({ target, other }) => {
360
+ console.log('Collision ended with', other.rigidBodyObject?.name)
361
+ }}
362
+ onContactForce={({ totalForce }) => {
363
+ console.log('Contact force:', totalForce)
364
+ }}
365
+ onSleep={() => console.log('Body went to sleep')}
366
+ onWake={() => console.log('Body woke up')}
367
+ >
368
+ <mesh>
369
+ <boxGeometry />
370
+ <meshStandardMaterial />
371
+ </mesh>
372
+ </RigidBody>
373
+ ```
374
+
375
+ ### On Colliders
376
+
377
+ ```tsx
378
+ <CuboidCollider
379
+ args={[1, 1, 1]}
380
+ onCollisionEnter={(payload) => console.log('Collider hit')}
381
+ onCollisionExit={(payload) => console.log('Collider exit')}
382
+ />
383
+ ```
384
+
385
+ ## Sensors
386
+
387
+ Detect overlaps without physical collision:
388
+
389
+ ```tsx
390
+ <RigidBody>
391
+ {/* Visible mesh */}
392
+ <mesh>
393
+ <boxGeometry />
394
+ <meshStandardMaterial />
395
+ </mesh>
396
+
397
+ {/* Invisible sensor trigger */}
398
+ <CuboidCollider
399
+ args={[2, 2, 2]}
400
+ sensor
401
+ onIntersectionEnter={() => console.log('Entered trigger zone')}
402
+ onIntersectionExit={() => console.log('Exited trigger zone')}
403
+ />
404
+ </RigidBody>
405
+
406
+ // Goal detection example
407
+ <RigidBody type="fixed">
408
+ <GoalPosts />
409
+ <CuboidCollider
410
+ args={[5, 5, 1]}
411
+ sensor
412
+ onIntersectionEnter={() => console.log('Goal!')}
413
+ />
414
+ </RigidBody>
415
+ ```
416
+
417
+ ## Collision Groups
418
+
419
+ Control which objects can collide:
420
+
421
+ ```tsx
422
+ import { interactionGroups } from '@react-three/rapier'
423
+
424
+ // Group 0, interacts with groups 0, 1, 2
425
+ <CuboidCollider collisionGroups={interactionGroups(0, [0, 1, 2])} />
426
+
427
+ // Group 12, interacts with all groups
428
+ <CuboidCollider collisionGroups={interactionGroups(12)} />
429
+
430
+ // Groups 0 and 5, only interacts with group 7
431
+ <CuboidCollider collisionGroups={interactionGroups([0, 5], 7)} />
432
+
433
+ // On RigidBody (applies to all auto-generated colliders)
434
+ <RigidBody collisionGroups={interactionGroups(1, [1, 2])}>
435
+ <mesh>...</mesh>
436
+ </RigidBody>
437
+ ```
438
+
439
+ ## Joints
440
+
441
+ Connect rigid bodies together.
442
+
443
+ ### Fixed Joint
444
+
445
+ Bodies don't move relative to each other:
446
+
447
+ ```tsx
448
+ import { useFixedJoint, RapierRigidBody } from '@react-three/rapier'
449
+
450
+ function FixedJointExample() {
451
+ const bodyA = useRef<RapierRigidBody>(null)
452
+ const bodyB = useRef<RapierRigidBody>(null)
453
+
454
+ useFixedJoint(bodyA, bodyB, [
455
+ [0, 0, 0], // Position in bodyA's local space
456
+ [0, 0, 0, 1], // Orientation in bodyA's local space (quaternion)
457
+ [0, -1, 0], // Position in bodyB's local space
458
+ [0, 0, 0, 1], // Orientation in bodyB's local space
459
+ ])
460
+
461
+ return (
462
+ <>
463
+ <RigidBody ref={bodyA} position={[0, 5, 0]}>
464
+ <mesh><boxGeometry /><meshStandardMaterial color="red" /></mesh>
465
+ </RigidBody>
466
+ <RigidBody ref={bodyB} position={[0, 4, 0]}>
467
+ <mesh><boxGeometry /><meshStandardMaterial color="blue" /></mesh>
468
+ </RigidBody>
469
+ </>
470
+ )
471
+ }
472
+ ```
473
+
474
+ ### Revolute Joint (Hinge)
475
+
476
+ Rotation around one axis:
477
+
478
+ ```tsx
479
+ import { useRevoluteJoint } from '@react-three/rapier'
480
+
481
+ function HingeDoor() {
482
+ const frame = useRef<RapierRigidBody>(null)
483
+ const door = useRef<RapierRigidBody>(null)
484
+
485
+ useRevoluteJoint(frame, door, [
486
+ [0.5, 0, 0], // Joint position in frame's local space
487
+ [-0.5, 0, 0], // Joint position in door's local space
488
+ [0, 1, 0], // Rotation axis
489
+ ])
490
+
491
+ return (
492
+ <>
493
+ <RigidBody ref={frame} type="fixed">
494
+ <mesh><boxGeometry args={[0.1, 2, 0.1]} /></mesh>
495
+ </RigidBody>
496
+ <RigidBody ref={door}>
497
+ <mesh><boxGeometry args={[1, 2, 0.1]} /></mesh>
498
+ </RigidBody>
499
+ </>
500
+ )
501
+ }
502
+ ```
503
+
504
+ ### Spherical Joint (Ball-Socket)
505
+
506
+ Rotation in all directions:
507
+
508
+ ```tsx
509
+ import { useSphericalJoint } from '@react-three/rapier'
510
+
511
+ function BallJoint() {
512
+ const bodyA = useRef<RapierRigidBody>(null)
513
+ const bodyB = useRef<RapierRigidBody>(null)
514
+
515
+ useSphericalJoint(bodyA, bodyB, [
516
+ [0, -0.5, 0], // Position in bodyA's local space
517
+ [0, 0.5, 0], // Position in bodyB's local space
518
+ ])
519
+
520
+ return (
521
+ <>
522
+ <RigidBody ref={bodyA} type="fixed" position={[0, 3, 0]}>
523
+ <mesh><sphereGeometry args={[0.2]} /></mesh>
524
+ </RigidBody>
525
+ <RigidBody ref={bodyB} position={[0, 2, 0]}>
526
+ <mesh><boxGeometry /></mesh>
527
+ </RigidBody>
528
+ </>
529
+ )
530
+ }
531
+ ```
532
+
533
+ ### Prismatic Joint (Slider)
534
+
535
+ Translation along one axis:
536
+
537
+ ```tsx
538
+ import { usePrismaticJoint } from '@react-three/rapier'
539
+
540
+ function Slider() {
541
+ const track = useRef<RapierRigidBody>(null)
542
+ const slider = useRef<RapierRigidBody>(null)
543
+
544
+ usePrismaticJoint(track, slider, [
545
+ [0, 0, 0], // Position in track's local space
546
+ [0, 0, 0], // Position in slider's local space
547
+ [1, 0, 0], // Axis of translation
548
+ ])
549
+
550
+ return (
551
+ <>
552
+ <RigidBody ref={track} type="fixed">
553
+ <mesh><boxGeometry args={[5, 0.1, 0.1]} /></mesh>
554
+ </RigidBody>
555
+ <RigidBody ref={slider}>
556
+ <mesh><boxGeometry args={[0.5, 0.5, 0.5]} /></mesh>
557
+ </RigidBody>
558
+ </>
559
+ )
560
+ }
561
+ ```
562
+
563
+ ### Spring Joint
564
+
565
+ Elastic connection:
566
+
567
+ ```tsx
568
+ import { useSpringJoint } from '@react-three/rapier'
569
+
570
+ function SpringConnection() {
571
+ const anchor = useRef<RapierRigidBody>(null)
572
+ const ball = useRef<RapierRigidBody>(null)
573
+
574
+ useSpringJoint(anchor, ball, [
575
+ [0, 0, 0], // Position in anchor's local space
576
+ [0, 0, 0], // Position in ball's local space
577
+ 2, // Rest length
578
+ 1000, // Stiffness
579
+ 10, // Damping
580
+ ])
581
+
582
+ return (
583
+ <>
584
+ <RigidBody ref={anchor} type="fixed" position={[0, 5, 0]}>
585
+ <mesh><sphereGeometry args={[0.1]} /></mesh>
586
+ </RigidBody>
587
+ <RigidBody ref={ball} position={[0, 3, 0]}>
588
+ <mesh><sphereGeometry args={[0.5]} /></mesh>
589
+ </RigidBody>
590
+ </>
591
+ )
592
+ }
593
+ ```
594
+
595
+ ### Rope Joint
596
+
597
+ Maximum distance constraint:
598
+
599
+ ```tsx
600
+ import { useRopeJoint } from '@react-three/rapier'
601
+
602
+ function RopeConnection() {
603
+ const anchor = useRef<RapierRigidBody>(null)
604
+ const weight = useRef<RapierRigidBody>(null)
605
+
606
+ useRopeJoint(anchor, weight, [
607
+ [0, 0, 0], // Position in anchor's local space
608
+ [0, 0, 0], // Position in weight's local space
609
+ 3, // Max distance (rope length)
610
+ ])
611
+
612
+ return (
613
+ <>
614
+ <RigidBody ref={anchor} type="fixed" position={[0, 5, 0]}>
615
+ <mesh><sphereGeometry args={[0.1]} /></mesh>
616
+ </RigidBody>
617
+ <RigidBody ref={weight} position={[0, 2, 0]}>
618
+ <mesh><sphereGeometry args={[0.5]} /></mesh>
619
+ </RigidBody>
620
+ </>
621
+ )
622
+ }
623
+ ```
624
+
625
+ ### Motorized Joints
626
+
627
+ ```tsx
628
+ import { useRevoluteJoint } from '@react-three/rapier'
629
+ import { useFrame } from '@react-three/fiber'
630
+
631
+ function MotorizedWheel({ bodyA, bodyB }) {
632
+ const joint = useRevoluteJoint(bodyA, bodyB, [
633
+ [0, 0, 0],
634
+ [0, 0, 0],
635
+ [0, 0, 1], // Rotation axis
636
+ ])
637
+
638
+ useFrame(() => {
639
+ if (joint.current) {
640
+ // Configure motor: velocity, damping
641
+ joint.current.configureMotorVelocity(10, 2)
642
+ }
643
+ })
644
+
645
+ return null
646
+ }
647
+ ```
648
+
649
+ ## Instanced Physics
650
+
651
+ Efficient physics for many identical objects:
652
+
653
+ ```tsx
654
+ import { InstancedRigidBodies, RapierRigidBody } from '@react-three/rapier'
655
+ import { useRef, useMemo } from 'react'
656
+
657
+ function InstancedBalls() {
658
+ const COUNT = 100
659
+ const rigidBodies = useRef<RapierRigidBody[]>(null)
660
+
661
+ const instances = useMemo(() => {
662
+ return Array.from({ length: COUNT }, (_, i) => ({
663
+ key: `ball-${i}`,
664
+ position: [
665
+ (Math.random() - 0.5) * 10,
666
+ Math.random() * 10 + 5,
667
+ (Math.random() - 0.5) * 10,
668
+ ] as [number, number, number],
669
+ rotation: [0, 0, 0] as [number, number, number],
670
+ }))
671
+ }, [])
672
+
673
+ return (
674
+ <InstancedRigidBodies
675
+ ref={rigidBodies}
676
+ instances={instances}
677
+ colliders="ball"
678
+ >
679
+ <instancedMesh args={[undefined, undefined, COUNT]}>
680
+ <sphereGeometry args={[0.5]} />
681
+ <meshStandardMaterial color="orange" />
682
+ </instancedMesh>
683
+ </InstancedRigidBodies>
684
+ )
685
+ }
686
+ ```
687
+
688
+ ## Accessing the World
689
+
690
+ ```tsx
691
+ import { useRapier } from '@react-three/rapier'
692
+ import { useEffect } from 'react'
693
+
694
+ function WorldAccess() {
695
+ const { world, rapier } = useRapier()
696
+
697
+ useEffect(() => {
698
+ // Change gravity
699
+ world.setGravity({ x: 0, y: -20, z: 0 })
700
+
701
+ // Iterate over bodies
702
+ world.bodies.forEach((body) => {
703
+ console.log(body.translation())
704
+ })
705
+ }, [world])
706
+
707
+ return null
708
+ }
709
+ ```
710
+
711
+ ### Manual Stepping
712
+
713
+ ```tsx
714
+ function ManualStep() {
715
+ const { step } = useRapier()
716
+
717
+ const advancePhysics = () => {
718
+ step(1 / 60) // Advance by one frame
719
+ }
720
+
721
+ return <button onClick={advancePhysics}>Step</button>
722
+ }
723
+ ```
724
+
725
+ ### World Snapshots
726
+
727
+ Save and restore physics state:
728
+
729
+ ```tsx
730
+ function SnapshotSystem() {
731
+ const { world, setWorld, rapier } = useRapier()
732
+ const snapshot = useRef<Uint8Array>()
733
+
734
+ const saveState = () => {
735
+ snapshot.current = world.takeSnapshot()
736
+ }
737
+
738
+ const loadState = () => {
739
+ if (snapshot.current) {
740
+ setWorld(rapier.World.restoreSnapshot(snapshot.current))
741
+ }
742
+ }
743
+
744
+ return (
745
+ <>
746
+ <button onClick={saveState}>Save</button>
747
+ <button onClick={loadState}>Load</button>
748
+ </>
749
+ )
750
+ }
751
+ ```
752
+
753
+ ## Attractors
754
+
755
+ From `@react-three/rapier-addons`:
756
+
757
+ ```tsx
758
+ import { Attractor } from '@react-three/rapier-addons'
759
+
760
+ // Attract nearby bodies
761
+ <Attractor
762
+ position={[0, 0, 0]}
763
+ range={10}
764
+ strength={5}
765
+ type="linear" // "static" | "linear" | "newtonian"
766
+ />
767
+
768
+ // Repel bodies
769
+ <Attractor range={10} strength={-5} position={[5, 0, 0]} />
770
+
771
+ // Selective attraction (only affect certain groups)
772
+ <Attractor
773
+ range={10}
774
+ strength={10}
775
+ collisionGroups={interactionGroups(0, [2, 3])}
776
+ />
777
+ ```
778
+
779
+ ## Debug Visualization
780
+
781
+ ```tsx
782
+ <Physics debug>
783
+ {/* All colliders shown as wireframes */}
784
+ </Physics>
785
+
786
+ // Conditional debug
787
+ <Physics debug={process.env.NODE_ENV === 'development'}>
788
+ ...
789
+ </Physics>
790
+ ```
791
+
792
+ ## Performance Tips
793
+
794
+ 1. **Use appropriate collider types**: `cuboid` and `ball` are fastest
795
+ 2. **Avoid `trimesh` for dynamic bodies**: Use `hull` instead
796
+ 3. **Enable sleeping**: Bodies at rest stop computing
797
+ 4. **Use collision groups**: Reduce collision checks
798
+ 5. **Limit active bodies**: Too many dynamic bodies hurts performance
799
+ 6. **Use instanced bodies**: For many identical objects
800
+ 7. **Fixed timestep**: More stable than variable
801
+
802
+ ```tsx
803
+ // Performance-optimized setup
804
+ <Physics
805
+ timeStep={1/60}
806
+ colliders="cuboid"
807
+ gravity={[0, -9.81, 0]}
808
+ >
809
+ {/* Use collision groups to limit checks */}
810
+ <RigidBody collisionGroups={interactionGroups(0, [0, 1])}>
811
+ ...
812
+ </RigidBody>
813
+ </Physics>
814
+ ```
815
+
816
+ ## See Also
817
+
818
+ - `r3f-fundamentals` - R3F basics and hooks
819
+ - `r3f-interaction` - User input and controls
820
+ - `r3f-animation` - Combining physics with animation