// This file is based on the Go implementation found here:
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.3:src/image/geom.go
// which has the copyright notice:
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// A Point is an X, Y coordinate pair. The axes increase right and down.
///|
pub(all) struct Point {
mut x : Double
mut y : Double
} derive(Show)
///|
/// to_string returns a string representation of p like "(3,4)".
pub fn Point::to_string(self : Point) -> String {
"(\{self.x},\{self.y})"
}
///|
/// add (+) returns the vector p+q.
pub impl Add for Point with add(self, q) {
{ x: self.x + q.x, y: self.y + q.y }
}
///|
/// sub (-) returns the vector p-q.
pub impl Sub for Point with sub(self, q) {
{ x: self.x - q.x, y: self.y - q.y }
}
///|
/// mul returns the vector p*k.
pub fn Point::mul(self : Point, k : Double) -> Point {
{ x: self.x * k, y: self.y * k }
}
///|
/// div returns the vector p/k.
pub fn Point::div(self : Point, k : Double) -> Point {
{ x: self.x / k, y: self.y / k }
}
///|
/// is_in reports whether p is in r.
pub fn Point::is_in(self : Point, r : Rectangle) -> Bool {
r.min.x <= self.x && self.x < r.max.x && r.min.y <= self.y && self.y < r.max.y
}
// /// mod returns the point q in r such that p.X-q.X is a multiple of r's width
// /// and p.Y-q.Y is a multiple of r's height.
// pub fn mod(self : Point, r : Rectangle) -> Point {
// let (w, h) = (r.dx(), r.dy())
// let p = self - r.min
// p.x = p.x % w
// if p.x < 0 {
// p.x += w
// }
// p.y = p.y % h
// if p.y < 0 {
// p.y += h
// }
// p + r.min
// }
///|
/// equal reports whether p and q are equal.
pub impl Eq for Point with equal(self, q) {
self.x == q.x && self.y == q.y
}
///|
/// pt is shorthand for [Point]{X, Y}.
pub fn pt(x : Double, y : Double) -> Point {
{ x, y }
}
///|
/// A Rectangle contains the points with Min.X <= X < Max.X, Min.Y <= Y < Max.Y.
/// It is well-formed if Min.X <= Max.X and likewise for Y. Points are always
/// well-formed. A rectangle's methods always return well-formed outputs for
/// well-formed inputs.
///
/// A Rectangle is also an [Image] whose bounds are the rectangle itself. At
/// returns color.Opaque for points in the rectangle and color.Transparent
/// otherwise.
pub(all) struct Rectangle {
min : Point
max : Point
} derive(Show)
///|
/// Rectangle::new returns an empty (all zeros) rectangle.
pub fn Rectangle::new() -> Rectangle {
{ min: pt(0, 0), max: pt(0, 0) }
}
///|
/// `bounds` returns the tuple `(xmin, ymin, xmax, ymax)`.
pub fn Rectangle::bounds(self : Rectangle) -> (Double, Double, Double, Double) {
(self.min.x, self.min.y, self.max.x, self.max.y)
}
///|
/// `clone` makes a copy of the provided rectangle without changing it.
pub fn Rectangle::clone(self : Rectangle) -> Rectangle {
{ min: pt(self.min.x, self.min.y), max: pt(self.max.x, self.max.y) }
}
///|
/// to_string returns a string representation of r like "(3,4)-(6,5)".
pub fn Rectangle::to_string(self : Rectangle) -> String {
"\{self.min}-\{self.max}"
}
///|
/// dx returns r's width.
pub fn Rectangle::dx(self : Rectangle) -> Double {
self.max.x - self.min.x
}
///|
/// dy returns r's height.
pub fn Rectangle::dy(self : Rectangle) -> Double {
self.max.y - self.min.y
}
///|
/// size returns r's width and height.
pub fn Rectangle::size(self : Rectangle) -> Point {
{ x: self.max.x - self.min.x, y: self.max.y - self.min.y }
}
///|
/// add returns the rectangle r translated by p.
pub fn Rectangle::add(self : Rectangle, p : Point) -> Rectangle {
{
min: pt(self.min.x + p.x, self.min.y + p.y),
max: pt(self.max.x + p.x, self.max.y + p.y),
}
}
///|
/// sub returns the rectangle r translated by -p.
pub fn Rectangle::sub(self : Rectangle, p : Point) -> Rectangle {
{
min: pt(self.min.x - p.x, self.min.y - p.y),
max: pt(self.max.x - p.x, self.max.y - p.y),
}
}
///|
/// inset returns the rectangle r inset by n, which may be negative. If either
/// of r's dimensions is less than 2*n then an empty rectangle near the center
/// of r will be returned.
pub fn Rectangle::inset(self : Rectangle, n : Double) -> Rectangle {
let r = self.clone()
if r.dx() < 2.0 * n {
r.min.x = (r.min.x + r.max.x) / 2
r.max.x = r.min.x
} else {
r.min.x += n
r.max.x -= n
}
if r.dy() < 2.0 * n {
r.min.y = (r.min.y + r.max.y) / 2
r.max.y = r.min.y
} else {
r.min.y += n
r.max.y -= n
}
r
}
///|
/// intersect returns the largest rectangle contained by both r and s. If the
/// two rectangles do not overlap then the zero rectangle will be returned.
pub fn Rectangle::intersect(self : Rectangle, s : Rectangle) -> Rectangle {
let r = self.clone()
if r.min.x < s.min.x {
r.min.x = s.min.x
}
if r.min.y < s.min.y {
r.min.y = s.min.y
}
if r.max.x > s.max.x {
r.max.x = s.max.x
}
if r.max.y > s.max.y {
r.max.y = s.max.y
}
// Letting r0 and s0 be the values of r and s at the time that the method
// is called, this next line is equivalent to:
//
// if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc }
if r.empty() {
return Rectangle::new()
}
r
}
///|
/// `extend` extends the current rectangle to include the provided point.
pub fn Rectangle::extend(self : Rectangle, pt : Point) -> Unit {
if pt.x < self.min.x {
self.min.x = pt.x
}
if pt.x > self.max.x {
self.max.x = pt.x
}
if pt.y < self.min.y {
self.min.y = pt.y
}
if pt.y > self.max.y {
self.max.y = pt.y
}
}
///|
/// union returns the smallest rectangle that contains both r and s.
pub fn Rectangle::union(self : Rectangle, s : Rectangle) -> Rectangle {
let r = self.clone()
if r.empty() {
return s
}
if s.empty() {
return r
}
if r.min.x > s.min.x {
r.min.x = s.min.x
}
if r.min.y > s.min.y {
r.min.y = s.min.y
}
if r.max.x < s.max.x {
r.max.x = s.max.x
}
if r.max.y < s.max.y {
r.max.y = s.max.y
}
r
}
///|
/// empty reports whether the rectangle contains no points.
pub fn Rectangle::empty(self : Rectangle) -> Bool {
self.min.x >= self.max.x || self.min.y >= self.max.y
}
///|
/// equal reports whether r and s contain the same set of points. All empty
/// rectangles are considered equal.
pub impl Eq for Rectangle with equal(self, s) {
(self.min == s.min && self.max == s.max) || (self.empty() && s.empty())
}
///|
/// overlaps reports whether r and s have a non-empty intersection.
pub fn Rectangle::overlaps(self : Rectangle, s : Rectangle) -> Bool {
not(self.empty()) &&
not(s.empty()) &&
self.min.x < s.max.x &&
s.min.x < self.max.x &&
self.min.y < s.max.y &&
s.min.y < self.max.y
}
///|
/// is_in reports whether every point in r is in s.
pub fn Rectangle::is_in(self : Rectangle, s : Rectangle) -> Bool {
if self.empty() {
return true
}
// note that self.max is an exclusive bound for r, so that self.in(s)
// does not require that self.max.in(s).
s.min.x <= self.min.x &&
self.max.x <= s.max.x &&
s.min.y <= self.min.y &&
self.max.y <= s.max.y
}
///|
/// canon returns the canonical version of r. The returned rectangle has minimum
/// and maximum coordinates swapped if necessary so that it is well-formed.
pub fn Rectangle::canon(self : Rectangle) -> Rectangle {
let r = self.clone()
if r.max.x < r.min.x {
let tmp = r.min.x
r.min.x = r.max.x
r.max.x = tmp
}
if r.max.y < r.min.y {
let tmp = r.min.y
r.min.y = r.max.y
r.max.y = tmp
}
r
}
///|
/// rect is shorthand for Rectangle::{min: pt(x0, y0), max: pt(x1, y1)}. The returned
/// rectangle has minimum and maximum coordinates swapped if necessary so that
/// it is well-formed.
pub fn rect(x0 : Double, y0 : Double, x1 : Double, y1 : Double) -> Rectangle {
let (x0, x1) = if x0 > x1 { (x1, x0) } else { (x0, x1) }
let (y0, y1) = if y0 > y1 { (y1, y0) } else { (y0, y1) }
{ min: pt(x0, y0), max: pt(x1, y1) }
}