Physics Deep Dive: Building Realistic Ragdoll Laundry
Creating realistic physics for falling laundry was one of the most satisfying parts of building Laundry Sort. Here's how we made those clothes tumble just right.
The Physics Challenge
A simple cube falling straight down isn't interesting. But a piece of laundry tumbling randomly, with realistic bounces and friction? That's where the magic happens. We needed:
- Realistic Mass & Gravity: Clothes should feel light but still fall steadily
- Friction Against Surfaces: So they don't slide forever on the bins
- Collision Detection: Accurate detection to know when a piece lands in the correct bin
- Angular Momentum: So they spin and rotate naturally as they fall
Rapier Physics Engine
We chose Rapier (integrated via @react-three/rapier) because it's:
- Written in Rust and compiled to WebAssembly for blazing-fast performance
- Perfect for web-based 3D physics
- Easy to integrate with React Three Fiber
- Incredibly stable and accurate
Setting Up Physics for Laundry
// Each laundry piece uses a RigidBody with custom physics parameters
<RigidBody
colliders="hull" // Convex hull collision
restitution={0.6} // Bounce factor
friction={0.4} // How much it sticks when sliding
linearDamping={0.1} // Air resistance
angularDamping={0.3} // Spin dampening
>
{/* Mesh and colliders */}
</RigidBody>
Key Parameters Explained
- Restitution (0.6): Controls bounciness. Lower = duller thuds, higher = super bouncy. We wanted clothes to feel light but not bouncy like rubber balls.
- Friction (0.4): How much resistance when sliding. This prevents clothes from sliding endlessly across bin surfaces.
- Linear Damping (0.1): Simulates air resistance. Makes falling feel more natural than instant acceleration.
- Angular Damping (0.3): Prevents spinning forever. Real clothes settle their rotation relatively quickly.
Collision Detection & Sorting
The real trick is knowing when a piece lands in the correct bin:
onCollisionEnter={({ other }) => {
const otherName = other.rigidBodyObject?.name || '';
const colorMatch = COLOR_NAMES.find(c => otherName.includes(c));
if (colorMatch && colorMatch === color) {
// Correct bin!
onSorted(true);
} else if (COLOR_NAMES.some(c => otherName.includes(c))) {
// Wrong bin
onSorted(false);
}
}}
We tag each bin with its color name, then check if the colliding object's color matches the bin. Simple, effective!
Performance Optimization
Rapier in WebAssembly is incredibly fast, but we still optimize:
- Collision Culling: We limit the number of active laundry pieces to prevent physics from becoming a bottleneck
- Memory Management: Old pieces are removed from the scene after 15 seconds to prevent memory leaks
- Fixed Timestep: Rapier handles its own fixed timestep for stability
The Result
The combination of realistic physics, proper parameter tuning, and careful collision detection creates that satisfying thunk when a shirt lands in exactly the right bin. It feels good to sort laundry in Laundry Sort!
What aspects of physics-based game design interest you most? Let us know in the comments below!
End of Log