# `BB.Collision.Primitives`
[🔗](https://github.com/beam-bots/bb/blob/main/lib/bb/collision/primitives.ex#L5)

Collision detection algorithms for primitive geometry pairs.

All functions take world-space geometry (position + orientation applied)
and return either `{:collision, penetration_depth}` or `:no_collision`.

Penetration depth is the estimated overlap distance - how far the geometries
would need to be separated to no longer collide.

## Supported Geometry Types

- Sphere: `{:sphere, centre :: Vec3.t(), radius :: float()}`
- Capsule: `{:capsule, point_a :: Vec3.t(), point_b :: Vec3.t(), radius :: float()}`
- Box (OBB): `{:box, centre :: Vec3.t(), half_extents :: Vec3.t(), axes :: {Vec3.t(), Vec3.t(), Vec3.t()}}`

Cylinders are converted to capsules internally for simpler, more conservative collision detection.

# `box`

```elixir
@type box() ::
  {:box, centre :: BB.Math.Vec3.t(), half_extents :: BB.Math.Vec3.t(),
   axes :: {BB.Math.Vec3.t(), BB.Math.Vec3.t(), BB.Math.Vec3.t()}}
```

# `capsule`

```elixir
@type capsule() ::
  {:capsule, point_a :: BB.Math.Vec3.t(), point_b :: BB.Math.Vec3.t(),
   radius :: float()}
```

# `collision_result`

```elixir
@type collision_result() :: {:collision, penetration_depth :: float()} | :no_collision
```

# `geometry`

```elixir
@type geometry() :: sphere() | capsule() | box()
```

# `sphere`

```elixir
@type sphere() :: {:sphere, centre :: BB.Math.Vec3.t(), radius :: float()}
```

# `box_box`

```elixir
@spec box_box(box(), box()) :: collision_result()
```

Test collision between two oriented bounding boxes using the Separating Axis Theorem.

Two convex shapes are separated if there exists an axis along which their
projections don't overlap. For two OBBs, we need to test 15 potential
separating axes:
- 3 face normals from box A
- 3 face normals from box B
- 9 cross products of edges from A and B

# `capsule_box`

```elixir
@spec capsule_box(capsule(), box()) :: collision_result()
```

Test collision between a capsule and an oriented bounding box.

Finds the closest distance between the capsule's line segment and the box,
then checks if it's less than the capsule's radius.

# `capsule_capsule`

```elixir
@spec capsule_capsule(capsule(), capsule()) :: collision_result()
```

Test collision between two capsules.

Two capsules collide if the closest distance between their line segments
is less than the sum of their radii.

# `closest_point_on_box`

```elixir
@spec closest_point_on_box(
  BB.Math.Vec3.t(),
  BB.Math.Vec3.t(),
  BB.Math.Vec3.t(),
  {BB.Math.Vec3.t(), BB.Math.Vec3.t(), BB.Math.Vec3.t()}
) :: {BB.Math.Vec3.t(), float()}
```

Find the closest point on an OBB to a given point.

Returns `{closest_point, distance}`.

# `closest_point_on_segment`

```elixir
@spec closest_point_on_segment(BB.Math.Vec3.t(), BB.Math.Vec3.t(), BB.Math.Vec3.t()) ::
  {BB.Math.Vec3.t(), float()}
```

Find the closest point on a line segment to a given point.

Returns `{closest_point, distance}`.

# `closest_points_segments`

```elixir
@spec closest_points_segments(
  BB.Math.Vec3.t(),
  BB.Math.Vec3.t(),
  BB.Math.Vec3.t(),
  BB.Math.Vec3.t()
) ::
  {BB.Math.Vec3.t(), BB.Math.Vec3.t(), float()}
```

Find the closest points between two line segments.

Returns `{closest_on_seg1, closest_on_seg2, distance}`.

Uses the algorithm from "Real-Time Collision Detection" by Christer Ericson.

# `sphere_box`

```elixir
@spec sphere_box(sphere(), box()) :: collision_result()
```

Test collision between a sphere and an oriented bounding box.

The sphere collides with the box if the closest point on the box
to the sphere's centre is within the sphere's radius.

# `sphere_capsule`

```elixir
@spec sphere_capsule(sphere(), capsule()) :: collision_result()
```

Test collision between a sphere and a capsule.

A sphere and capsule collide if the closest distance from the sphere's
centre to the capsule's line segment is less than the sum of their radii.

# `sphere_sphere`

```elixir
@spec sphere_sphere(sphere(), sphere()) :: collision_result()
```

Test collision between two spheres.

Two spheres collide if the distance between their centres is less than
the sum of their radii.

# `test`

```elixir
@spec test(geometry(), geometry()) :: collision_result()
```

Test two geometries for collision.

Dispatches to the appropriate collision test based on geometry types.
Order of arguments doesn't matter - the function handles symmetry internally.

## Examples

    iex> sphere1 = {:sphere, Vec3.new(0, 0, 0), 1.0}
    iex> sphere2 = {:sphere, Vec3.new(1.5, 0, 0), 1.0}
    iex> BB.Collision.Primitives.test(sphere1, sphere2)
    {:collision, 0.5}

    iex> sphere1 = {:sphere, Vec3.new(0, 0, 0), 1.0}
    iex> sphere2 = {:sphere, Vec3.new(3.0, 0, 0), 1.0}
    iex> BB.Collision.Primitives.test(sphere1, sphere2)
    :no_collision

# `test_with_margin`

```elixir
@spec test_with_margin(geometry(), geometry(), margin :: float()) ::
  collision_result()
```

Test two geometries with an additional margin/padding.

The margin is added to both geometries, effectively expanding them.
Useful for detecting "near misses" or adding safety buffers.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
