jagua_rs/geometry/primitives/
edge.rsuse crate::fsize;
use crate::geometry::geo_traits::{
CollidesWith, Distance, Shape, Transformable, TransformableFrom,
};
use crate::geometry::primitives::aa_rectangle::AARectangle;
use crate::geometry::primitives::point::Point;
use crate::geometry::transformation::Transformation;
#[derive(Clone, Debug, PartialEq)]
pub struct Edge {
pub start: Point,
pub end: Point,
}
impl Edge {
pub fn new(start: Point, end: Point) -> Self {
if start == end {
panic!("degenerate edge, {start:?} == {end:?}");
}
Edge { start, end }
}
pub fn extend_at_front(mut self, d: fsize) -> Self {
let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
let l = self.diameter();
self.start.0 -= dx * (d / l);
self.start.1 -= dy * (d / l);
self
}
pub fn extend_at_back(mut self, d: fsize) -> Self {
let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
let l = self.diameter();
self.end.0 += dx * (d / l);
self.end.1 += dy * (d / l);
self
}
pub fn scale(mut self, factor: fsize) -> Self {
let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
self.start.0 -= dx * (factor - 1.0) / 2.0;
self.start.1 -= dy * (factor - 1.0) / 2.0;
self.end.0 += dx * (factor - 1.0) / 2.0;
self.end.1 += dy * (factor - 1.0) / 2.0;
self
}
pub fn reverse(mut self) -> Self {
std::mem::swap(&mut self.start, &mut self.end);
self
}
pub fn collides_at(&self, other: &Edge) -> Option<Point> {
match edge_intersection(self, other, true) {
Intersection::No => None,
Intersection::Yes(point) => Some(
point.expect("Intersection::Yes, but returned no point when this was requested"),
),
}
}
pub fn closest_point_on_edge(&self, point: &Point) -> Point {
let Point(x1, y1) = self.start;
let Point(x2, y2) = self.end;
let Point(x, y) = point;
let a = x - x1;
let b = y - y1;
let c = x2 - x1;
let d = y2 - y1;
let dot = a * c + b * d;
let len_sq = c * c + d * d;
let mut param = -1.0;
if len_sq != 0.0 {
param = dot / len_sq;
}
let (xx, yy) = match param {
p if p < 0.0 => (x1, y1), p if p > 1.0 => (x2, y2), _ => (x1 + param * c, y1 + param * d), };
Point(xx, yy)
}
pub fn x_min(&self) -> fsize {
fsize::min(self.start.0, self.end.0)
}
pub fn y_min(&self) -> fsize {
fsize::min(self.start.1, self.end.1)
}
pub fn x_max(&self) -> fsize {
fsize::max(self.start.0, self.end.0)
}
pub fn y_max(&self) -> fsize {
fsize::max(self.start.1, self.end.1)
}
}
impl Transformable for Edge {
fn transform(&mut self, t: &Transformation) -> &mut Self {
let Edge { start, end } = self;
start.transform(t);
end.transform(t);
self
}
}
impl TransformableFrom for Edge {
fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
let Edge { start, end } = self;
start.transform_from(&reference.start, t);
end.transform_from(&reference.end, t);
self
}
}
impl Shape for Edge {
fn centroid(&self) -> Point {
Point(
(self.start.0 + self.end.0) / 2.0,
(self.start.1 + self.end.1) / 2.0,
)
}
fn area(&self) -> fsize {
0.0
}
fn bbox(&self) -> AARectangle {
AARectangle::new(self.x_min(), self.y_min(), self.x_max(), self.y_max())
}
fn diameter(&self) -> fsize {
self.start.distance(&self.end)
}
}
impl Distance<Point> for Edge {
#[inline(always)]
fn sq_distance(&self, point: &Point) -> fsize {
let Point(x, y) = point;
let Point(xx, yy) = self.closest_point_on_edge(point);
let (dx, dy) = (x - xx, y - yy);
dx.powi(2) + dy.powi(2)
}
#[inline(always)]
fn distance(&self, point: &Point) -> fsize {
fsize::sqrt(self.sq_distance(point))
}
}
impl CollidesWith<Edge> for Edge {
#[inline(always)]
fn collides_with(&self, other: &Edge) -> bool {
match edge_intersection(self, other, false) {
Intersection::No => false,
Intersection::Yes(_) => true,
}
}
}
impl CollidesWith<AARectangle> for Edge {
#[inline(always)]
fn collides_with(&self, other: &AARectangle) -> bool {
other.collides_with(self)
}
}
#[inline(always)]
fn edge_intersection(e1: &Edge, e2: &Edge, calculate_location: bool) -> Intersection {
if fsize::max(e1.x_min(), e2.x_min()) > fsize::min(e1.x_max(), e2.x_max())
|| fsize::max(e1.y_min(), e2.y_min()) > fsize::min(e1.y_max(), e2.y_max())
{
return Intersection::No;
}
let Point(x1, y1) = e1.start;
let Point(x2, y2) = e1.end;
let Point(x3, y3) = e2.start;
let Point(x4, y4) = e2.end;
let t_nom = (x2 - x4) * (y4 - y3) - (y2 - y4) * (x4 - x3);
let t_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
let u_nom = (x2 - x4) * (y2 - y1) - (y2 - y4) * (x2 - x1);
let u_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
if t_denom == 0.0 || u_denom == 0.0 {
Intersection::No
} else {
let t = t_nom / t_denom;
let u = u_nom / u_denom;
if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
if calculate_location {
let x = x2 + t * (x1 - x2);
let y = y2 + t * (y1 - y2);
Intersection::Yes(Some(Point(x, y)))
} else {
Intersection::Yes(None)
}
} else {
Intersection::No
}
}
}
enum Intersection {
Yes(Option<Point>),
No,
}