Parry

repository·master·Indexed 21 days ago

https://github.com/dimforge/parry

A 2D and 3D geometric library written in Rust for high-performance collision detection and geometric queries. It provides tools for bounding volumes, mass properties for rigid body physics, proximity queries via ClosestPoints, and contact manifold management for stable physics simulations. The library supports SIMD acceleration via the simba crate.

Tokens
12.5K
Snippets
29
Records
36
Agent score
74%

What's inside parry

  1. Handle closest points query results with ClosestPoints

    master

    The ClosestPoints enum represents the outcome of a proximity query between two shapes, considering a maximum search distance (max_dist). It is useful for AI perception, trigger zones, and physics optimizations.

    Variants

    • Intersecting: The shapes are overlapping or touching. For detailed contact information like penetration depth and normals, use the contact function instead.
    • WithinMargin(Vector, Vector): The shapes are separated but within the specified max_dist. It contains two world-space vectors:
      • The first vector is the closest point on the surface of the first shape.
      • The second vector is the closest point on the surface of the second shape.
    • Disjoint: The shapes are separated by more than the specified max_dist. No closest points are computed, which saves computation time.

    Use Cases

    • AI perception: Finding the nearest enemy within a detection range.
    • Trigger zones: Detecting when objects approach a specific threshold distance.
    • LOD systems: Computing detailed interactions only for nearby objects.
    • Physics optimization: Skipping expensive computations for distant pairs.
    # #[cfg(all(feature = "dim3", feature = "f32"))] {
    use parry3d::query::{closest_points, ClosestPoints};
    use parry3d::shape::Ball;
    use parry3d::math::Pose;
    
    let ball1 = Ball::new(1.0);
    let ball2 = Ball::new(1.0);
    
    let pos1 = Pose::translation(0.0, 0.0, 0.0);
    let pos2 = Pose::translation(5.0, 0.0, 0.0);
    
    // Search for closest points within 10.0 units
    let result = closest_points(&pos1, &ball1, &pos2, &ball2, 10.0).unwrap();
    
    match result {
        ClosestPoints::Intersecting => {
            println!("Shapes are overlapping!");
        }
        ClosestPoints::WithinMargin(pt1, pt2) => {
            println!("Closest point on shape1: {:?}", pt1);
            println!("Closest point on shape2: {:?}", pt2);
            let distance = (pt2 - pt1).length();
            println!("Distance between shapes: {}", distance);
        }
        ClosestPoints::Disjoint => {
            println!("Shapes are more than 10.0 units apart");
        }
    }
    # }
  2. Combine MassProperties for compound objects

    master

    You can combine multiple MassProperties instances using addition (+) or the sum() iterator method. This is useful for creating compound shapes where the total mass, center of mass, and inertia are calculated based on the constituent parts.

    Note: When adding properties, the center of mass and inertia are automatically adjusted using the parallel axis theorem to ensure the resulting MassProperties is physically accurate.

    # #[cfg(all(feature = "dim3", feature = "f32"))]
    use parry3d::mass_properties::MassProperties;
    use parry3d::shape::{Ball, Cuboid, Shape};
    use parry3d::math::Vector;
    
    let ball = Ball::new(1.0);
    let cuboid = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
    
    let ball_props = ball.mass_properties(1.0);
    let cuboid_props = cuboid.mass_properties(1.0);
    
    // Combined properties (ball + cuboid)
    let combined = ball_props + cuboid_props;
    
    // Total mass is sum of individual masses
    let total_mass = combined.mass();
    println!("Combined mass: {}", total_mass);
  3. Configure SIMD features in Parry

    master

    Parry supports SIMD (Single Instruction, Multiple Data) acceleration via the simba crate. The width of the SIMD lanes depends on the enabled features:

    • 4-lane SIMD: The default width for f32 types.
    • 8-lane SIMD: Available for f32 only by enabling the simd8 feature. Note that simd8 is incompatible with the enhanced-determinism feature because it breaks cross-platform determinism.
    • f64 SIMD: Always uses 4-lane SIMD, regardless of the simd8 setting.

    To achieve actual 256-bit instruction emission for 8-lane SIMD, you must target an AVX-enabled CPU using RUSTFLAGS="-C target-feature=+avx2,+fma" or -C target-cpu=native.

  4. Understand TrackedContact and contact point data

    master

    A TrackedContact<Data> represents a single point of contact between two shapes. It is stored in local space (the coordinate system of each shape) to ensure stability as shapes move or rotate. To obtain world-space positions, you must transform the local points by the respective shape's Pose.

    Contact Point Fields

    • local_p1: Contact point in the local space of the first shape.
    • local_p2: Contact point in the local space of the second shape.
    • dist: Signed distance.
      • dist < 0.0: Penetrating (overlapping). The absolute value is the penetration depth.
      • dist == 0.0: Exactly touching.
      • dist > 0.0: Separated (used in contact prediction).
    • fid1 / fid2: PackedFeatureId identifying the specific geometric feature (vertex, edge, or face) in contact. These are used to track contacts across frames.
    • data: User-defined data (e.g., for accumulated impulses or contact age).
    // Example: Creating a new tracked contact manually
    let contact = TrackedContact::<()>::new(
        Vector::new(1.0, 0.0, 0.0),  // Point on shape 1
        Vector::new(-1.0, 0.0, 0.0), // Point on shape 2
        PackedFeatureId::face(0),    // Face 0 of shape 1
        PackedFeatureId::face(0),    // Face 0 of shape 2
        -0.1,                         // Penetration depth of 0.1
    );
  5. Use MassProperties to define rigid body physics

    master

    The MassProperties struct defines how an object responds to forces and torques. It includes the mass, center of mass, and angular inertia.

    Physics engines typically use inverse values for stability and performance:

    • inv_mass: $1/\text{mass}$. A value of 0.0 represents infinite mass (an immovable/static object).
    • inv_principal_inertia: Inverse angular inertia along principal axes. Zero components indicate infinite inertia (no rotation) along that axis.

    In 3D, MassProperties also includes principal_inertia_local_frame, which is the rotation from local coordinates to the principal inertia axes where the inertia tensor is diagonal.

    # #[cfg(all(feature = "dim3", feature = "f32"))]
    use parry3d::mass_properties::MassProperties;
    use parry3d::shape::{Ball, Shape};
    use parry3d::math::Vector;
    
    // Compute mass properties for a unit ball with density 1.0
    let ball = Ball::new(1.0);
    let props = ball.mass_properties(1.0);
    
    // Mass of a unit sphere with density 1.0
    let mass = props.mass();
    println!("Mass: {}", mass);
    
    // Center of mass (at origin for a ball)
    assert_eq!(props.local_com, Vector::ZERO);
    
    // For simulation, use inverse values
    if props.inv_mass > 0.0 {
        // Object has finite mass - can be moved
        println!("Can apply forces");
    } else {
        // Object has infinite mass - immovable (like terrain)
        println!("Static/kinematic object");
    }
  6. Identify point locations on a Tetrahedron

    master

    The TetrahedronPointLocation enum describes where a point lies relative to the tetrahedron's features. This is useful for understanding spatial relationships in collision detection.

    Variants

    • OnVertex(u32): Point is at a vertex (0=a, 1=b, 2=c, 3=d).
    • OnEdge(u32, [Real; 2]): Point is on an edge with barycentric weights [u, v] where u + v = 1.0.
    • OnFace(u32, [Real; 3]): Point is on a face interior with barycentric weights [u, v, w] where u + v + w = 1.0.
    • OnSolid: Point is inside the volume.

    Methods

    • barycentric_coordinates(): Returns Some([wa, wb, wc, wd]) for points on vertices, edges, or faces, and None for OnSolid points.
    • same_feature_as(&other): Returns true if both locations refer to the same geometric feature (e.g., the same vertex index or the same edge index).
    use parry3d::shape::TetrahedronPointLocation;
    
    // Check if a point location is on a specific vertex
    let location = TetrahedronPointLocation::OnVertex(0);
    
    // Get the 4D barycentric coordinates from a location
    if let Some(bcoords) = location.barycentric_coordinates() {
        // bcoords = [wa, wb, wc, wd]
    }
    
    // Check if two locations share the same feature
    let loc1 = TetrahedronPointLocation::OnEdge(0, [0.5, 0.5]);
    let loc2 = TetrahedronPointLocation::OnEdge(0, [0.2, 0.8]);
    assert!(loc1.same_feature_as(&loc2)); // Both are on edge 0
  7. Use SegmentPointLocation to find points on a segment

    master

    The SegmentPointLocation enum describes where a point lies relative to a Segment. This is typically used in projection queries to return exactly where a point landed.

    • OnVertex(u32): The point is at an endpoint. 0 represents endpoint a, and 1 represents endpoint b.
    • OnEdge([Real; 2]): The point is in the interior. It contains barycentric coordinates [u, v] where u + v = 1.0 and the position is calculated as a * u + b * v.

    You can retrieve the barycentric coordinates for any location using the .barycentric_coordinates() method.

    # #[cfg(all(feature = "dim3", feature = "f32"))]
    use parry3d::shape::SegmentPointLocation;
    
    // Vector at first vertex
    let loc = SegmentPointLocation::OnVertex(0);
    assert_eq!(loc.barycentric_coordinates(), [1.0, 0.0]);
    
    // Vector at second vertex
    let loc = SegmentPointLocation::OnVertex(1);
    assert_eq!(loc.barycentric_coordinates(), [0.0, 1.0]);
    
    // Vector halfway along the segment
    let loc = SegmentPointLocation::OnEdge([0.5, 0.5]);
    assert_eq!(loc.barycentric_coordinates(), [0.5, 0.5]);
  8. Handle shape splitting with SplitResult

    master

    When performing a plane-splitting operation on a geometric shape (like an Aabb, Segment, or TriMesh), the result is returned as a SplitResult<T>. This enum describes whether the shape was cut into two pieces or if it lies entirely on one side of the plane.

    Half-Space Logic

    Given a plane with normal n and bias b, a point p is classified as:

    • Negative half-space: n · p < b (behind the plane)
    • Positive half-space: n · p > b (in front of the plane)
    • On the plane: n · p ≈ b (within epsilon tolerance)

    Variants

    • Pair(T, T): The shape was split. The first element is the piece in the negative half-space, and the second is the piece in the positive half-space. For closed meshes, these pieces are typically capped with new geometry to maintain valid closed shapes.
    • Negative: The entire shape is in the negative half-space.
    • Positive: The entire shape is in the positive half-space.
    // Example: Splitting an AABB
    use parry3d::bounding_volume::Aabb;
    use parry3d::math::Vector;
    use parry3d::query::SplitResult;
    
    let aabb = Aabb::new(Vector::new(0.0, 0.0, 0.0), Vector::new(10.0, 10.0, 10.0));
    
    // Split along X-axis at x = 5.0
    match aabb.canonical_split(0, 5.0, 1e-6) {
        SplitResult::Pair(left, right) => {
            println!("AABB split into two pieces");
            println!("Left AABB: {:?}", left);
            println!("Right AABB: {:?}", right);
        }
        SplitResult::Negative => {
            println!("AABB is entirely on the negative side (x < 5.0)");
        }
        SplitResult::Positive => {
            println!("AABB is entirely on the positive side (x > 5.0)");
        }
    }
  9. Use ContactManifold for stable physics simulation

    master

    A ContactManifold<ManifoldData, ContactData> groups contact points that share the same contact normal and kinematics. This is essential for stable physics because it provides a coherent representation of contact patches (e.g., the four corners of a box sitting on a plane).

    Key Properties

    • local_n1: The contact normal in the local space of the first shape.
    • local_n2: The contact normal in the local space of the second shape.
    • subshape1 / subshape2: Indices of the subshapes involved (useful for composite shapes like meshes).
    • data: User-defined data for the entire manifold.

    Normal Convention

    The normal points from the first shape toward the second. To separate them:

    • Move shape 1 in the direction of -local_n1.
    • Move shape 2 in the direction of local_n2.
  10. Handle plane intersections with IntersectResult

    master

    The IntersectResult<T> enum represents the outcome of computing the intersection between a geometric shape and a plane. Unlike SplitResult (which cuts a shape into pieces), IntersectResult produces the geometry that lies exactly on the plane (within epsilon tolerance).

    Use Cases

    • Cross-sectional analysis: Computing 2D slices of 3D geometry.
    • Contour generation: Finding outlines at specific heights.
    • Visualization: Displaying cutting planes through complex geometry.
    • CAD/CAM: Generating toolpaths.

    Variants

    • Intersect(T): The operation yielded geometry on the plane. For TriMesh, this is typically a Polyline representing the cross-section outline. This may consist of multiple disconnected loops if the mesh has holes or separate parts.
    • Negative: The shape is entirely in the negative half-space (behind/below the plane).
    • Positive: The shape is entirely in the positive half-space (in front of/above the plane).
    // Example: Computing a Mesh Cross-Section
    use parry3d::shape::TriMesh;
    use parry3d::math::Vector;
    use parry3d::query::IntersectResult;
    
    // Create a simple tetrahedron mesh
    let vertices = vec![
        Vector::new(0.0, 0.0, 0.0),
        Vector::new(1.0, 0.0, 0.0),
        Vector::new(0.5, 1.0, 0.0),
        Vector::new(0.5, 0.5, 1.0),
    ];
    let indices = vec![
        [0u32, 1, 2],  // Bottom face
        [0, 1, 3],     // Front face
        [1, 2, 3],     // Right face
        [2, 0, 3],     // Left face
    ];
    let mesh = TriMesh::new(vertices, indices).unwrap();
    
    // Compute cross-section at z = 0.5
    match mesh.canonical_intersection_with_plane(2, 0.5, 1e-6) {
        IntersectResult::Intersect(polyline) => {
            println!("Cross-section computed!");
            println!("Number of vertices: {}", polyline.vertices().len());
        }
        IntersectResult::Negative => {
            println!("Mesh is entirely below z = 0.5");
        }
        IntersectResult::Positive => {
            println!("Mesh is entirely above z = 0.5");
        }
    }
  11. Maintain persistent contact data across frames

    master

    To support features like warm-starting (reusing impulses) or contact aging, you must transfer user data from the previous frame's contacts to the new ones. This is done by matching contacts based on their geometric features.

    match_contacts (Feature-based)

    Matches contacts by comparing fid1 and fid2. This is the most robust method for tracking the same physical contact point even if the exact position shifts slightly.

    match_contacts_using_positions (Position-based)

    Matches contacts by comparing the distance between local_p1 and local_p2 against a dist_threshold. Use this if feature IDs are not available or sufficient.

    // Frame 2: Save old contacts, recompute
    let old_contacts = manifold.points.clone();
    contact_manifold_ball_ball(&pos12_frame2, &ball1, &ball2, 0.0, &mut manifold);
    
    // Transfer data from old to new based on feature ID matching
    manifold.match_contacts(&old_contacts);
    
    // Data is preserved!
    if let Some(contact) = manifold.points.first() {
        assert_eq!(contact.data.accumulated_impulse, 42.0);
    }
  12. Efficiently update contacts using spatial coherence

    master

    Instead of recomputing the entire contact manifold every frame, you can use try_update_contacts or try_update_contacts_eps. This exploits temporal coherence by checking if the shapes have moved enough to invalidate the existing contact points.

    Workflow

    1. Compute the initial manifold using a contact query function.
    2. In subsequent frames, call manifold.try_update_contacts(&new_relative_pose).
    3. If it returns true, the contacts were updated efficiently.
    4. If it returns false, fall back to a full recomputation.

    Custom Tolerances

    Use try_update_contacts_eps to provide custom angle_dot_threshold and dist_sq_threshold for more or less strict updates.

    // Frame 1: Initial computation
    let pos12_old = Pose::translation(1.9, 0.0, 0.0);
    contact_manifold_ball_ball(&pos12_old, &ball1, &ball2, 0.1, &mut manifold);
    
    // Frame 2: Small movement - try to update efficiently
    let pos12_new = Pose::translation(1.85, 0.05, 0.0);
    if manifold.try_update_contacts(&pos12_new) {
        println!("Updated contacts efficiently!");
    } else {
        println!("Need to recompute from scratch");
        contact_manifold_ball_ball(&pos12_new, &ball1, &ball2, 0.1, &mut manifold);
    }