///|
/// Axis-aligned bounding box.
pub struct Aabb {
  min : Vec2
  max : Vec2
}

///|
pub fn Aabb::new(min : Vec2, max : Vec2) -> Aabb {
  { min, max }
}

///|
pub fn Aabb::overlaps(self : Aabb, other : Aabb) -> Bool {
  if other.min.x > self.max.x || other.max.x < self.min.x {
    return false
  }
  if other.min.y > self.max.y || other.max.y < self.min.y {
    return false
  }
  true
}

///|
pub fn Aabb::contains_point(self : Aabb, p : Vec2) -> Bool {
  p.x >= self.min.x &&
  p.x <= self.max.x &&
  p.y >= self.min.y &&
  p.y <= self.max.y
}

///|
pub fn Aabb::union(self : Aabb, other : Aabb) -> Aabb {
  { min: self.min.min(other.min), max: self.max.max(other.max) }
}

///|
/// Grow the box by `margin` on every side.
pub fn Aabb::expanded(self : Aabb, margin : Double) -> Aabb {
  { min: self.min - Vec2::splat(margin), max: self.max + Vec2::splat(margin) }
}

///|
pub fn Aabb::center(self : Aabb) -> Vec2 {
  (self.min + self.max).scale(0.5)
}

///|
pub fn Aabb::width(self : Aabb) -> Double {
  self.max.x - self.min.x
}

///|
pub fn Aabb::height(self : Aabb) -> Double {
  self.max.y - self.min.y
}

///|
pub fn Aabb::min(self : Aabb) -> Vec2 {
  self.min
}

///|
pub fn Aabb::max(self : Aabb) -> Vec2 {
  self.max
}

///|
/// Mass properties of a shape with unit density scaled by `density`.
pub struct MassData {
  mass : Double
  inertia : Double
  center : Vec2
}

///|
pub fn MassData::mass(self : MassData) -> Double {
  self.mass
}

///|
pub fn MassData::inertia(self : MassData) -> Double {
  self.inertia
}

///|
pub fn MassData::center(self : MassData) -> Vec2 {
  self.center
}

///|
/// Collision geometry in local space.
///
/// `Polygon` vertices must form a convex counter-clockwise hull; use
/// `Shape::convex` to build one safely.
pub enum Shape {
  Circle(Double)
  Polygon(Array[Vec2])
}

///|
pub fn Shape::circle(radius : Double) -> Shape {
  Circle(radius)
}

///|
/// Axis-aligned box centered at the local origin.
pub fn Shape::box(half_width : Double, half_height : Double) -> Shape {
  Polygon([
    Vec2::new(-half_width, -half_height),
    Vec2::new(half_width, -half_height),
    Vec2::new(half_width, half_height),
    Vec2::new(-half_width, half_height),
  ])
}

///|
/// Build a convex polygon, forcing counter-clockwise winding.
pub fn Shape::convex(vertices : Array[Vec2]) -> Shape {
  if vertices.length() < 3 {
    abort("Shape::convex needs at least 3 vertices")
  }
  let vs = if signed_area(vertices) < 0.0 {
    reverse_vertices(vertices)
  } else {
    copy_vertices(vertices)
  }
  Polygon(vs)
}

///|
fn copy_vertices(vertices : Array[Vec2]) -> Array[Vec2] {
  let out : Array[Vec2] = []
  for v in vertices {
    out.push(v)
  }
  out
}

///|
fn reverse_vertices(vertices : Array[Vec2]) -> Array[Vec2] {
  let out : Array[Vec2] = []
  let mut i = vertices.length() - 1
  while i >= 0 {
    out.push(vertices[i])
    i = i - 1
  }
  out
}

///|
fn signed_area(vertices : Array[Vec2]) -> Double {
  let mut sum = 0.0
  let n = vertices.length()
  for i in 0.. Double {
  match self {
    Circle(r) => K_PI * r * r
    Polygon(vs) => signed_area(vs).abs()
  }
}

///|
/// Outward unit normal of polygon edge `i`.
pub fn Shape::edge_normal(self : Shape, i : Int) -> Vec2 {
  match self {
    Circle(_) => Vec2::zero()
    Polygon(vs) => {
      let n = vs.length()
      let edge = vs[(i + 1) % n] - vs[i]
      Vec2::new(edge.y, -edge.x).normalize()
    }
  }
}

///|
/// Radius of a circle shape, or `0.0` for polygons.
pub fn Shape::radius(self : Shape) -> Double {
  match self {
    Circle(r) => r
    Polygon(_) => 0.0
  }
}

///|
/// Local-space vertices; empty for circles.
pub fn Shape::vertices(self : Shape) -> Array[Vec2] {
  match self {
    Circle(_) => []
    Polygon(vs) => vs
  }
}

///|
pub fn Shape::is_circle(self : Shape) -> Bool {
  match self {
    Circle(_) => true
    Polygon(_) => false
  }
}

///|
/// Compute mass properties for the given density.
pub fn Shape::mass_data(self : Shape, density : Double) -> MassData {
  match self {
    Circle(r) => {
      let mass = density * K_PI * r * r
      { mass, inertia: 0.5 * mass * r * r, center: Vec2::zero() }
    }
    Polygon(vs) => polygon_mass_data(vs, density)
  }
}

///|
fn polygon_mass_data(vs : Array[Vec2], density : Double) -> MassData {
  let n = vs.length()
  let mut area_acc = 0.0
  let mut centroid_acc = Vec2::zero()
  let mut inertia_acc = 0.0
  for i in 0.. Aabb {
  match self {
    Circle(r) => {
      let c = transform.position
      Aabb::new(c - Vec2::splat(r), c + Vec2::splat(r))
    }
    Polygon(vs) => {
      let first = transform.apply(vs[0])
      let mut lo = first
      let mut hi = first
      let n = vs.length()
      for i in 1.. Bool {
  match self {
    Circle(r) => point.length_sq() <= r * r
    Polygon(vs) => {
      let n = vs.length()
      for i in 0.. K_EPSILON {
          return false
        }
      }
      true
    }
  }
}