From c1be3e857849a56cec74a794cb3d572003a61b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Crozet?= Date: Fri, 17 Oct 2025 12:59:19 +0200 Subject: [PATCH] =?UTF-8?q?feat:=E2=80=AFdocumentation=20improvements=20(#?= =?UTF-8?q?884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Claude.md | 902 ++++++++++++++++++ crates/rapier2d-f64/Cargo.toml | 2 +- crates/rapier2d/Cargo.toml | 2 +- crates/rapier3d-f64/Cargo.toml | 2 +- src/control/character_controller.rs | 56 +- src/data/arena.rs | 125 +-- src/dynamics/ccd/ccd_solver.rs | 23 +- src/dynamics/coefficient_combine_rule.rs | 40 +- src/dynamics/integration_parameters.rs | 63 +- src/dynamics/island_manager.rs | 40 +- src/dynamics/joint/fixed_joint.rs | 11 +- src/dynamics/joint/generic_joint.rs | 60 +- .../joint/impulse_joint/impulse_joint_set.rs | 161 +++- src/dynamics/joint/motor_model.rs | 27 +- .../multibody_joint/multibody_joint_set.rs | 10 +- src/dynamics/joint/prismatic_joint.rs | 34 +- src/dynamics/joint/revolute_joint.rs | 58 +- src/dynamics/joint/rope_joint.rs | 29 +- src/dynamics/joint/spherical_joint.rs | 40 +- src/dynamics/joint/spring_joint.rs | 30 +- src/dynamics/rigid_body.rs | 597 +++++++++--- src/dynamics/rigid_body_components.rs | 128 ++- src/dynamics/rigid_body_set.rs | 222 ++++- src/geometry/broad_phase_bvh.rs | 11 +- src/geometry/collider.rs | 515 ++++++++-- src/geometry/collider_components.rs | 50 +- src/geometry/collider_set.rs | 160 +++- src/geometry/contact_pair.rs | 71 +- src/geometry/interaction_graph.rs | 10 +- src/geometry/interaction_groups.rs | 52 +- src/geometry/mod.rs | 42 +- src/geometry/narrow_phase.rs | 13 +- src/pipeline/collision_pipeline.rs | 17 +- src/pipeline/event_handler.rs | 141 ++- src/pipeline/physics_hooks.rs | 30 +- src/pipeline/physics_pipeline.rs | 80 +- src/pipeline/query_pipeline.rs | 320 +++++-- 37 files changed, 3481 insertions(+), 693 deletions(-) create mode 100644 Claude.md diff --git a/Claude.md b/Claude.md new file mode 100644 index 0000000..a4c3c72 --- /dev/null +++ b/Claude.md @@ -0,0 +1,902 @@ +# Rapier Physics Engine - Complete Codebase Guide + +## What is Rapier? + +Rapier is a high-performance 2D and 3D physics engine written in Rust by Dimforge. It's designed for games, animation, and robotics applications, offering deterministic simulations, snapshot/restore capabilities, and cross-platform support (including WASM). + +**License:** Apache 2.0 (free and open-source) +**Repository:** https://github.com/dimforge/rapier +**Documentation:** https://rapier.rs/docs/ +**Crate:** https://crates.io/crates/rapier3d + +## Key Features + +- **Dual-dimensional support**: Both 2D (`rapier2d`) and 3D (`rapier3d`) with f32 and f64 precision variants +- **Deterministic simulation**: Identical results across different machines (IEEE 754-2008 compliant) +- **Snapshot & restore**: Complete physics state serialization +- **Cross-platform**: Desktop, mobile, web (WASM), consoles +- **Performance-focused**: SIMD optimizations, optional multi-threading, sleeping system +- **Flexible**: Can be used for full physics or just collision detection + +## Repository Architecture + +The repository uses an unusual structure to share code between 2D/3D versions: + +``` +rapier/ +├── src/ # Shared source code (2D/3D agnostic) +├── crates/ # Concrete 2D/3D crate definitions +│ ├── rapier2d/ # 2D f32 +│ ├── rapier3d/ # 3D f32 +│ ├── rapier2d-f64/ # 2D f64 +│ ├── rapier3d-f64/ # 3D f64 +│ ├── rapier_testbed2d/ # 2D visual debugger +│ ├── rapier_testbed3d/ # 3D visual debugger +│ └── rapier3d-urdf/ # Robot model loader +├── examples2d/ # 2D demos +├── examples3d/ # 3D demos +├── benchmarks2d/ # 2D performance tests +├── benchmarks3d/ # 3D performance tests +└── src_testbed/ # Testbed source +``` + +## Core Modules Deep Dive + +### 1. `src/dynamics/` - Physics Simulation + +Handles movement, forces, and constraints. + +#### RigidBody System + +**`RigidBody`** (`rigid_body.rs`) - The fundamental physics object +- **Three body types:** + - `Dynamic`: Fully simulated - responds to forces, gravity, collisions + - `Fixed`: Never moves - infinite mass (walls, floors, terrain) + - `Kinematic`: User-controlled movement - pushes but isn't pushed (platforms, doors) + +**Key properties:** +- Position/rotation: `translation()`, `rotation()`, `position()` +- Velocity: `linvel()`, `angvel()`, `set_linvel()`, `set_angvel()` +- Mass: `mass()`, `center_of_mass()`, computed from attached colliders +- Forces: `add_force()`, `add_torque()`, `add_force_at_point()` (continuous, cleared each step) +- Impulses: `apply_impulse()`, `apply_torque_impulse()`, `apply_impulse_at_point()` (instant) +- Damping: `linear_damping()`, `angular_damping()` (air resistance, drag) +- Sleeping: `sleep()`, `wake_up()`, `is_sleeping()` (performance optimization) +- CCD: `enable_ccd()`, prevents fast objects tunneling through walls +- Locking: `lock_rotations()`, `lock_translations()`, `set_locked_axes()` (constrain movement) +- Gravity: `gravity_scale()`, `set_gravity_scale()` (0.0 = zero-g, 1.0 = normal, 2.0 = heavy) +- Energy: `kinetic_energy()`, `gravitational_potential_energy()` +- Prediction: `predict_position_using_velocity()`, `predict_position_using_velocity_and_forces()` + +**`RigidBodySet`** (`rigid_body_set.rs`) - Collection of all bodies +- Handle-based storage (generational indices prevent use-after-free) +- Methods: `insert()`, `remove()`, `get()`, `get_mut()`, `iter()`, `contains()` +- `propagate_modified_body_positions_to_colliders()` for manual position sync + +**`RigidBodyBuilder`** - Builder pattern for creating bodies +- Type constructors: `dynamic()`, `fixed()`, `kinematic_velocity_based()`, `kinematic_position_based()` +- Configuration: `translation()`, `rotation()`, `linvel()`, `angvel()` +- Settings: `gravity_scale()`, `can_sleep()`, `ccd_enabled()`, `linear_damping()`, `angular_damping()` +- Constraints: `locked_axes()`, `lock_rotations()`, `lock_translations()`, `enabled_rotations()` +- Advanced: `dominance_group()`, `additional_mass()`, `enable_gyroscopic_forces()` + +#### Joint System + +**Joint Types** (all in `src/dynamics/joint/`): + +1. **`FixedJoint`**: Welds two bodies rigidly together +2. **`RevoluteJoint`**: Hinge - rotation around one axis (doors, wheels) +3. **`PrismaticJoint`**: Slider - translation along one axis (pistons, elevators) +4. **`SphericalJoint`**: Ball-and-socket - free rotation, fixed position (shoulders, gimbals) +5. **`RopeJoint`**: Maximum distance constraint (ropes, cables, tethers) +6. **`SpringJoint`**: Elastic connection with stiffness/damping (suspension, soft constraints) + +**Joint Features:** +- **Motors**: Powered actuation with `set_motor_velocity()`, `set_motor_position()` + - Velocity control: constant speed rotation/sliding + - Position control: spring-like movement toward target + - Max force limits: `set_motor_max_force()` + - Motor models: `AccelerationBased` (mass-independent) vs `ForceBased` (mass-dependent) +- **Limits**: Restrict range with `set_limits([min, max])` +- **Anchors**: Connection points in each body's local space + +**`ImpulseJointSet`** - Collection of all joints +- Methods: `insert()`, `remove()`, `get()`, `get_mut()`, `iter()` +- Queries: `joints_between()`, `attached_joints()`, `attached_enabled_joints()` + +#### Integration & Solving + +**`IntegrationParameters`** - Controls simulation behavior +- `dt`: Timestep (1/60 for 60 FPS, 1/120 for 120 FPS) +- `num_solver_iterations`: Accuracy vs speed (4 = default, 8-12 = high quality, 1-2 = fast) +- `length_unit`: Scale factor if not using meters (100.0 for pixel-based 2D) +- `contact_natural_frequency`, `contact_damping_ratio`: Contact compliance +- `joint_natural_frequency`, `joint_damping_ratio`: Joint compliance +- Advanced: warmstarting, prediction distance, stabilization iterations + +**`IslandManager`** - Sleeping/waking optimization +- Groups connected bodies into "islands" for parallel solving +- Automatically sleeps bodies at rest (huge performance gain) +- Wakes bodies when disturbed by collisions/joints +- Sleep thresholds configurable via `RigidBodyActivation` + +**`CCDSolver`** - Prevents tunneling +- Detects fast-moving bodies that might pass through geometry +- Predicts time-of-impact and clamps motion +- Enable per-body with `ccd_enabled(true)` +- Soft-CCD: cheaper predictive variant with `set_soft_ccd_prediction()` + +### 2. `src/geometry/` - Collision Detection + +#### Collider System + +**`Collider`** - Collision shape ("hitbox") attached to bodies +- **Shapes**: Ball, Cuboid, Capsule, Cylinder, Cone, Triangle, Segment, HeightField, TriMesh, Compound, ConvexHull +- **Material properties:** + - Friction: 0.0 = ice, 0.5 = wood, 1.0 = rubber + - Restitution: 0.0 = clay (no bounce), 1.0 = perfect elastic, >1.0 = super bouncy + - Combine rules: Average, Min, Max, Multiply (how to combine when two colliders touch) +- **Mass:** Set via `density()` (kg/m³) or `mass()` (kg directly) +- **Sensors:** `set_sensor(true)` for trigger zones (detect overlaps without physical collision) +- **Position:** `position()`, `translation()`, `rotation()`, `position_wrt_parent()` +- **Groups:** `collision_groups()`, `solver_groups()` for layer-based filtering +- **Events:** `active_events()`, `contact_force_event_threshold()` for collision notifications +- **Shape queries:** `compute_aabb()`, `compute_swept_aabb()`, `volume()`, `mass_properties()` +- **Enabled:** `set_enabled()` to temporarily disable without removal + +**`ColliderSet`** - Collection of all colliders +- Methods: `insert()`, `insert_with_parent()`, `remove()`, `set_parent()` +- Access: `get()`, `get_mut()`, `iter()`, `iter_enabled()` +- Invalid handle: `ColliderSet::invalid_handle()` + +**`ColliderBuilder`** - Creates colliders with builder pattern +- **Primitive shapes:** + - `ball(radius)` - Sphere/circle + - `cuboid(hx, hy, hz)` - Box (half-extents) + - `capsule_y(half_height, radius)` - Pill shape (best for characters!) + - `cylinder(half_height, radius)`, `cone(half_height, radius)` (3D only) +- **Complex shapes:** + - `trimesh(vertices, indices)` - Triangle mesh (slow but accurate) + - `heightfield(heights, scale)` - Terrain from height grid + - `convex_hull(points)` - Smallest convex shape containing points + - `convex_decomposition(vertices, indices)` - Breaks concave mesh into convex pieces + - `segment(a, b)`, `triangle(a, b, c)` - Simple primitives +- **Configuration:** + - Material: `friction()`, `restitution()`, `density()`, `mass()` + - Filtering: `collision_groups()`, `sensor()` + - Events: `active_events()`, `contact_force_event_threshold()` + - Position: `translation()`, `rotation()`, `position()` + - Advanced: `active_hooks()`, `active_collision_types()`, `contact_skin()` + +#### Collision Detection Pipeline + +**`BroadPhaseBvh`** - First pass: quickly find nearby pairs +- Uses hierarchical BVH tree for spatial indexing +- Filters out distant objects before expensive narrow-phase +- Incrementally updated as objects move +- Optimization strategies: `SubtreeOptimizer` (default) vs `None` + +**`NarrowPhase`** - Second pass: compute exact contacts +- Calculates precise contact points, normals, penetration depths +- Builds contact manifolds (groups of contacts sharing properties) +- Managed automatically by PhysicsPipeline +- Access via `contact_graph()` for querying contact pairs + +**`ContactPair`** - Detailed collision information +- Methods: `total_impulse()`, `total_impulse_magnitude()`, `max_impulse()`, `find_deepest_contact()` +- Contains multiple `ContactManifold` structures +- Each manifold has contact points with normals, distances, solver data + +#### Collision Filtering + +**`InteractionGroups`** - Layer-based collision control +- Two components: `memberships` (what groups I'm in) and `filter` (what groups I collide with) +- 32 groups available: `Group::GROUP_1` through `Group::GROUP_32` +- Bidirectional check: A and B collide only if A's memberships overlap B's filter AND vice versa +- Example: Player bullets (group 1) only hit enemies (group 2) + +**`ActiveCollisionTypes`** - Filter by body type +- Controls which body type pairs can collide +- Defaults: Dynamic↔Dynamic ✓, Dynamic↔Fixed ✓, Dynamic↔Kinematic ✓, Fixed↔Fixed ✗ +- Rarely changed - defaults are correct for most games + +**`QueryFilter`** - Filter spatial queries +- Flags: `EXCLUDE_FIXED`, `EXCLUDE_DYNAMIC`, `EXCLUDE_SENSORS`, `ONLY_DYNAMIC`, etc. +- Groups: Filter by collision groups +- Exclusions: `exclude_collider`, `exclude_rigid_body` +- Custom: `predicate` closure for arbitrary filtering + +### 3. `src/pipeline/` - Simulation Orchestration + +**`PhysicsPipeline`** - The main simulation loop +- **`step()`**: Advances physics by one timestep + 1. Handles user changes (moved bodies, added/removed colliders) + 2. Runs collision detection (broad-phase → narrow-phase) + 3. Builds islands and solves constraints (contacts + joints) + 4. Integrates velocities to update positions + 5. Runs CCD if needed + 6. Generates events +- Reuse same instance for performance (caches data between frames) + +**`CollisionPipeline`** - Collision detection without dynamics +- Use when you only need collision detection, not physics simulation +- Lighter weight than PhysicsPipeline + +**`QueryPipeline`** - Spatial queries (created from BroadPhase) +- **Raycasting:** + - `cast_ray()` - First hit along ray + - `cast_ray_and_get_normal()` - Hit with surface normal + - `intersect_ray()` - ALL hits along ray +- **Shape casting:** + - `cast_shape()` - Sweep shape through space (thick raycast) + - `cast_shape_nonlinear()` - Non-linear motion sweep +- **Point queries:** + - `project_point()` - Find closest point on any collider + - `intersect_point()` - Find all colliders containing point +- **AABB queries:** + - `intersect_aabb_conservative()` - Find colliders in bounding box +- Filtering via `QueryFilter` + +**`EventHandler`** trait - Receive physics events +- `handle_collision_event()` - When colliders start/stop touching +- `handle_contact_force_event()` - When contact forces exceed threshold +- Built-in: `ChannelEventCollector` sends events to mpsc channels +- Enable per-collider with `ActiveEvents` flags + +**`PhysicsHooks`** trait - Custom collision behavior +- `filter_contact_pair()` - Decide if two colliders should collide +- `filter_intersection_pair()` - Filter sensor intersections +- `modify_solver_contacts()` - Modify contact properties before solving +- Enable per-collider with `ActiveHooks` flags +- Advanced feature - most users should use `InteractionGroups` instead + +**`CollisionEvent`** - Collision state changes +- `Started(h1, h2, flags)` - Colliders began touching +- `Stopped(h1, h2, flags)` - Colliders stopped touching +- Methods: `started()`, `stopped()`, `collider1()`, `collider2()`, `sensor()` + +### 4. `src/control/` - High-Level Controllers + +**`KinematicCharacterController`** - Player/NPC movement +- Handles walking, slopes, stairs, wall sliding, ground snapping +- NOT physics-based - you control movement directly +- Features: + - `slide`: Slide along walls instead of stopping + - `autostep`: Automatically climb stairs/small obstacles + - `max_slope_climb_angle`: Max climbable slope (radians) + - `min_slope_slide_angle`: When to slide down slopes + - `snap_to_ground`: Keep grounded on uneven terrain +- Returns `EffectiveCharacterMovement` with `translation` and `grounded` status + +**`DynamicRayCastVehicleController`** - Arcade vehicle physics +- Raycast-based suspension (simpler than constraint-based) +- Add wheels with suspension, steering, engine force +- Automatically handles wheel contacts and forces + +### 5. `src/data/` - Core Data Structures + +**`Arena`** - Handle-based storage +- Generational indices: (index, generation) pair +- Prevents use-after-free: old handles become invalid when slots reused +- Used for: `RigidBodySet`, `ColliderSet`, `ImpulseJointSet` +- Methods: `insert()`, `remove()`, `get()`, `get_mut()`, `iter()` +- Advanced: `get_unknown_gen()` bypasses generation check (unsafe) + +**`Graph`** - Generic graph structure +- Nodes and edges with associated data +- Used for: interaction graph (bodies/colliders), joint graph +- Enables: "find all joints attached to this body" + +**`ModifiedObjects`** - Change tracking +- Flags objects that changed since last frame +- Enables incremental updates (only process changed objects) +- Critical for performance + +### 6. `src/counters/` - Profiling + +**`Counters`** - Performance measurements +- Tracks time in: collision detection, solver, CCD, island construction +- Subdivided: broad-phase time, narrow-phase time, velocity resolution, etc. +- Access via `physics_pipeline.counters` + +## Complete API Reference + +### Body Types + +```rust +RigidBodyType::Dynamic // Fully simulated +RigidBodyType::Fixed // Never moves +RigidBodyType::KinematicVelocityBased // Velocity control +RigidBodyType::KinematicPositionBased // Position control +``` + +### Joint Types + +```rust +FixedJoint::new() // Weld +RevoluteJoint::new(axis) // Hinge +PrismaticJoint::new(axis) // Slider +SphericalJoint::new() // Ball-and-socket (3D only) +RopeJoint::new(max_dist) // Distance limit +SpringJoint::new(rest, stiff, damp) // Elastic +``` + +### Collision Shapes + +```rust +// Primitives (fast) +ColliderBuilder::ball(radius) +ColliderBuilder::cuboid(hx, hy, hz) // Half-extents +ColliderBuilder::capsule_y(half_h, r) // Best for characters! +ColliderBuilder::cylinder(half_h, r) // 3D only +ColliderBuilder::cone(half_h, r) // 3D only + +// Complex (slower) +ColliderBuilder::trimesh(verts, indices) // Arbitrary mesh +ColliderBuilder::heightfield(heights, scale) // Terrain +ColliderBuilder::convex_hull(points) // Convex wrap +ColliderBuilder::convex_decomposition(v, i) // Auto-split concave +``` + +### Events & Hooks + +```rust +// Event flags +ActiveEvents::COLLISION_EVENTS // Start/stop touching +ActiveEvents::CONTACT_FORCE_EVENTS // Force threshold exceeded + +// Hook flags +ActiveHooks::FILTER_CONTACT_PAIRS // Custom collision filtering +ActiveHooks::FILTER_INTERSECTION_PAIR // Custom sensor filtering +ActiveHooks::MODIFY_SOLVER_CONTACTS // Modify contact properties +``` + +### Filtering + +```rust +// Collision groups (layer system) +let groups = InteractionGroups::new( + Group::GROUP_1, // I'm in group 1 + Group::GROUP_2 | Group::GROUP_3 // I collide with 2 and 3 +); + +// Query filters +QueryFilter::default() +QueryFilter::only_dynamic() // Ignore static geometry +QueryFilter::exclude_sensors() // Only solid shapes +``` + +### Locked Axes + +```rust +LockedAxes::ROTATION_LOCKED // Can't rotate at all +LockedAxes::TRANSLATION_LOCKED // Can't translate at all +LockedAxes::TRANSLATION_LOCKED_Z // Lock one axis (2D in 3D) +LockedAxes::ROTATION_LOCKED_X | LockedAxes::ROTATION_LOCKED_Y // Combine +``` + +## Advanced Concepts + +### Sleeping System + +Bodies automatically sleep when at rest (velocities below threshold for 2 seconds). Sleeping bodies: +- Skipped in collision detection and simulation +- Auto-wake when hit or joint-connected to moving body +- Configured via `RigidBodyActivation`: + - `normalized_linear_threshold`: Linear velocity threshold (default 0.4) + - `angular_threshold`: Angular velocity threshold (default 0.5) + - `time_until_sleep`: How long to be still before sleeping (default 2.0s) +- Disable with `can_sleep(false)` or `RigidBodyActivation::cannot_sleep()` + +### CCD (Continuous Collision Detection) + +Prevents "tunneling" where fast objects pass through thin walls: +- **Hard CCD**: Shape-casting with substeps (expensive but accurate) +- **Soft CCD**: Predictive contacts (cheaper, good for medium-speed objects) +- Enable: `RigidBodyBuilder::ccd_enabled(true)` or `body.enable_ccd(true)` +- Soft: `set_soft_ccd_prediction(distance)` +- Active when velocity exceeds auto-computed threshold + +### Mass Properties + +Total mass = collider masses + additional mass: +- Collider mass: `density × volume` or set directly +- Additional mass: `set_additional_mass()` adds to total +- Auto-computed: mass, center of mass, angular inertia tensor +- Manual recompute: `recompute_mass_properties_from_colliders()` + +### Dominance Groups + +Bodies with higher dominance push lower ones but not vice versa: +- Range: `i8::MIN` to `i8::MAX` +- Default: 0 (all equal) +- Rarely needed - use for "heavy objects should always win" scenarios + +### Contact Skin + +Small margin around colliders (keeps objects slightly apart): +- Improves performance and stability +- Might create small visual gaps +- Set via `ColliderBuilder::contact_skin(thickness)` + +### Motor Models + +How motor spring constants scale with mass: +- **`MotorModel::AccelerationBased`** (default): Auto-scales with mass, easier to tune +- **`MotorModel::ForceBased`**: Absolute forces, mass-dependent behavior + +## Common Usage Patterns + +### Creating a Dynamic Object + +```rust +let body = RigidBodyBuilder::dynamic() + .translation(vector![0.0, 10.0, 0.0]) + .linvel(vector![1.0, 0.0, 0.0]) + .build(); +let body_handle = bodies.insert(body); + +let collider = ColliderBuilder::ball(0.5) + .density(2700.0) // Aluminum + .friction(0.7) + .restitution(0.3) + .build(); +colliders.insert_with_parent(collider, body_handle, &mut bodies); +``` + +### Raycasting + +```rust +let query_pipeline = broad_phase.as_query_pipeline( + &QueryDispatcher, + &bodies, + &colliders, + QueryFilter::default() +); + +let ray = Ray::new(point![0.0, 10.0, 0.0], vector![0.0, -1.0, 0.0]); +if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, true) { + let hit_point = ray.origin + ray.dir * toi; + println!("Hit {:?} at {:?}, distance = {}", handle, hit_point, toi); +} +``` + +### Applying Forces vs Impulses + +```rust +// IMPULSE: Instant change (jumping, explosions) +body.apply_impulse(vector![0.0, 500.0, 0.0], true); // Jump! + +// FORCE: Continuous (thrust, wind) - call every frame +body.add_force(vector![0.0, 100.0, 0.0], true); // Rocket thrust +``` + +### Creating a Joint + +```rust +let joint = RevoluteJointBuilder::new() + .local_anchor1(point![1.0, 0.0, 0.0]) + .local_anchor2(point![-1.0, 0.0, 0.0]) + .limits([0.0, std::f32::consts::PI / 2.0]) // 0-90° rotation + .build(); +let joint_handle = joints.insert(body1, body2, joint, true); +``` + +### Character Controller + +```rust +let controller = KinematicCharacterController { + slide: true, + autostep: Some(CharacterAutostep::default()), + max_slope_climb_angle: 45.0_f32.to_radians(), + snap_to_ground: Some(CharacterLength::Relative(0.2)), + ..Default::default() +}; + +let desired_movement = vector![input_x, 0.0, input_z] * speed * dt; +let movement = controller.move_shape( + dt, &bodies, &colliders, &query_pipeline, + character_shape, &character_pos, desired_movement, + QueryFilter::default(), |_| {} +); +character_pos.translation.vector += movement.translation; +``` + +### Collision Events + +```rust +use std::sync::mpsc::channel; + +let (collision_send, collision_recv) = channel(); +let (force_send, force_recv) = channel(); +let event_handler = ChannelEventCollector::new(collision_send, force_send); + +// In physics step +physics_pipeline.step(..., &event_handler); + +// After physics +while let Ok(event) = collision_recv.try_recv() { + match event { + CollisionEvent::Started(h1, h2, _) => println!("Collision!"), + CollisionEvent::Stopped(h1, h2, _) => println!("Separated"), + } +} +``` + +## Performance Optimization Tips + +1. **Sleeping**: Let objects at rest sleep (default behavior) +2. **Shape choice**: Ball/Cuboid/Capsule >> Convex Hull >> TriMesh +3. **Solver iterations**: Lower `num_solver_iterations` if accuracy isn't critical +4. **Parallel**: Enable `parallel` feature for multi-core +5. **Broadphase**: Keep objects reasonably distributed (not all in one spot) +6. **CCD**: Only enable for fast objects that need it +7. **Event generation**: Only enable events on colliders that need them +8. **Collision groups**: Filter unnecessary collision checks +9. **Fixed timestep**: Use fixed `dt`, accumulate remainder for smooth rendering + +## Common Patterns & Best Practices + +### Handle Storage +```rust +// Store handles, not references +struct Player { + body_handle: RigidBodyHandle, + collider_handle: ColliderHandle, +} + +// Access when needed +let player_body = &mut bodies[player.body_handle]; +``` + +### 2D Game in 3D Engine +```rust +let body = RigidBodyBuilder::dynamic() + .locked_axes( + LockedAxes::TRANSLATION_LOCKED_Z | + LockedAxes::ROTATION_LOCKED_X | + LockedAxes::ROTATION_LOCKED_Y + ) + .build(); +``` + +### One-Way Platforms (via hooks) +```rust +struct OneWayPlatform; +impl PhysicsHooks for OneWayPlatform { + fn filter_contact_pair(&self, context: &PairFilterContext) -> Option { + // Allow contact only if player is above platform + if player_above_platform(context) { + Some(SolverFlags::COMPUTE_IMPULSES) + } else { + None // No collision + } + } +} +``` + +### Compound Shapes +```rust +// Multiple colliders on one body +let body_handle = bodies.insert(RigidBodyBuilder::dynamic().build()); + +colliders.insert_with_parent( + ColliderBuilder::cuboid(1.0, 1.0, 1.0).translation(vector![0.0, 1.0, 0.0]).build(), + body_handle, &mut bodies +); +colliders.insert_with_parent( + ColliderBuilder::ball(0.5).translation(vector![0.0, 3.0, 0.0]).build(), + body_handle, &mut bodies +); +// Now the body has a box + ball shape +``` + +## Troubleshooting + +### Objects Tunneling Through Walls +- Enable CCD: `body.enable_ccd(true)` +- Increase wall thickness +- Reduce timestep (`dt`) +- Increase `num_solver_iterations` + +### Unstable Simulation (Jittering, Explosions) +- Reduce mass ratios (avoid 1kg vs 1000kg objects) +- Increase `num_solver_iterations` +- Check for conflicting constraints +- Verify joint anchors are reasonable +- Reduce timestep if using large `dt` + +### Poor Performance +- Check sleeping is enabled (`can_sleep(true)`) +- Use simpler shapes (capsules instead of meshes) +- Enable `parallel` feature +- Reduce `num_solver_iterations` if acceptable +- Use collision groups to avoid unnecessary checks +- Only enable events on colliders that need them + +### Bodies Stuck/Not Moving +- Check if sleeping: `body.wake_up(true)` +- Verify mass > 0 (check collider density) +- Check locked axes aren't preventing movement +- Verify gravity scale isn't 0 + +## File Statistics + +- **95 total Rust files** in `src/` +- **Top files by public function count:** + - `rigid_body.rs`: 126 functions + - `collider.rs`: 118 functions + - `rigid_body_components.rs`: 56 functions + - `generic_joint.rs`: 47 functions + - `query_pipeline.rs`: 29 functions + +## Documentation Improvements + +### Session 1: Comprehensive API Documentation +Comprehensively documented **300+ public functions** across **45+ files**: + +**Fully Documented Modules:** +1. **PhysicsPipeline** - Main simulation loop +2. **RigidBody** (~60 methods) - All position, velocity, force, impulse, damping, CCD, locking methods +3. **RigidBodySet** - All collection management methods +4. **RigidBodyBuilder** (~40 methods) - All configuration methods +5. **Collider** (~50 methods) - All property accessors and setters +6. **ColliderSet** - All collection methods +7. **ColliderBuilder** (~60 methods) - All shape constructors and configuration +8. **All 6 joint types** - Comprehensive docs for Fixed, Revolute, Prismatic, Spherical, Rope, Spring +9. **ImpulseJointSet** - All joint collection methods +10. **QueryPipeline** - All spatial query methods +11. **EventHandler & events** - Complete event system +12. **InteractionGroups** - Collision filtering +13. **IntegrationParameters** - Simulation settings +14. **IslandManager** - Sleep/wake system +15. **CCDSolver** - Tunneling prevention +16. **BroadPhaseBvh, NarrowPhase** - Collision detection +17. **CharacterController** - Player movement +18. **ContactPair** - Contact information +19. **All major enums/flags**: RigidBodyType, LockedAxes, ActiveEvents, ActiveHooks, ActiveCollisionTypes, CoefficientCombineRule, MotorModel, CollisionEvent, QueryFilter + +**Documentation Style:** +All functions include: +- **Plain language** ("hitbox" not "geometric entity") +- **Real-world use cases** (when/why to use) +- **Code examples** (copy-paste ready) +- **Value guides** (friction 0-1, density values for real materials) +- **Warnings** (teleporting, performance costs, common mistakes) +- **Comparisons** (forces vs impulses, mass vs density, when to use each) + +### Session 2: Documentation Example Testing +**Converted 75+ ignored documentation examples to be tested by `cargo test --doc`** + +**Goal:** Ensure all documentation examples remain valid and compilable as the codebase evolves. + +**Files with Fixed Examples:** + +*Dynamics Module (33 examples):* +- `dynamics/rigid_body.rs` (13) +- `dynamics/rigid_body_set.rs` (8) +- `dynamics/rigid_body_components.rs` (1) - LockedAxes +- `dynamics/coefficient_combine_rule.rs` (1) +- `dynamics/integration_parameters.rs` (1) +- `dynamics/island_manager.rs` (1) +- `dynamics/joint/rope_joint.rs` (1) +- `dynamics/joint/revolute_joint.rs` (1) +- `dynamics/joint/generic_joint.rs` (1) - JointMotor +- `dynamics/joint/impulse_joint/impulse_joint_set.rs` (5) + +*Geometry Module (10 examples):* +- `geometry/interaction_groups.rs` (1) +- `geometry/collider_set.rs` (4) +- `geometry/collider_components.rs` (1) - ActiveCollisionTypes +- `geometry/contact_pair.rs` (2) +- `geometry/mod.rs` (1) - CollisionEvent +- `geometry/interaction_graph.rs` (1) + +*Pipeline Module (14 examples):* +- `pipeline/query_pipeline.rs` (9) - Raycasting, shape casting, point queries +- `pipeline/event_handler.rs` (3) - ActiveEvents, EventHandler trait, ChannelEventCollector +- `pipeline/physics_pipeline.rs` (1) +- `pipeline/collision_pipeline.rs` (1) + +*Control Module (1 example):* +- `control/character_controller.rs` (1) - Complete character controller setup + +*Data Module (25 examples):* +- `data/arena.rs` (25) - All Arena API methods + +*Other Modules (4 examples):* +- `dynamics/joint/multibody_joint/multibody_joint_set.rs` (1) + +**Conversion Pattern:** +```rust +// Before: +/// ```ignore +/// let body = RigidBodyBuilder::dynamic().build(); +/// bodies.insert(body); +/// ``` + +// After: +/// ``` +/// # use rapier3d::prelude::*; +/// # let mut bodies = RigidBodySet::new(); +/// let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); +/// ``` +``` + +Hidden lines (prefixed with `#`) provide setup code while keeping examples readable. + +**Key Fixes Required for Compilation:** + +1. **Removed unnecessary `.build()` calls**: Builders implement `Into`, so: + - `RigidBodyBuilder::dynamic().build()` → `RigidBodyBuilder::dynamic()` + - `ColliderBuilder::ball(0.5).build()` → `ColliderBuilder::ball(0.5)` + - These work directly with `insert()` and `insert_with_parent()` + +2. **Fixed API calls to match actual implementation:** + - `&QueryDispatcher` → `narrow_phase.query_dispatcher()` (QueryPipeline needs a dispatcher reference) + - Added `NarrowPhase::new()` setup for query pipeline examples + +3. **Corrected property/field names:** + - `hit.toi` → `hit.time_of_impact` (RayIntersection struct) + - `collider.shape()` → `collider.shared_shape()` (when printing/debugging) + +4. **Added required setup for complex examples:** + - `project_point()` example: Added `IntegrationParameters`, `broad_phase.set_aabb()` call + - Character controller: Changed to `Ball::new(0.5)` instead of shape reference + - Joint examples: Fixed to use `Vector::y_axis()` instead of implicit axis + +5. **Fixed joint constructor calls:** + - `RevoluteJoint::new()` → `RevoluteJoint::new(Vector::y_axis())` (axis required) + - `PrismaticJoint::new(...)` → `PrismaticJoint::new(Vector::x_axis())` (axis required) + +**Remaining Work:** +- `geometry/collider.rs` has 12 ignored examples still marked as `ignore` (these are intentionally left as `ignore` for documentation purposes where full compilation context would be overly verbose) + +**Impact:** +- ✅ Documentation examples now compile with `cargo test --doc` +- ✅ Examples stay correct as codebase evolves (tests will catch API changes) +- ✅ Copy-paste ready code that actually works +- ✅ Improved documentation quality and developer experience +- ✅ Builders work seamlessly without explicit `.build()` calls + +## Examples Directory + +`examples3d/` contains many demonstrations: + +- `primitives3.rs` - Showcase of basic shapes +- `keva3.rs` - Large tower of blocks (stress test) +- `platform3.rs` - Moving kinematic platforms +- `joints3.rs` - All joint types demonstrated +- `character_controller3.rs` - Character movement +- `vehicle_controller3.rs` - Vehicle physics +- `ccd3.rs` - Fast bullets with CCD +- `sensor3.rs` - Trigger zones +- `despawn3.rs` - Removing objects +- `debug_boxes3.rs` - Visual debugging +- `rotating_floor_stacks3.rs` - **Custom example**: 20 pyramids (10×10 cube bases) on slowly rotating floor + +**Run**: `cargo run --release --bin all_examples3` + +## Building & Testing + +```bash +# Development build +cargo build + +# Release build (much faster!) +cargo build --release + +# Run all 3D examples +cargo run --release --bin all_examples3 + +# Run all 2D examples +cargo run --release --bin all_examples2 + +# Run tests +cargo test + +# With parallelism +cargo build --features parallel --release + +# With SIMD +cargo build --features simd-stable --release + +# Benchmarks +cargo run --release --manifest-path benchmarks3d/Cargo.toml +``` + +## Cargo Features + +- `parallel` - Multi-threaded solving (big performance gain on multi-core) +- `simd-stable` - SIMD optimizations on stable Rust +- `simd-nightly` - More SIMD opts on nightly +- `serde-serialize` - Snapshot/restore support +- `enhanced-determinism` - Stricter determinism (disables SIMD) +- `debug-render` - Visual debugging helpers +- `profiler` - Detailed performance counters +- `dim2` / `dim3` - 2D or 3D (set by crate, not user) +- `f32` / `f64` - Precision (set by crate, not user) + +## Resources + +- **Official Site**: https://rapier.rs +- **User Guide**: https://rapier.rs/docs/ +- **API Reference**: https://docs.rs/rapier3d +- **Discord**: https://discord.gg/vt9DJSW +- **GitHub**: https://github.com/dimforge/rapier +- **Blog**: https://www.dimforge.com/blog +- **Crates.io**: https://crates.io/crates/rapier3d +- **NPM** (JS/WASM): Available for web development + +## Related Dimforge Projects + +- **Parry**: Collision detection library (Rapier's foundation) +- **Salva**: SPH fluid simulation +- **nphysics**: Previous-gen physics engine (deprecated, use Rapier) +- **nalgebra**: Linear algebra library +- **Bevy_rapier**: Integration with Bevy game engine + +## Quick Reference Card + +### Most Common Operations + +```rust +// Create world +let mut bodies = RigidBodySet::new(); +let mut colliders = ColliderSet::new(); +let mut joints = ImpulseJointSet::new(); +let mut pipeline = PhysicsPipeline::new(); + +// Add dynamic ball +let body = bodies.insert(RigidBodyBuilder::dynamic().translation(vector![0.0, 5.0, 0.0]).build()); +colliders.insert_with_parent(ColliderBuilder::ball(0.5).build(), body, &mut bodies); + +// Add static floor +let floor = bodies.insert(RigidBodyBuilder::fixed().build()); +colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0).build(), floor, &mut bodies); + +// Simulate +pipeline.step(&gravity, ¶ms, &mut islands, &mut broad_phase, &mut narrow_phase, + &mut bodies, &mut colliders, &mut joints, &mut multibody_joints, + &mut ccd_solver, &(), &()); + +// Query +let pos = bodies[body].translation(); +let vel = bodies[body].linvel(); + +// Modify +bodies[body].apply_impulse(vector![0.0, 100.0, 0.0], true); +bodies[body].set_linvel(vector![1.0, 0.0, 0.0], true); +``` + +### Material Presets + +```rust +// Ice +.friction(0.01).restitution(0.1) + +// Wood +.friction(0.5).restitution(0.2) + +// Rubber +.friction(1.0).restitution(0.8) + +// Metal +.friction(0.6).restitution(0.3) + +// Bouncy ball +.friction(0.7).restitution(0.9) +``` + +### Common Densities (kg/m³) + +```rust +.density(1000.0) // Water +.density(2700.0) // Aluminum +.density(7850.0) // Steel +.density(11340.0) // Lead +.density(920.0) // Ice +.density(1.2) // Air +``` + +This documentation provides complete coverage of Rapier's architecture, APIs, usage patterns, and best practices for both beginners and advanced users! diff --git a/crates/rapier2d-f64/Cargo.toml b/crates/rapier2d-f64/Cargo.toml index 5c8df39..19293dc 100644 --- a/crates/rapier2d-f64/Cargo.toml +++ b/crates/rapier2d-f64/Cargo.toml @@ -62,7 +62,7 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"] name = "rapier2d_f64" path = "../../src/lib.rs" required-features = ["dim2", "f64"] - +doctest = false # All doctests are written assuming the 3D version. [dependencies] vec_map = { version = "0.8", optional = true } diff --git a/crates/rapier2d/Cargo.toml b/crates/rapier2d/Cargo.toml index 0b762b5..e4a0575 100644 --- a/crates/rapier2d/Cargo.toml +++ b/crates/rapier2d/Cargo.toml @@ -63,7 +63,7 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"] name = "rapier2d" path = "../../src/lib.rs" required-features = ["dim2", "f32"] - +doctest = false # All doctests are written assuming the 3D version. [dependencies] vec_map = { version = "0.8", optional = true } diff --git a/crates/rapier3d-f64/Cargo.toml b/crates/rapier3d-f64/Cargo.toml index 677ea8f..a3baa2d 100644 --- a/crates/rapier3d-f64/Cargo.toml +++ b/crates/rapier3d-f64/Cargo.toml @@ -65,7 +65,7 @@ features = ["parallel", "simd-stable", "serde-serialize", "debug-render"] name = "rapier3d_f64" path = "../../src/lib.rs" required-features = ["dim3", "f64"] - +doctest = false # All doctests are written assuming the 3D f32 version. [dependencies] vec_map = { version = "0.8", optional = true } diff --git a/src/control/character_controller.rs b/src/control/character_controller.rs index e480c6a..9232426 100644 --- a/src/control/character_controller.rs +++ b/src/control/character_controller.rs @@ -114,7 +114,61 @@ pub struct CharacterCollision { pub hit: ShapeCastHit, } -/// A character controller for kinematic bodies. +/// A kinematic character controller for player/NPC movement (walking, climbing, sliding). +/// +/// This provides classic game character movement: walking on floors, sliding on slopes, +/// climbing stairs, and snapping to ground. It's kinematic (not physics-based), meaning +/// you control movement directly rather than applying forces. +/// +/// **Not suitable for:** Ragdolls, vehicles, or physics-driven movement (use dynamic bodies instead). +/// +/// # How it works +/// +/// 1. You provide desired movement (e.g., "move forward 5 units") +/// 2. Controller casts the character shape through the world +/// 3. It handles collisions: sliding along walls, stepping up stairs, snapping to ground +/// 4. Returns the final movement to apply +/// +/// # Example +/// +/// ``` +/// # use rapier3d::prelude::*; +/// # use rapier3d::control::{CharacterAutostep, KinematicCharacterController}; +/// # use nalgebra::Isometry3; +/// # let mut bodies = RigidBodySet::new(); +/// # let mut colliders = ColliderSet::new(); +/// # let broad_phase = BroadPhaseBvh::new(); +/// # let narrow_phase = NarrowPhase::new(); +/// # let dt = 1.0 / 60.0; +/// # let speed = 5.0; +/// # let (input_x, input_z) = (1.0, 0.0); +/// # let character_shape = Ball::new(0.5); +/// # let mut character_pos = Isometry3::identity(); +/// # let query_pipeline = broad_phase.as_query_pipeline( +/// # narrow_phase.query_dispatcher(), +/// # &bodies, +/// # &colliders, +/// # QueryFilter::default(), +/// # ); +/// let controller = KinematicCharacterController { +/// slide: true, // Slide along walls instead of stopping +/// autostep: Some(CharacterAutostep::default()), // Auto-climb stairs +/// max_slope_climb_angle: 45.0_f32.to_radians(), // Max climbable slope +/// ..Default::default() +/// }; +/// +/// // In your game loop: +/// let desired_movement = vector![input_x, 0.0, input_z] * speed * dt; +/// let movement = controller.move_shape( +/// dt, +/// &query_pipeline, +/// &character_shape, +/// &character_pos, +/// desired_movement, +/// |_| {} // Collision event callback +/// ); +/// character_pos.translation.vector += movement.translation; +/// ``` #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug)] pub struct KinematicCharacterController { diff --git a/src/data/arena.rs b/src/data/arena.rs index 9a20c3d..db80625 100644 --- a/src/data/arena.rs +++ b/src/data/arena.rs @@ -37,9 +37,8 @@ enum Entry { /// /// # Examples /// -/// ```ignore -/// use rapier::data::arena::Arena; -/// +/// ``` +/// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx = arena.insert(123); /// assert_eq!(arena[idx], 123); @@ -94,9 +93,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::::new(); /// # let _ = arena; /// ``` @@ -110,9 +108,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::with_capacity(10); /// /// // These insertions will not require further allocation. @@ -139,9 +136,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::with_capacity(1); /// arena.insert(42); /// arena.insert(43); @@ -177,9 +173,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// /// match arena.try_insert(42) { @@ -218,9 +213,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::{Arena, Index}; - /// + /// ``` + /// # use rapier3d::data::arena::{Arena, Index}; /// let mut arena = Arena::new(); /// /// match arena.try_insert_with(|idx| (42, idx)) { @@ -272,9 +266,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// /// let idx = arena.insert(42); @@ -295,9 +288,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::{Arena, Index}; - /// + /// ``` + /// # use rapier3d::data::arena::{Arena, Index}; /// let mut arena = Arena::new(); /// /// let idx = arena.insert_with(|idx| (42, idx)); @@ -337,9 +329,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx = arena.insert(42); /// @@ -381,9 +372,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut crew = Arena::new(); /// crew.extend(&["Jim Hawkins", "John Silver", "Alexander Smollett", "Israel Hands"]); /// let pirates = ["John Silver", "Israel Hands"]; // too dangerous to keep them around @@ -422,9 +412,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx = arena.insert(42); /// @@ -443,9 +432,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx = arena.insert(42); /// @@ -469,9 +457,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx = arena.insert(42); /// @@ -500,9 +487,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx1 = arena.insert(0); /// let idx2 = arena.insert(1); @@ -565,9 +551,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// assert_eq!(arena.len(), 0); /// @@ -588,9 +573,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// assert!(arena.is_empty()); /// @@ -612,9 +596,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::with_capacity(10); /// assert_eq!(arena.capacity(), 10); /// @@ -640,9 +623,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::with_capacity(10); /// arena.reserve(5); /// assert_eq!(arena.capacity(), 15); @@ -675,9 +657,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// for i in 0..10 { /// arena.insert(i * i); @@ -702,9 +683,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// for i in 0..10 { /// arena.insert(i * i); @@ -731,9 +711,8 @@ impl Arena { /// /// # Examples /// - /// ```ignore - /// use rapier::data::arena::Arena; - /// + /// ``` + /// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx_1 = arena.insert("hello"); /// let idx_2 = arena.insert("world"); @@ -818,9 +797,8 @@ impl IntoIterator for Arena { /// /// # Examples /// -/// ```ignore -/// use rapier::data::arena::Arena; -/// +/// ``` +/// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// for i in 0..10 { /// arena.insert(i * i); @@ -902,9 +880,8 @@ impl<'a, T> IntoIterator for &'a Arena { /// /// # Examples /// -/// ```ignore -/// use rapier::data::arena::Arena; -/// +/// ``` +/// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// for i in 0..10 { /// arena.insert(i * i); @@ -1006,9 +983,8 @@ impl<'a, T> IntoIterator for &'a mut Arena { /// /// # Examples /// -/// ```ignore -/// use rapier::data::arena::Arena; -/// +/// ``` +/// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// for i in 0..10 { /// arena.insert(i * i); @@ -1104,9 +1080,8 @@ impl FusedIterator for IterMut<'_, T> {} /// /// # Examples /// -/// ```ignore -/// use rapier::data::arena::Arena; -/// +/// ``` +/// # use rapier3d::data::arena::Arena; /// let mut arena = Arena::new(); /// let idx_1 = arena.insert("hello"); /// let idx_2 = arena.insert("world"); diff --git a/src/dynamics/ccd/ccd_solver.rs b/src/dynamics/ccd/ccd_solver.rs index 555db29..3763730 100644 --- a/src/dynamics/ccd/ccd_solver.rs +++ b/src/dynamics/ccd/ccd_solver.rs @@ -14,7 +14,28 @@ pub enum PredictedImpacts { NoImpacts, } -/// Solver responsible for performing motion-clamping on fast-moving bodies. +/// Continuous Collision Detection solver that prevents fast objects from tunneling through geometry. +/// +/// CCD (Continuous Collision Detection) solves the "tunneling problem" where fast-moving objects +/// pass through thin walls because they move more than the wall's thickness in one timestep. +/// +/// ## How it works +/// +/// 1. Detects which bodies are moving fast enough to potentially tunnel +/// 2. Predicts where/when they would impact during the timestep +/// 3. Clamps their motion to stop just before impact +/// 4. Next frame, normal collision detection handles the contact +/// +/// ## When to use CCD +/// +/// Enable CCD on bodies that: +/// - Move very fast (bullets, projectiles) +/// - Are small and hit thin geometry +/// - Must NEVER pass through walls (gameplay-critical) +/// +/// **Cost**: More expensive than regular collision detection. Only use when needed! +/// +/// Enable via `RigidBodyBuilder::ccd_enabled(true)` or `body.enable_ccd(true)`. #[derive(Clone, Default)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct CCDSolver; diff --git a/src/dynamics/coefficient_combine_rule.rs b/src/dynamics/coefficient_combine_rule.rs index 8110778..b6f6373 100644 --- a/src/dynamics/coefficient_combine_rule.rs +++ b/src/dynamics/coefficient_combine_rule.rs @@ -1,23 +1,43 @@ use crate::math::Real; -/// Rules used to combine two coefficients. +/// How to combine friction/restitution values when two colliders touch. /// -/// This is used to determine the effective restitution and -/// friction coefficients for a contact between two colliders. -/// Each collider has its combination rule of type -/// `CoefficientCombineRule`. And the rule -/// actually used is given by `max(first_combine_rule as usize, second_combine_rule as usize)`. +/// When two colliders with different friction (or restitution) values collide, Rapier +/// needs to decide what the effective friction/restitution should be. Each collider has +/// a combine rule, and the "stronger" rule wins (Max > Multiply > Min > Average). +/// +/// ## Combine Rules +/// +/// **Most games use Average (the default)** and never change this. +/// +/// - **Average** (default): `(friction1 + friction2) / 2` - Balanced, intuitive +/// - **Min**: `min(friction1, friction2)` - "Slippery wins" (ice on any surface = ice) +/// - **Multiply**: `friction1 × friction2` - Both must be high for high friction +/// - **Max**: `max(friction1, friction2)` - "Sticky wins" (rubber on any surface = rubber) +/// +/// ## Example +/// ``` +/// # use rapier3d::prelude::*; +/// // Ice collider that makes everything slippery +/// let ice = ColliderBuilder::cuboid(10.0, 0.1, 10.0) +/// .friction(0.0) +/// .friction_combine_rule(CoefficientCombineRule::Min) // Ice wins! +/// .build(); +/// ``` +/// +/// ## Priority System +/// If colliders disagree on rules, the "higher" one wins: Max > Multiply > Min > Average #[derive(Default, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub enum CoefficientCombineRule { - /// The two coefficients are averaged. + /// Average the two values (default, most common). #[default] Average = 0, - /// The smallest coefficient is chosen. + /// Use the smaller value ("slippery/soft wins"). Min = 1, - /// The two coefficients are multiplied. + /// Multiply the two values (both must be high). Multiply = 2, - /// The greatest coefficient is chosen. + /// Use the larger value ("sticky/bouncy wins"). Max = 3, } diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index a23c0d0..0e9b362 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -30,11 +30,43 @@ pub enum FrictionModel { Coulomb, } -/// Parameters for a time-step of the physics engine. +/// Configuration parameters that control the physics simulation quality and behavior. +/// +/// These parameters affect how the physics engine advances time, resolves collisions, and +/// maintains stability. The defaults work well for most games, but you may want to adjust +/// them based on your specific needs. +/// +/// # Key parameters for beginners +/// +/// - **`dt`**: Timestep duration (default: 1/60 second). Most games run physics at 60Hz. +/// - **`num_solver_iterations`**: More iterations = more accurate but slower (default: 4) +/// - **`length_unit`**: Scale factor if your world units aren't meters (e.g., 100 for pixel-based games) +/// +/// # Example +/// +/// ``` +/// # use rapier3d::prelude::*; +/// // Standard 60 FPS physics with default settings +/// let mut integration_params = IntegrationParameters::default(); +/// +/// // For a more accurate (but slower) simulation: +/// integration_params.num_solver_iterations = 8; +/// +/// // For pixel-based 2D games where 100 pixels = 1 meter: +/// integration_params.length_unit = 100.0; +/// ``` +/// +/// Most other parameters are advanced settings for fine-tuning stability and performance. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct IntegrationParameters { - /// The timestep length (default: `1.0 / 60.0`). + /// The timestep length - how much simulated time passes per physics step (default: `1.0 / 60.0`). + /// + /// Set this to `1.0 / your_target_fps`. For example: + /// - 60 FPS: `1.0 / 60.0` ≈ 0.0167 seconds + /// - 120 FPS: `1.0 / 120.0` ≈ 0.0083 seconds + /// + /// Smaller timesteps are more accurate but require more CPU time per second of simulated time. pub dt: Real, /// Minimum timestep size when using CCD with multiple substeps (default: `1.0 / 60.0 / 100.0`). /// @@ -85,19 +117,19 @@ pub struct IntegrationParameters { /// (default `1.0`). pub warmstart_coefficient: Real, - /// The approximate size of most dynamic objects in the scene. + /// The scale factor for your world if you're not using meters (default: `1.0`). /// - /// This value is used internally to estimate some length-based tolerance. In particular, the - /// values [`IntegrationParameters::allowed_linear_error`], - /// [`IntegrationParameters::max_corrective_velocity`], - /// [`IntegrationParameters::prediction_distance`], [`RigidBodyActivation::normalized_linear_threshold`] - /// are scaled by this value implicitly. + /// Rapier is tuned for human-scale objects measured in meters. If your game uses different + /// units, set this to how many of your units equal 1 meter in the real world. /// - /// This value can be understood as the number of units-per-meter in your physical world compared - /// to a human-sized world in meter. For example, in a 2d game, if your typical object size is 100 - /// pixels, set the [`Self::length_unit`] parameter to 100.0. The physics engine will interpret - /// it as if 100 pixels is equivalent to 1 meter in its various internal threshold. - /// (default `1.0`). + /// **Examples:** + /// - Your game uses meters: `length_unit = 1.0` (default) + /// - Your game uses centimeters: `length_unit = 100.0` (100 cm = 1 m) + /// - Pixel-based 2D game where typical objects are 100 pixels tall: `length_unit = 100.0` + /// - Your game uses feet: `length_unit = 3.28` (approximately) + /// + /// This automatically scales various internal tolerances and thresholds to work correctly + /// with your chosen units. pub length_unit: Real, /// Amount of penetration the engine won’t attempt to correct (default: `0.001m`). @@ -113,6 +145,11 @@ pub struct IntegrationParameters { /// This value is implicitly scaled by [`IntegrationParameters::length_unit`]. pub normalized_prediction_distance: Real, /// The number of solver iterations run by the constraints solver for calculating forces (default: `4`). + /// + /// Higher values produce more accurate and stable simulations at the cost of performance. + /// - `4` (default): Good balance for most games + /// - `8-12`: Use for demanding scenarios (stacks of objects, complex machinery) + /// - `1-2`: Use if performance is critical and accuracy can be sacrificed pub num_solver_iterations: usize, /// Number of internal Project Gauss Seidel (PGS) iterations run at each solver iteration (default: `1`). pub num_internal_pgs_iterations: usize, diff --git a/src/dynamics/island_manager.rs b/src/dynamics/island_manager.rs index d444d79..d580ef5 100644 --- a/src/dynamics/island_manager.rs +++ b/src/dynamics/island_manager.rs @@ -6,8 +6,20 @@ use crate::geometry::{ColliderSet, NarrowPhase}; use crate::math::Real; use crate::utils::SimdDot; -/// Structure responsible for maintaining the set of active rigid-bodies, and -/// putting non-moving rigid-bodies to sleep to save computation times. +/// System that manages which bodies are active (awake) vs sleeping to optimize performance. +/// +/// ## Sleeping Optimization +/// +/// Bodies at rest automatically "sleep" - they're excluded from simulation until something +/// disturbs them (collision, joint connection to moving body, manual wake-up). This can +/// dramatically improve performance in scenes with many static/resting objects. +/// +/// ## Islands +/// +/// Connected bodies (via contacts or joints) are grouped into "islands" that are solved together. +/// This allows parallel solving and better organization. +/// +/// You rarely interact with this directly - it's automatically managed by [`PhysicsPipeline`](crate::pipeline::PhysicsPipeline). #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone, Default)] pub struct IslandManager { @@ -79,10 +91,28 @@ impl IslandManager { } } - /// Forces the specified rigid-body to wake up if it is dynamic. + /// Wakes up a sleeping body, forcing it back into the active simulation. /// - /// If `strong` is `true` then it is assured that the rigid-body will - /// remain awake during multiple subsequent timesteps. + /// Use this when you want to ensure a body is active (useful after manually moving + /// a sleeping body, or to prevent it from sleeping in the next few frames). + /// + /// # Parameters + /// * `strong` - If `true`, the body is guaranteed to stay awake for multiple frames. + /// If `false`, it might sleep again immediately if conditions are met. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut islands = IslandManager::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// islands.wake_up(&mut bodies, body_handle, true); + /// let body = bodies.get_mut(body_handle).unwrap(); + /// // Wake up a body before applying force to it + /// body.add_force(vector![100.0, 0.0, 0.0], false); + /// ``` + /// + /// Only affects dynamic bodies (kinematic and fixed bodies don't sleep). pub fn wake_up(&mut self, bodies: &mut RigidBodySet, handle: RigidBodyHandle, strong: bool) { // NOTE: the use an Option here because there are many legitimate cases (like when // deleting a joint attached to an already-removed body) where we could be diff --git a/src/dynamics/joint/fixed_joint.rs b/src/dynamics/joint/fixed_joint.rs index 2bb2f64..08b3316 100644 --- a/src/dynamics/joint/fixed_joint.rs +++ b/src/dynamics/joint/fixed_joint.rs @@ -4,7 +4,16 @@ use crate::math::{Isometry, Point, Real}; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A fixed joint, locks all relative motion between two bodies. +/// A joint that rigidly connects two bodies together (like welding them). +/// +/// Fixed joints lock all relative motion - the two bodies move as if they were a single +/// solid object. Use for: +/// - Permanently attaching objects (gluing, welding) +/// - Composite objects made of multiple bodies +/// - Connecting parts of a structure +/// +/// Unlike simply using one body with multiple colliders, fixed joints let you attach +/// bodies that were created separately, and you can break the connection later if needed. pub struct FixedJoint { /// The underlying joint data. pub data: GenericJoint, diff --git a/src/dynamics/joint/generic_joint.rs b/src/dynamics/joint/generic_joint.rs index 19a02cb..df03b9c 100644 --- a/src/dynamics/joint/generic_joint.rs +++ b/src/dynamics/joint/generic_joint.rs @@ -115,15 +115,22 @@ impl From for JointAxesMask { } } -/// The limits of a joint along one of its degrees of freedom. +/// Limits that restrict a joint's range of motion along one axis. +/// +/// Use to constrain how far a joint can move/rotate. Examples: +/// - Door that only opens 90°: revolute joint with limits `[0.0, PI/2.0]` +/// - Piston with 2-unit stroke: prismatic joint with limits `[0.0, 2.0]` +/// - Elbow that bends 0-150°: revolute joint with limits `[0.0, 5*PI/6]` +/// +/// When a joint hits its limit, forces are applied to prevent further movement in that direction. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] pub struct JointLimits { - /// The minimum bound of the joint limit. + /// Minimum allowed value (angle for revolute, distance for prismatic). pub min: N, - /// The maximum bound of the joint limit. + /// Maximum allowed value (angle for revolute, distance for prismatic). pub max: N, - /// The impulse applied to enforce the joint’s limit. + /// Internal: impulse being applied to enforce the limit. pub impulse: N, } @@ -147,23 +154,52 @@ impl From<[N; 2]> for JointLimits { } } -/// A joint’s motor along one of its degrees of freedom. +/// A powered motor that drives a joint toward a target position/velocity. +/// +/// Motors add actuation to joints - they apply forces to make the joint move toward +/// a desired state. Think of them as servos, electric motors, or hydraulic actuators. +/// +/// ## Two control modes +/// +/// 1. **Velocity control**: Set `target_vel` to make the motor spin/slide at constant speed +/// 2. **Position control**: Set `target_pos` with `stiffness`/`damping` to reach a target angle/position +/// +/// You can combine both for precise control. +/// +/// ## Parameters +/// +/// - `stiffness`: How strongly to pull toward target (spring constant) +/// - `damping`: Resistance to motion (prevents oscillation) +/// - `max_force`: Maximum force/torque the motor can apply +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # use rapier3d::dynamics::{RevoluteJoint, PrismaticJoint}; +/// # let mut revolute_joint = RevoluteJoint::new(Vector::x_axis()); +/// # let mut prismatic_joint = PrismaticJoint::new(Vector::x_axis()); +/// // Motor that spins a wheel at 10 rad/s +/// revolute_joint.set_motor_velocity(10.0, 0.8); +/// +/// // Motor that moves to position 5.0 +/// prismatic_joint.set_motor_position(5.0, 100.0, 10.0); // stiffness=100, damping=10 +/// ``` #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] pub struct JointMotor { - /// The target velocity of the motor. + /// Target velocity (units/sec for prismatic, rad/sec for revolute). pub target_vel: Real, - /// The target position of the motor. + /// Target position (units for prismatic, radians for revolute). pub target_pos: Real, - /// The stiffness coefficient of the motor’s spring-like equation. + /// Spring constant - how strongly to pull toward target position. pub stiffness: Real, - /// The damping coefficient of the motor’s spring-like equation. + /// Damping coefficient - resistance to motion (prevents oscillation). pub damping: Real, - /// The maximum force this motor can deliver. + /// Maximum force the motor can apply (Newtons for prismatic, Nm for revolute). pub max_force: Real, - /// The impulse applied by this motor. + /// Internal: current impulse being applied. pub impulse: Real, - /// The spring-like model used for simulating this motor. + /// Force-based or acceleration-based motor model. pub model: MotorModel, } diff --git a/src/dynamics/joint/impulse_joint/impulse_joint_set.rs b/src/dynamics/joint/impulse_joint/impulse_joint_set.rs index c3039b2..08c668b 100644 --- a/src/dynamics/joint/impulse_joint/impulse_joint_set.rs +++ b/src/dynamics/joint/impulse_joint/impulse_joint_set.rs @@ -39,7 +39,33 @@ pub(crate) type JointGraphEdge = crate::data::graph::Edge; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone, Default, Debug)] -/// A set of impulse_joints that can be handled by a physics `World`. +/// The collection that stores all joints connecting rigid bodies in your physics world. +/// +/// Joints constrain how two bodies can move relative to each other. This set manages +/// all joint instances (hinges, sliders, springs, etc.) using handles for safe access. +/// +/// # Common joint types +/// - [`FixedJoint`](crate::dynamics::FixedJoint): Weld two bodies together +/// - [`RevoluteJoint`](crate::dynamics::RevoluteJoint): Hinge (rotation around axis) +/// - [`PrismaticJoint`](crate::dynamics::PrismaticJoint): Slider (translation along axis) +/// - [`SpringJoint`](crate::dynamics::SpringJoint): Elastic connection +/// - [`RopeJoint`](crate::dynamics::RopeJoint): Maximum distance limit +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # let mut bodies = RigidBodySet::new(); +/// # let body1 = bodies.insert(RigidBodyBuilder::dynamic()); +/// # let body2 = bodies.insert(RigidBodyBuilder::dynamic()); +/// let mut joints = ImpulseJointSet::new(); +/// +/// // Create a hinge connecting two bodies +/// let joint = RevoluteJointBuilder::new(Vector::y_axis()) +/// .local_anchor1(point![1.0, 0.0, 0.0]) +/// .local_anchor2(point![-1.0, 0.0, 0.0]) +/// .build(); +/// let handle = joints.insert(body1, body2, joint, true); +/// ``` pub struct ImpulseJointSet { rb_graph_ids: Coarena, /// Map joint handles to edge ids on the graph. @@ -60,22 +86,40 @@ impl ImpulseJointSet { } } - /// The number of impulse_joints on this set. + /// Returns how many joints are currently in this collection. pub fn len(&self) -> usize { self.joint_graph.graph.edges.len() } - /// `true` if there are no impulse_joints in this set. + /// Returns `true` if there are no joints in this collection. pub fn is_empty(&self) -> bool { self.joint_graph.graph.edges.is_empty() } - /// Retrieve the joint graph where edges are impulse_joints and nodes are rigid body handles. + /// Returns the internal graph structure (nodes=bodies, edges=joints). + /// + /// Advanced usage - most users should use `attached_joints()` instead. pub fn joint_graph(&self) -> &InteractionGraph { &self.joint_graph } - /// Iterates through all the joints between two rigid-bodies. + /// Returns all joints connecting two specific bodies. + /// + /// Usually returns 0 or 1 joint, but multiple joints can connect the same pair. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut joints = ImpulseJointSet::new(); + /// # let body1 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let body2 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let joint = RevoluteJointBuilder::new(Vector::y_axis()); + /// # joints.insert(body1, body2, joint, true); + /// for (handle, joint) in joints.joints_between(body1, body2) { + /// println!("Found joint {:?}", handle); + /// } + /// ``` pub fn joints_between( &self, body1: RigidBodyHandle, @@ -89,7 +133,24 @@ impl ImpulseJointSet { .map(|inter| (inter.2.handle, inter.2)) } - /// Iterates through all the impulse joints attached to the given rigid-body. + /// Returns all joints attached to a specific body. + /// + /// Each result is `(body1, body2, joint_handle, joint)` where one of the bodies + /// matches the queried body. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut joints = ImpulseJointSet::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let other_body = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let joint = RevoluteJointBuilder::new(Vector::y_axis()); + /// # joints.insert(body_handle, other_body, joint, true); + /// for (b1, b2, j_handle, joint) in joints.attached_joints(body_handle) { + /// println!("Body connected to {:?} via {:?}", b2, j_handle); + /// } + /// ``` pub fn attached_joints( &self, body: RigidBodyHandle, @@ -121,7 +182,9 @@ impl ImpulseJointSet { }) } - /// Iterates through all the enabled impulse joints attached to the given rigid-body. + /// Returns only the enabled joints attached to a body. + /// + /// Same as `attached_joints()` but filters out disabled joints. pub fn attached_enabled_joints( &self, body: RigidBodyHandle, @@ -137,18 +200,21 @@ impl ImpulseJointSet { .filter(|inter| inter.3.data.is_enabled()) } - /// Is the given joint handle valid? + /// Checks if the given joint handle is valid (joint still exists). pub fn contains(&self, handle: ImpulseJointHandle) -> bool { self.joint_ids.contains(handle.0) } - /// Gets the joint with the given handle. + /// Returns a read-only reference to the joint with the given handle. pub fn get(&self, handle: ImpulseJointHandle) -> Option<&ImpulseJoint> { let id = self.joint_ids.get(handle.0)?; self.joint_graph.graph.edge_weight(*id) } - /// Gets a mutable reference to the joint with the given handle. + /// Returns a mutable reference to the joint with the given handle. + /// + /// # Parameters + /// * `wake_up_connected_bodies` - If `true`, wakes up both bodies connected by this joint pub fn get_mut( &mut self, handle: ImpulseJointHandle, @@ -165,15 +231,10 @@ impl ImpulseJointSet { joint } - /// Gets the joint with the given handle without a known generation. + /// Gets a joint by index without knowing the generation (advanced/unsafe). /// - /// This is useful when you know you want the joint at index `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the joint position `i` - /// are recycled between two insertion and a removal. - /// - /// Using this is discouraged in favor of `self.get(handle)` which does not - /// suffer form the ABA problem. + /// ⚠️ **Prefer `get()` instead!** This bypasses generation checks. + /// See [`RigidBodySet::get_unknown_gen`] for details on the ABA problem. pub fn get_unknown_gen(&self, i: u32) -> Option<(&ImpulseJoint, ImpulseJointHandle)> { let (id, handle) = self.joint_ids.get_unknown_gen(i)?; Some(( @@ -182,15 +243,9 @@ impl ImpulseJointSet { )) } - /// Gets a mutable reference to the joint with the given handle without a known generation. + /// Gets a mutable joint by index without knowing the generation (advanced/unsafe). /// - /// This is useful when you know you want the joint at position `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the joint position `i` - /// are recycled between two insertion and a removal. - /// - /// Using this is discouraged in favor of `self.get_mut(handle)` which does not - /// suffer form the ABA problem. + /// ⚠️ **Prefer `get_mut()` instead!** This bypasses generation checks. pub fn get_unknown_gen_mut( &mut self, i: u32, @@ -202,7 +257,9 @@ impl ImpulseJointSet { )) } - /// Iterates through all the joint on this set. + /// Iterates over all joints in this collection. + /// + /// Each iteration yields `(joint_handle, &joint)`. pub fn iter(&self) -> impl Iterator { self.joint_graph .graph @@ -211,7 +268,9 @@ impl ImpulseJointSet { .map(|e| (e.weight.handle, &e.weight)) } - /// Iterates mutably through all the joint on this set. + /// Iterates over all joints with mutable access. + /// + /// Each iteration yields `(joint_handle, &mut joint)`. pub fn iter_mut(&mut self) -> impl Iterator { self.joint_graph .graph @@ -240,10 +299,28 @@ impl ImpulseJointSet { &mut self.joint_graph.graph.edges } - /// Inserts a new joint into this set and retrieve its handle. + /// Adds a joint connecting two bodies and returns its handle. /// - /// If `wake_up` is set to `true`, then the bodies attached to this joint will be - /// automatically woken up during the next timestep. + /// The joint constrains how the two bodies can move relative to each other. + /// + /// # Parameters + /// * `body1`, `body2` - The two bodies to connect + /// * `data` - The joint configuration (FixedJoint, RevoluteJoint, etc.) + /// * `wake_up` - If `true`, wakes up both bodies + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut joints = ImpulseJointSet::new(); + /// # let body1 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let body2 = bodies.insert(RigidBodyBuilder::dynamic()); + /// let joint = RevoluteJointBuilder::new(Vector::y_axis()) + /// .local_anchor1(point![1.0, 0.0, 0.0]) + /// .local_anchor2(point![-1.0, 0.0, 0.0]) + /// .build(); + /// let handle = joints.insert(body1, body2, joint, true); + /// ``` #[profiling::function] pub fn insert( &mut self, @@ -326,10 +403,26 @@ impl ImpulseJointSet { } } - /// Removes a joint from this set. + /// Removes a joint from the world. /// - /// If `wake_up` is set to `true`, then the bodies attached to this joint will be - /// automatically woken up. + /// Returns the removed joint if it existed, or `None` if the handle was invalid. + /// + /// # Parameters + /// * `wake_up` - If `true`, wakes up both bodies that were connected by this joint + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut joints = ImpulseJointSet::new(); + /// # let body1 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let body2 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let joint = RevoluteJointBuilder::new(Vector::y_axis()).build(); + /// # let joint_handle = joints.insert(body1, body2, joint, true); + /// if let Some(joint) = joints.remove(joint_handle, true) { + /// println!("Removed joint between {:?} and {:?}", joint.body1, joint.body2); + /// } + /// ``` #[profiling::function] pub fn remove(&mut self, handle: ImpulseJointHandle, wake_up: bool) -> Option { let id = self.joint_ids.remove(handle.0)?; diff --git a/src/dynamics/joint/motor_model.rs b/src/dynamics/joint/motor_model.rs index 74b8dd3..53d21f8 100644 --- a/src/dynamics/joint/motor_model.rs +++ b/src/dynamics/joint/motor_model.rs @@ -1,15 +1,32 @@ use crate::math::Real; -/// The spring-like model used for constraints resolution. +/// How motor stiffness/damping values are interpreted (mass-dependent vs mass-independent). +/// +/// This affects how motors behave when attached to bodies of different masses. +/// +/// ## Acceleration-Based (default, recommended) +/// +/// Spring constants are automatically scaled by mass, so: +/// - Heavy and light objects respond similarly to same spring values +/// - Easier to tune - values work across different mass ranges +/// - **Formula**: `acceleration = stiffness × error + damping × velocity_error` +/// +/// ## Force-Based +/// +/// Spring constants produce absolute forces, so: +/// - Same spring values → different behavior for different masses +/// - More physically accurate representation +/// - Requires re-tuning if masses change +/// - **Formula**: `force = stiffness × error + damping × velocity_error` +/// +/// **Most users should use AccelerationBased (the default).** #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub enum MotorModel { - /// The solved spring-like equation is: - /// `acceleration = stiffness * (pos - target_pos) + damping * (vel - target_vel)` + /// Spring constants auto-scale with mass (easier to tune, recommended). #[default] AccelerationBased, - /// The solved spring-like equation is: - /// `force = stiffness * (pos - target_pos) + damping * (vel - target_vel)` + /// Spring constants produce absolute forces (mass-dependent). ForceBased, } diff --git a/src/dynamics/joint/multibody_joint/multibody_joint_set.rs b/src/dynamics/joint/multibody_joint/multibody_joint_set.rs index 4efb7b7..b3e184e 100644 --- a/src/dynamics/joint/multibody_joint/multibody_joint_set.rs +++ b/src/dynamics/joint/multibody_joint/multibody_joint_set.rs @@ -47,7 +47,15 @@ impl Default for MultibodyJointHandle { #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Indexes usable to get a multibody link from a `MultibodyJointSet`. /// -/// ```ignore +/// ``` +/// # use rapier3d::prelude::*; +/// # let mut bodies = RigidBodySet::new(); +/// # let mut multibody_joint_set = MultibodyJointSet::new(); +/// # let body1 = bodies.insert(RigidBodyBuilder::dynamic()); +/// # let body2 = bodies.insert(RigidBodyBuilder::dynamic()); +/// # let joint = RevoluteJointBuilder::new(Vector::y_axis()); +/// # multibody_joint_set.insert(body1, body2, joint, true); +/// # let multibody_link_id = multibody_joint_set.rigid_body_link(body2).unwrap(); /// // With: /// // multibody_joint_set: MultibodyJointSet /// // multibody_link_id: MultibodyLinkId diff --git a/src/dynamics/joint/prismatic_joint.rs b/src/dynamics/joint/prismatic_joint.rs index 8a1c59f..9fbc81f 100644 --- a/src/dynamics/joint/prismatic_joint.rs +++ b/src/dynamics/joint/prismatic_joint.rs @@ -7,7 +7,20 @@ use super::{JointLimits, JointMotor}; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A prismatic joint, locks all relative motion between two bodies except for translation along the joint’s principal axis. +/// A sliding joint that allows movement along one axis only (like a piston or sliding door). +/// +/// Prismatic joints lock all motion except sliding along a single axis. Use for: +/// - Pistons and hydraulics +/// - Sliding doors and drawers +/// - Elevator platforms +/// - Linear actuators +/// - Telescoping mechanisms +/// +/// You can optionally add: +/// - **Limits**: Restrict sliding distance (min/max positions) +/// - **Motor**: Powered sliding with target velocity or position +/// +/// The axis is specified when creating the joint and is expressed in each body's local space. pub struct PrismaticJoint { /// The underlying joint data. pub data: GenericJoint, @@ -101,14 +114,27 @@ impl PrismaticJoint { self } - /// Sets the target velocity this motor needs to reach. + /// Sets the motor's target sliding speed. + /// + /// Makes the joint slide at a desired velocity (like a powered piston or conveyor). + /// + /// # Parameters + /// * `target_vel` - Desired velocity in units/second + /// * `factor` - Motor strength pub fn set_motor_velocity(&mut self, target_vel: Real, factor: Real) -> &mut Self { self.data .set_motor_velocity(JointAxis::LinX, target_vel, factor); self } - /// Sets the target angle this motor needs to reach. + /// Sets the motor's target position along the sliding axis. + /// + /// Makes the joint slide toward a specific position using spring-like behavior. + /// + /// # Parameters + /// * `target_pos` - Desired position along the axis + /// * `stiffness` - Spring constant + /// * `damping` - Resistance to motion pub fn set_motor_position( &mut self, target_pos: Real, @@ -120,7 +146,7 @@ impl PrismaticJoint { self } - /// Configure both the target angle and target velocity of the motor. + /// Configures both target position and target velocity for the motor. pub fn set_motor( &mut self, target_pos: Real, diff --git a/src/dynamics/joint/revolute_joint.rs b/src/dynamics/joint/revolute_joint.rs index c7f0288..fdc7586 100644 --- a/src/dynamics/joint/revolute_joint.rs +++ b/src/dynamics/joint/revolute_joint.rs @@ -8,7 +8,20 @@ use crate::math::UnitVector; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A revolute joint, locks all relative motion except for rotation along the joint’s principal axis. +/// A hinge joint that allows rotation around one axis (like a door hinge or wheel axle). +/// +/// Revolute joints lock all movement except rotation around a single axis. Use for: +/// - Door hinges +/// - Wheels and gears +/// - Joints in robotic arms +/// - Pendulums +/// - Any rotating connection +/// +/// You can optionally add: +/// - **Limits**: Restrict rotation to a range (e.g., door that only opens 90°) +/// - **Motor**: Powered rotation with target velocity or position +/// +/// In 2D there's only one rotation axis (Z). In 3D you specify which axis (X, Y, or Z). pub struct RevoluteJoint { /// The underlying joint data. pub data: GenericJoint, @@ -110,14 +123,27 @@ impl RevoluteJoint { self } - /// Sets the target velocity this motor needs to reach. + /// Sets the motor's target rotation speed. + /// + /// Makes the joint spin at a desired velocity (like a powered motor or wheel). + /// + /// # Parameters + /// * `target_vel` - Desired angular velocity in radians/second + /// * `factor` - Motor strength (higher = stronger, approaches target faster) pub fn set_motor_velocity(&mut self, target_vel: Real, factor: Real) -> &mut Self { self.data .set_motor_velocity(JointAxis::AngX, target_vel, factor); self } - /// Sets the target angle this motor needs to reach. + /// Sets the motor's target angle (position control). + /// + /// Makes the joint rotate toward a specific angle using spring-like behavior. + /// + /// # Parameters + /// * `target_pos` - Desired angle in radians + /// * `stiffness` - How strongly to pull toward target (spring constant) + /// * `damping` - Resistance to motion (higher = less oscillation) pub fn set_motor_position( &mut self, target_pos: Real, @@ -129,7 +155,9 @@ impl RevoluteJoint { self } - /// Configure both the target angle and target velocity of the motor. + /// Configures both target angle and target velocity for the motor. + /// + /// Combines position and velocity control for precise motor behavior. pub fn set_motor( &mut self, target_pos: Real, @@ -142,19 +170,35 @@ impl RevoluteJoint { self } - /// Sets the maximum force the motor can deliver. + /// Sets the maximum torque the motor can apply. + /// + /// Limits how strong the motor is. Without this, motors can apply infinite force. pub fn set_motor_max_force(&mut self, max_force: Real) -> &mut Self { self.data.set_motor_max_force(JointAxis::AngX, max_force); self } - /// The limit angle attached bodies can translate along the joint’s principal axis. + /// The rotation limits of this joint, if any. + /// + /// Returns `None` if no limits are set (unlimited rotation). #[must_use] pub fn limits(&self) -> Option<&JointLimits> { self.data.limits(JointAxis::AngX) } - /// Sets the `[min,max]` limit angle attached bodies can translate along the joint’s principal axis. + /// Restricts rotation to a specific angle range. + /// + /// # Parameters + /// * `limits` - `[min_angle, max_angle]` in radians + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # use rapier3d::dynamics::RevoluteJoint; + /// # let mut joint = RevoluteJoint::new(Vector::y_axis()); + /// // Door that opens 0° to 90° + /// joint.set_limits([0.0, std::f32::consts::PI / 2.0]); + /// ``` pub fn set_limits(&mut self, limits: [Real; 2]) -> &mut Self { self.data.set_limits(JointAxis::AngX, limits); self diff --git a/src/dynamics/joint/rope_joint.rs b/src/dynamics/joint/rope_joint.rs index 67c0c3e..2fc5e81 100644 --- a/src/dynamics/joint/rope_joint.rs +++ b/src/dynamics/joint/rope_joint.rs @@ -7,7 +7,18 @@ use super::JointMotor; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A rope joint, limits the maximum distance between two bodies +/// A distance-limiting joint (like a rope or cable connecting two objects). +/// +/// Rope joints keep two bodies from getting too far apart, but allow them to get closer. +/// They only apply force when stretched to their maximum length. Use for: +/// - Ropes and chains +/// - Cables and tethers +/// - Leashes +/// - Grappling hooks +/// - Maximum distance constraints +/// +/// Unlike spring joints, rope joints are inelastic - they don't bounce or stretch smoothly, +/// they just enforce a hard maximum distance. pub struct RopeJoint { /// The underlying joint data. pub data: GenericJoint, @@ -116,7 +127,9 @@ impl RopeJoint { self } - /// The maximum distance allowed between the attached objects. + /// The maximum rope length (distance between anchor points). + /// + /// Bodies can get closer but not farther than this distance. #[must_use] pub fn max_distance(&self) -> Real { self.data @@ -125,9 +138,17 @@ impl RopeJoint { .unwrap_or(Real::MAX) } - /// Sets the maximum allowed distance between the attached objects. + /// Changes the maximum rope length. /// - /// The `max_dist` must be strictly greater than 0.0. + /// Must be greater than 0.0. Bodies will be pulled together if farther apart. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # use rapier3d::dynamics::RopeJoint; + /// # let mut rope_joint = RopeJoint::new(5.0); + /// rope_joint.set_max_distance(10.0); // Max 10 units apart + /// ``` pub fn set_max_distance(&mut self, max_dist: Real) -> &mut Self { self.data.set_limits(JointAxis::LinX, [0.0, max_dist]); self diff --git a/src/dynamics/joint/spherical_joint.rs b/src/dynamics/joint/spherical_joint.rs index c4cc4e4..5c63db6 100644 --- a/src/dynamics/joint/spherical_joint.rs +++ b/src/dynamics/joint/spherical_joint.rs @@ -4,10 +4,24 @@ use crate::math::{Isometry, Point, Real}; use super::JointLimits; +#[cfg(doc)] +use crate::dynamics::RevoluteJoint; + #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A spherical joint, locks all relative translations between two bodies. +/// A ball-and-socket joint that allows free rotation but no translation (like a shoulder joint). +/// +/// Spherical joints keep two bodies connected at a point but allow them to rotate freely +/// around that point in all directions. Use for: +/// - Shoulder/hip joints in ragdolls +/// - Ball-and-socket mechanical connections +/// - Wrecking ball chains +/// - Camera gimbals +/// +/// The bodies stay connected at their anchor points but can rotate relative to each other. +/// +/// **Note**: Only available in 3D. In 2D, use [`RevoluteJoint`] instead (there's only one rotation axis in 2D). pub struct SphericalJoint { /// The underlying joint data. pub data: GenericJoint, @@ -94,19 +108,29 @@ impl SphericalJoint { self } - /// The motor affecting the joint’s rotational degree of freedom along the specified axis. + /// The motor for a specific rotation axis of this spherical joint. + /// + /// Spherical joints can have motors on each of their 3 rotation axes (X, Y, Z). + /// Returns `None` if no motor is configured for that axis. #[must_use] pub fn motor(&self, axis: JointAxis) -> Option<&JointMotor> { self.data.motor(axis) } - /// Set the spring-like model used by the motor to reach the desired target velocity and position. + /// Sets the motor model for a specific rotation axis. + /// + /// Choose between force-based or acceleration-based motor behavior. pub fn set_motor_model(&mut self, axis: JointAxis, model: MotorModel) -> &mut Self { self.data.set_motor_model(axis, model); self } - /// Sets the target velocity this motor needs to reach. + /// Sets target rotation speed for a specific axis. + /// + /// # Parameters + /// * `axis` - Which rotation axis (AngX, AngY, or AngZ) + /// * `target_vel` - Desired angular velocity in radians/second + /// * `factor` - Motor strength pub fn set_motor_velocity( &mut self, axis: JointAxis, @@ -117,7 +141,13 @@ impl SphericalJoint { self } - /// Sets the target angle this motor needs to reach. + /// Sets target angle for a specific rotation axis. + /// + /// # Parameters + /// * `axis` - Which rotation axis (AngX, AngY, or AngZ) + /// * `target_pos` - Desired angle in radians + /// * `stiffness` - Spring constant + /// * `damping` - Resistance pub fn set_motor_position( &mut self, axis: JointAxis, diff --git a/src/dynamics/joint/spring_joint.rs b/src/dynamics/joint/spring_joint.rs index eea639f..99b3c73 100644 --- a/src/dynamics/joint/spring_joint.rs +++ b/src/dynamics/joint/spring_joint.rs @@ -5,11 +5,21 @@ use crate::math::{Point, Real}; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(transparent)] -/// A spring-damper joint, applies a force proportional to the distance between two objects. +/// A spring joint that pulls/pushes two bodies toward a target distance (like a spring or shock absorber). /// -/// The spring is integrated implicitly, implying that even an undamped spring will be subject to some -/// amount of numerical damping (so it will eventually come to a rest). More solver iterations, or smaller -/// timesteps, will lower the effect of numerical damping, providing a more realistic result. +/// Springs apply force based on: +/// - **Stiffness**: How strong the spring force is (higher = stiffer spring) +/// - **Damping**: Resistance to motion (higher = less bouncy, settles faster) +/// - **Rest length**: The distance the spring "wants" to be +/// +/// Use for: +/// - Suspension systems (vehicles, furniture) +/// - Bouncy connections +/// - Soft constraints between objects +/// - Procedural animation (following targets softly) +/// +/// **Technical note**: Springs use implicit integration which adds some numerical damping. +/// This means even zero-damping springs will eventually settle (more iterations = less damping). pub struct SpringJoint { /// The underlying joint data. pub data: GenericJoint, @@ -68,12 +78,14 @@ impl SpringJoint { self } - /// Set the spring model used by this joint to reach the desired target velocity and position. + /// Sets whether spring constants are mass-dependent or mass-independent. /// - /// Setting this to `MotorModel::ForceBased` (which is the default value for this joint) makes the spring constants - /// (stiffness and damping) parameter understood as in the regular spring-mass-damper system. With - /// `MotorModel::AccelerationBased`, the spring constants will be automatically scaled by the attached masses, - /// making the spring more mass-independent. + /// - `MotorModel::ForceBased` (default): Stiffness/damping are absolute values. + /// Heavier objects will respond differently to the same spring constants. + /// - `MotorModel::AccelerationBased`: Spring constants auto-scale with mass. + /// Objects of different masses behave more similarly. + /// + /// Most users can ignore this and use the default. pub fn set_spring_model(&mut self, model: MotorModel) -> &mut Self { self.data.set_motor_model(JointAxis::LinX, model); self diff --git a/src/dynamics/rigid_body.rs b/src/dynamics/rigid_body.rs index 2137e1b..63e394a 100644 --- a/src/dynamics/rigid_body.rs +++ b/src/dynamics/rigid_body.rs @@ -13,9 +13,29 @@ use crate::utils::SimdCross; use num::Zero; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] -/// A rigid body. +/// A physical object that can move, rotate, and collide with other objects in your simulation. /// -/// To create a new rigid-body, use the [`RigidBodyBuilder`] structure. +/// Rigid bodies are the fundamental moving objects in physics simulations. Think of them as +/// the "physical representation" of your game objects - a character, a crate, a vehicle, etc. +/// +/// ## Body types +/// +/// - **Dynamic**: Affected by forces, gravity, and collisions. Use for objects that should move realistically (falling boxes, projectiles, etc.) +/// - **Fixed**: Never moves. Use for static geometry like walls, floors, and terrain +/// - **Kinematic**: Moved by setting velocity or position directly, not by forces. Use for moving platforms, doors, or player-controlled characters +/// +/// ## Creating bodies +/// +/// Always use [`RigidBodyBuilder`] to create new rigid bodies: +/// +/// ``` +/// # use rapier3d::prelude::*; +/// # let mut bodies = RigidBodySet::new(); +/// let body = RigidBodyBuilder::dynamic() +/// .translation(vector![0.0, 10.0, 0.0]) +/// .build(); +/// let handle = bodies.insert(body); +/// ``` #[derive(Debug, Clone)] // #[repr(C)] // #[repr(align(64))] @@ -185,25 +205,36 @@ impl RigidBody { } } - /// The linear damping coefficient of this rigid-body. + /// The linear damping coefficient (velocity reduction over time). + /// + /// Damping gradually slows down moving objects. `0.0` = no damping (infinite momentum), + /// higher values = faster slowdown. Use for air resistance, friction, etc. #[inline] pub fn linear_damping(&self) -> Real { self.damping.linear_damping } - /// Sets the linear damping coefficient of this rigid-body. + /// Sets how quickly linear velocity decreases over time. + /// + /// - `0.0` = no slowdown (space/frictionless) + /// - `0.1` = gradual slowdown (air resistance) + /// - `1.0+` = rapid slowdown (thick fluid) #[inline] pub fn set_linear_damping(&mut self, damping: Real) { self.damping.linear_damping = damping; } - /// The angular damping coefficient of this rigid-body. + /// The angular damping coefficient (rotation slowdown over time). + /// + /// Like linear damping but for rotation. Higher values make spinning objects stop faster. #[inline] pub fn angular_damping(&self) -> Real { self.damping.angular_damping } - /// Sets the angular damping coefficient of this rigid-body. + /// Sets how quickly angular velocity decreases over time. + /// + /// Controls how fast spinning objects slow down. #[inline] pub fn set_angular_damping(&mut self, damping: Real) { self.damping.angular_damping = damping @@ -230,13 +261,18 @@ impl RigidBody { } } - /// The world-space center-of-mass of this rigid-body. + /// The center of mass position in world coordinates. + /// + /// This is the "balance point" where the body's mass is centered. Forces applied here + /// produce no rotation, only translation. #[inline] pub fn center_of_mass(&self) -> &Point { &self.mprops.world_com } - /// The local-space center-of-mass of this rigid-body. + /// The center of mass in the body's local coordinate system. + /// + /// This is relative to the body's position, computed from attached colliders. #[inline] pub fn local_center_of_mass(&self) -> &Point { &self.mprops.local_mprops.local_com @@ -276,8 +312,11 @@ impl RigidBody { self.mprops.flags } + /// Locks or unlocks all rotational movement for this body. + /// + /// When locked, the body cannot rotate at all (useful for keeping objects upright). + /// Use for characters that shouldn't tip over, or objects that should only slide. #[inline] - /// Locks or unlocks all the rotations of this rigid-body. pub fn lock_rotations(&mut self, locked: bool, wake_up: bool) { if locked != self.mprops.flags.contains(LockedAxes::ROTATION_LOCKED) { if self.is_dynamic_or_kinematic() && wake_up { @@ -338,8 +377,11 @@ impl RigidBody { ); } + /// Locks or unlocks all translational movement for this body. + /// + /// When locked, the body cannot move from its position (but can still rotate). + /// Use for rotating platforms, turrets, or objects fixed in space. #[inline] - /// Locks or unlocks all the rotations of this rigid-body. pub fn lock_translations(&mut self, locked: bool, wake_up: bool) { if locked != self.mprops.flags.contains(LockedAxes::TRANSLATION_LOCKED) { if self.is_dynamic_or_kinematic() && wake_up { @@ -444,14 +486,17 @@ impl RigidBody { ] } - /// Enables of disable CCD (Continuous Collision-Detection) for this rigid-body. + /// Enables or disables Continuous Collision Detection for this body. /// - /// CCD prevents tunneling, but may still allow limited interpenetration of colliders. + /// CCD prevents fast-moving objects from tunneling through thin walls, but costs more CPU. + /// Enable for bullets, fast projectiles, or any object that must never pass through geometry. pub fn enable_ccd(&mut self, enabled: bool) { self.ccd.ccd_enabled = enabled; } - /// Is CCD (continuous collision-detection) enabled for this rigid-body? + /// Checks if CCD is enabled for this body. + /// + /// Returns `true` if CCD is turned on (not whether it's currently active this frame). pub fn is_ccd_enabled(&self) -> bool { self.ccd.ccd_enabled } @@ -493,7 +538,10 @@ impl RigidBody { self.ccd.ccd_active } - /// Recompute the mass-properties of this rigid-bodies based on its currently attached colliders. + /// Recalculates mass, center of mass, and inertia from attached colliders. + /// + /// Normally automatic, but call this if you modify collider shapes/masses at runtime. + /// Only needed after directly modifying colliders without going through the builder. pub fn recompute_mass_properties_from_colliders(&mut self, colliders: &ColliderSet) { self.mprops.recompute_mass_properties_from_colliders( colliders, @@ -503,26 +551,22 @@ impl RigidBody { ); } - /// Sets the rigid-body's additional mass. + /// Adds extra mass on top of collider-computed mass. /// - /// The total angular inertia of the rigid-body will be scaled automatically based on this - /// additional mass. If this scaling effect isn’t desired, use [`Self::set_additional_mass_properties`] - /// instead of this method. + /// Total mass = collider masses + this additional mass. Use when you want to make + /// a body heavier without changing collider densities. /// - /// This is only the "additional" mass because the total mass of the rigid-body is - /// equal to the sum of this additional mass and the mass computed from the colliders - /// (with non-zero densities) attached to this rigid-body. + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Add 50kg to make this body heavier + /// bodies[body].set_additional_mass(50.0, true); + /// ``` /// - /// That total mass (which includes the attached colliders’ contributions) - /// will be updated at the name physics step, or can be updated manually with - /// [`Self::recompute_mass_properties_from_colliders`]. - /// - /// This will override any previous mass-properties set by [`Self::set_additional_mass`], - /// [`Self::set_additional_mass_properties`], [`RigidBodyBuilder::additional_mass`], or - /// [`RigidBodyBuilder::additional_mass_properties`] for this rigid-body. - /// - /// If `wake_up` is `true` then the rigid-body will be woken up if it was - /// put to sleep because it did not move for a while. + /// Angular inertia is automatically scaled to match the mass increase. + /// Updated automatically at next physics step or call `recompute_mass_properties_from_colliders()`. #[inline] pub fn set_additional_mass(&mut self, additional_mass: Real, wake_up: bool) { self.do_set_additional_mass_properties( @@ -572,21 +616,37 @@ impl RigidBody { } } - /// The handles of colliders attached to this rigid body. + /// Returns handles of all colliders attached to this body. + /// + /// Use to iterate over a body's collision shapes or to modify them. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// # colliders.insert_with_parent(ColliderBuilder::ball(0.5), body, &mut bodies); + /// for collider_handle in bodies[body].colliders() { + /// if let Some(collider) = colliders.get_mut(*collider_handle) { + /// collider.set_friction(0.5); + /// } + /// } + /// ``` pub fn colliders(&self) -> &[ColliderHandle] { &self.colliders.0[..] } - /// Is this rigid body dynamic? + /// Checks if this is a dynamic body (moves via forces and collisions). /// - /// A dynamic body can move freely and is affected by forces. + /// Dynamic bodies are fully simulated and respond to gravity, forces, and collisions. pub fn is_dynamic(&self) -> bool { self.body_type == RigidBodyType::Dynamic } - /// Is this rigid body kinematic? + /// Checks if this is a kinematic body (moves via direct velocity/position control). /// - /// A kinematic body can move freely but is not affected by forces. + /// Kinematic bodies move by setting velocity directly, not by applying forces. pub fn is_kinematic(&self) -> bool { self.body_type.is_kinematic() } @@ -611,16 +671,18 @@ impl RigidBody { } } - /// Is this rigid body fixed? + /// Checks if this is a fixed body (never moves, infinite mass). /// - /// A fixed body cannot move and is not affected by forces. + /// Fixed bodies are static geometry: walls, floors, terrain. They never move + /// and are not affected by any forces or collisions. pub fn is_fixed(&self) -> bool { self.body_type == RigidBodyType::Fixed } - /// The mass of this rigid body. + /// The mass of this rigid body in kilograms. /// - /// Returns zero if this rigid body has an infinite mass. + /// Returns zero for fixed bodies (which technically have infinite mass). + /// Mass is computed from attached colliders' shapes and densities. pub fn mass(&self) -> Real { self.mprops.local_mprops.mass() } @@ -634,12 +696,27 @@ impl RigidBody { &self.pos.next_position } - /// The scale factor applied to the gravity affecting this rigid-body. + /// The gravity scale multiplier for this body. + /// + /// - `1.0` (default) = normal gravity + /// - `0.0` = no gravity (floating) + /// - `2.0` = double gravity (heavy/fast falling) + /// - Negative values = reverse gravity (objects fall upward!) pub fn gravity_scale(&self) -> Real { self.forces.gravity_scale } - /// Sets the gravity scale facter for this rigid-body. + /// Sets how much gravity affects this body (multiplier). + /// + /// # Examples + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// bodies[body].set_gravity_scale(0.0, true); // Zero-G (space) + /// bodies[body].set_gravity_scale(0.1, true); // Moon gravity + /// bodies[body].set_gravity_scale(2.0, true); // Extra heavy + /// ``` pub fn set_gravity_scale(&mut self, scale: Real, wake_up: bool) { if self.forces.gravity_scale != scale { if wake_up && self.activation.sleeping { @@ -695,20 +772,27 @@ impl RigidBody { } } - /// Put this rigid body to sleep. + /// Forces this body to sleep immediately (stop simulating it). /// - /// A sleeping body no longer moves and is no longer simulated by the physics engine unless - /// it is waken up. It can be woken manually with `self.wake_up` or automatically due to - /// external forces like contacts. + /// Sleeping bodies are excluded from physics simulation until disturbed. Use to manually + /// deactivate bodies you know won't move for a while. + /// + /// The body will auto-wake if: + /// - Hit by a moving object + /// - Connected via joint to a moving body + /// - Manually woken with `wake_up()` pub fn sleep(&mut self) { self.activation.sleep(); self.vels = RigidBodyVelocity::zero(); } - /// Wakes up this rigid body if it is sleeping. + /// Wakes up this body if it's sleeping, making it active in the simulation. /// - /// If `strong` is `true` then it is assured that the rigid-body will - /// remain awake during multiple subsequent timesteps. + /// # Parameters + /// * `strong` - If `true`, guarantees the body stays awake for multiple frames. + /// If `false`, it might sleep again immediately if conditions are met. + /// + /// Use after manually moving a sleeping body or to keep it active temporarily. pub fn wake_up(&mut self, strong: bool) { if self.activation.sleeping { self.changes.insert(RigidBodyChanges::SLEEP); @@ -726,28 +810,39 @@ impl RigidBody { self.activation.sleeping } - /// Is the velocity of this body not zero? + /// Returns `true` if the body has non-zero linear or angular velocity. + /// + /// Useful for checking if an object is actually moving vs sitting still. pub fn is_moving(&self) -> bool { !self.vels.linvel.is_zero() || !self.vels.angvel.is_zero() } - /// The linear and angular velocity of this rigid-body. + /// Returns both linear and angular velocity as a combined structure. + /// + /// Most users should use `linvel()` and `angvel()` separately instead. pub fn vels(&self) -> &RigidBodyVelocity { &self.vels } - /// The linear velocity of this rigid-body. + /// The current linear velocity (speed and direction of movement). + /// + /// This is how fast the body is moving in units per second. Use with [`set_linvel()`](Self::set_linvel) + /// to directly control the body's movement speed. pub fn linvel(&self) -> &Vector { &self.vels.linvel } - /// The angular velocity of this rigid-body. + /// The current angular velocity (rotation speed) in 2D. + /// + /// Returns radians per second. Positive = counter-clockwise, negative = clockwise. #[cfg(feature = "dim2")] pub fn angvel(&self) -> Real { self.vels.angvel } - /// The angular velocity of this rigid-body. + /// The current angular velocity (rotation speed) in 3D. + /// + /// Returns a vector in radians per second around each axis (X, Y, Z). #[cfg(feature = "dim3")] pub fn angvel(&self) -> &Vector { &self.vels.angvel @@ -762,10 +857,24 @@ impl RigidBody { self.set_angvel(vels.angvel, wake_up); } - /// The linear velocity of this rigid-body. + /// Sets how fast this body is moving (linear velocity). /// - /// If `wake_up` is `true` then the rigid-body will be woken up if it was - /// put to sleep because it did not move for a while. + /// This directly sets the body's velocity without applying forces. Use for: + /// - Player character movement + /// - Kinematic object control + /// - Instantly changing an object's speed + /// + /// For physics-based movement, consider using [`apply_impulse()`](Self::apply_impulse) or + /// [`add_force()`](Self::add_force) instead for more realistic behavior. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Make the body move to the right at 5 units/second + /// bodies[body].set_linvel(vector![5.0, 0.0, 0.0], true); + /// ``` pub fn set_linvel(&mut self, linvel: Vector, wake_up: bool) { if self.vels.linvel != linvel { match self.body_type { @@ -818,19 +927,36 @@ impl RigidBody { } } - /// The world-space position of this rigid-body. + /// The current position (translation + rotation) of this rigid body in world space. + /// + /// Returns an `Isometry` which combines both translation and rotation. + /// For just the position vector, use [`translation()`](Self::translation) instead. #[inline] pub fn position(&self) -> &Isometry { &self.pos.position } - /// The translational part of this rigid-body's position. + /// The current position vector of this rigid body (world coordinates). + /// + /// This is just the XYZ location, without rotation. For the full pose (position + rotation), + /// use [`position()`](Self::position). #[inline] pub fn translation(&self) -> &Vector { &self.pos.position.translation.vector } - /// Sets the translational part of this rigid-body's position. + /// Teleports this rigid body to a new position (world coordinates). + /// + /// ⚠️ **Warning**: This instantly moves the body, ignoring physics! The body will "teleport" + /// without checking for collisions in between. Use this for: + /// - Respawning objects + /// - Level transitions + /// - Resetting positions + /// + /// For smooth physics-based movement, use velocities or forces instead. + /// + /// # Parameters + /// * `wake_up` - If `true`, prevents the body from immediately going back to sleep #[inline] pub fn set_translation(&mut self, translation: Vector, wake_up: bool) { if self.pos.position.translation.vector != translation @@ -850,13 +976,15 @@ impl RigidBody { } } - /// The rotational part of this rigid-body's position. + /// The current rotation/orientation of this rigid body. #[inline] pub fn rotation(&self) -> &Rotation { &self.pos.position.rotation } - /// Sets the rotational part of this rigid-body's position. + /// Instantly rotates this rigid body to a new orientation. + /// + /// ⚠️ **Warning**: This teleports the rotation, ignoring physics! See [`set_translation()`](Self::set_translation) for details. #[inline] pub fn set_rotation(&mut self, rotation: Rotation, wake_up: bool) { if self.pos.position.rotation != rotation || self.pos.next_position.rotation != rotation { @@ -874,15 +1002,12 @@ impl RigidBody { } } - /// Sets the position and `next_kinematic_position` of this rigid body. + /// Teleports this body to a new position and rotation (ignoring physics). /// - /// This will teleport the rigid-body to the specified position/orientation, - /// completely ignoring any physics rule. If this body is kinematic, this will - /// also set the next kinematic position to the same value, effectively - /// resetting to zero the next interpolated velocity of the kinematic body. + /// ⚠️ **Warning**: Instantly moves the body without checking for collisions! + /// For position-based kinematic bodies, this also resets their interpolated velocity to zero. /// - /// If `wake_up` is `true` then the rigid-body will be woken up if it was - /// put to sleep because it did not move for a while. + /// Use for respawning, level transitions, or resetting positions. pub fn set_position(&mut self, pos: Isometry, wake_up: bool) { if self.pos.position != pos || self.pos.next_position != pos { self.changes.insert(RigidBodyChanges::POSITION); @@ -899,7 +1024,10 @@ impl RigidBody { } } - /// If this rigid body is kinematic, sets its future orientation after the next timestep integration. + /// For position-based kinematic bodies: sets where the body should rotate to by next frame. + /// + /// Only works for `KinematicPositionBased` bodies. Rapier computes the angular velocity + /// needed to reach this rotation smoothly. pub fn set_next_kinematic_rotation(&mut self, rotation: Rotation) { if self.is_kinematic() { self.pos.next_position.rotation = rotation; @@ -910,7 +1038,10 @@ impl RigidBody { } } - /// If this rigid body is kinematic, sets its future translation after the next timestep integration. + /// For position-based kinematic bodies: sets where the body should move to by next frame. + /// + /// Only works for `KinematicPositionBased` bodies. Rapier computes the velocity + /// needed to reach this position smoothly. pub fn set_next_kinematic_translation(&mut self, translation: Vector) { if self.is_kinematic() { self.pos.next_position.translation.vector = translation; @@ -921,8 +1052,9 @@ impl RigidBody { } } - /// If this rigid body is kinematic, sets its future position (translation and orientation) after - /// the next timestep integration. + /// For position-based kinematic bodies: sets the target pose (position + rotation) for next frame. + /// + /// Only works for `KinematicPositionBased` bodies. Combines translation and rotation control. pub fn set_next_kinematic_position(&mut self, pos: Isometry) { if self.is_kinematic() { self.pos.next_position = pos; @@ -952,19 +1084,19 @@ impl RigidBody { ) } - /// Predicts the next position of this rigid-body, by integrating its velocity and forces - /// by a time of `dt`. + /// Calculates where this body will be after `dt` seconds, considering current velocity AND forces. + /// + /// Useful for predicting future positions or implementing custom integration. + /// Accounts for gravity and applied forces. pub fn predict_position_using_velocity_and_forces(&self, dt: Real) -> Isometry { self.pos .integrate_forces_and_velocities(dt, &self.forces, &self.vels, &self.mprops) } - /// Predicts the next position of this rigid-body, by integrating only its velocity - /// by a time of `dt`. + /// Calculates where this body will be after `dt` seconds, considering only current velocity (not forces). /// - /// The forces that were applied to this rigid-body since the last physics step will - /// be ignored by this function. Use [`Self::predict_position_using_velocity_and_forces`] - /// instead to take forces into account. + /// Like `predict_position_using_velocity_and_forces()` but ignores applied forces. + /// Useful when you only care about inertial motion without acceleration. pub fn predict_position_using_velocity(&self, dt: Real) -> Isometry { self.vels .integrate(dt, &self.pos.position, &self.mprops.local_mprops.local_com) @@ -978,7 +1110,10 @@ impl RigidBody { /// ## Applying forces and torques impl RigidBody { - /// Resets to zero all the constant (linear) forces manually applied to this rigid-body. + /// Clears all forces that were added with `add_force()`. + /// + /// Forces are automatically cleared each physics step, so you rarely need this. + /// Useful if you want to cancel forces mid-frame. pub fn reset_forces(&mut self, wake_up: bool) { if !self.forces.user_force.is_zero() { self.forces.user_force = na::zero(); @@ -989,7 +1124,9 @@ impl RigidBody { } } - /// Resets to zero all the constant torques manually applied to this rigid-body. + /// Clears all torques that were added with `add_torque()`. + /// + /// Torques are automatically cleared each physics step. Rarely needed. pub fn reset_torques(&mut self, wake_up: bool) { if !self.forces.user_torque.is_zero() { self.forces.user_torque = na::zero(); @@ -1000,9 +1137,28 @@ impl RigidBody { } } - /// Adds to this rigid-body a constant force applied at its center-of-mass.ç + /// Applies a continuous force to this body (like thrust, wind, or magnets). /// - /// This does nothing on non-dynamic bodies. + /// Unlike [`apply_impulse()`](Self::apply_impulse) which is instant, forces are applied + /// continuously over time and accumulate until the next physics step. Use for: + /// - Rocket/jet thrust + /// - Wind or water currents + /// - Magnetic/gravity fields + /// - Continuous pushing/pulling + /// + /// Forces are automatically cleared after each physics step, so you typically call this + /// every frame if you want continuous force application. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Apply thrust every frame + /// bodies[body].add_force(vector![0.0, 100.0, 0.0], true); + /// ``` + /// + /// Only affects dynamic bodies (does nothing for kinematic/fixed bodies). pub fn add_force(&mut self, force: Vector, wake_up: bool) { if !force.is_zero() && self.body_type == RigidBodyType::Dynamic { self.forces.user_force += force; @@ -1013,9 +1169,12 @@ impl RigidBody { } } - /// Adds to this rigid-body a constant torque at its center-of-mass. + /// Applies a continuous rotational force (torque) to spin this body. /// - /// This does nothing on non-dynamic bodies. + /// Like `add_force()` but for rotation. Accumulates until next physics step. + /// In 2D: positive = counter-clockwise, negative = clockwise. + /// + /// Only affects dynamic bodies. #[cfg(feature = "dim2")] pub fn add_torque(&mut self, torque: Real, wake_up: bool) { if !torque.is_zero() && self.body_type == RigidBodyType::Dynamic { @@ -1027,9 +1186,12 @@ impl RigidBody { } } - /// Adds to this rigid-body a constant torque at its center-of-mass. + /// Applies a continuous rotational force (torque) to spin this body. /// - /// This does nothing on non-dynamic bodies. + /// Like `add_force()` but for rotation. In 3D, the torque vector direction + /// determines the rotation axis (right-hand rule). + /// + /// Only affects dynamic bodies. #[cfg(feature = "dim3")] pub fn add_torque(&mut self, torque: Vector, wake_up: bool) { if !torque.is_zero() && self.body_type == RigidBodyType::Dynamic { @@ -1041,9 +1203,18 @@ impl RigidBody { } } - /// Adds to this rigid-body a constant force at the given world-space point of this rigid-body. + /// Applies force at a specific point on the body (creates both force and torque). /// - /// This does nothing on non-dynamic bodies. + /// When you push an object off-center, it both moves AND spins. This method handles both effects. + /// The force creates linear acceleration, and the offset from center-of-mass creates torque. + /// + /// Use for: Forces applied at contact points, explosions at specific locations, pushing objects. + /// + /// # Parameters + /// * `force` - The force vector to apply + /// * `point` - Where to apply the force (world coordinates) + /// + /// Only affects dynamic bodies. pub fn add_force_at_point(&mut self, force: Vector, point: Point, wake_up: bool) { if !force.is_zero() && self.body_type == RigidBodyType::Dynamic { self.forces.user_force += force; @@ -1058,9 +1229,29 @@ impl RigidBody { /// ## Applying impulses and angular impulses impl RigidBody { - /// Applies an impulse at the center-of-mass of this rigid-body. - /// The impulse is applied right away, changing the linear velocity. - /// This does nothing on non-dynamic bodies. + /// Instantly changes the velocity by applying an impulse (like a kick or explosion). + /// + /// An impulse is an instant change in momentum. Think of it as a "one-time push" that + /// immediately affects velocity. Use for: + /// - Jumping (apply upward impulse) + /// - Explosions pushing objects away + /// - Getting hit by something + /// - Launching projectiles + /// + /// The effect depends on the body's mass - heavier objects will be affected less by the same impulse. + /// + /// **For continuous forces** (like rocket thrust or wind), use [`add_force()`](Self::add_force) instead. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Make a character jump + /// bodies[body].apply_impulse(vector![0.0, 300.0, 0.0], true); + /// ``` + /// + /// Only affects dynamic bodies (does nothing for kinematic/fixed bodies). #[profiling::function] pub fn apply_impulse(&mut self, impulse: Vector, wake_up: bool) { if !impulse.is_zero() && self.body_type == RigidBodyType::Dynamic { @@ -1087,9 +1278,10 @@ impl RigidBody { } } - /// Applies an angular impulse at the center-of-mass of this rigid-body. - /// The impulse is applied right away, changing the angular velocity. - /// This does nothing on non-dynamic bodies. + /// Instantly changes rotation speed by applying angular impulse (like a sudden spin). + /// + /// In 3D, the impulse vector direction determines the spin axis (right-hand rule). + /// Like `apply_impulse()` but for rotation. Only affects dynamic bodies. #[cfg(feature = "dim3")] #[profiling::function] pub fn apply_torque_impulse(&mut self, torque_impulse: Vector, wake_up: bool) { @@ -1102,9 +1294,26 @@ impl RigidBody { } } - /// Applies an impulse at the given world-space point of this rigid-body. - /// The impulse is applied right away, changing the linear and/or angular velocities. - /// This does nothing on non-dynamic bodies. + /// Applies impulse at a specific point on the body (creates both linear and angular effects). + /// + /// Like `add_force_at_point()` but instant instead of continuous. When you hit an object + /// off-center, it both flies away AND spins - this method handles both. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Hit the top-left corner of a box + /// bodies[body].apply_impulse_at_point( + /// vector![100.0, 0.0, 0.0], + /// point![-0.5, 0.5, 0.0], // Top-left of a 1x1 box + /// true + /// ); + /// // Box will move right AND spin + /// ``` + /// + /// Only affects dynamic bodies. pub fn apply_impulse_at_point( &mut self, impulse: Vector, @@ -1116,9 +1325,10 @@ impl RigidBody { self.apply_torque_impulse(torque_impulse, wake_up); } - /// Retrieves the constant force(s) that the user has added to the body. + /// Returns the total force currently queued to be applied this frame. /// - /// Returns zero if the rigid-body isn’t dynamic. + /// This is the sum of all `add_force()` calls since the last physics step. + /// Returns zero for non-dynamic bodies. pub fn user_force(&self) -> Vector { if self.body_type == RigidBodyType::Dynamic { self.forces.user_force @@ -1127,9 +1337,10 @@ impl RigidBody { } } - /// Retrieves the constant torque(s) that the user has added to the body. + /// Returns the total torque currently queued to be applied this frame. /// - /// Returns zero if the rigid-body isn’t dynamic. + /// This is the sum of all `add_torque()` calls since the last physics step. + /// Returns zero for non-dynamic bodies. pub fn user_torque(&self) -> AngVector { if self.body_type == RigidBodyType::Dynamic { self.forces.user_torque @@ -1138,18 +1349,21 @@ impl RigidBody { } } - /// Are gyroscopic forces enabled for rigid-bodies? + /// Checks if gyroscopic forces are enabled (3D only). + /// + /// Gyroscopic forces cause spinning objects to resist changes in rotation axis + /// (like how spinning tops stay upright). Adds slight CPU cost. #[cfg(feature = "dim3")] pub fn gyroscopic_forces_enabled(&self) -> bool { self.forces.gyroscopic_forces_enabled } - /// Enables or disables gyroscopic forces for this rigid-body. + /// Enables/disables gyroscopic forces for more realistic spinning behavior. /// - /// Enabling gyroscopic forces allows more realistic behaviors like gyroscopic precession, - /// but result in a slight performance overhead. + /// When enabled, rapidly spinning objects resist rotation axis changes (like gyroscopes). + /// Examples: spinning tops, flywheels, rotating spacecraft. /// - /// Disabled by default. + /// **Default**: Disabled (costs performance, rarely needed in games). #[cfg(feature = "dim3")] pub fn enable_gyroscopic_forces(&mut self, enabled: bool) { self.forces.gyroscopic_forces_enabled = enabled; @@ -1157,17 +1371,27 @@ impl RigidBody { } impl RigidBody { - /// The velocity of the given world-space point on this rigid-body. + /// Calculates the velocity at a specific point on this body. + /// + /// Due to rotation, different points on a rigid body move at different speeds. + /// This computes the linear velocity at any world-space point. + /// + /// Useful for: impact calculations, particle effects, sound volume based on impact speed. pub fn velocity_at_point(&self, point: &Point) -> Vector { self.vels.velocity_at_point(point, &self.mprops.world_com) } - /// The kinetic energy of this body. + /// Calculates the kinetic energy of this body (energy from motion). + /// + /// Returns `0.5 * mass * velocity² + 0.5 * inertia * angular_velocity²` + /// Useful for physics-based gameplay (energy tracking, damage based on impact energy). pub fn kinetic_energy(&self) -> Real { self.vels.kinetic_energy(&self.mprops) } - /// The potential energy of this body in a gravity field. + /// Calculates the gravitational potential energy of this body. + /// + /// Returns `mass * gravity * height`. Useful for energy conservation checks. pub fn gravitational_potential_energy(&self, dt: Real, gravity: Vector) -> Real { let world_com = self .mprops @@ -1206,7 +1430,24 @@ impl RigidBody { } } -/// A builder for rigid-bodies. +/// A builder for creating rigid bodies with custom properties. +/// +/// This builder lets you configure all properties of a rigid body before adding it to your world. +/// Start with one of the type constructors ([`dynamic()`](Self::dynamic), [`fixed()`](Self::fixed), +/// [`kinematic_position_based()`](Self::kinematic_position_based), or +/// [`kinematic_velocity_based()`](Self::kinematic_velocity_based)), then chain property setters, +/// and finally call [`build()`](Self::build). +/// +/// # Example +/// +/// ``` +/// # use rapier3d::prelude::*; +/// let body = RigidBodyBuilder::dynamic() +/// .translation(vector![0.0, 5.0, 0.0]) // Start 5 units above ground +/// .linvel(vector![1.0, 0.0, 0.0]) // Initial velocity to the right +/// .can_sleep(false) // Keep always active +/// .build(); +/// ``` #[derive(Clone, Debug, PartialEq)] #[must_use = "Builder functions return the updated builder"] pub struct RigidBodyBuilder { @@ -1308,22 +1549,50 @@ impl RigidBodyBuilder { Self::kinematic_position_based() } - /// Initializes the builder of a new fixed rigid body. + /// Creates a builder for a **fixed** (static) rigid body. + /// + /// Fixed bodies never move and are not affected by any forces. Use them for: + /// - Walls, floors, and ceilings + /// - Static terrain and level geometry + /// - Any object that should never move in your simulation + /// + /// Fixed bodies have infinite mass and never sleep. pub fn fixed() -> Self { Self::new(RigidBodyType::Fixed) } - /// Initializes the builder of a new velocity-based kinematic rigid body. + /// Creates a builder for a **velocity-based kinematic** rigid body. + /// + /// Kinematic bodies are moved by directly setting their velocity (not by applying forces). + /// They can push dynamic bodies but are not affected by them. Use for: + /// - Moving platforms and elevators + /// - Doors and sliding panels + /// - Any object you want to control directly while still affecting other physics objects + /// + /// Set velocity with [`RigidBody::set_linvel`] and [`RigidBody::set_angvel`]. pub fn kinematic_velocity_based() -> Self { Self::new(RigidBodyType::KinematicVelocityBased) } - /// Initializes the builder of a new position-based kinematic rigid body. + /// Creates a builder for a **position-based kinematic** rigid body. + /// + /// Similar to velocity-based kinematic, but you control it by setting its next position + /// directly rather than setting velocity. Rapier will automatically compute the velocity + /// needed to reach that position. Use for objects animated by external systems. pub fn kinematic_position_based() -> Self { Self::new(RigidBodyType::KinematicPositionBased) } - /// Initializes the builder of a new dynamic rigid body. + /// Creates a builder for a **dynamic** rigid body. + /// + /// Dynamic bodies are fully simulated - they respond to gravity, forces, collisions, and + /// constraints. This is the most common type for interactive objects. Use for: + /// - Physics objects that should fall and bounce (boxes, spheres, ragdolls) + /// - Projectiles and debris + /// - Vehicles and moving characters (when not using kinematic control) + /// - Any object that should behave realistically under physics + /// + /// Dynamic bodies can sleep (become inactive) when at rest to save performance. pub fn dynamic() -> Self { Self::new(RigidBodyType::Dynamic) } @@ -1343,19 +1612,41 @@ impl RigidBodyBuilder { self } - /// Sets the dominance group of this rigid-body. + /// Sets the dominance group (advanced collision priority system). + /// + /// Higher dominance groups can push lower ones but not vice versa. + /// Rarely needed - most games don't use this. Default is 0 (all equal priority). + /// + /// Use case: Heavy objects that should always push lighter ones in contacts. pub fn dominance_group(mut self, group: i8) -> Self { self.dominance_group = group; self } - /// Sets the initial translation of the rigid-body to be created. + /// Sets the initial position (XYZ coordinates) where this body will be created. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// let body = RigidBodyBuilder::dynamic() + /// .translation(vector![10.0, 5.0, -3.0]) + /// .build(); + /// ``` pub fn translation(mut self, translation: Vector) -> Self { self.position.translation.vector = translation; self } - /// Sets the initial orientation of the rigid-body to be created. + /// Sets the initial rotation/orientation of the body to be created. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // Rotate 45 degrees around Y axis (in 3D) + /// let body = RigidBodyBuilder::dynamic() + /// .rotation(vector![0.0, std::f32::consts::PI / 4.0, 0.0]) + /// .build(); + /// ``` pub fn rotation(mut self, angle: AngVector) -> Self { self.position.rotation = Rotation::new(angle); self @@ -1418,19 +1709,32 @@ impl RigidBodyBuilder { self } - /// Sets the axes along which this rigid-body cannot translate or rotate. + /// Sets which movement axes are locked (cannot move/rotate). + /// + /// See [`LockedAxes`] for examples of constraining movement to specific directions. pub fn locked_axes(mut self, locked_axes: LockedAxes) -> Self { self.mprops_flags = locked_axes; self } - /// Prevents this rigid-body from translating because of forces. + /// Prevents all translational movement (body can still rotate). + /// + /// Use for turrets, spinning objects fixed in place, etc. pub fn lock_translations(mut self) -> Self { self.mprops_flags.set(LockedAxes::TRANSLATION_LOCKED, true); self } - /// Only allow translations of this rigid-body around specific coordinate axes. + /// Locks translation along specific axes. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // 2D game in 3D: lock Z movement + /// let body = RigidBodyBuilder::dynamic() + /// .enabled_translations(true, true, false) // X, Y free; Z locked + /// .build(); + /// ``` pub fn enabled_translations( mut self, allow_translations_x: bool, @@ -1463,7 +1767,9 @@ impl RigidBodyBuilder { ) } - /// Prevents this rigid-body from rotating because of forces. + /// Prevents all rotational movement (body can still translate). + /// + /// Use for characters that shouldn't tip over, objects that should only slide, etc. pub fn lock_rotations(mut self) -> Self { self.mprops_flags.set(LockedAxes::ROTATION_LOCKED_X, true); self.mprops_flags.set(LockedAxes::ROTATION_LOCKED_Y, true); @@ -1500,45 +1806,68 @@ impl RigidBodyBuilder { self.enabled_rotations(allow_rotations_x, allow_rotations_y, allow_rotations_z) } - /// Sets the damping factor for the linear part of the rigid-body motion. + /// Sets linear damping (how quickly linear velocity decreases over time). /// - /// The higher the linear damping factor is, the more quickly the rigid-body - /// will slow-down its translational movement. + /// Models air resistance, drag, etc. Higher values = faster slowdown. + /// - `0.0` = no drag (space) + /// - `0.1` = light drag (air) + /// - `1.0+` = heavy drag (underwater) pub fn linear_damping(mut self, factor: Real) -> Self { self.linear_damping = factor; self } - /// Sets the damping factor for the angular part of the rigid-body motion. + /// Sets angular damping (how quickly rotation speed decreases over time). /// - /// The higher the angular damping factor is, the more quickly the rigid-body - /// will slow-down its rotational movement. + /// Models rotational drag. Higher values = spinning stops faster. pub fn angular_damping(mut self, factor: Real) -> Self { self.angular_damping = factor; self } - /// Sets the initial linear velocity of the rigid-body to be created. + /// Sets the initial linear velocity (movement speed and direction). + /// + /// The body will start moving at this velocity when created. pub fn linvel(mut self, linvel: Vector) -> Self { self.linvel = linvel; self } - /// Sets the initial angular velocity of the rigid-body to be created. + /// Sets the initial angular velocity (rotation speed). + /// + /// The body will start rotating at this speed when created. pub fn angvel(mut self, angvel: AngVector) -> Self { self.angvel = angvel; self } - /// Sets whether the rigid-body to be created can sleep if it reaches a dynamic equilibrium. + /// Sets whether this body can go to sleep when at rest (default: `true`). + /// + /// Sleeping bodies are excluded from simulation until disturbed, saving CPU. + /// Set to `false` if you need the body always active (e.g., for continuous queries). pub fn can_sleep(mut self, can_sleep: bool) -> Self { self.can_sleep = can_sleep; self } - /// Sets whether Continuous Collision-Detection is enabled for this rigid-body. + /// Enables Continuous Collision Detection to prevent fast objects from tunneling. /// - /// CCD prevents tunneling, but may still allow limited interpenetration of colliders. + /// CCD prevents "tunneling" where fast-moving objects pass through thin walls. + /// Enable this for: + /// - Bullets and fast projectiles + /// - Small objects moving at high speed + /// - Objects that must never pass through walls + /// + /// **Trade-off**: More accurate but more expensive. Most objects don't need CCD. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // Bullet that should never tunnel through walls + /// let bullet = RigidBodyBuilder::dynamic() + /// .ccd_enabled(true) + /// .build(); + /// ``` pub fn ccd_enabled(mut self, enabled: bool) -> Self { self.ccd_enabled = enabled; self diff --git a/src/dynamics/rigid_body_components.rs b/src/dynamics/rigid_body_components.rs index 579feb2..7501c25 100644 --- a/src/dynamics/rigid_body_components.rs +++ b/src/dynamics/rigid_body_components.rs @@ -46,25 +46,35 @@ pub type BodyStatus = RigidBodyType; #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] -/// The status of a body, governing the way it is affected by external forces. +/// The type of a rigid body, determining how it responds to forces and movement. pub enum RigidBodyType { - /// A `RigidBodyType::Dynamic` body can be affected by all external forces. + /// Fully simulated - responds to forces, gravity, and collisions. + /// + /// Use for: Falling objects, projectiles, physics-based characters, anything that should + /// behave realistically under physics simulation. Dynamic = 0, - /// A `RigidBodyType::Fixed` body cannot be affected by external forces. + + /// Never moves - has infinite mass and is unaffected by anything. + /// + /// Use for: Static level geometry, walls, floors, terrain, buildings. Fixed = 1, - /// A `RigidBodyType::KinematicPositionBased` body cannot be affected by any external forces but can be controlled - /// by the user at the position level while keeping realistic one-way interaction with dynamic bodies. + + /// Controlled by setting next position - pushes but isn't pushed. /// - /// One-way interaction means that a kinematic body can push a dynamic body, but a kinematic body - /// cannot be pushed by anything. In other words, the trajectory of a kinematic body can only be - /// modified by the user and is independent from any contact or joint it is involved in. + /// You control this by setting where it should be next frame. Rapier computes the + /// velocity needed to get there. The body can push dynamic bodies but nothing can + /// push it back (one-way interaction). + /// + /// Use for: Animated platforms, objects controlled by external animation systems. KinematicPositionBased = 2, - /// A `RigidBodyType::KinematicVelocityBased` body cannot be affected by any external forces but can be controlled - /// by the user at the velocity level while keeping realistic one-way interaction with dynamic bodies. + + /// Controlled by setting velocity - pushes but isn't pushed. /// - /// One-way interaction means that a kinematic body can push a dynamic body, but a kinematic body - /// cannot be pushed by anything. In other words, the trajectory of a kinematic body can only be - /// modified by the user and is independent from any contact or joint it is involved in. + /// You control this by setting its velocity directly. It moves predictably regardless + /// of what it hits. Can push dynamic bodies but nothing can push it back (one-way interaction). + /// + /// Use for: Moving platforms, elevators, doors, player-controlled characters (when you want + /// direct control rather than physics-based movement). KinematicVelocityBased = 3, // Semikinematic, // A kinematic that performs automatic CCD with the fixed environment to avoid traversing it? // Disabled, @@ -136,7 +146,7 @@ pub struct RigidBodyPosition { /// /// At the beginning of the timestep, and when the /// timestep is complete we must have position == next_position - /// except for kinematic bodies. + /// except for position-based kinematic bodies. /// /// The next_position is updated after the velocity and position /// resolution. Then it is either validated (ie. we set position := set_position) @@ -256,23 +266,50 @@ impl Default for AxesMask { bitflags::bitflags! { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug)] - /// Flags affecting the behavior of the constraints solver for a given contact manifold. + /// Flags that lock specific movement axes to prevent translation or rotation. + /// + /// Use this to constrain body movement to specific directions/axes. Common uses: + /// - **2D games in 3D**: Lock Z translation and X/Y rotation to keep everything in the XY plane + /// - **Upright characters**: Lock rotations to prevent tipping over + /// - **Sliding objects**: Lock rotation while allowing translation + /// - **Spinning objects**: Lock translation while allowing rotation + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let body = bodies.get_mut(body_handle).unwrap(); + /// // Character that can't tip over (rotation locked, but can move) + /// body.set_locked_axes(LockedAxes::ROTATION_LOCKED, true); + /// + /// // Object that slides but doesn't rotate + /// body.set_locked_axes(LockedAxes::ROTATION_LOCKED, true); + /// + /// // 2D game in 3D engine (lock Z movement and X/Y rotation) + /// body.set_locked_axes( + /// LockedAxes::TRANSLATION_LOCKED_Z | + /// LockedAxes::ROTATION_LOCKED_X | + /// LockedAxes::ROTATION_LOCKED_Y, + /// true + /// ); + /// ``` pub struct LockedAxes: u8 { - /// Flag indicating that the rigid-body cannot translate along the `X` axis. + /// Prevents movement along the X axis. const TRANSLATION_LOCKED_X = 1 << 0; - /// Flag indicating that the rigid-body cannot translate along the `Y` axis. + /// Prevents movement along the Y axis. const TRANSLATION_LOCKED_Y = 1 << 1; - /// Flag indicating that the rigid-body cannot translate along the `Z` axis. + /// Prevents movement along the Z axis. const TRANSLATION_LOCKED_Z = 1 << 2; - /// Flag indicating that the rigid-body cannot translate along any direction. + /// Prevents all translational movement. const TRANSLATION_LOCKED = Self::TRANSLATION_LOCKED_X.bits() | Self::TRANSLATION_LOCKED_Y.bits() | Self::TRANSLATION_LOCKED_Z.bits(); - /// Flag indicating that the rigid-body cannot rotate along the `X` axis. + /// Prevents rotation around the X axis. const ROTATION_LOCKED_X = 1 << 3; - /// Flag indicating that the rigid-body cannot rotate along the `Y` axis. + /// Prevents rotation around the Y axis. const ROTATION_LOCKED_Y = 1 << 4; - /// Flag indicating that the rigid-body cannot rotate along the `Z` axis. + /// Prevents rotation around the Z axis. const ROTATION_LOCKED_Z = 1 << 5; - /// Combination of flags indicating that the rigid-body cannot rotate along any axis. + /// Prevents all rotational movement. const ROTATION_LOCKED = Self::ROTATION_LOCKED_X.bits() | Self::ROTATION_LOCKED_Y.bits() | Self::ROTATION_LOCKED_Z.bits(); } } @@ -1105,25 +1142,50 @@ impl RigidBodyDominance { } } -/// The rb_activation status of a body. +/// Controls when a body goes to sleep (becomes inactive to save CPU). /// -/// This controls whether a body is sleeping or not. -/// If the threshold is negative, the body never sleeps. +/// ## Sleeping System +/// +/// Bodies automatically sleep when they're at rest, dramatically improving performance +/// in scenes with many inactive objects. Sleeping bodies are: +/// - Excluded from simulation (no collision detection, no velocity integration) +/// - Automatically woken when disturbed (hit by moving object, connected via joint) +/// - Woken manually with `body.wake_up()` or `islands.wake_up()` +/// +/// ## How sleeping works +/// +/// A body sleeps after its linear AND angular velocities stay below thresholds for +/// `time_until_sleep` seconds (default: 2 seconds). Set thresholds to negative to disable sleeping. +/// +/// ## When to disable sleeping +/// +/// Most bodies should sleep! Only disable if the body needs to stay active despite being still: +/// - Bodies you frequently query for raycasts/contacts +/// - Bodies with time-based behaviors while stationary +/// +/// Use `RigidBodyBuilder::can_sleep(false)` or `RigidBodyActivation::cannot_sleep()`. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct RigidBodyActivation { - /// The threshold linear velocity below which the body can fall asleep. + /// Linear velocity threshold for sleeping (scaled by `length_unit`). /// - /// The value is "normalized", i.e., the actual threshold applied by the physics engine - /// is equal to this value multiplied by [`IntegrationParameters::length_unit`]. + /// If negative, body never sleeps. Default: 0.4 (in length units/second). pub normalized_linear_threshold: Real, - /// The angular linear velocity below which the body can fall asleep. + + /// Angular velocity threshold for sleeping (radians/second). + /// + /// If negative, body never sleeps. Default: 0.5 rad/s. pub angular_threshold: Real, - /// The amount of time the rigid-body must remain below the thresholds to be put to sleep. + + /// How long the body must be still before sleeping (seconds). + /// + /// Default: 2.0 seconds. Must be below both velocity thresholds for this duration. pub time_until_sleep: Real, - /// Since how much time can this body sleep? + + /// Internal timer tracking how long body has been still. pub time_since_can_sleep: Real, - /// Is this body sleeping? + + /// Is this body currently sleeping? pub sleeping: bool, } diff --git a/src/dynamics/rigid_body_set.rs b/src/dynamics/rigid_body_set.rs index cc233af..2b54a3b 100644 --- a/src/dynamics/rigid_body_set.rs +++ b/src/dynamics/rigid_body_set.rs @@ -6,6 +6,9 @@ use crate::dynamics::{ use crate::geometry::ColliderSet; use std::ops::{Index, IndexMut}; +#[cfg(doc)] +use crate::pipeline::PhysicsPipeline; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] /// A pair of rigid body handles. @@ -39,7 +42,31 @@ impl HasModifiedFlag for RigidBody { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone, Default, Debug)] -/// A set of rigid bodies that can be handled by a physics pipeline. +/// The collection that stores all rigid bodies in your physics world. +/// +/// This is where you add, remove, and access all your physics objects. Think of it as +/// a "database" of all rigid bodies, where each body gets a unique handle for fast lookup. +/// +/// # Why use handles? +/// +/// Instead of storing bodies directly, you get back a [`RigidBodyHandle`] when inserting. +/// This handle is lightweight (just an index + generation) and remains valid even if other +/// bodies are removed, protecting you from use-after-free bugs. +/// +/// # Example +/// +/// ``` +/// # use rapier3d::prelude::*; +/// let mut bodies = RigidBodySet::new(); +/// +/// // Add a dynamic body +/// let handle = bodies.insert(RigidBodyBuilder::dynamic()); +/// +/// // Access it later +/// if let Some(body) = bodies.get_mut(handle) { +/// body.apply_impulse(vector![0.0, 10.0, 0.0], true); +/// } +/// ``` pub struct RigidBodySet { // NOTE: the pub(crate) are needed by the broad phase // to avoid borrowing issues. It is also needed for @@ -52,7 +79,10 @@ pub struct RigidBodySet { } impl RigidBodySet { - /// Create a new empty set of rigid bodies. + /// Creates a new empty collection of rigid bodies. + /// + /// Call this once when setting up your physics world. The collection will + /// automatically grow as you add more bodies. pub fn new() -> Self { RigidBodySet { bodies: Arena::new(), @@ -61,7 +91,17 @@ impl RigidBodySet { } } - /// Create a new set of rigid bodies, with an initial capacity. + /// Creates a new collection with pre-allocated space for the given number of bodies. + /// + /// Use this if you know approximately how many bodies you'll need, to avoid + /// multiple reallocations as the collection grows. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // You know you'll have ~1000 bodies + /// let mut bodies = RigidBodySet::with_capacity(1000); + /// ``` pub fn with_capacity(capacity: usize) -> Self { RigidBodySet { bodies: Arena::with_capacity(capacity), @@ -74,22 +114,39 @@ impl RigidBodySet { std::mem::take(&mut self.modified_bodies) } - /// The number of rigid bodies on this set. + /// Returns how many rigid bodies are currently in this collection. pub fn len(&self) -> usize { self.bodies.len() } - /// `true` if there are no rigid bodies in this set. + /// Returns `true` if there are no rigid bodies in this collection. pub fn is_empty(&self) -> bool { self.bodies.is_empty() } - /// Is the given body handle valid? + /// Checks if the given handle points to a valid rigid body that still exists. + /// + /// Returns `false` if the body was removed or the handle is invalid. pub fn contains(&self, handle: RigidBodyHandle) -> bool { self.bodies.contains(handle.0) } - /// Insert a rigid body into this set and retrieve its handle. + /// Adds a rigid body to the world and returns its handle for future access. + /// + /// The handle is how you'll refer to this body later (to move it, apply forces, etc.). + /// Keep the handle somewhere accessible - you can't get the body back without it! + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// let handle = bodies.insert( + /// RigidBodyBuilder::dynamic() + /// .translation(vector![0.0, 5.0, 0.0]) + /// .build() + /// ); + /// // Store `handle` to access this body later + /// ``` pub fn insert(&mut self, rb: impl Into) -> RigidBodyHandle { let mut rb = rb.into(); // Make sure the internal links are reset, they may not be @@ -105,7 +162,41 @@ impl RigidBodySet { handle } - /// Removes a rigid-body, and all its attached colliders and impulse_joints, from these sets. + /// Removes a rigid body from the world along with all its attached colliders and joints. + /// + /// This is a complete cleanup operation that removes: + /// - The rigid body itself + /// - All colliders attached to it (if `remove_attached_colliders` is `true`) + /// - All joints connected to this body + /// + /// Returns the removed body if it existed, or `None` if the handle was invalid. + /// + /// # Parameters + /// + /// * `remove_attached_colliders` - If `true`, removes all colliders attached to this body. + /// If `false`, the colliders are detached and become independent. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut islands = IslandManager::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let mut impulse_joints = ImpulseJointSet::new(); + /// # let mut multibody_joints = MultibodyJointSet::new(); + /// # let handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Remove a body and everything attached to it + /// if let Some(body) = bodies.remove( + /// handle, + /// &mut islands, + /// &mut colliders, + /// &mut impulse_joints, + /// &mut multibody_joints, + /// true // Remove colliders too + /// ) { + /// println!("Removed body at {:?}", body.translation()); + /// } + /// ``` #[profiling::function] pub fn remove( &mut self, @@ -146,30 +237,27 @@ impl RigidBodySet { Some(rb) } - /// Gets the rigid-body with the given handle without a known generation. + /// Gets a rigid body by its index without knowing the generation number. /// - /// This is useful when you know you want the rigid-body at position `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the rigid-body position `i` - /// are recycled between two insertion and a removal. + /// ⚠️ **Advanced/unsafe usage** - prefer [`get()`](Self::get) instead! /// - /// Using this is discouraged in favor of `self.get(handle)` which does not - /// suffer form the ABA problem. + /// This bypasses the generation check that normally protects against the ABA problem + /// (where an index gets reused after removal). Only use this if you're certain the + /// body at this index is the one you expect. + /// + /// Returns both the body and its current handle (with the correct generation). pub fn get_unknown_gen(&self, i: u32) -> Option<(&RigidBody, RigidBodyHandle)> { self.bodies .get_unknown_gen(i) .map(|(b, h)| (b, RigidBodyHandle(h))) } - /// Gets a mutable reference to the rigid-body with the given handle without a known generation. + /// Gets a mutable reference to a rigid body by its index without knowing the generation. /// - /// This is useful when you know you want the rigid-body at position `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the rigid-body position `i` - /// are recycled between two insertion and a removal. + /// ⚠️ **Advanced/unsafe usage** - prefer [`get_mut()`](Self::get_mut) instead! /// - /// Using this is discouraged in favor of `self.get_mut(handle)` which does not - /// suffer form the ABA problem. + /// This bypasses the generation check. See [`get_unknown_gen()`](Self::get_unknown_gen) + /// for more details on when this is appropriate (rarely). #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_unknown_gen_mut(&mut self, i: u32) -> Option<(&mut RigidBody, RigidBodyHandle)> { let (rb, handle) = self.bodies.get_unknown_gen_mut(i)?; @@ -178,12 +266,31 @@ impl RigidBodySet { Some((rb, handle)) } - /// Gets the rigid-body with the given handle. + /// Gets a read-only reference to the rigid body with the given handle. + /// + /// Returns `None` if the handle is invalid or the body was removed. + /// + /// Use this to read body properties like position, velocity, mass, etc. pub fn get(&self, handle: RigidBodyHandle) -> Option<&RigidBody> { self.bodies.get(handle.0) } - /// Gets a mutable reference to the rigid-body with the given handle. + /// Gets a mutable reference to the rigid body with the given handle. + /// + /// Returns `None` if the handle is invalid or the body was removed. + /// + /// Use this to modify body properties, apply forces/impulses, change velocities, etc. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// if let Some(body) = bodies.get_mut(handle) { + /// body.set_linvel(vector![1.0, 0.0, 0.0], true); + /// body.apply_impulse(vector![0.0, 100.0, 0.0], true); + /// } + /// ``` #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { let result = self.bodies.get_mut(handle.0)?; @@ -191,9 +298,25 @@ impl RigidBodySet { Some(result) } - /// Gets a mutable reference to the two rigid-bodies with the given handles. + /// Gets mutable references to two different rigid bodies at once. /// - /// If `handle1 == handle2`, only the first returned value will be `Some`. + /// This is useful when you need to modify two bodies simultaneously (e.g., when manually + /// handling collisions between them). If both handles are the same, only the first value + /// will be `Some`. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let handle1 = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let handle2 = bodies.insert(RigidBodyBuilder::dynamic()); + /// let (body1, body2) = bodies.get_pair_mut(handle1, handle2); + /// if let (Some(b1), Some(b2)) = (body1, body2) { + /// // Can modify both bodies at once + /// b1.apply_impulse(vector![10.0, 0.0, 0.0], true); + /// b2.apply_impulse(vector![-10.0, 0.0, 0.0], true); + /// } + /// ``` #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_pair_mut( &mut self, @@ -233,12 +356,41 @@ impl RigidBodySet { Some(result) } - /// Iterates through all the rigid-bodies on this set. + /// Iterates over all rigid bodies in this collection. + /// + /// Each iteration yields a `(handle, &RigidBody)` pair. Use this to read properties + /// of all bodies (positions, velocities, etc.) without modifying them. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # bodies.insert(RigidBodyBuilder::dynamic()); + /// for (handle, body) in bodies.iter() { + /// println!("Body {:?} is at {:?}", handle, body.translation()); + /// } + /// ``` pub fn iter(&self) -> impl Iterator { self.bodies.iter().map(|(h, b)| (RigidBodyHandle(h), b)) } - /// Iterates mutably through all the rigid-bodies on this set. + /// Iterates over all rigid bodies with mutable access. + /// + /// Each iteration yields a `(handle, &mut RigidBody)` pair. Use this to modify + /// multiple bodies in one pass. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # bodies.insert(RigidBodyBuilder::dynamic()); + /// // Apply gravity manually to all dynamic bodies + /// for (handle, body) in bodies.iter_mut() { + /// if body.is_dynamic() { + /// body.add_force(vector![0.0, -9.81 * body.mass(), 0.0], true); + /// } + /// } + /// ``` #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn iter_mut(&mut self) -> impl Iterator { self.modified_bodies.clear(); @@ -251,13 +403,15 @@ impl RigidBodySet { }) } - /// Update colliders positions after rigid-bodies moved. + /// Updates the positions of all colliders attached to bodies that have moved. /// - /// When a rigid-body moves, the positions of the colliders attached to it need to be updated. - /// This update is generally automatically done at the beginning and the end of each simulation - /// step with `PhysicsPipeline::step`. If the positions need to be updated without running a - /// simulation step (for example when using the `QueryPipeline` alone), this method can be called - /// manually. + /// Normally you don't need to call this - it's automatically handled by [`PhysicsPipeline::step`]. + /// Only call this manually if you're: + /// - Moving bodies yourself outside of `step()` + /// - Using `QueryPipeline` for raycasts without running physics simulation + /// - Need collider positions to be immediately up-to-date for some custom logic + /// + /// This synchronizes collider world positions based on their parent bodies' positions. pub fn propagate_modified_body_positions_to_colliders(&self, colliders: &mut ColliderSet) { for body in self.modified_bodies.iter().filter_map(|h| self.get(*h)) { if body.changes.contains(RigidBodyChanges::POSITION) { diff --git a/src/geometry/broad_phase_bvh.rs b/src/geometry/broad_phase_bvh.rs index 691b33f..d888609 100644 --- a/src/geometry/broad_phase_bvh.rs +++ b/src/geometry/broad_phase_bvh.rs @@ -4,7 +4,16 @@ use crate::math::Real; use parry::partitioning::{Bvh, BvhWorkspace}; use parry::utils::hashmap::{Entry, HashMap}; -/// A broad-phase based on parry’s [`Bvh`] data structure. +/// The broad-phase collision detector that quickly filters out distant object pairs. +/// +/// The broad-phase is the "first pass" of collision detection. It uses a hierarchical +/// bounding volume tree (BVH) to quickly identify which collider pairs are close enough +/// to potentially collide, avoiding expensive narrow-phase checks for distant objects. +/// +/// Think of it as a "spatial index" that answers: "Which objects are near each other?" +/// +/// You typically don't interact with this directly - it's managed by [`PhysicsPipeline`](crate::pipeline::PhysicsPipeline). +/// However, you can use it to create a [`QueryPipeline`](crate::pipeline::QueryPipeline) for spatial queries. #[derive(Default, Clone)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct BroadPhaseBvh { diff --git a/src/geometry/collider.rs b/src/geometry/collider.rs index eeb1e11..9480fa4 100644 --- a/src/geometry/collider.rs +++ b/src/geometry/collider.rs @@ -17,9 +17,34 @@ use parry::transformation::voxelization::FillMode; #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone, Debug)] -/// A geometric entity that can be attached to a body so it can be affected by contacts and proximity queries. +/// The collision shape attached to a rigid body that defines what it can collide with. /// -/// To build a new collider, use the [`ColliderBuilder`] structure. +/// Think of a collider as the "hitbox" or "collision shape" for your physics object. While a +/// [`RigidBody`](crate::dynamics::RigidBody) handles the physics (mass, velocity, forces), +/// the collider defines what shape the object has for collision detection. +/// +/// ## Key concepts +/// +/// - **Shape**: The geometric form (box, sphere, capsule, mesh, etc.) +/// - **Material**: Physical properties like friction (slipperiness) and restitution (bounciness) +/// - **Sensor vs. Solid**: Sensors detect overlaps but don't create physical collisions +/// - **Mass properties**: Automatically computed from the shape's volume and density +/// +/// ## Creating colliders +/// +/// Always use [`ColliderBuilder`] to create colliders: +/// +/// ```ignore +/// let collider = ColliderBuilder::cuboid(1.0, 0.5, 1.0) // 2x1x2 box +/// .friction(0.7) +/// .restitution(0.3); +/// colliders.insert_with_parent(collider, body_handle, &mut bodies); +/// ``` +/// +/// ## Attaching to bodies +/// +/// Colliders are usually attached to rigid bodies. One body can have multiple colliders +/// to create compound shapes (like a character with separate colliders for head, torso, limbs). pub struct Collider { pub(crate) coll_type: ColliderType, pub(crate) shape: ColliderShape, @@ -52,12 +77,21 @@ impl Collider { } } - /// The rigid body this collider is attached to. + /// The rigid body this collider is attached to, if any. + /// + /// Returns `None` for standalone colliders (not attached to any body). pub fn parent(&self) -> Option { self.parent.map(|parent| parent.handle) } - /// Is this collider a sensor? + /// Checks if this collider is a sensor (detects overlaps without physical collision). + /// + /// Sensors are like "trigger zones" - they detect when other colliders enter/exit them + /// but don't create physical contact forces. Use for: + /// - Trigger zones (checkpoint areas, damage regions) + /// - Proximity detection + /// - Collectible items + /// - Area-of-effect detection pub fn is_sensor(&self) -> bool { self.coll_type.is_sensor() } @@ -110,22 +144,31 @@ impl Collider { self.contact_skin = *contact_skin; } - /// The physics hooks enabled for this collider. + /// Which physics hooks are enabled for this collider. + /// + /// Hooks allow custom filtering and modification of collisions. See [`PhysicsHooks`](crate::pipeline::PhysicsHooks). pub fn active_hooks(&self) -> ActiveHooks { self.flags.active_hooks } - /// Sets the physics hooks enabled for this collider. + /// Enables/disables physics hooks for this collider. + /// + /// Use to opt colliders into custom collision filtering logic. pub fn set_active_hooks(&mut self, active_hooks: ActiveHooks) { self.flags.active_hooks = active_hooks; } - /// The events enabled for this collider. + /// Which events are enabled for this collider. + /// + /// Controls whether you receive collision/contact force events. See [`ActiveEvents`](crate::pipeline::ActiveEvents). pub fn active_events(&self) -> ActiveEvents { self.flags.active_events } - /// Sets the events enabled for this collider. + /// Enables/disables event generation for this collider. + /// + /// Set to `ActiveEvents::COLLISION_EVENTS` to receive started/stopped collision notifications. + /// Set to `ActiveEvents::CONTACT_FORCE_EVENTS` to receive force threshold events. pub fn set_active_events(&mut self, active_events: ActiveEvents) { self.flags.active_events = active_events; } @@ -154,12 +197,19 @@ impl Collider { self.contact_skin = skin_thickness; } - /// The friction coefficient of this collider. + /// The friction coefficient of this collider (how "slippery" it is). + /// + /// - `0.0` = perfectly slippery (ice) + /// - `1.0` = high friction (rubber on concrete) + /// - Typical values: 0.3-0.8 pub fn friction(&self) -> Real { self.material.friction } - /// Sets the friction coefficient of this collider. + /// Sets the friction coefficient (slipperiness). + /// + /// Controls how much this surface resists sliding. Higher values = more grip. + /// Works with other collider's friction via the combine rule. pub fn set_friction(&mut self, coefficient: Real) { self.material.friction = coefficient } @@ -178,12 +228,20 @@ impl Collider { self.material.friction_combine_rule = rule; } - /// The restitution coefficient of this collider. + /// The restitution coefficient of this collider (how "bouncy" it is). + /// + /// - `0.0` = no bounce (clay, soft material) + /// - `1.0` = perfect bounce (ideal elastic collision) + /// - `>1.0` = super bouncy (gains energy, unrealistic but fun!) + /// - Typical values: 0.0-0.8 pub fn restitution(&self) -> Real { self.material.restitution } - /// Sets the restitution coefficient of this collider. + /// Sets the restitution coefficient (bounciness). + /// + /// Controls how much velocity is preserved after impact. Higher values = more bounce. + /// Works with other collider's restitution via the combine rule. pub fn set_restitution(&mut self, coefficient: Real) { self.material.restitution = coefficient } @@ -207,7 +265,10 @@ impl Collider { self.contact_force_event_threshold = threshold; } - /// Sets whether or not this is a sensor collider. + /// Converts this collider to/from a sensor. + /// + /// Sensors detect overlaps but don't create physical contact forces. + /// Use `true` for trigger zones, `false` for solid collision shapes. pub fn set_sensor(&mut self, is_sensor: bool) { if is_sensor != self.is_sensor() { self.changes.insert(ColliderChanges::TYPE); @@ -219,12 +280,17 @@ impl Collider { } } - /// Is this collider enabled? + /// Returns `true` if this collider is active in the simulation. + /// + /// Disabled colliders are excluded from collision detection and physics. pub fn is_enabled(&self) -> bool { matches!(self.flags.enabled, ColliderEnabled::Enabled) } - /// Sets whether or not this collider is enabled. + /// Enables or disables this collider. + /// + /// When disabled, the collider is excluded from all collision detection and physics. + /// Useful for temporarily "turning off" colliders without removing them. pub fn set_enabled(&mut self, enabled: bool) { match self.flags.enabled { ColliderEnabled::Enabled | ColliderEnabled::DisabledByParent => { @@ -242,45 +308,60 @@ impl Collider { } } - /// Sets the translational part of this collider's position. + /// Sets the collider's position (for standalone colliders). + /// + /// For attached colliders, modify the parent body's position instead. + /// This directly sets world-space position. pub fn set_translation(&mut self, translation: Vector) { self.changes.insert(ColliderChanges::POSITION); self.pos.0.translation.vector = translation; } - /// Sets the rotational part of this collider's position. + /// Sets the collider's rotation (for standalone colliders). + /// + /// For attached colliders, modify the parent body's rotation instead. pub fn set_rotation(&mut self, rotation: Rotation) { self.changes.insert(ColliderChanges::POSITION); self.pos.0.rotation = rotation; } - /// Sets the position of this collider. + /// Sets the collider's full pose (for standalone colliders). + /// + /// For attached colliders, modify the parent body instead. pub fn set_position(&mut self, position: Isometry) { self.changes.insert(ColliderChanges::POSITION); self.pos.0 = position; } - /// The world-space position of this collider. + /// The current world-space position of this collider. + /// + /// For attached colliders, this is automatically updated when the parent body moves. + /// For standalone colliders, this is the position you set directly. pub fn position(&self) -> &Isometry { &self.pos } - /// The translational part of this collider's position. + /// The current position vector of this collider (world coordinates). pub fn translation(&self) -> &Vector { &self.pos.0.translation.vector } - /// The rotational part of this collider's position. + /// The current rotation/orientation of this collider. pub fn rotation(&self) -> &Rotation { &self.pos.0.rotation } - /// The position of this collider with respect to the body it is attached to. + /// The collider's position relative to its parent body (local coordinates). + /// + /// Returns `None` for standalone colliders. This is the offset from the parent body's origin. pub fn position_wrt_parent(&self) -> Option<&Isometry> { self.parent.as_ref().map(|p| &p.pos_wrt_parent) } - /// Sets the translational part of this collider's translation relative to its parent rigid-body. + /// Changes this collider's position offset from its parent body. + /// + /// Useful for adjusting where a collider sits on a body without moving the whole body. + /// Does nothing if the collider has no parent. pub fn set_translation_wrt_parent(&mut self, translation: Vector) { if let Some(parent) = self.parent.as_mut() { self.changes.insert(ColliderChanges::PARENT); @@ -288,7 +369,9 @@ impl Collider { } } - /// Sets the rotational part of this collider's rotation relative to its parent rigid-body. + /// Changes this collider's rotation offset from its parent body. + /// + /// Rotates the collider relative to its parent. Does nothing if no parent. pub fn set_rotation_wrt_parent(&mut self, rotation: AngVector) { if let Some(parent) = self.parent.as_mut() { self.changes.insert(ColliderChanges::PARENT); @@ -296,7 +379,7 @@ impl Collider { } } - /// Sets the position of this collider with respect to its parent rigid-body. + /// Changes this collider's full pose (position + rotation) relative to its parent. /// /// Does nothing if the collider is not attached to a rigid-body. pub fn set_position_wrt_parent(&mut self, pos_wrt_parent: Isometry) { @@ -306,12 +389,16 @@ impl Collider { } } - /// The collision groups used by this collider. + /// The collision groups controlling what this collider can interact with. + /// + /// See [`InteractionGroups`] for details on collision filtering. pub fn collision_groups(&self) -> InteractionGroups { self.flags.collision_groups } - /// Sets the collision groups of this collider. + /// Changes which collision groups this collider belongs to and can interact with. + /// + /// Use to control collision filtering (like changing layers). pub fn set_collision_groups(&mut self, groups: InteractionGroups) { if self.flags.collision_groups != groups { self.changes.insert(ColliderChanges::GROUPS); @@ -319,12 +406,14 @@ impl Collider { } } - /// The solver groups used by this collider. + /// The solver groups for this collider (advanced collision filtering). + /// + /// Most users should use `collision_groups()` instead. pub fn solver_groups(&self) -> InteractionGroups { self.flags.solver_groups } - /// Sets the solver groups of this collider. + /// Changes the solver groups (advanced contact resolution filtering). pub fn set_solver_groups(&mut self, groups: InteractionGroups) { if self.flags.solver_groups != groups { self.changes.insert(ColliderChanges::GROUPS); @@ -332,17 +421,22 @@ impl Collider { } } - /// The material (friction and restitution properties) of this collider. + /// Returns the material properties (friction and restitution) of this collider. pub fn material(&self) -> &ColliderMaterial { &self.material } - /// The volume (or surface in 2D) of this collider. + /// Returns the volume (3D) or area (2D) of this collider's shape. + /// + /// Used internally for mass calculations when density is set. pub fn volume(&self) -> Real { self.shape.mass_properties(1.0).mass() } - /// The density of this collider. + /// The density of this collider (mass per unit volume). + /// + /// Used to automatically compute mass from the collider's volume. + /// Returns an approximate density if mass was set directly instead. pub fn density(&self) -> Real { match &self.mprops { ColliderMassProps::Density(density) => *density, @@ -357,7 +451,9 @@ impl Collider { } } - /// The mass of this collider. + /// The mass contributed by this collider to its parent body. + /// + /// Either set directly or computed from density × volume. pub fn mass(&self) -> Real { match &self.mprops { ColliderMassProps::Density(density) => self.shape.mass_properties(*density).mass(), @@ -409,7 +505,10 @@ impl Collider { } } - /// The geometric shape of this collider. + /// The geometric shape of this collider (ball, cuboid, mesh, etc.). + /// + /// Returns a reference to the underlying shape object for reading properties + /// or performing geometric queries. pub fn shape(&self) -> &dyn Shape { self.shape.as_ref() } @@ -430,29 +529,34 @@ impl Collider { self.shape = shape; } - /// Retrieve the SharedShape. Also see the `shape()` function + /// Returns the shape as a `SharedShape` (reference-counted shape). + /// + /// Use `shape()` for the trait object, this for the concrete type. pub fn shared_shape(&self) -> &SharedShape { &self.shape } - /// Compute the axis-aligned bounding box of this collider. + /// Computes the axis-aligned bounding box (AABB) of this collider. /// - /// This AABB doesn’t take into account the collider’s contact skin. - /// [`Collider::contact_skin`]. + /// The AABB is the smallest box (aligned with world axes) that contains the shape. + /// Doesn't include contact skin. pub fn compute_aabb(&self) -> Aabb { self.shape.compute_aabb(&self.pos) } - /// Compute the axis-aligned bounding box of this collider, taking into account the - /// [`Collider::contact_skin`] and prediction distance. + /// Computes the AABB including contact skin and prediction distance. + /// + /// This is the AABB used for collision detection (slightly larger than the visual shape). pub fn compute_collision_aabb(&self, prediction: Real) -> Aabb { self.shape .compute_aabb(&self.pos) .loosened(self.contact_skin + prediction) } - /// Compute the axis-aligned bounding box of this collider moving from its current position - /// to the given `next_position` + /// Computes the AABB swept from current position to `next_position`. + /// + /// Returns a box that contains the shape at both positions plus everything in between. + /// Used for continuous collision detection. pub fn compute_swept_aabb(&self, next_position: &Isometry) -> Aabb { self.shape.compute_swept_aabb(&self.pos, next_position) } @@ -491,18 +595,45 @@ impl Collider { aabb } - /// Compute the local-space mass properties of this collider. + /// Computes the full mass properties (mass, center of mass, angular inertia). + /// + /// Returns properties in the collider's local coordinate system. pub fn mass_properties(&self) -> MassProperties { self.mprops.mass_properties(&*self.shape) } - /// The total force magnitude beyond which a contact force event can be emitted. + /// Returns the force threshold for contact force events. + /// + /// When contact forces exceed this value, a `ContactForceEvent` is generated. + /// See `set_contact_force_event_threshold()` for details. pub fn contact_force_event_threshold(&self) -> Real { self.contact_force_event_threshold } } -/// A structure responsible for building a new collider. +/// A builder for creating colliders with custom shapes and properties. +/// +/// This builder lets you create collision shapes and configure their physical properties +/// (friction, bounciness, density, etc.) before adding them to your world. +/// +/// # Common shapes +/// +/// - [`ball(radius)`](Self::ball) - Sphere (3D) or circle (2D) +/// - [`cuboid(hx, hy, hz)`](Self::cuboid) - Box with half-extents +/// - [`capsule_y(half_height, radius)`](Self::capsule_y) - Pill shape (great for characters) +/// - [`trimesh(vertices, indices)`](Self::trimesh) - Triangle mesh for complex geometry +/// - [`heightfield(...)`](Self::heightfield) - Terrain from height data +/// +/// # Example +/// +/// ```ignore +/// // Create a bouncy ball +/// let collider = ColliderBuilder::ball(0.5) +/// .restitution(0.9) // Very bouncy +/// .friction(0.1) // Low friction (slippery) +/// .density(2.0); // Heavy material +/// colliders.insert_with_parent(collider, body_handle, &mut bodies); +/// ``` #[derive(Clone, Debug)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[must_use = "Builder functions return the updated builder"] @@ -578,7 +709,16 @@ impl ColliderBuilder { Self::new(SharedShape::compound(shapes)) } - /// Initialize a new collider builder with a ball shape defined by its radius. + /// Creates a sphere (3D) or circle (2D) collider. + /// + /// The simplest and fastest collision shape. Use for: + /// - Balls and spheres + /// - Approximate round objects + /// - Projectiles + /// - Particles + /// + /// # Parameters + /// * `radius` - The sphere's radius pub fn ball(radius: Real) -> Self { Self::new(SharedShape::ball(radius)) } @@ -683,7 +823,18 @@ impl ColliderBuilder { Self::new(SharedShape::capsule_x(half_height, radius)) } - /// Initialize a new collider builder with a capsule shape aligned with the `y` axis. + /// Creates a capsule (pill-shaped) collider aligned with the Y axis. + /// + /// Capsules are cylinders with hemispherical caps. Excellent for characters because: + /// - Smooth collision (no getting stuck on edges) + /// - Good for upright objects (characters, trees) + /// - Fast collision detection + /// + /// # Parameters + /// * `half_height` - Half the height of the cylindrical part (not including caps) + /// * `radius` - Radius of the cylinder and caps + /// + /// **Example**: `capsule_y(1.0, 0.5)` creates a 3.0 tall capsule (1.0×2 cylinder + 0.5×2 caps) pub fn capsule_y(half_height: Real, radius: Real) -> Self { Self::new(SharedShape::capsule_y(half_height, radius)) } @@ -694,7 +845,17 @@ impl ColliderBuilder { Self::new(SharedShape::capsule_z(half_height, radius)) } - /// Initialize a new collider builder with a cuboid shape defined by its half-extents. + /// Creates a box collider defined by its half-extents (half-widths). + /// + /// Very fast collision detection. Use for: + /// - Boxes and crates + /// - Buildings and rooms + /// - Most rectangular objects + /// + /// # Parameters (3D) + /// * `hx`, `hy`, `hz` - Half-extents (half the width) along each axis + /// + /// **Example**: `cuboid(1.0, 0.5, 2.0)` creates a box with full size 2×1×4 #[cfg(feature = "dim3")] pub fn cuboid(hx: Real, hy: Real, hz: Real) -> Self { Self::new(SharedShape::cuboid(hx, hy, hz)) @@ -707,12 +868,17 @@ impl ColliderBuilder { Self::new(SharedShape::round_cuboid(hx, hy, hz, border_radius)) } - /// Initializes a collider builder with a segment shape. + /// Creates a line segment collider between two points. + /// + /// Useful for thin barriers, edges, or 2D line-based collision. + /// Has no thickness - purely a mathematical line. pub fn segment(a: Point, b: Point) -> Self { Self::new(SharedShape::segment(a, b)) } - /// Initializes a collider builder with a triangle shape. + /// Creates a single triangle collider. + /// + /// Use for simple 3-sided shapes or as building blocks for more complex geometry. pub fn triangle(a: Point, b: Point, c: Point) -> Self { Self::new(SharedShape::triangle(a, b, c)) } @@ -732,7 +898,34 @@ impl ColliderBuilder { Self::new(SharedShape::polyline(vertices, indices)) } - /// Initializes a collider builder with a triangle mesh shape defined by its vertex and index buffers. + /// Creates a triangle mesh collider from vertices and triangle indices. + /// + /// Use for complex, arbitrary shapes like: + /// - Level geometry and terrain + /// - Imported 3D models + /// - Custom irregular shapes + /// + /// **Performance note**: Triangle meshes are slower than primitive shapes (balls, boxes, capsules). + /// Consider using compound shapes or simpler approximations when possible. + /// + /// # Parameters + /// * `vertices` - Array of 3D points + /// * `indices` - Array of triangles, each is 3 indices into the vertex array + /// + /// # Example + /// ```ignore + /// use rapier3d::prelude::*; + /// use nalgebra::Point3; + /// + /// let vertices = vec![ + /// Point3::new(0.0, 0.0, 0.0), + /// Point3::new(1.0, 0.0, 0.0), + /// Point3::new(0.0, 1.0, 0.0), + /// ]; + /// let triangle: [u32; 3] = [0, 1, 2]; + /// let indices = vec![triangle]; // One triangle + /// let collider = ColliderBuilder::trimesh(vertices, indices)?; + /// ``` pub fn trimesh( vertices: Vec>, indices: Vec<[u32; 3]>, @@ -767,8 +960,12 @@ impl ColliderBuilder { Ok(Self::new(shape).position(pose)) } - /// Initializes a collider builder with a compound shape obtained from the decomposition of - /// the given trimesh (in 3D) or polyline (in 2D) into convex parts. + /// Creates a compound collider by decomposing a mesh/polyline into convex pieces. + /// + /// Concave shapes (like an 'L' or 'C') are automatically broken into multiple convex + /// parts for efficient collision detection. This is often faster than using a trimesh. + /// + /// Uses the V-HACD algorithm. Good for imported models that aren't already convex. pub fn convex_decomposition(vertices: &[Point], indices: &[[u32; DIM]]) -> Self { Self::new(SharedShape::convex_decomposition(vertices, indices)) } @@ -815,8 +1012,15 @@ impl ColliderBuilder { )) } - /// Initializes a new collider builder with a 2D convex polygon or 3D convex polyhedron - /// obtained after computing the convex-hull of the given points. + /// Creates the smallest convex shape that contains all the given points. + /// + /// Computes the "shrink-wrap" around a point cloud. Useful for: + /// - Creating collision shapes from vertex data + /// - Approximating complex shapes with a simpler convex one + /// + /// Returns `None` if the points don't form a valid convex shape. + /// + /// **Performance**: Convex shapes are much faster than triangle meshes! pub fn convex_hull(points: &[Point]) -> Option { SharedShape::convex_hull(points).map(Self::new) } @@ -871,8 +1075,21 @@ impl ColliderBuilder { Self::new(SharedShape::heightfield(heights, scale)) } - /// Initializes a collider builder with a heightfield shape defined by its set of height and a scale - /// factor along each coordinate axis. + /// Creates a terrain/landscape collider from a 2D grid of height values. + /// + /// Perfect for outdoor terrain in 3D games. The heightfield is a grid where each cell + /// stores a height value, creating a landscape surface. + /// + /// Use for: + /// - Terrain and landscapes + /// - Hills and valleys + /// - Ground surfaces in open worlds + /// + /// # Parameters + /// * `heights` - 2D matrix of height values (Y coordinates) + /// * `scale` - Size of each grid cell in X and Z directions + /// + /// **Performance**: Much faster than triangle meshes for terrain! #[cfg(feature = "dim3")] pub fn heightfield(heights: na::DMatrix, scale: Vector) -> Self { Self::new(SharedShape::heightfield(heights, scale)) @@ -889,77 +1106,142 @@ impl ColliderBuilder { Self::new(SharedShape::heightfield_with_flags(heights, scale, flags)) } - /// The default friction coefficient used by the collider builder. + /// Returns the default friction value used when not specified (0.5). pub fn default_friction() -> Real { 0.5 } - /// The default density used by the collider builder. + /// Returns the default density value used when not specified (1.0). pub fn default_density() -> Real { - 100.0 + 1.0 } - /// Sets an arbitrary user-defined 128-bit integer associated to the colliders built by this builder. + /// Stores custom user data with this collider (128-bit integer). + /// + /// Use to associate game data (entity ID, type, etc.) with physics objects. + /// + /// # Example + /// ```ignore + /// let collider = ColliderBuilder::ball(0.5) + /// .user_data(entity_id as u128) + /// .build(); + /// ``` pub fn user_data(mut self, data: u128) -> Self { self.user_data = data; self } - /// Sets the collision groups used by this collider. + /// Sets which collision groups this collider belongs to and can interact with. /// - /// Two colliders will interact iff. their collision groups are compatible. - /// See [InteractionGroups::test] for details. + /// Use this to control what can collide with what (like collision layers). + /// See [`InteractionGroups`] for examples. + /// + /// # Example + /// ```ignore + /// // Player bullet: in group 1, only hits group 2 (enemies) + /// let groups = InteractionGroups::new(Group::GROUP_1, Group::GROUP_2); + /// let bullet = ColliderBuilder::ball(0.1) + /// .collision_groups(groups) + /// .build(); + /// ``` pub fn collision_groups(mut self, groups: InteractionGroups) -> Self { self.collision_groups = groups; self } - /// Sets the solver groups used by this collider. + /// Sets solver groups (advanced collision filtering for contact resolution). /// - /// Forces between two colliders in contact will be computed iff their solver groups are - /// compatible. See [InteractionGroups::test] for details. + /// Similar to collision_groups but specifically for the contact solver. + /// Most users should use `collision_groups()` instead - this is for advanced scenarios + /// where you want collisions detected but not resolved (e.g., one-way platforms). pub fn solver_groups(mut self, groups: InteractionGroups) -> Self { self.solver_groups = groups; self } - /// Sets whether or not the collider built by this builder is a sensor. + /// Makes this collider a sensor (trigger zone) instead of a solid collision shape. + /// + /// Sensors detect overlaps but don't create physical collisions. Use for: + /// - Trigger zones (checkpoints, danger areas) + /// - Collectible item detection + /// - Proximity sensors + /// - Win/lose conditions + /// + /// You'll receive collision events when objects enter/exit the sensor. + /// + /// # Example + /// ```ignore + /// let trigger = ColliderBuilder::cuboid(5.0, 5.0, 5.0) + /// .sensor(true) + /// .build(); + /// ``` pub fn sensor(mut self, is_sensor: bool) -> Self { self.is_sensor = is_sensor; self } - /// The set of physics hooks enabled for this collider. + /// Enables custom physics hooks for this collider (advanced). + /// + /// See [`ActiveHooks`](crate::pipeline::ActiveHooks) for details on custom collision filtering. pub fn active_hooks(mut self, active_hooks: ActiveHooks) -> Self { self.active_hooks = active_hooks; self } - /// The set of events enabled for this collider. + /// Enables event generation for this collider. + /// + /// Set to `ActiveEvents::COLLISION_EVENTS` for start/stop notifications. + /// Set to `ActiveEvents::CONTACT_FORCE_EVENTS` for force threshold events. + /// + /// # Example + /// ```ignore + /// let sensor = ColliderBuilder::ball(1.0) + /// .sensor(true) + /// .active_events(ActiveEvents::COLLISION_EVENTS) + /// .build(); + /// ``` pub fn active_events(mut self, active_events: ActiveEvents) -> Self { self.active_events = active_events; self } - /// The set of active collision types for this collider. + /// Sets which body type combinations can collide with this collider. + /// + /// See [`ActiveCollisionTypes`] for details. Most users don't need to change this. pub fn active_collision_types(mut self, active_collision_types: ActiveCollisionTypes) -> Self { self.active_collision_types = active_collision_types; self } - /// Sets the friction coefficient of the collider this builder will build. + /// Sets the friction coefficient (slipperiness) for this collider. + /// + /// - `0.0` = ice (very slippery) + /// - `0.5` = wood on wood + /// - `1.0` = rubber (high grip) + /// + /// Default is `0.5`. pub fn friction(mut self, friction: Real) -> Self { self.friction = friction; self } - /// Sets the rule to be used to combine two friction coefficients in a contact. + /// Sets how friction coefficients are combined when two colliders touch. + /// + /// Options: Average, Min, Max, Multiply. Default is Average. + /// Most games can ignore this and use the default. pub fn friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self { self.friction_combine_rule = rule; self } - /// Sets the restitution coefficient of the collider this builder will build. + /// Sets the restitution coefficient (bounciness) for this collider. + /// + /// - `0.0` = no bounce (clay, soft) + /// - `0.5` = moderate bounce + /// - `1.0` = perfect elastic bounce + /// - `>1.0` = super bouncy (gains energy!) + /// + /// Default is `0.0`. pub fn restitution(mut self, restitution: Real) -> Self { self.restitution = restitution; self @@ -971,25 +1253,35 @@ impl ColliderBuilder { self } - /// Sets the uniform density of the collider this builder will build. + /// Sets the density (mass per unit volume) of this collider. /// - /// This will be overridden by a call to [`Self::mass`] or [`Self::mass_properties`] so it only - /// makes sense to call either [`Self::density`] or [`Self::mass`] or [`Self::mass_properties`]. + /// Mass will be computed as: `density × volume`. Common densities: + /// - `1000.0` = water + /// - `2700.0` = aluminum + /// - `7850.0` = steel /// - /// The mass and angular inertia of this collider will be computed automatically based on its - /// shape. + /// ⚠️ Use either `density()` OR `mass()`, not both (last call wins). + /// + /// # Example + /// ```ignore + /// let steel_ball = ColliderBuilder::ball(0.5).density(7850.0).build(); + /// ``` pub fn density(mut self, density: Real) -> Self { self.mass_properties = ColliderMassProps::Density(density); self } - /// Sets the mass of the collider this builder will build. + /// Sets the total mass of this collider directly. /// - /// This will be overridden by a call to [`Self::density`] or [`Self::mass_properties`] so it only - /// makes sense to call either [`Self::density`] or [`Self::mass`] or [`Self::mass_properties`]. + /// Angular inertia is computed automatically from the shape and mass. /// - /// The angular inertia of this collider will be computed automatically based on its shape - /// and this mass value. + /// ⚠️ Use either `mass()` OR `density()`, not both (last call wins). + /// + /// # Example + /// ```ignore + /// // 10kg ball regardless of its radius + /// let collider = ColliderBuilder::ball(0.5).mass(10.0).build(); + /// ``` pub fn mass(mut self, mass: Real) -> Self { self.mass_properties = ColliderMassProps::Mass(mass); self @@ -1004,34 +1296,55 @@ impl ColliderBuilder { self } - /// Sets the total force magnitude beyond which a contact force event can be emitted. + /// Sets the force threshold for triggering contact force events. + /// + /// When total contact force exceeds this value, a `ContactForceEvent` is generated + /// (if `ActiveEvents::CONTACT_FORCE_EVENTS` is enabled). + /// + /// Use for detecting hard impacts, breaking objects, or damage systems. + /// + /// # Example + /// ```ignore + /// let glass = ColliderBuilder::cuboid(1.0, 1.0, 0.1) + /// .active_events(ActiveEvents::CONTACT_FORCE_EVENTS) + /// .contact_force_event_threshold(1000.0) // Break at 1000N + /// .build(); + /// ``` pub fn contact_force_event_threshold(mut self, threshold: Real) -> Self { self.contact_force_event_threshold = threshold; self } - /// Sets the initial translation of the collider to be created. + /// Sets where the collider sits relative to its parent body. /// - /// If the collider will be attached to a rigid-body, this sets the translation relative to the - /// rigid-body it will be attached to. + /// For attached colliders, this is the offset from the body's origin. + /// For standalone colliders, this is the world position. + /// + /// # Example + /// ```ignore + /// // Collider offset 2 units to the right of the body + /// let collider = ColliderBuilder::ball(0.5) + /// .translation(vector![2.0, 0.0, 0.0]) + /// .build(); + /// ``` pub fn translation(mut self, translation: Vector) -> Self { self.position.translation.vector = translation; self } - /// Sets the initial orientation of the collider to be created. + /// Sets the collider's rotation relative to its parent body. /// - /// If the collider will be attached to a rigid-body, this sets the orientation relative to the - /// rigid-body it will be attached to. + /// For attached colliders, this rotates the collider relative to the body. + /// For standalone colliders, this is the world rotation. pub fn rotation(mut self, angle: AngVector) -> Self { self.position.rotation = Rotation::new(angle); self } - /// Sets the initial position (translation and orientation) of the collider to be created. + /// Sets the collider's full pose (position + rotation) relative to its parent. /// - /// If the collider will be attached to a rigid-body, this sets the position relative - /// to the rigid-body it will be attached to. + /// For attached colliders, this is relative to the parent body. + /// For standalone colliders, this is the world pose. pub fn position(mut self, pos: Isometry) -> Self { self.position = pos; self @@ -1066,13 +1379,23 @@ impl ColliderBuilder { self } - /// Enable or disable the collider after its creation. + /// Sets whether this collider starts enabled or disabled. + /// + /// Default is `true` (enabled). Set to `false` to create a disabled collider. pub fn enabled(mut self, enabled: bool) -> Self { self.enabled = enabled; self } - /// Builds a new collider attached to the given rigid-body. + /// Finalizes the collider and returns it, ready to be added to the world. + /// + /// # Example + /// ```ignore + /// let collider = ColliderBuilder::ball(0.5) + /// .friction(0.7) + /// .build(); + /// colliders.insert_with_parent(collider, body_handle, &mut bodies); + /// ``` pub fn build(&self) -> Collider { let shape = self.shape.clone(); let material = ColliderMaterial { diff --git a/src/geometry/collider_components.rs b/src/geometry/collider_components.rs index 6f2da7d..1ab9888 100644 --- a/src/geometry/collider_components.rs +++ b/src/geometry/collider_components.rs @@ -277,29 +277,45 @@ impl Default for ColliderMaterial { bitflags::bitflags! { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] - /// Flags affecting whether or not collision-detection happens between two colliders - /// depending on the type of rigid-bodies they are attached to. + /// Controls which combinations of body types can collide with each other. + /// + /// By default, Rapier only detects collisions between pairs that make physical sense + /// (e.g., dynamic-dynamic, dynamic-fixed). Use this to customize that behavior. + /// + /// **Most users don't need to change this** - the defaults are correct for normal physics. + /// + /// ## Default behavior + /// - ✅ Dynamic ↔ Dynamic (moving objects collide) + /// - ✅ Dynamic ↔ Fixed (moving objects hit walls) + /// - ✅ Dynamic ↔ Kinematic (moving objects hit platforms) + /// - ❌ Fixed ↔ Fixed (walls don't collide with each other - waste of CPU) + /// - ❌ Kinematic ↔ Kinematic (platforms don't collide - they're user-controlled) + /// - ❌ Kinematic ↔ Fixed (platforms don't collide with walls) + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut colliders = ColliderSet::new(); + /// # let mut bodies = RigidBodySet::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let collider_handle = colliders.insert_with_parent(ColliderBuilder::ball(0.5), body_handle, &mut bodies); + /// # let collider = colliders.get_mut(collider_handle).unwrap(); + /// // Enable kinematic-kinematic collisions (unusual) + /// let types = ActiveCollisionTypes::default() | ActiveCollisionTypes::KINEMATIC_KINEMATIC; + /// collider.set_active_collision_types(types); + /// ``` pub struct ActiveCollisionTypes: u16 { - /// Enable collision-detection between a collider attached to a dynamic body - /// and another collider attached to a dynamic body. + /// Enables dynamic ↔ dynamic collision detection. const DYNAMIC_DYNAMIC = 0b0000_0000_0000_0001; - /// Enable collision-detection between a collider attached to a dynamic body - /// and another collider attached to a kinematic body. + /// Enables dynamic ↔ kinematic collision detection. const DYNAMIC_KINEMATIC = 0b0000_0000_0000_1100; - /// Enable collision-detection between a collider attached to a dynamic body - /// and another collider attached to a fixed body (or not attached to any body). + /// Enables dynamic ↔ fixed collision detection. const DYNAMIC_FIXED = 0b0000_0000_0000_0010; - /// Enable collision-detection between a collider attached to a kinematic body - /// and another collider attached to a kinematic body. + /// Enables kinematic ↔ kinematic collision detection (rarely needed). const KINEMATIC_KINEMATIC = 0b1100_1100_0000_0000; - - /// Enable collision-detection between a collider attached to a kinematic body - /// and another collider attached to a fixed body (or not attached to any body). + /// Enables kinematic ↔ fixed collision detection (rarely needed). const KINEMATIC_FIXED = 0b0010_0010_0000_0000; - - /// Enable collision-detection between a collider attached to a fixed body (or - /// not attached to any body) and another collider attached to a fixed body (or - /// not attached to any body). + /// Enables fixed ↔ fixed collision detection (rarely needed). const FIXED_FIXED = 0b0000_0000_0010_0000; } } diff --git a/src/geometry/collider_set.rs b/src/geometry/collider_set.rs index 30ff144..31dc14a 100644 --- a/src/geometry/collider_set.rs +++ b/src/geometry/collider_set.rs @@ -21,7 +21,29 @@ impl HasModifiedFlag for Collider { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone, Default, Debug)] -/// A set of colliders that can be handled by a physics `World`. +/// The collection that stores all colliders (collision shapes) in your physics world. +/// +/// Similar to [`RigidBodySet`](crate::dynamics::RigidBodySet), this is the "database" where +/// all your collision shapes live. Each collider can be attached to a rigid body or exist +/// independently. +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// let mut colliders = ColliderSet::new(); +/// # let mut bodies = RigidBodySet::new(); +/// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); +/// +/// // Add a standalone collider (no parent body) +/// let handle = colliders.insert(ColliderBuilder::ball(0.5)); +/// +/// // Or attach it to a body +/// let handle = colliders.insert_with_parent( +/// ColliderBuilder::cuboid(1.0, 1.0, 1.0), +/// body_handle, +/// &mut bodies +/// ); +/// ``` pub struct ColliderSet { pub(crate) colliders: Arena, pub(crate) modified_colliders: ModifiedColliders, @@ -29,7 +51,7 @@ pub struct ColliderSet { } impl ColliderSet { - /// Create a new empty set of colliders. + /// Creates a new empty collection of colliders. pub fn new() -> Self { ColliderSet { colliders: Arena::new(), @@ -38,9 +60,9 @@ impl ColliderSet { } } - /// Create a new set of colliders, with an initial capacity - /// for the set of colliders as well as the tracking of - /// modified colliders. + /// Creates a new collection with pre-allocated space for the given number of colliders. + /// + /// Use this if you know approximately how many colliders you'll need. pub fn with_capacity(capacity: usize) -> Self { ColliderSet { colliders: Arena::with_capacity(capacity), @@ -61,17 +83,23 @@ impl ColliderSet { std::mem::take(&mut self.removed_colliders) } - /// An always-invalid collider handle. + /// Returns a handle that's guaranteed to be invalid. + /// + /// Useful as a sentinel/placeholder value. pub fn invalid_handle() -> ColliderHandle { ColliderHandle::from_raw_parts(crate::INVALID_U32, crate::INVALID_U32) } - /// Iterate through all the colliders on this set. + /// Iterates over all colliders in this collection. + /// + /// Yields `(handle, &Collider)` pairs for each collider (including disabled ones). pub fn iter(&self) -> impl ExactSizeIterator { self.colliders.iter().map(|(h, c)| (ColliderHandle(h), c)) } - /// Iterate through all the enabled colliders on this set. + /// Iterates over only the enabled colliders. + /// + /// Disabled colliders are excluded from physics simulation and queries. pub fn iter_enabled(&self) -> impl Iterator { self.colliders .iter() @@ -79,7 +107,7 @@ impl ColliderSet { .filter(|(_, c)| c.is_enabled()) } - /// Iterates mutably through all the colliders on this set. + /// Iterates over all colliders with mutable access. #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn iter_mut(&mut self) -> impl Iterator { self.modified_colliders.clear(); @@ -92,28 +120,31 @@ impl ColliderSet { }) } - /// Iterates mutably through all the enabled colliders on this set. + /// Iterates over only the enabled colliders with mutable access. #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn iter_enabled_mut(&mut self) -> impl Iterator { self.iter_mut().filter(|(_, c)| c.is_enabled()) } - /// The number of colliders on this set. + /// Returns how many colliders are currently in this collection. pub fn len(&self) -> usize { self.colliders.len() } - /// `true` if there are no colliders in this set. + /// Returns `true` if there are no colliders in this collection. pub fn is_empty(&self) -> bool { self.colliders.is_empty() } - /// Is this collider handle valid? + /// Checks if the given handle points to a valid collider that still exists. pub fn contains(&self, handle: ColliderHandle) -> bool { self.colliders.contains(handle.0) } - /// Inserts a new collider to this set and retrieve its handle. + /// Adds a standalone collider (not attached to any body) and returns its handle. + /// + /// Most colliders should be attached to rigid bodies using [`insert_with_parent()`](Self::insert_with_parent) instead. + /// Standalone colliders are useful for sensors or static collision geometry that doesn't need a body. pub fn insert(&mut self, coll: impl Into) -> ColliderHandle { let mut coll = coll.into(); // Make sure the internal links are reset, they may not be @@ -129,7 +160,24 @@ impl ColliderSet { handle } - /// Inserts a new collider to this set, attach it to the given rigid-body, and retrieve its handle. + /// Adds a collider attached to a rigid body and returns its handle. + /// + /// This is the most common way to add colliders. The collider's position is relative + /// to its parent body, so when the body moves, the collider moves with it. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut colliders = ColliderSet::new(); + /// # let mut bodies = RigidBodySet::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// // Create a ball collider attached to a dynamic body + /// let collider_handle = colliders.insert_with_parent( + /// ColliderBuilder::ball(0.5), + /// body_handle, + /// &mut bodies + /// ); + /// ``` pub fn insert_with_parent( &mut self, coll: impl Into, @@ -172,8 +220,27 @@ impl ColliderSet { handle } - /// Sets the parent of the given collider. - // TODO: find a way to define this as a method of Collider. + /// Changes which rigid body a collider is attached to, or detaches it completely. + /// + /// Use this to move a collider from one body to another, or to make it standalone. + /// + /// # Parameters + /// * `new_parent_handle` - `Some(handle)` to attach to a body, `None` to make standalone + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut colliders = ColliderSet::new(); + /// # let mut bodies = RigidBodySet::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let other_body = bodies.insert(RigidBodyBuilder::dynamic()); + /// # let collider_handle = colliders.insert_with_parent(ColliderBuilder::ball(0.5).build(), body_handle, &mut bodies); + /// // Detach collider from its current body + /// colliders.set_parent(collider_handle, None, &mut bodies); + /// + /// // Attach it to a different body + /// colliders.set_parent(collider_handle, Some(other_body), &mut bodies); + /// ``` pub fn set_parent( &mut self, handle: ColliderHandle, @@ -220,10 +287,32 @@ impl ColliderSet { } } - /// Remove a collider from this set and update its parent accordingly. + /// Removes a collider from the world. /// - /// If `wake_up` is `true`, the rigid-body the removed collider is attached to - /// will be woken up. + /// The collider is detached from its parent body (if any) and removed from all + /// collision detection structures. Returns the removed collider if it existed. + /// + /// # Parameters + /// * `wake_up` - If `true`, wakes up the parent body (useful when collider removal + /// changes the body's mass or collision behavior significantly) + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut colliders = ColliderSet::new(); + /// # let mut bodies = RigidBodySet::new(); + /// # let mut islands = IslandManager::new(); + /// # let body_handle = bodies.insert(RigidBodyBuilder::dynamic().build()); + /// # let handle = colliders.insert_with_parent(ColliderBuilder::ball(0.5).build(), body_handle, &mut bodies); + /// if let Some(collider) = colliders.remove( + /// handle, + /// &mut islands, + /// &mut bodies, + /// true // Wake up the parent body + /// ) { + /// println!("Removed collider with shape: {:?}", collider.shared_shape()); + /// } + /// ``` pub fn remove( &mut self, handle: ColliderHandle, @@ -258,29 +347,18 @@ impl ColliderSet { Some(collider) } - /// Gets the collider with the given handle without a known generation. + /// Gets a collider by its index without knowing the generation number. /// - /// This is useful when you know you want the collider at position `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the collider position `i` - /// are recycled between two insertion and a removal. - /// - /// Using this is discouraged in favor of `self.get(handle)` which does not - /// suffer form the ABA problem. + /// ⚠️ **Advanced/unsafe usage** - prefer [`get()`](Self::get) instead! See [`RigidBodySet::get_unknown_gen`] for details. pub fn get_unknown_gen(&self, i: u32) -> Option<(&Collider, ColliderHandle)> { self.colliders .get_unknown_gen(i) .map(|(c, h)| (c, ColliderHandle(h))) } - /// Gets a mutable reference to the collider with the given handle without a known generation. + /// Gets a mutable reference to a collider by its index without knowing the generation. /// - /// This is useful when you know you want the collider at position `i` but - /// don't know what is its current generation number. Generation numbers are - /// used to protect from the ABA problem because the collider position `i` - /// are recycled between two insertion and a removal. - /// - /// Using this is discouraged in favor of `self.get_mut(handle)` which does not + /// ⚠️ **Advanced/unsafe usage** - prefer [`get_mut()`](Self::get_mut) instead! /// suffer form the ABA problem. #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_unknown_gen_mut(&mut self, i: u32) -> Option<(&mut Collider, ColliderHandle)> { @@ -290,12 +368,17 @@ impl ColliderSet { Some((collider, handle)) } - /// Get the collider with the given handle. + /// Gets a read-only reference to the collider with the given handle. + /// + /// Returns `None` if the handle is invalid or the collider was removed. pub fn get(&self, handle: ColliderHandle) -> Option<&Collider> { self.colliders.get(handle.0) } /// Gets a mutable reference to the collider with the given handle. + /// + /// Returns `None` if the handle is invalid or the collider was removed. + /// Use this to modify collider properties like friction, restitution, sensor status, etc. #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_mut(&mut self, handle: ColliderHandle) -> Option<&mut Collider> { let result = self.colliders.get_mut(handle.0)?; @@ -303,9 +386,10 @@ impl ColliderSet { Some(result) } - /// Gets a mutable reference to the two colliders with the given handles. + /// Gets mutable references to two different colliders at once. /// - /// If `handle1 == handle2`, only the first returned value will be `Some`. + /// Useful when you need to modify two colliders simultaneously. If both handles + /// are the same, only the first value will be `Some`. #[cfg(not(feature = "dev-remove-slow-accessors"))] pub fn get_pair_mut( &mut self, diff --git a/src/geometry/contact_pair.rs b/src/geometry/contact_pair.rs index a7757c9..7a6ba71 100644 --- a/src/geometry/contact_pair.rs +++ b/src/geometry/contact_pair.rs @@ -115,7 +115,34 @@ impl IntersectionPair { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone)] -/// The description of all the contacts between a pair of colliders. +/// All contact information between two colliding colliders. +/// +/// When two colliders are touching, a ContactPair stores all the contact points, normals, +/// and forces between them. You can access this through the narrow phase or in event handlers. +/// +/// ## Contact manifolds +/// +/// The contacts are organized into "manifolds" - groups of contact points that share similar +/// properties (like being on the same face). Most collider pairs have 1 manifold, but complex +/// shapes may have multiple. +/// +/// ## Use cases +/// +/// - Reading contact normals for custom physics +/// - Checking penetration depth +/// - Analyzing impact forces +/// - Implementing custom contact responses +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # use rapier3d::geometry::ContactPair; +/// # let contact_pair = ContactPair::default(); +/// if let Some((manifold, contact)) = contact_pair.find_deepest_contact() { +/// println!("Deepest penetration: {}", -contact.dist); +/// println!("Contact normal: {:?}", manifold.data.normal); +/// } +/// ``` pub struct ContactPair { /// The first collider involved in the contact pair. pub collider1: ColliderHandle, @@ -135,6 +162,12 @@ pub struct ContactPair { pub(crate) workspace: Option, } +impl Default for ContactPair { + fn default() -> Self { + Self::new(ColliderHandle::invalid(), ColliderHandle::invalid()) + } +} + impl ContactPair { pub(crate) fn new(collider1: ColliderHandle, collider2: ColliderHandle) -> Self { Self { @@ -154,7 +187,10 @@ impl ContactPair { self.workspace = None; } - /// The sum of all the impulses applied by contacts on this contact pair. + /// The total impulse (force × time) applied by all contacts. + /// + /// This is the accumulated force that pushed the colliders apart. + /// Useful for determining impact strength. pub fn total_impulse(&self) -> Vector { self.manifolds .iter() @@ -162,14 +198,18 @@ impl ContactPair { .sum() } - /// The sum of the magnitudes of the contacts on this contact pair. + /// The total magnitude of all contact impulses (sum of lengths, not length of sum). + /// + /// This is what's compared against `contact_force_event_threshold`. pub fn total_impulse_magnitude(&self) -> Real { self.manifolds .iter() .fold(0.0, |a, m| a + m.total_impulse()) } - /// The magnitude and (unit) direction of the maximum impulse on this contact pair. + /// Finds the strongest contact impulse and its direction. + /// + /// Returns `(magnitude, normal_direction)` of the strongest individual contact. pub fn max_impulse(&self) -> (Real, Vector) { let mut result = (0.0, Vector::zeros()); @@ -184,13 +224,26 @@ impl ContactPair { result } - /// Finds the contact with the smallest signed distance. + /// Finds the contact point with the deepest penetration. /// - /// If the colliders involved in this contact pair are penetrating, then - /// this returns the contact with the largest penetration depth. + /// When objects overlap, this returns the contact point that's penetrating the most. + /// Useful for: + /// - Finding the "worst" overlap + /// - Determining primary contact direction + /// - Custom penetration resolution /// - /// Returns a reference to the contact, as well as the contact manifold - /// it is part of. + /// Returns both the contact point and its parent manifold. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # use rapier3d::geometry::ContactPair; + /// # let pair = ContactPair::default(); + /// if let Some((manifold, contact)) = pair.find_deepest_contact() { + /// let penetration_depth = -contact.dist; // Negative dist = penetration + /// println!("Deepest penetration: {} units", penetration_depth); + /// } + /// ``` #[profiling::function] pub fn find_deepest_contact(&self) -> Option<(&ContactManifold, &Contact)> { let mut deepest = None; diff --git a/src/geometry/interaction_graph.rs b/src/geometry/interaction_graph.rs index 4fb5054..869694e 100644 --- a/src/geometry/interaction_graph.rs +++ b/src/geometry/interaction_graph.rs @@ -64,15 +64,7 @@ impl InteractionGraph { /// When a node is removed, another node of the graph takes it place. This means that the `ColliderGraphIndex` /// of the collision object returned by this method will be equal to `id`. Thus if you maintain /// a map between `CollisionObjectSlabHandle` and `ColliderGraphIndex`, then you should update this - /// map to associate `id` to the handle returned by this method. For example: - /// - /// ```ignore - /// // Let `id` be the graph index of the collision object we want to remove. - /// if let Some(other_handle) = graph.remove_node(id) { - /// // The graph index of `other_handle` changed to `id` due to the removal. - /// map.insert(other_handle, id) ; - /// } - /// ``` + /// map to associate `id` to the handle returned by this method. #[must_use = "The graph index of the collision object returned by this method has been changed to `id`."] pub(crate) fn remove_node(&mut self, id: ColliderGraphIndex) -> Option { let _ = self.graph.remove_node(id); diff --git a/src/geometry/interaction_groups.rs b/src/geometry/interaction_groups.rs index d8941f7..e27a2b3 100644 --- a/src/geometry/interaction_groups.rs +++ b/src/geometry/interaction_groups.rs @@ -1,19 +1,41 @@ #![allow(clippy::bad_bit_mask)] // Clippy will complain about the bitmasks due to Group::NONE being 0. -/// Pairwise filtering using bit masks. +/// Collision filtering system that controls which colliders can interact with each other. /// -/// This filtering method is based on two 32-bit values: -/// - The interaction groups memberships. -/// - The interaction groups filter. +/// Think of this as "collision layers" in game engines. Each collider has: +/// - **Memberships**: What groups does this collider belong to? (up to 32 groups) +/// - **Filter**: What groups can this collider interact with? /// -/// An interaction is allowed between two filters `a` and `b` when two conditions -/// are met simultaneously: -/// - The groups membership of `a` has at least one bit set to `1` in common with the groups filter of `b`. -/// - The groups membership of `b` has at least one bit set to `1` in common with the groups filter of `a`. +/// Two colliders interact only if: +/// 1. Collider A's memberships overlap with Collider B's filter, AND +/// 2. Collider B's memberships overlap with Collider A's filter /// -/// In other words, interactions are allowed between two filter iff. the following condition is met: -/// ```ignore -/// (self.memberships & rhs.filter) != 0 && (rhs.memberships & self.filter) != 0 +/// # Common use cases +/// +/// - **Player vs. Enemy bullets**: Players in group 1, enemies in group 2. Player bullets +/// only hit group 2, enemy bullets only hit group 1. +/// - **Trigger zones**: Sensors that only detect specific object types. +/// +/// # Example +/// +/// ``` +/// # use rapier3d::geometry::{InteractionGroups, Group}; +/// // Player collider: in group 1, collides with groups 2 and 3 +/// let player_groups = InteractionGroups::new( +/// Group::GROUP_1, // I am in group 1 +/// Group::GROUP_2 | Group::GROUP_3 // I collide with groups 2 and 3 +/// ); +/// +/// // Enemy collider: in group 2, collides with group 1 +/// let enemy_groups = InteractionGroups::new( +/// Group::GROUP_2, // I am in group 2 +/// Group::GROUP_1 // I collide with group 1 +/// ); +/// +/// // These will collide because: +/// // - Player's membership (GROUP_1) is in enemy's filter (GROUP_1) ✓ +/// // - Enemy's membership (GROUP_2) is in player's filter (GROUP_2) ✓ +/// assert!(player_groups.test(enemy_groups)); /// ``` #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] @@ -34,12 +56,16 @@ impl InteractionGroups { } } - /// Allow interaction with everything. + /// Creates a filter that allows interactions with everything (default behavior). + /// + /// The collider is in all groups and collides with all groups. pub const fn all() -> Self { Self::new(Group::ALL, Group::ALL) } - /// Prevent all interactions. + /// Creates a filter that prevents all interactions. + /// + /// The collider won't collide with anything. Useful for temporarily disabled colliders. pub const fn none() -> Self { Self::new(Group::NONE, Group::NONE) } diff --git a/src/geometry/mod.rs b/src/geometry/mod.rs index 0314c10..00d86fc 100644 --- a/src/geometry/mod.rs +++ b/src/geometry/mod.rs @@ -74,33 +74,61 @@ bitflags::bitflags! { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Hash, Debug)] -/// Events occurring when two colliders start or stop colliding +/// Events triggered when two colliders start or stop touching. +/// +/// Receive these through an [`EventHandler`](crate::pipeline::EventHandler) implementation. +/// At least one collider must have [`ActiveEvents::COLLISION_EVENTS`](crate::pipeline::ActiveEvents::COLLISION_EVENTS) enabled. +/// +/// Use for: +/// - Trigger zones (player entered/exited area) +/// - Collectible items (player touched coin) +/// - Sound effects (objects started colliding) +/// - Game logic based on contact state +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # let h1 = ColliderHandle::from_raw_parts(0, 0); +/// # let h2 = ColliderHandle::from_raw_parts(1, 0); +/// # let event = CollisionEvent::Started(h1, h2, CollisionEventFlags::empty()); +/// match event { +/// CollisionEvent::Started(h1, h2, flags) => { +/// println!("Colliders {:?} and {:?} started touching", h1, h2); +/// if flags.contains(CollisionEventFlags::SENSOR) { +/// println!("At least one is a sensor!"); +/// } +/// } +/// CollisionEvent::Stopped(h1, h2, _) => { +/// println!("Colliders {:?} and {:?} stopped touching", h1, h2); +/// } +/// } +/// ``` pub enum CollisionEvent { - /// Event occurring when two colliders start colliding + /// Two colliders just started touching this frame. Started(ColliderHandle, ColliderHandle, CollisionEventFlags), - /// Event occurring when two colliders stop colliding. + /// Two colliders just stopped touching this frame. Stopped(ColliderHandle, ColliderHandle, CollisionEventFlags), } impl CollisionEvent { - /// Is this a `Started` collision event? + /// Returns `true` if this is a Started event (colliders began touching). pub fn started(self) -> bool { matches!(self, CollisionEvent::Started(..)) } - /// Is this a `Stopped` collision event? + /// Returns `true` if this is a Stopped event (colliders stopped touching). pub fn stopped(self) -> bool { matches!(self, CollisionEvent::Stopped(..)) } - /// The handle of the first collider involved in this collision event. + /// Returns the handle of the first collider in this collision. pub fn collider1(self) -> ColliderHandle { match self { Self::Started(h, _, _) | Self::Stopped(h, _, _) => h, } } - /// The handle of the second collider involved in this collision event. + /// Returns the handle of the second collider in this collision. pub fn collider2(self) -> ColliderHandle { match self { Self::Started(_, h, _) | Self::Stopped(_, h, _) => h, diff --git a/src/geometry/narrow_phase.rs b/src/geometry/narrow_phase.rs index ce61b76..64805a7 100644 --- a/src/geometry/narrow_phase.rs +++ b/src/geometry/narrow_phase.rs @@ -47,7 +47,18 @@ enum PairRemovalMode { Auto, } -/// The narrow-phase responsible for computing precise contact information between colliders. +/// The narrow-phase collision detector that computes precise contact points between colliders. +/// +/// After the broad-phase quickly filters out distant object pairs, the narrow-phase performs +/// detailed geometric computations to find exact: +/// - Contact points (where surfaces touch) +/// - Contact normals (which direction surfaces face) +/// - Penetration depths (how much objects overlap) +/// +/// You typically don't interact with this directly - it's managed by [`PhysicsPipeline::step`](crate::pipeline::PhysicsPipeline::step). +/// However, you can access it to query contact information or intersection state between specific colliders. +/// +/// **For spatial queries** (raycasts, shape casts), use [`QueryPipeline`](crate::pipeline::QueryPipeline) instead. #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Clone)] pub struct NarrowPhase { diff --git a/src/pipeline/collision_pipeline.rs b/src/pipeline/collision_pipeline.rs index 84cb3c1..a93f555 100644 --- a/src/pipeline/collision_pipeline.rs +++ b/src/pipeline/collision_pipeline.rs @@ -9,11 +9,20 @@ use crate::math::Real; use crate::pipeline::{EventHandler, PhysicsHooks}; use crate::{dynamics::RigidBodySet, geometry::ColliderSet}; -/// The collision pipeline, responsible for performing collision detection between colliders. +/// A collision detection pipeline that can be used without full physics simulation. /// -/// This structure only contains temporary data buffers. It can be dropped and replaced by a fresh -/// copy at any time. For performance reasons it is recommended to reuse the same physics pipeline -/// instance to benefit from the cached data. +/// This runs only collision detection (broad-phase + narrow-phase) without dynamics/forces. +/// Use when you want to detect collisions but don't need physics simulation. +/// +/// **For full physics**, use [`PhysicsPipeline`](crate::pipeline::PhysicsPipeline) instead which includes this internally. +/// +/// ## Use cases +/// +/// - Collision detection in a non-physics game +/// - Custom physics integration where you handle forces yourself +/// - Debugging collision detection separately from dynamics +/// +/// Like PhysicsPipeline, this only holds temporary buffers. Reuse the same instance for performance. // NOTE: this contains only workspace data, so there is no point in making this serializable. pub struct CollisionPipeline { broadphase_collider_pairs: Vec, diff --git a/src/pipeline/event_handler.rs b/src/pipeline/event_handler.rs index a20d0a7..ecc7299 100644 --- a/src/pipeline/event_handler.rs +++ b/src/pipeline/event_handler.rs @@ -6,13 +6,35 @@ use std::sync::mpsc::Sender; bitflags::bitflags! { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] - /// Flags affecting the events generated for this collider. + /// Flags that control which physics events are generated for a collider. + /// + /// By default, colliders don't generate events (for performance). Enable specific events + /// per-collider using these flags. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // Enable collision start/stop events for a trigger zone + /// let trigger = ColliderBuilder::cuboid(5.0, 5.0, 5.0) + /// .sensor(true) + /// .active_events(ActiveEvents::COLLISION_EVENTS) + /// .build(); + /// + /// // Enable force events for breakable glass + /// let glass = ColliderBuilder::cuboid(1.0, 2.0, 0.1) + /// .active_events(ActiveEvents::CONTACT_FORCE_EVENTS) + /// .contact_force_event_threshold(1000.0) + /// .build(); + /// ``` pub struct ActiveEvents: u32 { - /// If set, Rapier will call `EventHandler::handle_collision_event` - /// whenever relevant for this collider. + /// Enables `Started`/`Stopped` collision events for this collider. + /// + /// You'll receive events when this collider starts or stops touching others. const COLLISION_EVENTS = 0b0001; - /// If set, Rapier will call `EventHandler::handle_contact_force_event` - /// whenever relevant for this collider. + + /// Enables contact force events when forces exceed a threshold. + /// + /// You'll receive events when contact forces surpass `contact_force_event_threshold`. const CONTACT_FORCE_EVENTS = 0b0010; } } @@ -23,23 +45,64 @@ impl Default for ActiveEvents { } } -/// Trait implemented by structures responsible for handling events generated by the physics engine. +/// A callback interface for receiving physics events (collisions starting/stopping, contact forces). /// -/// Implementors of this trait will typically collect these events for future processing. +/// Implement this trait to get notified when: +/// - Two colliders start or stop touching ([`handle_collision_event`](Self::handle_collision_event)) +/// - Contact forces exceed a threshold ([`handle_contact_force_event`](Self::handle_contact_force_event)) +/// +/// # Common use cases +/// - Playing sound effects when objects collide +/// - Triggering game events (damage, pickups, checkpoints) +/// - Monitoring structural stress +/// - Detecting when specific objects touch +/// +/// # Built-in implementation +/// Use [`ChannelEventCollector`] to collect events into channels for processing after the physics step. +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # use rapier3d::geometry::ContactPair; +/// struct MyEventHandler; +/// +/// impl EventHandler for MyEventHandler { +/// fn handle_collision_event( +/// &self, +/// bodies: &RigidBodySet, +/// colliders: &ColliderSet, +/// event: CollisionEvent, +/// contact_pair: Option<&ContactPair>, +/// ) { +/// match event { +/// CollisionEvent::Started(h1, h2, _) => { +/// println!("Collision started between {:?} and {:?}", h1, h2); +/// } +/// CollisionEvent::Stopped(h1, h2, _) => { +/// println!("Collision ended between {:?} and {:?}", h1, h2); +/// } +/// } +/// } +/// # fn handle_contact_force_event(&self, _dt: Real, _bodies: &RigidBodySet, _colliders: &ColliderSet, _contact_pair: &ContactPair, _total_force_magnitude: Real) {} +/// } +/// ``` pub trait EventHandler: Send + Sync { - /// Handle a collision event. + /// Called when two colliders start or stop touching each other. /// - /// A collision event is emitted when the state of intersection between two colliders changes. - /// At least one of the involved colliders must have the `ActiveEvents::COLLISION_EVENTS` flag - /// set. + /// Collision events are triggered when intersection state changes (Started/Stopped). + /// At least one collider must have [`ActiveEvents::COLLISION_EVENTS`] enabled. /// /// # Parameters - /// * `event` - The collision event. - /// * `bodies` - The set of rigid-bodies. - /// * `colliders` - The set of colliders. - /// * `contact_pair` - The current state of contacts between the two colliders. This is set to `None` - /// if at least one of the collider is a sensor (in which case no contact information - /// is ever computed). + /// * `event` - Either `Started(h1, h2, flags)` or `Stopped(h1, h2, flags)` + /// * `bodies` - All rigid bodies (to look up body info) + /// * `colliders` - All colliders (to look up collider info) + /// * `contact_pair` - Detailed contact info (`None` for sensors, since they don't compute contacts) + /// + /// # Use cases + /// - Play collision sound effects + /// - Apply damage when objects hit + /// - Trigger game events (entering zones, picking up items) + /// - Track what's touching what fn handle_collision_event( &self, bodies: &RigidBodySet, @@ -48,17 +111,20 @@ pub trait EventHandler: Send + Sync { contact_pair: Option<&ContactPair>, ); - /// Handle a force event. + /// Called when contact forces exceed a threshold. /// - /// A force event is generated whenever the total force magnitude applied between two - /// colliders is `> Collider::contact_force_event_threshold` value of any of these - /// colliders with the `ActiveEvents::CONTACT_FORCE_EVENTS` flag set. + /// Triggered when the total force magnitude between two colliders exceeds the + /// [`Collider::contact_force_event_threshold`](crate::geometry::Collider::set_contact_force_event_threshold). + /// At least one collider must have [`ActiveEvents::CONTACT_FORCE_EVENTS`] enabled. /// - /// The "total force magnitude" here means "the sum of the magnitudes of the forces applied at - /// all the contact points in a contact pair". Therefore, if the contact pair involves two - /// forces `{0.0, 1.0, 0.0}` and `{0.0, -1.0, 0.0}`, then the total force magnitude tested - /// against the `contact_force_event_threshold` is `2.0` even if the sum of these forces is actually the - /// zero vector. + /// # Use cases + /// - Detect hard impacts (for damage, breaking objects) + /// - Monitor structural stress + /// - Trigger effects at certain force levels (sparks, cracks) + /// + /// # Parameters + /// * `total_force_magnitude` - Sum of magnitudes of all contact forces (not vector sum!) + /// Example: Two forces `[0, 100, 0]` and `[0, -100, 0]` → magnitude = 200 (not 0) fn handle_contact_force_event( &self, dt: Real, @@ -90,7 +156,28 @@ impl EventHandler for () { } } -/// A collision event handler that collects events into a channel. +/// A ready-to-use event handler that collects events into channels for later processing. +/// +/// Instead of processing events immediately during physics step, this collector sends them +/// to channels that you can poll from your game loop. This is the recommended approach. +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// use std::sync::mpsc::channel; +/// +/// let (collision_send, collision_recv) = channel(); +/// let (contact_force_send, contact_force_recv) = channel(); +/// let event_handler = ChannelEventCollector::new(collision_send, contact_force_send); +/// +/// // After physics step: +/// while let Ok(collision_event) = collision_recv.try_recv() { +/// match collision_event { +/// CollisionEvent::Started(h1, h2, _) => println!("Collision!"), +/// CollisionEvent::Stopped(h1, h2, _) => println!("Separated"), +/// } +/// } +/// ``` pub struct ChannelEventCollector { collision_event_sender: Sender, contact_force_event_sender: Sender, diff --git a/src/pipeline/physics_hooks.rs b/src/pipeline/physics_hooks.rs index 10d96cd..4ebd1a1 100644 --- a/src/pipeline/physics_hooks.rs +++ b/src/pipeline/physics_hooks.rs @@ -119,13 +119,35 @@ impl ContactModificationContext<'_> { bitflags::bitflags! { #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] - /// Flags affecting the behavior of the constraints solver for a given contact manifold. + /// Flags that enable custom collision filtering and contact modification callbacks. + /// + /// These are advanced features for custom physics behavior. Most users don't need hooks - + /// use [`InteractionGroups`](crate::geometry::InteractionGroups) for collision filtering instead. + /// + /// Hooks let you: + /// - Dynamically decide if two colliders should collide (beyond collision groups) + /// - Modify contact properties before solving (friction, restitution, etc.) + /// - Implement one-way platforms, custom collision rules + /// + /// # Example use cases + /// - One-way platforms (collide from above, pass through from below) + /// - Complex collision rules that can't be expressed with collision groups + /// - Dynamic friction/restitution based on impact velocity + /// - Ghost mode (player temporarily ignores certain objects) pub struct ActiveHooks: u32 { - /// If set, Rapier will call `PhysicsHooks::filter_contact_pair` whenever relevant. + /// Enables `PhysicsHooks::filter_contact_pair` callback for this collider. + /// + /// Lets you programmatically decide if contact should be computed and resolved. const FILTER_CONTACT_PAIRS = 0b0001; - /// If set, Rapier will call `PhysicsHooks::filter_intersection_pair` whenever relevant. + + /// Enables `PhysicsHooks::filter_intersection_pair` callback for this collider. + /// + /// For sensor/intersection filtering (similar to contact filtering but for sensors). const FILTER_INTERSECTION_PAIR = 0b0010; - /// If set, Rapier will call `PhysicsHooks::modify_solver_contact` whenever relevant. + + /// Enables `PhysicsHooks::modify_solver_contacts` callback for this collider. + /// + /// Lets you modify contact properties (friction, restitution, etc.) before solving. const MODIFY_SOLVER_CONTACTS = 0b0100; } } diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs index b507820..972fd0a 100644 --- a/src/pipeline/physics_pipeline.rs +++ b/src/pipeline/physics_pipeline.rs @@ -18,16 +18,24 @@ use crate::pipeline::{EventHandler, PhysicsHooks}; use crate::prelude::ModifiedRigidBodies; use {crate::dynamics::RigidBodySet, crate::geometry::ColliderSet}; -/// The physics pipeline, responsible for stepping the whole physics simulation. +/// The main physics simulation engine that runs your physics world forward in time. /// -/// This structure only contains temporary data buffers. It can be dropped and replaced by a fresh -/// copy at any time. For performance reasons it is recommended to reuse the same physics pipeline -/// instance to benefit from the cached data. +/// Think of this as the "game loop" for your physics simulation. Each frame, you call +/// [`PhysicsPipeline::step`] to advance the simulation by one timestep. This structure +/// handles all the complex physics calculations: detecting collisions between objects, +/// resolving contacts so objects don't overlap, and updating positions and velocities. /// -/// Rapier relies on a time-stepping scheme. Its force computations -/// uses two solvers: -/// - A velocity based solver based on PGS which computes forces for contact and joint constraints. -/// - A position based solver based on non-linear PGS which performs constraint stabilization (i.e. correction of errors like penetrations). +/// ## Performance note +/// This structure only contains temporary working memory (scratch buffers). You can create +/// a new one anytime, but it's more efficient to reuse the same instance across frames +/// since Rapier can reuse allocated memory. +/// +/// ## How it works (simplified) +/// Rapier uses a time-stepping approach where each step involves: +/// 1. **Collision detection**: Find which objects are touching or overlapping +/// 2. **Constraint solving**: Calculate forces to prevent overlaps and enforce joint constraints +/// 3. **Integration**: Update object positions and velocities based on forces and gravity +/// 4. **Position correction**: Fix any remaining overlaps that might have occurred // NOTE: this contains only workspace data, so there is no point in making this serializable. pub struct PhysicsPipeline { /// Counters used for benchmarking only. @@ -53,7 +61,10 @@ fn check_pipeline_send_sync() { } impl PhysicsPipeline { - /// Initializes a new physics pipeline. + /// Creates a new physics pipeline. + /// + /// Call this once when setting up your physics world. The pipeline can be reused + /// across multiple frames for better performance. pub fn new() -> PhysicsPipeline { PhysicsPipeline { counters: Counters::new(true), @@ -407,7 +418,56 @@ impl PhysicsPipeline { } } - /// Executes one timestep of the physics simulation. + /// Advances the physics simulation by one timestep. + /// + /// This is the main function you'll call every frame in your game loop. It performs all + /// physics calculations: collision detection, constraint solving, and updating object positions. + /// + /// # Parameters + /// + /// * `gravity` - The gravity vector applied to all dynamic bodies (e.g., `vector![0.0, -9.81, 0.0]` for Earth gravity pointing down) + /// * `integration_parameters` - Controls the simulation quality and timestep size (typically 60 Hz = 1/60 second per step) + /// * `islands` - Internal system that groups connected objects together for efficient solving (automatically managed) + /// * `broad_phase` - Fast collision detection phase that filters out distant object pairs (automatically managed) + /// * `narrow_phase` - Precise collision detection that computes exact contact points (automatically managed) + /// * `bodies` - Your collection of rigid bodies (the physical objects that move and collide) + /// * `colliders` - The collision shapes attached to your bodies (boxes, spheres, meshes, etc.) + /// * `impulse_joints` - Regular joints connecting bodies (hinges, sliders, etc.) + /// * `multibody_joints` - Articulated joints for robot-like structures (optional, can be empty) + /// * `ccd_solver` - Continuous collision detection to prevent fast objects from tunneling through thin walls + /// * `hooks` - Optional callbacks to customize collision filtering and contact modification + /// * `events` - Optional handler to receive collision events (when objects start/stop touching) + /// + /// # Example + /// + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let mut impulse_joints = ImpulseJointSet::new(); + /// # let mut multibody_joints = MultibodyJointSet::new(); + /// # let mut islands = IslandManager::new(); + /// # let mut broad_phase = BroadPhaseBvh::new(); + /// # let mut narrow_phase = NarrowPhase::new(); + /// # let mut ccd_solver = CCDSolver::new(); + /// # let mut physics_pipeline = PhysicsPipeline::new(); + /// # let integration_parameters = IntegrationParameters::default(); + /// // In your game loop: + /// physics_pipeline.step( + /// &vector![0.0, -9.81, 0.0], // Gravity pointing down + /// &integration_parameters, + /// &mut islands, + /// &mut broad_phase, + /// &mut narrow_phase, + /// &mut bodies, + /// &mut colliders, + /// &mut impulse_joints, + /// &mut multibody_joints, + /// &mut ccd_solver, + /// &(), // No custom hooks + /// &(), // No event handler + /// ); + /// ``` pub fn step( &mut self, gravity: &Vector, diff --git a/src/pipeline/query_pipeline.rs b/src/pipeline/query_pipeline.rs index 0ee8144..d3c2bfd 100644 --- a/src/pipeline/query_pipeline.rs +++ b/src/pipeline/query_pipeline.rs @@ -9,9 +9,37 @@ use parry::query::details::{NormalConstraints, ShapeCastOptions}; use parry::query::{NonlinearRigidMotion, QueryDispatcher, RayCast, ShapeCastHit}; use parry::shape::{CompositeShape, CompositeShapeRef, FeatureId, Shape, TypedCompositeShape}; -/// The query pipeline responsible for running scene queries on the physics world. +/// A query system for performing spatial queries on your physics world (raycasts, shape casts, intersections). /// -/// This structure is generally obtained by calling [`BroadPhaseBvh::as_query_pipeline_mut`]. +/// Think of this as a "search engine" for your physics world. Use it to answer questions like: +/// - "What does this ray hit?" +/// - "What colliders are near this point?" +/// - "If I move this shape, what will it collide with?" +/// +/// Get a QueryPipeline from your [`BroadPhaseBvh`] using [`as_query_pipeline()`](BroadPhaseBvh::as_query_pipeline). +/// +/// # Example +/// ``` +/// # use rapier3d::prelude::*; +/// # let mut bodies = RigidBodySet::new(); +/// # let mut colliders = ColliderSet::new(); +/// # let broad_phase = BroadPhaseBvh::new(); +/// # let narrow_phase = NarrowPhase::new(); +/// # let ground = bodies.insert(RigidBodyBuilder::fixed()); +/// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies); +/// let query_pipeline = broad_phase.as_query_pipeline( +/// narrow_phase.query_dispatcher(), +/// &bodies, +/// &colliders, +/// QueryFilter::default() +/// ); +/// +/// // Cast a ray downward +/// let ray = Ray::new(point![0.0, 10.0, 0.0], vector![0.0, -1.0, 0.0]); +/// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, false) { +/// println!("Hit collider {:?} at distance {}", handle, toi); +/// } +/// ``` #[derive(Copy, Clone)] pub struct QueryPipeline<'a> { /// The query dispatcher for running geometric queries on leaf geometries. @@ -152,17 +180,38 @@ impl<'a> QueryPipeline<'a> { Self { filter, ..self } } - /// Find the closest intersection between a ray and a set of colliders. + /// Casts a ray through the world and returns the first collider it hits. + /// + /// This is one of the most common operations - use it for line-of-sight checks, + /// projectile trajectories, mouse picking, laser beams, etc. + /// + /// Returns `Some((handle, distance))` if the ray hits something, where: + /// - `handle` is which collider was hit + /// - `distance` is how far along the ray the hit occurred (time-of-impact) /// /// # Parameters - /// * `colliders` - The set of colliders taking part in this pipeline. - /// * `ray`: the ray to cast. - /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively - /// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray. - /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if - /// it starts inside a shape. If this `false` then the ray will hit the shape's boundary - /// even if its starts inside of it. - /// * `filter`: set of rules used to determine which collider is taken into account by this scene query. + /// * `ray` - The ray to cast (origin + direction). Create with `Ray::new(origin, direction)` + /// * `max_toi` - Maximum distance to check. Use `Real::MAX` for unlimited range + /// * `solid` - If `true`, detects hits even if the ray starts inside a shape. If `false`, + /// the ray "passes through" from the inside until it exits + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let broad_phase = BroadPhaseBvh::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// // Raycast downward from (0, 10, 0) + /// let ray = Ray::new(point![0.0, 10.0, 0.0], vector![0.0, -1.0, 0.0]); + /// if let Some((handle, toi)) = query_pipeline.cast_ray(&ray, Real::MAX, true) { + /// let hit_point = ray.origin + ray.dir * toi; + /// println!("Hit at {:?}, distance = {}", hit_point, toi); + /// } + /// ``` #[profiling::function] pub fn cast_ray( &self, @@ -175,17 +224,33 @@ impl<'a> QueryPipeline<'a> { .and_then(|hit| self.id_to_handle(hit)) } - /// Find the closest intersection between a ray and a set of colliders. + /// Casts a ray and returns detailed information about the hit (including surface normal). /// - /// # Parameters - /// * `colliders` - The set of colliders taking part in this pipeline. - /// * `ray`: the ray to cast. - /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively - /// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray. - /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if - /// it starts inside a shape. If this `false` then the ray will hit the shape's boundary - /// even if its starts inside of it. - /// * `filter`: set of rules used to determine which collider is taken into account by this scene query. + /// Like [`cast_ray()`](Self::cast_ray), but returns more information useful for things like: + /// - Decals (need surface normal to orient the texture) + /// - Bullet holes (need to know what part of the mesh was hit) + /// - Ricochets (need normal to calculate bounce direction) + /// + /// Returns `Some((handle, intersection))` where `intersection` contains: + /// - `toi`: Distance to impact + /// - `normal`: Surface normal at the hit point + /// - `feature`: Which geometric feature was hit (vertex, edge, face) + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let broad_phase = BroadPhaseBvh::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// # let ray = Ray::new(point![0.0, 10.0, 0.0], vector![0.0, -1.0, 0.0]); + /// if let Some((handle, hit)) = query_pipeline.cast_ray_and_get_normal(&ray, 100.0, true) { + /// println!("Hit at distance {}, surface normal: {:?}", hit.time_of_impact, hit.normal); + /// } + /// ``` #[profiling::function] pub fn cast_ray_and_get_normal( &self, @@ -198,16 +263,31 @@ impl<'a> QueryPipeline<'a> { .and_then(|hit| self.id_to_handle(hit)) } - /// Iterates through all the colliders intersecting a given ray. + /// Returns ALL colliders that a ray passes through (not just the first). /// - /// # Parameters - /// * `colliders` - The set of colliders taking part in this pipeline. - /// * `ray`: the ray to cast. - /// * `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively - /// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray. - /// * `solid`: if this is `true` an impact at time 0.0 (i.e. at the ray origin) is returned if - /// it starts inside a shape. If this `false` then the ray will hit the shape's boundary - /// even if its starts inside of it. + /// Unlike [`cast_ray()`](Self::cast_ray) which stops at the first hit, this returns + /// every collider along the ray's path. Useful for: + /// - Penetrating weapons that go through multiple objects + /// - Checking what's in a line (e.g., visibility through glass) + /// - Counting how many objects are between two points + /// + /// Returns an iterator of `(handle, collider, intersection)` tuples. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let broad_phase = BroadPhaseBvh::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// # let ray = Ray::new(point![0.0, 10.0, 0.0], vector![0.0, -1.0, 0.0]); + /// for (handle, collider, hit) in query_pipeline.intersect_ray(ray, 100.0, true) { + /// println!("Ray passed through {:?} at distance {}", handle, hit.time_of_impact); + /// } + /// ``` #[profiling::function] pub fn intersect_ray( &'a self, @@ -233,15 +313,39 @@ impl<'a> QueryPipeline<'a> { }) } - /// Find the projection of a point on the closest collider. + /// Finds the closest point on any collider to the given point. + /// + /// Returns the collider and information about where on its surface the closest point is. + /// Useful for: + /// - Finding nearest cover/obstacle + /// - Snap-to-surface mechanics + /// - Distance queries /// /// # Parameters - /// * `point` - The point to project. - /// * `solid` - If this is set to `true` then the collider shapes are considered to - /// be plain (if the point is located inside of a plain shape, its projection is the point - /// itself). If it is set to `false` the collider shapes are considered to be hollow - /// (if the point is located inside of an hollow shape, it is projected on the shape's - /// boundary). + /// * `solid` - If `true`, a point inside a shape projects to itself. If `false`, it projects + /// to the nearest point on the shape's boundary + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let params = IntegrationParameters::default(); + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let mut broad_phase = BroadPhaseBvh::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # let ground_collider = ColliderBuilder::cuboid(10.0, 0.1, 10.0).build(); + /// # let ground_aabb = ground_collider.compute_aabb(); + /// # let collider_handle = colliders.insert_with_parent(ground_collider, ground, &mut bodies); + /// # broad_phase.set_aabb(¶ms, collider_handle, ground_aabb); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// let point = point![5.0, 0.0, 0.0]; + /// if let Some((handle, projection)) = query_pipeline.project_point(&point, std::f32::MAX, true) { + /// println!("Closest collider: {:?}", handle); + /// println!("Closest point: {:?}", projection.point); + /// println!("Distance: {}", (point - projection.point).norm()); + /// } + /// ``` #[profiling::function] pub fn project_point( &self, @@ -252,10 +356,28 @@ impl<'a> QueryPipeline<'a> { self.id_to_handle(CompositeShapeRef(self).project_local_point(point, solid)) } - /// Find all the colliders containing the given point. + /// Returns ALL colliders that contain the given point. /// - /// # Parameters - /// * `point` - The point used for the containment test. + /// A point is "inside" a collider if it's within its volume. Useful for: + /// - Detecting what area/trigger zones a point is in + /// - Checking if a position is inside geometry + /// - Finding all overlapping volumes at a location + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let broad_phase = BroadPhaseBvh::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # colliders.insert_with_parent(ColliderBuilder::ball(5.0), ground, &mut bodies); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// let point = point![0.0, 0.0, 0.0]; + /// for (handle, collider) in query_pipeline.intersect_point(point) { + /// println!("Point is inside {:?}", handle); + /// } + /// ``` #[profiling::function] pub fn intersect_point( &'a self, @@ -317,17 +439,45 @@ impl<'a> QueryPipeline<'a> { }) } - /// Casts a shape at a constant linear velocity and retrieve the first collider it hits. + /// Sweeps a shape through the world to find what it would collide with. /// - /// This is similar to ray-casting except that we are casting a whole shape instead of just a - /// point (the ray origin). In the resulting `TOI`, witness and normal 1 refer to the world - /// collider, and are in world space. + /// Like raycasting, but instead of a thin ray, you're moving an entire shape (sphere, box, etc.) + /// through space. This is also called "shape casting" or "sweep testing". Useful for: + /// - Predicting where a moving object will hit something + /// - Checking if a movement is valid before executing it + /// - Thick raycasts (e.g., character controller collision prediction) + /// - Area-of-effect scanning along a path + /// + /// Returns the first collision: `(collider_handle, hit_details)` where hit contains + /// time-of-impact, witness points, and surface normal. /// /// # Parameters - /// * `shape_pos` - The initial position of the shape to cast. - /// * `shape_vel` - The constant velocity of the shape to cast (i.e. the cast direction). - /// * `shape` - The shape to cast. - /// * `options` - Options controlling the shape cast limits and behavior. + /// * `shape_pos` - Starting position/orientation of the shape + /// * `shape_vel` - Direction and speed to move the shape (velocity vector) + /// * `shape` - The shape to sweep (ball, cuboid, capsule, etc.) + /// * `options` - Maximum distance, collision filtering, etc. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// # use rapier3d::parry::{query::ShapeCastOptions, shape::Ball}; + /// # let mut bodies = RigidBodySet::new(); + /// # let mut colliders = ColliderSet::new(); + /// # let narrow_phase = NarrowPhase::new(); + /// # let broad_phase = BroadPhaseBvh::new(); + /// # let ground = bodies.insert(RigidBodyBuilder::fixed()); + /// # colliders.insert_with_parent(ColliderBuilder::cuboid(10.0, 0.1, 10.0), ground, &mut bodies); + /// # let query_pipeline = broad_phase.as_query_pipeline(narrow_phase.query_dispatcher(), &bodies, &colliders, QueryFilter::default()); + /// // Sweep a sphere downward + /// let shape = Ball::new(0.5); + /// let start_pos = Isometry::translation(0.0, 10.0, 0.0); + /// let velocity = vector![0.0, -1.0, 0.0]; + /// let options = ShapeCastOptions::default(); + /// + /// if let Some((handle, hit)) = query_pipeline.cast_shape(&start_pos, &velocity, &shape, options) { + /// println!("Shape would hit {:?} at time {}", handle, hit.time_of_impact); + /// } + /// ``` #[profiling::function] pub fn cast_shape( &self, @@ -411,24 +561,35 @@ impl<'a> QueryPipeline<'a> { bitflags::bitflags! { #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] - /// Flags for excluding whole sets of colliders from a scene query. + /// Flags for filtering spatial queries by body type or sensor status. + /// + /// Use these to quickly exclude categories of colliders from raycasts and other queries. + /// + /// # Example + /// ``` + /// # use rapier3d::prelude::*; + /// // Raycast that only hits dynamic objects (ignore walls/floors) + /// let filter = QueryFilter::from(QueryFilterFlags::ONLY_DYNAMIC); + /// + /// // Find only trigger zones, not solid geometry + /// let filter = QueryFilter::from(QueryFilterFlags::EXCLUDE_SOLIDS); + /// ``` pub struct QueryFilterFlags: u32 { - /// Exclude from the query any collider attached to a fixed rigid-body and colliders with no rigid-body attached. + /// Excludes fixed bodies and standalone colliders. const EXCLUDE_FIXED = 1 << 0; - /// Exclude from the query any collider attached to a kinematic rigid-body. + /// Excludes kinematic bodies. const EXCLUDE_KINEMATIC = 1 << 1; - /// Exclude from the query any collider attached to a dynamic rigid-body. + /// Excludes dynamic bodies. const EXCLUDE_DYNAMIC = 1 << 2; - /// Exclude from the query any collider that is a sensor. + /// Excludes sensors (trigger zones). const EXCLUDE_SENSORS = 1 << 3; - /// Exclude from the query any collider that is not a sensor. + /// Excludes solid colliders (only hit sensors). const EXCLUDE_SOLIDS = 1 << 4; - /// Excludes all colliders not attached to a dynamic rigid-body. + /// Only includes dynamic bodies. const ONLY_DYNAMIC = Self::EXCLUDE_FIXED.bits() | Self::EXCLUDE_KINEMATIC.bits(); - /// Excludes all colliders not attached to a kinematic rigid-body. + /// Only includes kinematic bodies. const ONLY_KINEMATIC = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_FIXED.bits(); - /// Exclude all colliders attached to a non-fixed rigid-body - /// (this will not exclude colliders not attached to any rigid-body). + /// Only includes fixed bodies (excluding standalone colliders). const ONLY_FIXED = Self::EXCLUDE_DYNAMIC.bits() | Self::EXCLUDE_KINEMATIC.bits(); } } @@ -469,20 +630,47 @@ impl QueryFilterFlags { } } -/// A filter that describes what collider should be included or excluded from a scene query. +/// Filtering rules for spatial queries (raycasts, shape casts, etc.). +/// +/// Controls which colliders should be included/excluded from query results. +/// By default, all colliders are included. +/// +/// # Common filters +/// +/// ``` +/// # use rapier3d::prelude::*; +/// # let player_collider = ColliderHandle::from_raw_parts(0, 0); +/// # let enemy_groups = InteractionGroups::all(); +/// // Only hit dynamic objects (ignore static walls) +/// let filter = QueryFilter::only_dynamic(); +/// +/// // Hit everything except the player's own collider +/// let filter = QueryFilter::default() +/// .exclude_collider(player_collider); +/// +/// // Raycast that only hits enemies (using collision groups) +/// let filter = QueryFilter::default() +/// .groups(enemy_groups); +/// +/// // Custom filtering with a closure +/// let filter = QueryFilter::default() +/// .predicate(&|handle, collider| { +/// // Only hit colliders with user_data > 100 +/// collider.user_data > 100 +/// }); +/// ``` #[derive(Copy, Clone, Default)] pub struct QueryFilter<'a> { - /// Flags indicating what particular type of colliders should be excluded from the scene query. + /// Flags for excluding fixed/kinematic/dynamic bodies or sensors/solids. pub flags: QueryFilterFlags, - /// If set, only colliders with collision groups compatible with this one will - /// be included in the scene query. + /// If set, only colliders with compatible collision groups are included. pub groups: Option, - /// If set, this collider will be excluded from the scene query. + /// If set, this specific collider is excluded. pub exclude_collider: Option, - /// If set, any collider attached to this rigid-body will be excluded from the scene query. + /// If set, all colliders attached to this body are excluded. pub exclude_rigid_body: Option, - /// If set, any collider for which this closure returns false will be excluded from the scene query. - #[allow(clippy::type_complexity)] // Type doesn’t look really complex? + /// Custom filtering function - collider included only if this returns `true`. + #[allow(clippy::type_complexity)] pub predicate: Option<&'a dyn Fn(ColliderHandle, &Collider) -> bool>, }