1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
// Copyright (c) 2018-2020 Thomas Kramer.
// SPDX-FileCopyrightText: 2018-2022 Thomas Kramer
//
// SPDX-License-Identifier: AGPL-3.0-or-later
//! Common traits for geometrical objects.
use crate::point::Point;
use crate::rect::Rect;
use crate::vector::Vector;
use crate::types::Angle;
use num_traits::cast::NumCast;
use num_traits::Zero;
use std::ops::{Add, Mul, Sub};
/// Calculation of the 'bounding box', i.e. the smallest rectangle that contains the geometrical object.
pub trait BoundingBox<T> {
/// Return the bounding box of this geometry.
fn bounding_box(&self) -> Rect<T>;
}
/// Try the calculation of the 'bounding box', i.e. the smallest rectangle that contains the geometrical object.
/// In some cases this is not always possible, so the try might fail.
/// For instance a set of polygons does not have a bounding box if the set is empty.
pub trait TryBoundingBox<T> {
/// Return the bounding box of this geometry if a bounding box is defined.
fn try_bounding_box(&self) -> Option<Rect<T>>;
}
/// Try to compute the bounding box while consuming the data.
/// This is intended to be used for computing bounding boxes over iterators.
pub trait TryIntoBoundingBox<T> {
/// Return the bounding box of this geometry if such bounding box is defined.
fn try_into_bounding_box(self) -> Option<Rect<T>>;
}
/// Compute the bounding box of many objects that may have a bounding box.
impl<'a, I, B, T> TryIntoBoundingBox<T> for I
where
I: Iterator<Item = &'a B>,
B: TryBoundingBox<T> + 'a,
T: Copy + PartialOrd,
{
fn try_into_bounding_box(self) -> Option<Rect<T>> {
self.filter_map(|p| p.try_bounding_box())
.reduce(|acc, b| acc.add_rect(&b))
}
}
/// Calculate the doubled oriented area of a geometry.
/// Using the doubled area allows to compute the area without using fractions. This is especially
/// helpful when computing in integer coordinates.
///
/// * `A`: Area type.
pub trait DoubledOrientedArea<A> {
/// Calculate doubled oriented area of this geometry.
/// Using the doubled area allows to compute the area without using fractions.
fn area_doubled_oriented(&self) -> A;
}
/// Calculate the area of a geometry.
pub trait Area<F> {
/// Compute the area of a geometrical shape by first computing
/// doubled oriented area and then taking absolute value of the half.
fn area(&self) -> F;
}
// impl<T, F: Float + NumCast, DA: DoubledOrientedArea<T>> Area<F> for DA {
// fn area(&self) -> F {
// F::abs(F::from(self.area_doubled_oriented()).unwrap() / (F::one() + F::one()))
// }
// }
/// Translate the geometrical object by a vector.
pub trait Translate<T> {
/// Translate the geometrical object by a vector `v`.
fn translate(&self, v: Vector<T>) -> Self;
}
/// Scale the geometrical shape. Scaling center is the origin `(0, 0)`.
pub trait Scale<T> {
/// Scale the geometrical shape. Scaling center is the origin `(0, 0)`.
fn scale(&self, factor: T) -> Self;
}
/// Transform the geometrical object by transforming each point of it.
pub trait MapPointwise<T> {
/// Point wise transformation.
fn transform<F>(&self, transformation: F) -> Self
where
F: Fn(Point<T>) -> Point<T>;
}
impl<S, T> Scale<T> for S
where
T: Copy + Mul<Output = T>,
S: MapPointwise<T>,
{
fn scale(&self, factor: T) -> S {
self.transform(|p: Point<T>| p * factor)
}
}
impl<S, T> Translate<T> for S
where
T: Copy + Add<Output = T>,
S: MapPointwise<T>,
{
fn translate(&self, v: Vector<T>) -> S {
self.transform(|p: Point<T>| p + v)
}
}
/// Compute the winding number of a geometrical object around a point.
/// The winding number is used to check if a point is contained in a shape.
pub trait WindingNumber<T> {
/// Calculate the winding number of the polygon around this point.
///
/// TODO: Define how point on edges and vertices is handled.
///
/// See: <http://geomalgorithms.com/a03-_inclusion.html>
fn winding_number(&self, point: Point<T>) -> isize;
/// Check if `point` is inside the polygon, i.e. the polygons winds around the point
/// a non-zero number of times.
///
/// For points on edges the following convention is used:
/// Points on left or bottom edges are inside, points on right or top edges outside.
fn contains_point_non_oriented(&self, point: Point<T>) -> bool {
self.winding_number(point) != 0
}
/// Check if `point` is inside the polygon, i.e. the polygon winds around the point
/// an odd number of times.
///
/// For points on edges the following convention is used:
/// Points on left or bottom edges are inside, points on right or top edges outside.
fn contains_point(&self, point: Point<T>) -> bool {
(self.winding_number(point) % 2 + 2) % 2 == 1
}
}
/// Rotate by a integer multiple of 90 degrees.
pub trait RotateOrtho<T> {
/// Rotate the geometrical shape by a multiple of 90 degrees.
fn rotate_ortho(&self, a: Angle) -> Self;
}
impl<S, T> RotateOrtho<T> for S
where
T: Copy + Zero + Sub<Output = T>,
S: MapPointwise<T>,
{
fn rotate_ortho(&self, a: Angle) -> S {
self.transform(|p: Point<T>| match a {
Angle::R0 => p,
Angle::R90 => Point::new(T::zero() - p.y, p.x),
Angle::R180 => Point::zero() - p.v(),
Angle::R270 => Point::new(p.y, T::zero() - p.x),
})
}
}
/// Mirror at the x or y axis.
pub trait Mirror<T> {
/// Mirror this shape at the `x`-axis.
fn mirror_x(&self) -> Self;
/// Mirror this shape at the `y`-axis.
fn mirror_y(&self) -> Self;
}
impl<S, T> Mirror<T> for S
where
T: Copy + Zero + Sub<Output = T>,
S: MapPointwise<T>,
{
/// Return the geometrical object mirrored at the `x` axis.
fn mirror_x(&self) -> S {
self.transform(|p| Point::new(T::zero() - p.x, p.y))
}
/// Return the geometrical object mirrored at the `y` axis.
fn mirror_y(&self) -> S {
self.transform(|p| Point::new(p.x, T::zero() - p.y))
}
}
//
//pub trait EachPoint<'a, T: 'a>
// where T: CoordinateType {
// type EachPoints: Iterator<Item=&'a Point<T>>;
// fn each_point(&self) -> Self::EachPoints;
//}
/// This trait defines the type-casting of the coordinate types for geometrical objects.
pub trait TryCastCoord<Src, Dst>
where
Src: NumCast,
Dst: NumCast,
{
/// Output type of the cast. This is likely the same geometrical type just with other
/// coordinate types.
type Output;
/// Try to cast to target data type.
///
/// Conversion from float to int can fail and will return `None`.
/// Float values like infinity or non-a-number
/// have no integer representation.
fn try_cast(&self) -> Option<Self::Output>;
/// Cast to target data type.
///
/// Conversion from float to int can fail and panic because float values
/// like infinity or non-a-number have no integer representation.
///
/// # Panics
/// Panics if casting of the coordinate values fails. For instance when trying to cast a 'NaN' float into a integer.
/// Use `try_cast` to detect and handle this failure.
fn cast(&self) -> Self::Output {
self.try_cast().unwrap()
}
}