use crate::{
geometry::{Angle, Point, PointExt, Real, Trigonometry},
primitives::{common::LineSide, Line},
};
pub const NORMAL_VECTOR_SCALE: i32 = 1 << 10;
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct LinearEquation {
pub normal_vector: Point,
pub origin_distance: i32,
}
impl LinearEquation {
pub fn with_angle_and_distance(angle: Angle, origin_distance: i32) -> Self {
Self {
normal_vector: OriginLinearEquation::with_angle(angle).normal_vector,
origin_distance,
}
}
pub fn from_line(line: &Line) -> Self {
let normal_vector = line.delta().rotate_90();
let origin_distance = line.start.dot_product(normal_vector);
Self {
normal_vector,
origin_distance,
}
}
pub fn distance(&self, point: Point) -> i32 {
point.dot_product(self.normal_vector) - self.origin_distance
}
pub fn check_side(&self, point: Point, side: LineSide) -> bool {
let distance = self.distance(point);
match side {
LineSide::Right => distance <= 0,
LineSide::Left => distance >= 0,
}
}
}
#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)]
pub struct OriginLinearEquation {
pub normal_vector: Point,
}
impl OriginLinearEquation {
pub fn with_angle(angle: Angle) -> Self {
let normal_vector = if angle == Angle::from_degrees(180.0) {
Point::new(0, NORMAL_VECTOR_SCALE)
} else {
-Point::new(
i32::from(angle.sin() * Real::from(NORMAL_VECTOR_SCALE)),
i32::from(angle.cos() * Real::from(NORMAL_VECTOR_SCALE)),
)
};
Self { normal_vector }
}
pub fn new_horizontal() -> Self {
Self {
normal_vector: Point::new(0, -NORMAL_VECTOR_SCALE),
}
}
pub fn distance(&self, point: Point) -> i32 {
point.dot_product(self.normal_vector)
}
pub fn check_side(&self, point: Point, side: LineSide) -> bool {
let distance = self.distance(point);
match side {
LineSide::Right => distance <= 0,
LineSide::Left => distance >= 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::AngleUnit;
#[test]
fn from_line() {
assert_eq!(
LinearEquation::from_line(&Line::new(Point::zero(), Point::new(1, 0))),
LinearEquation {
normal_vector: Point::new(0, -1),
origin_distance: 0, }
);
assert_eq!(
LinearEquation::from_line(&Line::new(Point::zero(), Point::new(0, 1))),
LinearEquation {
normal_vector: Point::new(1, 0),
origin_distance: 0, }
);
assert_eq!(
LinearEquation::from_line(&Line::new(Point::new(2, 3), Point::new(-2, 3))),
LinearEquation {
normal_vector: Point::new(0, 4),
origin_distance: 12,
}
);
}
#[test]
fn with_angle() {
assert_eq!(
OriginLinearEquation::with_angle(0.0.deg()),
OriginLinearEquation {
normal_vector: Point::new(0, -NORMAL_VECTOR_SCALE),
}
);
assert_eq!(
OriginLinearEquation::with_angle(90.0.deg()),
OriginLinearEquation {
normal_vector: Point::new(-NORMAL_VECTOR_SCALE, 0),
}
);
}
#[test]
fn distance() {
let line = OriginLinearEquation::with_angle(90.0.deg());
assert_eq!(line.distance(Point::new(-1, 0)), NORMAL_VECTOR_SCALE);
assert_eq!(line.distance(Point::new(1, 0)), -NORMAL_VECTOR_SCALE);
}
#[test]
fn check_side_horizontal() {
let line = OriginLinearEquation::with_angle(0.0.deg());
assert!(line.check_side(Point::new(0, 0), LineSide::Left));
assert!(line.check_side(Point::new(1, 0), LineSide::Right));
assert!(!line.check_side(Point::new(-2, 1), LineSide::Left));
assert!(line.check_side(Point::new(3, 1), LineSide::Right));
assert!(line.check_side(Point::new(-4, -1), LineSide::Left));
assert!(!line.check_side(Point::new(5, -1), LineSide::Right));
let line = OriginLinearEquation::with_angle(180.0.deg());
assert!(line.check_side(Point::new(0, 0), LineSide::Left));
assert!(line.check_side(Point::new(1, 0), LineSide::Right));
assert!(line.check_side(Point::new(-2, 1), LineSide::Left));
assert!(!line.check_side(Point::new(3, 1), LineSide::Right));
assert!(!line.check_side(Point::new(-4, -1), LineSide::Left));
assert!(line.check_side(Point::new(5, -1), LineSide::Right));
}
#[test]
fn check_side_vertical() {
let line = OriginLinearEquation::with_angle(90.0.deg());
assert!(line.check_side(Point::new(0, 0), LineSide::Left));
assert!(line.check_side(Point::new(0, -1), LineSide::Right));
assert!(line.check_side(Point::new(-1, 2), LineSide::Left));
assert!(!line.check_side(Point::new(-1, -3), LineSide::Right));
assert!(!line.check_side(Point::new(1, 4), LineSide::Left));
assert!(line.check_side(Point::new(1, -5), LineSide::Right));
let line = OriginLinearEquation::with_angle(270.0.deg());
assert!(line.check_side(Point::new(0, 0), LineSide::Left));
assert!(line.check_side(Point::new(0, 1), LineSide::Right));
assert!(!line.check_side(Point::new(-1, -2), LineSide::Left));
assert!(line.check_side(Point::new(-1, 3), LineSide::Right));
assert!(line.check_side(Point::new(1, -4), LineSide::Left));
assert!(!line.check_side(Point::new(1, 5), LineSide::Right));
}
}