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