///|
/// 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
}
}
}