expresso/collision

Collision detection with spatial acceleration.

This module provides efficient collision detection using spatial partitioning data structures from the spatial library.

Features

Collision Detection Strategies

Brute Force (< 10 bodies)

BVH (10-1000+ bodies, dynamic)

Grid (100-1000+ bodies, uniform)

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

Contact information for a collision between two bodies

pub type Contact(id) {
  Contact(
    body_a: id,
    body_b: id,
    normal: vec3.Vec3(Float),
    penetration: Float,
    point: vec3.Vec3(Float),
  )
}

Constructors

  • Contact(
      body_a: id,
      body_b: id,
      normal: vec3.Vec3(Float),
      penetration: Float,
      point: vec3.Vec3(Float),
    )

    Arguments

    body_a

    First body ID

    body_b

    Second body ID

    normal

    Contact normal (points from A to B)

    penetration

    Penetration depth (how much they overlap)

    point

    Contact point in world space

Result of a raycast

pub type RaycastHit(id) {
  RaycastHit(
    body_id: id,
    body: body.Body(id),
    point: vec3.Vec3(Float),
    normal: vec3.Vec3(Float),
    distance: Float,
  )
}

Constructors

  • RaycastHit(
      body_id: id,
      body: body.Body(id),
      point: vec3.Vec3(Float),
      normal: vec3.Vec3(Float),
      distance: Float,
    )

    Arguments

    body_id

    Body that was hit

    body

    Body that was hit

    point

    Point where the ray hit the body

    normal

    Normal at the hit point

    distance

    Distance from ray origin to hit point

Internal spatial index type (stores IDs for efficient updates)

pub type SpatialIndex(id) {
  BVHIndex(tree: bvh.BVH(id))
  GridIndex(grid: grid.Grid(id))
}

Constructors

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.

Search Document