///|
/// A small immutable-friendly 2D vector value type.
pub(all) struct Vec2 {
  x : Double
  y : Double
} derive(Eq, Debug, ToJson)

///|
pub fn Vec2::new(x : Double, y : Double) -> Vec2 {
  { x, y }
}

///|
pub fn Vec2::zero() -> Vec2 {
  { x: 0.0, y: 0.0 }
}

///|
pub fn Vec2::add(self : Vec2, other : Vec2) -> Vec2 {
  { x: self.x + other.x, y: self.y + other.y }
}

///|
pub fn Vec2::sub(self : Vec2, other : Vec2) -> Vec2 {
  { x: self.x - other.x, y: self.y - other.y }
}

///|
pub fn Vec2::scale(self : Vec2, factor : Double) -> Vec2 {
  { x: self.x * factor, y: self.y * factor }
}

///|
pub fn Vec2::dot(self : Vec2, other : Vec2) -> Double {
  self.x * other.x + self.y * other.y
}

///|
pub fn Vec2::cross(self : Vec2, other : Vec2) -> Double {
  self.x * other.y - self.y * other.x
}

///|
pub fn Vec2::length(self : Vec2) -> Double {
  self.dot(self).sqrt()
}

///|
pub fn Vec2::distance(self : Vec2, other : Vec2) -> Double {
  self.sub(other).length()
}

///|
pub fn Vec2::normalized(self : Vec2) -> Vec2 {
  let len = self.length()
  if len == 0.0 {
    Vec2::zero()
  } else {
    self.scale(1.0 / len)
  }
}

///|
pub fn Vec2::rotate(
  self : Vec2,
  angle : Double,
  center? : Vec2 = Vec2::zero(),
) -> Vec2 {
  let relative = self.sub(center)
  let c = @math.cos(angle)
  let s = @math.sin(angle)
  center.add({
    x: relative.x * c - relative.y * s,
    y: relative.x * s + relative.y * c,
  })
}

///|
pub fn Vec2::lerp(self : Vec2, other : Vec2, amount : Double) -> Vec2 {
  self.add(other.sub(self).scale(amount))
}

///|
/// Axis-aligned rectangle used for clipping, layout and measurement.
pub(all) struct Rect {
  x : Double
  y : Double
  width : Double
  height : Double
} derive(Eq, Debug, ToJson)

///|
pub fn Rect::new(
  x : Double,
  y : Double,
  width : Double,
  height : Double,
) -> Rect {
  {
    x,
    y,
    width: if width < 0.0 {
      0.0
    } else {
      width
    },
    height: if height < 0.0 {
      0.0
    } else {
      height
    },
  }
}

///|
pub fn Rect::right(self : Rect) -> Double {
  self.x + self.width
}

///|
pub fn Rect::bottom(self : Rect) -> Double {
  self.y + self.height
}

///|
pub fn Rect::center(self : Rect) -> Vec2 {
  { x: self.x + self.width / 2.0, y: self.y + self.height / 2.0 }
}

///|
pub fn Rect::contains(self : Rect, point : Vec2) -> Bool {
  point.x >= self.x &&
  point.x <= self.right() &&
  point.y >= self.y &&
  point.y <= self.bottom()
}

///|
pub fn Rect::expand(self : Rect, amount : Double) -> Rect {
  Rect::new(
    self.x - amount,
    self.y - amount,
    self.width + 2.0 * amount,
    self.height + 2.0 * amount,
  )
}

///|
pub fn Rect::intersect(self : Rect, other : Rect) -> Rect {
  let left = if self.x > other.x { self.x } else { other.x }
  let top = if self.y > other.y { self.y } else { other.y }
  let right = if self.right() < other.right() {
    self.right()
  } else {
    other.right()
  }
  let bottom = if self.bottom() < other.bottom() {
    self.bottom()
  } else {
    other.bottom()
  }
  Rect::new(left, top, right - left, bottom - top)
}

///|
pub fn Rect::union(self : Rect, other : Rect) -> Rect {
  let left = if self.x < other.x { self.x } else { other.x }
  let top = if self.y < other.y { self.y } else { other.y }
  let right = if self.right() > other.right() {
    self.right()
  } else {
    other.right()
  }
  let bottom = if self.bottom() > other.bottom() {
    self.bottom()
  } else {
    other.bottom()
  }
  Rect::new(left, top, right - left, bottom - top)
}

///|
/// Returns the bounding rectangle of a point collection.
pub fn bounds_of_points(points : Array[Vec2]) -> Rect {
  if points.length() == 0 {
    Rect::new(0.0, 0.0, 0.0, 0.0)
  } else {
    let mut min_x = points[0].x
    let mut max_x = points[0].x
    let mut min_y = points[0].y
    let mut max_y = points[0].y
    for point in points {
      if point.x < min_x {
        min_x = point.x
      }
      if point.x > max_x {
        max_x = point.x
      }
      if point.y < min_y {
        min_y = point.y
      }
      if point.y > max_y {
        max_y = point.y
      }
    }
    Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
  }
}

///|
/// Signed polygon area. Counter-clockwise polygons are positive.
pub fn polygon_area(points : Array[Vec2]) -> Double {
  if points.length() < 3 {
    0.0
  } else {
    let mut sum = 0.0
    for i in 0.. Double {
  if points.length() < 2 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0.. Bool {
  if polygon.length() < 3 {
    false
  } else {
    let mut inside = false
    let mut previous = polygon.length() - 1
    for current in 0.. point.y) != (b.y > point.y)
      if crosses {
        let at_x = (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x
        if point.x <= at_x {
          inside = !inside
        }
      }
      previous = current
    }
    inside
  }
}

///|
/// Uniformly samples a regular polygon around a center.
pub fn regular_polygon(
  center : Vec2,
  sides : Int,
  radius : Double,
  phase? : Double = 0.0,
) -> Array[Vec2] {
  let result = []
  if sides >= 3 {
    for i in 0..