@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.
- package/package.json +8 -13
- package/steering/html/skills/rich-interactive-design/SKILL.md +5 -2
- package/steering/html/skills/rich-interactive-design/references/tasks/interactive-scene.md +1 -0
- package/steering/vite-react/skills/react-three-fiber/SKILL.md +214 -0
- package/steering/vite-react/skills/react-three-fiber/references/animation.md +1001 -0
- package/steering/vite-react/skills/react-three-fiber/references/fundamentals.md +877 -0
- package/steering/vite-react/skills/react-three-fiber/references/geometry.md +717 -0
- package/steering/vite-react/skills/react-three-fiber/references/interaction.md +880 -0
- package/steering/vite-react/skills/react-three-fiber/references/lighting.md +668 -0
- package/steering/vite-react/skills/react-three-fiber/references/loaders.md +607 -0
- package/steering/vite-react/skills/react-three-fiber/references/materials.md +601 -0
- package/steering/vite-react/skills/react-three-fiber/references/physics.md +820 -0
- package/steering/vite-react/skills/react-three-fiber/references/postprocessing.md +754 -0
- package/steering/vite-react/skills/react-three-fiber/references/shaders.md +874 -0
- package/steering/vite-react/skills/react-three-fiber/references/textures.md +635 -0
|
@@ -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
|