expresso/collision
Collision detection with spatial acceleration.
This module provides efficient collision detection using spatial partitioning
data structures from the spatial library.
Features
- Multi-shape support: Sphere, Box, Capsule, and Cylinder colliders
- Spatial acceleration: BVH (O(n log n)) and Grid (O(c + k))
- Adaptive selection: Auto-choose BVH or Grid based on distribution
- Accurate detection: Uses
spatiallibrary’s collision algorithms
Collision Detection Strategies
Brute Force (< 10 bodies)
- Complexity: O(n²)
- Used automatically for small scenes
- No spatial index overhead
BVH (10-1000+ bodies, dynamic)
- Complexity: O(n log n + k) where k = collision pairs
- Best for dynamic scenes with moving objects
- Handles non-uniform distributions well
Grid (100-1000+ bodies, uniform)
- Complexity: O(c + k) where c = cells checked, k = collision pairs
- Best for particle systems and uniformly distributed bodies
- Excellent for 1000s of particles
Usage
Most users won’t call this module directly - the world module handles
collision detection automatically. However, advanced users can use these
functions for custom collision detection:
import expresso/collision
import gleam/dict
// Simple collision detection (builds BVH internally)
let contacts = collision.detect_collisions(bodies)
// With pre-built BVH (more efficient if reusing)
let bvh = collision.build_bvh(bodies)
let contacts = collision.detect_collisions_with_bvh(bodies, option.Some(bvh))
Types
Values
pub fn analyze_distribution(
bodies: dict.Dict(id, body.Body(id)),
) -> #(Bool, Float)
Analyze body distribution to determine if uniform
Returns #(is_uniform, average_bounding_radius) Optimized: Single pass to compute count, total_radius, and positions
pub fn bodies_to_bvh_items(
body_list: List(#(String, body.Body(id))),
) -> List(#(vec3.Vec3(Float), body.Body(id)))
Convert bodies to BVH items
pub fn build_bvh(
bodies: dict.Dict(id, body.Body(id)),
) -> Result(bvh.BVH(#(id, body.Body(id))), Nil)
Build a BVH spatial index from bodies
pub fn calculate_world_bounds(
bodies: dict.Dict(id, body.Body(id)),
) -> collider.Collider
Calculate world bounds for Grid creation Optimized: Single pass with dict.fold instead of dict.to_list + list.fold
pub fn detect_collisions(
bodies: dict.Dict(id, body.Body(id)),
) -> List(Contact(id))
Detect all collisions between bodies
Returns a list of contacts for all overlapping pairs.
Performance:
- For best performance, use
detect_collisions_with_index()with a pre-built spatial index - This function uses brute force or builds a BVH internally
pub fn detect_collisions_with_bvh(
bodies: dict.Dict(id, body.Body(id)),
bvh_option: option.Option(bvh.BVH(#(id, body.Body(id)))),
) -> List(Contact(id))
Detect collisions using an optional pre-built BVH for acceleration
This is the recommended API for performance-critical code.
pub fn detect_collisions_with_index(
bodies: dict.Dict(id, body.Body(id)),
spatial_index: SpatialIndex(id),
) -> List(Contact(id))
Detect collisions using a spatial index (BVH or Grid)
pub fn extract_bodies_from_results(
results: List(#(vec3.Vec3(Float), id)),
bodies: dict.Dict(id, body.Body(id)),
) -> List(#(id, body.Body(id)))
Extract body IDs from query results and lookup bodies from dict
pub fn filter_by_radius(
body_list: List(#(id, body.Body(id))),
center: vec3.Vec3(Float),
radius: Float,
) -> List(#(id, body.Body(id)))
Filter bodies by radius (brute force)
pub fn filter_by_region(
body_list: List(#(id, body.Body(id))),
min: vec3.Vec3(Float),
max: vec3.Vec3(Float),
) -> List(#(id, body.Body(id)))
Filter bodies by rectangular region (brute force)
pub fn find_nearest(
candidates: List(#(id, body.Body(id))),
point: vec3.Vec3(Float),
) -> option.Option(#(id, body.Body(id), Float))
Find the nearest body to a point
pub fn raycast(
bodies: dict.Dict(id, body.Body(id)),
spatial_index: option.Option(SpatialIndex(id)),
origin: vec3.Vec3(Float),
direction: vec3.Vec3(Float),
max_distance: Float,
layer_mask: option.Option(Int),
) -> option.Option(RaycastHit(id))
Cast a ray and find the first body it hits
Returns the closest hit along the ray, or None if no hit.