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
use euclid::{Point2D, Vector2D};

/// The cartesian coordinate system used by everything in a drawing.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum DrawingSpace {}

/// The coordinate system used for graphical objects rendered to a canvas.
///
/// The convention is for the canvas/window's top-left corner to be the origin,
/// with positive `x` going to the right and positive `y` going down the screen.
///
/// To convert from [`DrawingSpace`] to [`CanvasSpace`] you'll need a
/// [`crate::components::Viewport`] representing the area on the drawing the
/// canvas will display. The [`crate::window`] module exposes various utility
/// functions for converting back and forth, with
/// [`crate::window::to_drawing_coordinates()`] and
/// [`crate::window::to_canvas_coordinates()`] being the most useful.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub enum CanvasSpace {}

/// A 2D vector for working in [`DrawingSpace`].
pub type Vector = Vector2D<f64, DrawingSpace>;
/// A transform matrix which for translating something within [`DrawingSpace`].
pub type Transform = euclid::Transform2D<f64, DrawingSpace, DrawingSpace>;
/// A strongly-typed angle, useful for dealing with the pesky modular arithmetic
/// normally associated with circles and angles.
pub type Angle = euclid::Angle<f64>;
/// A location in [`DrawingSpace`].
pub type Point = Point2D<f64, DrawingSpace>;
/// A length in [`DrawingSpace`].
pub type Length = euclid::Length<f64, DrawingSpace>;

/// How something may be oriented.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Orientation {
    Clockwise,
    Anticlockwise,
    Collinear,
}

impl Orientation {
    /// Find the orientation of 3 [`Point`]s.
    pub fn of<S>(
        first: Point2D<f64, S>,
        second: Point2D<f64, S>,
        third: Point2D<f64, S>,
    ) -> Orientation {
        let value = (second.y - first.y) * (third.x - second.x)
            - (second.x - first.x) * (third.y - second.y);

        if value > 0.0 {
            Orientation::Clockwise
        } else if value < 0.0 {
            Orientation::Anticlockwise
        } else {
            Orientation::Collinear
        }
    }
}

/// Find the centre of an arc which passes through 3 [`Point`]s.
///
/// # Note
///
/// If the points are collinear then the problem is ambiguous, the radius
/// effectively becomes infinite and our centre could be literally anywhere.
///
/// ```rust
/// # use arcs::Point;
/// let first = Point::new(0.0, 0.0);
/// let second = Point::new(1.0, 0.0);
/// let third = Point::new(25.0, 0.0);
///
/// let got = arcs::centre_of_three_points(first, second, third);
///
/// assert!(got.is_none());
/// ```
pub fn centre_of_three_points<S>(
    first: Point2D<f64, S>,
    second: Point2D<f64, S>,
    third: Point2D<f64, S>,
) -> Option<Point2D<f64, S>> {
    // it's easier to do the math using vectors, but for semantic correctness we
    // accept points
    let first = first.to_vector();
    let second = second.to_vector();
    let third = third.to_vector();

    let temp = Vector2D::dot(second, second);
    let bc = (Vector2D::dot(first, first) - temp) / 2.0;
    let cd = (temp - third.x * third.x - third.y * third.y) / 2.0;
    let determinant = (first.x - second.x) * (second.y - third.y)
        - (second.x - third.x) * (first.y - second.y);

    if determinant == 0.0 {
        // the points are collinear
        return None;
    }

    let x =
        (bc * (second.y - third.y) - cd * (first.y - second.y)) / determinant;
    let y =
        ((first.x - second.x) * cd - (second.x - third.x) * bc) / determinant;

    Some(Point2D::new(x, y))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn find_centre_of_three_points() {
        let a = Point::new(1.0, 0.0);
        let b = Point::new(-1.0, 0.0);
        let c = Point::new(0.0, 1.0);

        let centre = centre_of_three_points(a, b, c).unwrap();

        assert_eq!(centre, Point::zero());
    }
}