Colliders
An Elixir library for detecting whether a bounding box (bbox) meaningfully overlaps a polygon. Designed for use cases such as checking if an AI model detection (a bbox) falls within a region of interest (a polygon).
A detection is considered a hit only when the overlap meets a configurable minimum threshold (default: 5%).
How It Works
Colliders uses a two-phase approach for both correctness and performance:
Axis-Aligned Bounding Box (AABB) filter — Compares the bbox against the polygon's precomputed axis-aligned bounding box. If the bbox is entirely outside it,
falseis returned immediately without any further computation.Sutherland-Hodgman clipping — If the AABB check passes, the polygon is clipped to the bbox using the Sutherland-Hodgman algorithm. The area of the resulting intersection polygon is then compared to the bbox area to compute the overlap percentage.
Installation
Add colliders to your dependencies in mix.exs:
def deps do
[
{:colliders, "~> 0.1.3"}
]
endThen fetch dependencies:
mix deps.getUsage
1. Define your polygon
Use Colliders.Polygon.new/2 to create a prepared polygon. This precomputes the
polygon's bounding box bounds once, so repeated checks against it are fast.
The points list accepts %PolygonPoint{} structs, maps with x/y keys, or
{x, y} tuples.
alias Colliders.Polygon
alias Colliders.Types.PolygonPoint
polygon =
Polygon.new([
%PolygonPoint{x: 100, y: 50},
%PolygonPoint{x: 300, y: 50},
%PolygonPoint{x: 300, y: 200},
%PolygonPoint{x: 100, y: 200}
])You can also attach arbitrary metadata to a polygon (e.g. an ID or label):
polygon = Polygon.new(points, %{id: "zone_a", label: "Entrance"})2. Check if a bounding box hits the polygon
alias Colliders
alias Colliders.Types.BBox
# A detection bbox: x/y is the top-left corner, w/h are width and height
bbox = %BBox{x: 295, y: 100, w: 30, h: 40}
Colliders.bbox_intersects_polygon?(bbox, polygon)
# => true
The default threshold is 5% — the bbox must overlap at least 5% of its own
area with the polygon to return true.
3. Custom threshold
# Require at least 50% of the bbox to be inside the polygon
Colliders.bbox_intersects_polygon?(bbox, polygon, 50.0)4. Get the exact overlap percentage
Colliders.bbox_overlap_percentage(bbox, polygon)
# => 16.666...
Returns a float between 0.0 (no overlap) and 100.0 (bbox fully inside the
polygon).
Types
%Colliders.Types.PolygonPoint{}
A vertex in a polygon.
| Field | Type | Description |
|---|---|---|
x | float | Horizontal coordinate |
y | float | Vertical coordinate |
Use PolygonPoint.new/1 to build one from a map or tuple — integers are
automatically converted to floats:
PolygonPoint.new({10, 20}) # => %PolygonPoint{x: 10.0, y: 20.0}
PolygonPoint.new(%{x: 10.0, y: 20.0}) # => %PolygonPoint{x: 10.0, y: 20.0}
PolygonPoint.new(%{"x" => 10, "y" => 20}) # => %PolygonPoint{x: 10.0, y: 20.0}%Colliders.Types.BBox{}
An axis-aligned bounding box. x and y are the top-left corner.
| Field | Type | Description |
|---|---|---|
x | float | Left edge |
y | float | Top edge |
w | float | Width |
h | float | Height |
Use BBox.new/1 to build one from a map or tuple:
BBox.new({10, 20, 100, 50}) # => %BBox{x: 10.0, y: 20.0, w: 100.0, h: 50.0}
BBox.new(%{x: 10.0, y: 20.0, w: 100.0, h: 50.0}) # => %BBox{x: 10.0, y: 20.0, w: 100.0, h: 50.0}
BBox.new(%{"x" => 10, "y" => 20, "w" => 100, "h" => 50}) # => %BBox{x: 10.0, y: 20.0, w: 100.0, h: 50.0}%Colliders.Polygon{}
A prepared polygon. Create it with Colliders.Polygon.new/2 — do not build
the struct manually, as the precomputed AABB bounds (min_x, max_x, min_y,
max_y) will be missing.
| Field | Type | Description |
|---|---|---|
points | list(PolygonPoint.t()) | Polygon vertices |
meta | map() | Arbitrary metadata |
min_x | float | Precomputed left bound |
max_x | float | Precomputed right bound |
min_y | float | Precomputed top bound |
max_y | float | Precomputed bottom bound |
API
Colliders.bbox_intersects_polygon?(bbox, polygon, threshold \\ 5.0)
Returns true if at least threshold% of the bbox area overlaps the polygon.
Colliders.bbox_overlap_percentage(bbox, polygon)
Returns the percentage of the bbox area that overlaps the polygon, as a float
between 0.0 and 100.0.
Colliders.Polygon.new(points, meta \\ %{})
Creates a %Polygon{} struct, precomputing its AABB bounds. The points list
accepts %PolygonPoint{} structs, atom/string-key maps, or {x, y} tuples —
all are normalized to %PolygonPoint{} with float coordinates. Raises
ArgumentError if fewer than 3 points are given.
Colliders.Types.PolygonPoint.new(point)
Creates a %PolygonPoint{} from a {x, y} tuple, an atom-key map
(%{x: ..., y: ...}), or a string-key map (%{"x" => ..., "y" => ...}).
Integer values are converted to floats. Raises ArgumentError for any other
input.
Colliders.Types.BBox.new(coords)
Creates a %BBox{} from a {x, y, w, h} tuple, an atom-key map
(%{x: ..., y: ..., w: ..., h: ...}), or a string-key map
(%{"x" => ..., ...}). Integer values are converted to floats. Raises
ArgumentError for any other input.
Contributing
- Fork the repository
-
Create a feature branch (
git checkout -b my-feature) - Write tests for your changes
-
Make sure all tests pass (
mix test) - Open a pull request
License
See LICENSE for details.