From 7f1a2d1137856df4de0eff75e823c847d0de3173 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 29 May 2024 21:20:22 +1000 Subject: [PATCH 01/27] init rebase to main --- src/lib.rs | 2 + src/triangle.rs | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ src/vec2.rs | 3 + 3 files changed, 370 insertions(+) create mode 100644 src/triangle.rs diff --git a/src/lib.rs b/src/lib.rs index f0b69b5f..64a3b465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,7 @@ mod size; mod stroke; mod svg; mod translate_scale; +mod triangle; mod vec2; pub use crate::affine::Affine; @@ -143,4 +144,5 @@ pub use crate::stroke::{ }; pub use crate::svg::{SvgArc, SvgParseError}; pub use crate::translate_scale::TranslateScale; +pub use crate::triangle::{Triangle, TrianglePathIter}; pub use crate::vec2::Vec2; diff --git a/src/triangle.rs b/src/triangle.rs new file mode 100644 index 00000000..206a85f5 --- /dev/null +++ b/src/triangle.rs @@ -0,0 +1,365 @@ +// Copyright 2024 the Kurbo Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Triangle shape +use crate::{ + Ellipse, Insets, PathEl, Point, Rect, RoundedRect, RoundedRectRadii, Shape, Size, Vec2, +}; + +use core::ops::{Add, Sub}; + +/// Triangle +// A +// * +// / \ +// / \ +// *-----* +// B C +#[derive(Clone, Copy, PartialEq)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Triangle { + pub a: Point, + pub b: Point, + pub c: Point, +} + +impl Default for Triangle { + fn default() -> Self { + Self::IDENTITY + } +} + +impl Triangle { + /// The empty triangle at the origin + pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); + + pub const IDENTITY: Self = Self::from_coords((1.0, 1.0), (0.0, 0.0), (2.0, 0.0)); + + /// A new [`Triangle`] from three vertices ([`Points`]) + #[inline] + pub fn new(a: impl Into, b: impl Into, c: impl Into) -> Self { + Self { + a: a.into(), + b: b.into(), + c: c.into(), + } + } + + /// A new [`Triangle`] from three float vertex coordinates + #[inline] + pub const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { + Self { + a: Point::new(a.0, a.1), + b: Point::new(b.0, b.1), + c: Point::new(c.0, c.1), + } + } + + /// A new `Triangle` from centroid and sizes + #[inline] + pub fn from_centroid_sizes(centroid: impl Into, sizes: [f64; 3]) -> Self { + let centroid = centroid.into(); + Self::new( + (centroid.x, centroid.y + sizes[0]), + centroid + Vec2::IDENTITY * sizes[1], + centroid + Vec2::IDENTITY * sizes[2], + ) + } + + /// A new equilateral [`Triangle`] + /// takes the center and a point equidistant to the midpoints of the vertices (radius) + #[inline] + pub fn from_center_size(center: impl Into, radius: f64) -> Self { + const THETA: f64 = 60.0; // equilateral triangle guarantee + let center = center.into().to_vec2(); + + let h = (radius / ((THETA / 2.0).tan()) * radius / ((THETA / 2.0).tan()) + radius * radius) + .sqrt(); + let a = center.y + radius; + let b = center + Vec2::IDENTITY * h; + let c = center - Vec2::IDENTITY * h; + + Self::new((center.x, a), b.to_point(), c.to_point()) + } + + /// A new [`Triangle`] moved to `centroid` + #[inline] + pub fn with_centroid(self, centroid: impl Into) -> Self { + Self::from_centroid_sizes(centroid, self.sizes()) + } + + /// Creates a new [`Triangle`] with new `sizes` + #[inline] + pub fn with_sizes(self, sizes: [f64; 3]) -> Self { + Self::from_centroid_sizes(self.centroid(), sizes) + } + + /// The centroid of the [`Triangle`] + #[inline] + pub fn centroid(&self) -> Point { + Point::new( + (self.a.x + self.b.x + self.c.x) / 3.0, + (self.a.y + self.b.y + self.c.y) / 3.0, + ) + } + + /// The distance of each vertex from the centroid + #[inline] + pub fn sizes(&self) -> [f64; 3] { + let centroid = self.centroid(); + + [ + self.a.distance(centroid), + self.b.distance(centroid), + self.c.distance(centroid), + ] + } + + /// The area of the [`Triangle`] + #[inline] + pub fn area(&self) -> f64 { + let ab = self.a.distance(self.b); + let ac = self.a.distance(self.c); + let bc = self.b.distance(self.c); + + // cos rule + let theta = ((bc * bc - ab * ab - ac * ac) / -2.0 * ab * ac).acos(); + + // A = 1/2*a*b*sin(C) + (ab * ac * theta.sin()) / 2.0 + } + + // TODO: maybe make height functions for right and non right angled triangles + + /// The area of a right angled [`Triangle`] + #[inline] + pub fn right_angled_area(&self) -> f64 { + // A = 1/2*b*h + (self.b.distance(self.c) * self.b.midpoint(self.c).distance(self.a)) / 2.0 + } + + /// Maximum x-coordinate of the [`Triangle`]'s vertices + #[inline] + pub fn max_x(&self) -> f64 { + self.a.x.max(self.b.x.max(self.c.x)) + } + + /// Minimum x-coordinate of the [`Triangle`]'s vertices + #[inline] + pub fn min_x(&self) -> f64 { + self.a.x.min(self.b.x.min(self.c.x)) + } + + /// Mayimum y-coordinate of the [`Triangle`]'s vertices + #[inline] + pub fn max_y(&self) -> f64 { + self.a.y.max(self.b.y.max(self.c.y)) + } + + /// Minimum y-coordinate of the [`Triangle`]'s vertices + #[inline] + pub fn min_y(&self) -> f64 { + self.a.y.min(self.b.y.min(self.c.y)) + } + + /// Whether this [`Triangle`] has no (zero) area + #[inline] + pub fn is_empty(&self) -> bool { + self.area() == 0.0 + } + + /// Whether this right angled [`Triangle`] has no (zero) area + #[inline] + pub fn is_empty_right_angled(&self) -> bool { + self.right_angled_area() == 0.0 + } + + /// `true` if `point` lies within `self` + #[inline] + pub fn contains(&self, point: Point) -> bool { + point.x >= self.min_x() + && point.x < self.max_x() + && point.y >= self.min_y() + && point.y < self.max_y() + } + + /// The smallest radius of a circle that is within the [`Triangle`] + #[inline] + pub fn radius(&self) -> f64 { + let ab = self.a.distance(self.b); + let bc = self.b.distance(self.c); + let ac = self.a.distance(self.c); + + let s = (ab + bc + ac) / 2.0; + + // Heron's formula + let area = (s * (s - ab) * (s - bc) * (s - ac)).sqrt(); + + area / s + } + + /// Expand the triangle by a constant amount (`sizes`) in all directions + pub fn inflate(&self, sizes: [f64; 3]) -> Self { + Self::new( + (self.a.to_vec2() + Vec2::IDENTITY * sizes[0]).to_point(), + (self.b.to_vec2() + Vec2::IDENTITY * sizes[1]).to_point(), + (self.c.to_vec2() + Vec2::IDENTITY * sizes[2]).to_point(), + ) + } + + /// A new [`Triangle`] with each vertecie's ordinates rounded to the nearest integer + #[inline] + pub fn round(self) -> Self { + Self::new(self.a.round(), self.b.round(), self.c.round()) + } + + /// A new [`Triangle`] with each vertecie's ordinates rounded up to the nearest integer + #[inline] + pub fn ceil(self) -> Self { + Self::new(self.a.ceil(), self.b.ceil(), self.c.ceil()) + } + + /// A new [`Triangle`] with each vertecie's ordinates rounded down to the nearest integer + #[inline] + pub fn floor(self) -> Self { + Self::new(self.a.floor(), self.b.floor(), self.c.floor()) + } + + /// A new [`Triangle`], + /// with each coordinate value rounded towards the center of the [`Triangle`] + /// to the nearest integer, unless they are already an integer. + /// That is to say this function will return the biggest possible [`Triangle`] + /// with integer coordinates that is a subset of `self`. + #[inline] + pub fn expand(self) -> Self { + Self::new(self.a.expand(), self.b.expand(), self.c.expand()) + } + + /// Returns a new [`Triangle`], + /// with each coordinate value rounded towards the center of the [`Triangle`] + /// to the nearest integer, unless they are already an integer. + /// That is to say this function will return the biggest possible [`Triangle`] + /// with integer coordinates that is a subset of `self`. + #[inline] + pub fn trunc(self) -> Self { + Self::new(self.a.trunc(), self.b.trunc(), self.c.trunc()) + } + + /// Scales the [`Triangle`] by a `factor` + #[inline] + pub fn scale(self, factor: f64) -> Self { + Self::new( + (self.a.to_vec2() * factor).to_point(), + (self.b.to_vec2() * factor).to_point(), + (self.c.to_vec2() * factor).to_point(), + ) + } + + // TODO: functions: insets, to_ellipse (see ellipse TODO), aspect ratio, finite & nan +} + +impl From<(Point, Point, Point)> for Triangle { + fn from(points: (Point, Point, Point)) -> Triangle { + Triangle::new(points.0, points.1, points.2) + } +} + +impl From<(Point, f64)> for Triangle { + fn from(params: (Point, f64)) -> Triangle { + Triangle::from_center_size(params.0, params.1) + } +} + +impl Add for Triangle { + type Output = Triangle; + + #[inline] + fn add(self, v: Vec2) -> Triangle { + Triangle::new( + (self.a.to_vec2() + v).to_point(), + (self.b.to_vec2() + v).to_point(), + (self.c.to_vec2() + v).to_point(), + ) + } +} + +impl Sub for Triangle { + type Output = Triangle; + + #[inline] + fn sub(self, v: Vec2) -> Triangle { + Triangle::new( + (self.a.to_vec2() - v).to_point(), + (self.b.to_vec2() - v).to_point(), + (self.c.to_vec2() - v).to_point(), + ) + } +} + +// TODO: sub, see insets TODO + +// TEMP +// TODO: implement +#[doc(hidden)] +pub struct TrianglePathIter { + triangle: Triangle, + ix: usize, +} + +impl Shape for Triangle { + type PathElementsIter<'iter> = TrianglePathIter; + + fn path_elements(&self, _tolerance: f64) -> TrianglePathIter { + TrianglePathIter { + triangle: *self, + ix: 0, + } + } + + #[inline] + fn area(&self) -> f64 { + Triangle::area(self) + } + + #[inline] + fn perimeter(&self, _accuracy: f64) -> f64 { + self.a.distance(self.b) + self.b.distance(self.c) + self.c.distance(self.a) + } + + #[inline] + fn winding(&self, pt: Point) -> i32 { + let xmin = self.min_x(); + let xmax = self.max_x(); + let ymin = self.min_y(); + let ymax = self.max_y(); + + // TODO: may not be correct + if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax { + if (self.c.x > self.b.x) ^ (self.a.y > self.b.y || self.a.y > self.c.y) { + -1 + } else { + 1 + } + } else { + 0 + } + } + + #[inline] + fn bounding_box(&self) -> Rect { + Rect::new(self.min_x(), self.min_y(), self.max_x(), self.max_y()) + } + + #[inline] + fn as_rect(&self) -> Option { + Some(self.bounding_box()) + } + + #[inline] + fn contains(&self, pt: Point) -> bool { + self.contains(pt) + } +} + +// TODO: tests diff --git a/src/vec2.rs b/src/vec2.rs index b3d16f81..44692cd7 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -31,6 +31,9 @@ impl Vec2 { /// The vector (0, 0). pub const ZERO: Vec2 = Vec2::new(0., 0.); + // The vector (1, 1) + pub const IDENTITY: Vec2 = Vec2::new(1., 1.); + /// Create a new vector. #[inline] pub const fn new(x: f64, y: f64) -> Vec2 { From 2d0d6f90f9ec77da837401c059ee6ebda2d91f46 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 30 May 2024 17:34:18 +1000 Subject: [PATCH 02/27] path iterator --- src/triangle.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 206a85f5..969458e9 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -7,6 +7,7 @@ use crate::{ }; use core::ops::{Add, Sub}; +use core::f64::consts::FRAC_PI_3; /// Triangle // A @@ -71,7 +72,7 @@ impl Triangle { /// takes the center and a point equidistant to the midpoints of the vertices (radius) #[inline] pub fn from_center_size(center: impl Into, radius: f64) -> Self { - const THETA: f64 = 60.0; // equilateral triangle guarantee + const THETA: f64 = FRAC_PI_3; // equilateral triangle guarantee let center = center.into().to_vec2(); let h = (radius / ((THETA / 2.0).tan()) * radius / ((THETA / 2.0).tan()) + radius * radius) @@ -299,8 +300,6 @@ impl Sub for Triangle { // TODO: sub, see insets TODO -// TEMP -// TODO: implement #[doc(hidden)] pub struct TrianglePathIter { triangle: Triangle, @@ -362,4 +361,22 @@ impl Shape for Triangle { } } +// Anticlockwise direction from vertices a, b, c +// NOTE: vertices a, b and c are not garunteed to be in order as described in the struct comments +// (i.e. vertex a is topmost, vertex b is leftmost, and vertex c is rightmost) +impl Iterator for TrianglePathIter { + type Item = PathEl; + + fn next(&mut self) -> Option { + self.ix += 1; + match self.ix { + 1 => Some(PathEl::MoveTo(self.triangle.a)), + 2 => Some(PathEl::LineTo(self.triangle.b)), + 3 => Some(PathEl::LineTo(self.triangle.c)), + 4 => Some(PathEl::ClosePath), + _ => None, + } + } +} + // TODO: tests From 20dc3095b62578e29cbf78f849f4187f70950f48 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 30 May 2024 18:34:28 +1000 Subject: [PATCH 03/27] extend api fix TODOS is_nan is_finite ellipses --- src/ellipse.rs | 20 +++++++++++++++++++- src/triangle.rs | 36 ++++++++++++++++++++++++++---------- src/vec2.rs | 2 +- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/ellipse.rs b/src/ellipse.rs index 6ded7dab..7541e464 100644 --- a/src/ellipse.rs +++ b/src/ellipse.rs @@ -9,7 +9,7 @@ use core::{ ops::{Add, Mul, Sub}, }; -use crate::{Affine, Arc, ArcAppendIter, Circle, PathEl, Point, Rect, Shape, Size, Vec2}; +use crate::{Affine, Arc, ArcAppendIter, Circle, PathEl, Point, Rect, Shape, Size, Triangle, Vec2}; #[cfg(not(feature = "std"))] use crate::common::FloatFuncs; @@ -58,6 +58,24 @@ impl Ellipse { Ellipse::private_new(center, width, height, 0.0) } + // TODO: this returns the largest circle not ellipse + /// Returns the largest ellipse that can be bounded by this [`Triangle`]. + /// + /// This ellipse is always axis-aligned; to apply rotation you can call + /// [`with_rotation`] with the result. + /// + /// [`with_rotation`]: Ellipse::with_rotation + #[inline] + pub fn from_triangle(triangle: Triangle) -> Self { + let radius = triangle.radius(); + Ellipse::private_new( + triangle.centroid().to_vec2(), + radius / 2.0, + radius / 2.0, + 0.0, + ) + } + /// Create an ellipse from an affine transformation of the unit circle. #[inline] pub fn from_affine(affine: Affine) -> Self { diff --git a/src/triangle.rs b/src/triangle.rs index 969458e9..02857481 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,12 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{ - Ellipse, Insets, PathEl, Point, Rect, RoundedRect, RoundedRectRadii, Shape, Size, Vec2, -}; +use crate::{Ellipse, PathEl, Point, Rect, Shape, Vec2}; -use core::ops::{Add, Sub}; use core::f64::consts::FRAC_PI_3; +use core::ops::{Add, Sub}; /// Triangle // A @@ -20,8 +18,11 @@ use core::f64::consts::FRAC_PI_3; #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Triangle { + /// vertex a pub a: Point, + /// vertex b pub b: Point, + /// vertex c pub c: Point, } @@ -35,7 +36,8 @@ impl Triangle { /// The empty triangle at the origin pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); - pub const IDENTITY: Self = Self::from_coords((1.0, 1.0), (0.0, 0.0), (2.0, 0.0)); + /// Triangle identity + pub const IDENTITY: Self = Self::from_coords((3.0, 6.0), (0.0, 0.0), (6.0, 0.0)); /// A new [`Triangle`] from three vertices ([`Points`]) #[inline] @@ -185,7 +187,7 @@ impl Triangle { && point.y < self.max_y() } - /// The smallest radius of a circle that is within the [`Triangle`] + /// The greatest radius of a circle that is within the [`Triangle`] #[inline] pub fn radius(&self) -> f64 { let ab = self.a.distance(self.b); @@ -257,7 +259,23 @@ impl Triangle { ) } - // TODO: functions: insets, to_ellipse (see ellipse TODO), aspect ratio, finite & nan + /// Returns the [`Ellipse`] that is bounded by this [`Triangle`]. + #[inline] + pub fn to_ellipse(self) -> Ellipse { + Ellipse::from_triangle(self) + } + + /// Is this [`Triangle`] finite? + #[inline] + pub fn is_finite(&self) -> bool { + self.a.is_finite() && self.b.is_finite() && self.c.is_finite() + } + + /// Is this [`Triangle`] NaN? + #[inline] + pub fn is_nan(&self) -> bool { + self.a.is_nan() || self.b.is_nan() || self.c.is_nan() + } } impl From<(Point, Point, Point)> for Triangle { @@ -298,8 +316,6 @@ impl Sub for Triangle { } } -// TODO: sub, see insets TODO - #[doc(hidden)] pub struct TrianglePathIter { triangle: Triangle, @@ -363,7 +379,7 @@ impl Shape for Triangle { // Anticlockwise direction from vertices a, b, c // NOTE: vertices a, b and c are not garunteed to be in order as described in the struct comments -// (i.e. vertex a is topmost, vertex b is leftmost, and vertex c is rightmost) +// (i.e. as "vertex a is topmost, vertex b is leftmost, and vertex c is rightmost") impl Iterator for TrianglePathIter { type Item = PathEl; diff --git a/src/vec2.rs b/src/vec2.rs index 44692cd7..44e27d63 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -31,7 +31,7 @@ impl Vec2 { /// The vector (0, 0). pub const ZERO: Vec2 = Vec2::new(0., 0.); - // The vector (1, 1) + /// The vector (1, 1) pub const IDENTITY: Vec2 = Vec2::new(1., 1.); /// Create a new vector. From 7181e4f5b6beb0dd052c6d79d5f468b3af1c3d7b Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 30 May 2024 18:37:05 +1000 Subject: [PATCH 04/27] fix ci --- src/triangle.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/triangle.rs b/src/triangle.rs index 02857481..ffcf9db7 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -7,6 +7,9 @@ use crate::{Ellipse, PathEl, Point, Rect, Shape, Vec2}; use core::f64::consts::FRAC_PI_3; use core::ops::{Add, Sub}; +#[cfg(not(feature = "std"))] +use crate::common::FloatFuncs; + /// Triangle // A // * From a7f3a75f9881c0b51b94f076958cecc7d9cdae05 Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 1 Jun 2024 13:11:56 +1000 Subject: [PATCH 05/27] TODO completion progress more functions & start testing --- src/size.rs | 10 +++ src/triangle.rs | 174 ++++++++++++++++++++++++++++++++++++++++++------ src/vec2.rs | 10 ++- 3 files changed, 174 insertions(+), 20 deletions(-) diff --git a/src/size.rs b/src/size.rs index 3fe4b3a0..083afc96 100644 --- a/src/size.rs +++ b/src/size.rs @@ -358,6 +358,16 @@ impl SubAssign for Size { } } +impl From for Size { + #[inline] + fn from(s: f64) -> Size { + Size { + width: s, + height: s, + } + } +} + impl From<(f64, f64)> for Size { #[inline] fn from(v: (f64, f64)) -> Size { diff --git a/src/triangle.rs b/src/triangle.rs index ffcf9db7..e2e4a124 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,10 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{Ellipse, PathEl, Point, Rect, Shape, Vec2}; +use crate::{Ellipse, PathEl, Point, Rect, Shape, Size, Vec2}; use core::f64::consts::FRAC_PI_3; use core::ops::{Add, Sub}; +use std::cmp::*; #[cfg(not(feature = "std"))] use crate::common::FloatFuncs; @@ -17,7 +18,7 @@ use crate::common::FloatFuncs; // / \ // *-----* // B C -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Debug)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Triangle { @@ -62,29 +63,45 @@ impl Triangle { } } - /// A new `Triangle` from centroid and sizes + /// A new [`Triangle`] from centroid and sizes ([`Size`]) - offset of each vertex from the centroid #[inline] - pub fn from_centroid_sizes(centroid: impl Into, sizes: [f64; 3]) -> Self { + pub fn from_centroid_sizes(centroid: impl Into, sizes: [impl Into; 3]) -> Self { let centroid = centroid.into(); + + let mut points: [Point; 3] = Default::default(); + for (i, size) in sizes.into_iter().enumerate() { + points[i] = centroid + size.into().to_vec2(); + } + + Self::new(points[0], points[1], points[2]) + } + + /// A new [`Triangle`] with vertex distances of `distances` from the `centroid` + /// + /// NOTE: the new [`Triangle`] does not keep `centroid` + #[inline] + pub fn from_centroid_distances(centroid: impl Into, distances: [f64; 3]) -> Self { + let centroid = centroid.into(); + Self::new( - (centroid.x, centroid.y + sizes[0]), - centroid + Vec2::IDENTITY * sizes[1], - centroid + Vec2::IDENTITY * sizes[2], + centroid + (0.0, distances[0]), + centroid - Vec2::ONE * distances[1] / 2.0, + centroid + Vec2::ONE * distances[2] / 2.0, ) } /// A new equilateral [`Triangle`] /// takes the center and a point equidistant to the midpoints of the vertices (radius) #[inline] - pub fn from_center_size(center: impl Into, radius: f64) -> Self { + pub fn from_center_radius(center: impl Into, radius: f64) -> Self { const THETA: f64 = FRAC_PI_3; // equilateral triangle guarantee let center = center.into().to_vec2(); let h = (radius / ((THETA / 2.0).tan()) * radius / ((THETA / 2.0).tan()) + radius * radius) .sqrt(); let a = center.y + radius; - let b = center + Vec2::IDENTITY * h; - let c = center - Vec2::IDENTITY * h; + let b = center + Vec2::ONE * h; + let c = center - Vec2::ONE * h; Self::new((center.x, a), b.to_point(), c.to_point()) } @@ -95,12 +112,18 @@ impl Triangle { Self::from_centroid_sizes(centroid, self.sizes()) } - /// Creates a new [`Triangle`] with new `sizes` + /// Creates a new [`Triangle`] with `sizes` #[inline] - pub fn with_sizes(self, sizes: [f64; 3]) -> Self { + pub fn with_sizes(self, sizes: [impl Into; 3]) -> Self { Self::from_centroid_sizes(self.centroid(), sizes) } + /// Creates a new [`Triangle`] with each vertice's `distances` offset from centroid + #[inline] + pub fn with_distances(self, distances: [f64; 3]) -> Self { + Self::from_centroid_distances(self.centroid(), distances) + } + /// The centroid of the [`Triangle`] #[inline] pub fn centroid(&self) -> Point { @@ -110,9 +133,9 @@ impl Triangle { ) } - /// The distance of each vertex from the centroid + /// The euclidean distance of each vertex from the centroid #[inline] - pub fn sizes(&self) -> [f64; 3] { + pub fn distance(&self) -> [f64; 3] { let centroid = self.centroid(); [ @@ -122,6 +145,18 @@ impl Triangle { ] } + /// The offset of each vertex from the centroid + #[inline] + pub fn sizes(&self) -> [Size; 3] { + let centroid = self.centroid().to_vec2(); + + [ + (centroid - self.a.to_vec2()).to_size(), + (centroid - self.b.to_vec2()).to_size(), + (centroid - self.c.to_vec2()).to_size(), + ] + } + /// The area of the [`Triangle`] #[inline] pub fn area(&self) -> f64 { @@ -145,6 +180,65 @@ impl Triangle { (self.b.distance(self.c) * self.b.midpoint(self.c).distance(self.a)) / 2.0 } + /// Updates [`Triangle`]'s vertice positions + /// such that [`Triangle::a`] is topmost, [`Triangle::b`] is leftmost, and [`Triangle::c`] is rightmost + #[inline] + pub fn organise(self) -> Self { + Self::new(self.topmost(), self.leftmost(), self.rightmost()) + } + + /// Vertex coordinate ([`Point`]) of [`Triangle`] with `x` ordinate + #[inline] + pub fn vertex_from_x(&self, x: f64) -> Option { + self.as_array().iter().find(|&v| v.x == x).copied() + } + + /// Vertex coordinate ([`Point`]) of [`Triangle`] with `y` ordinate + #[inline] + pub fn vertex_from_y(&self, y: f64) -> Option { + self.as_array().iter().find(|&v| v.y == y).copied() + } + + /// The topmost vertex of `self` as a [`Point`] + #[inline] + pub fn topmost(&self) -> Point { + *self + .as_array() + .iter() + .max_by(|x, y| x.y.total_cmp(&y.y)) + .unwrap() + } + + /// The bottommost vertex of `self` as a [`Point`] + #[inline] + pub fn bottommost(&self) -> Point { + *self + .as_array() + .iter() + .max_by(|x, y| y.y.total_cmp(&x.y)) + .unwrap() + } + + /// The rightmost vertex of `self` as a [`Point`] + #[inline] + pub fn rightmost(&self) -> Point { + *self + .as_array() + .iter() + .max_by(|x, y| x.x.total_cmp(&y.x)) + .unwrap() + } + + /// The leftmost vertex of `self` as a [`Point`] + #[inline] + pub fn leftmost(&self) -> Point { + *self + .as_array() + .iter() + .max_by(|x, y| y.x.total_cmp(&x.x)) + .unwrap() + } + /// Maximum x-coordinate of the [`Triangle`]'s vertices #[inline] pub fn max_x(&self) -> f64 { @@ -208,9 +302,9 @@ impl Triangle { /// Expand the triangle by a constant amount (`sizes`) in all directions pub fn inflate(&self, sizes: [f64; 3]) -> Self { Self::new( - (self.a.to_vec2() + Vec2::IDENTITY * sizes[0]).to_point(), - (self.b.to_vec2() + Vec2::IDENTITY * sizes[1]).to_point(), - (self.c.to_vec2() + Vec2::IDENTITY * sizes[2]).to_point(), + (self.a.to_vec2() + Vec2::ONE * sizes[0]).to_point(), + (self.b.to_vec2() + Vec2::ONE * sizes[1]).to_point(), + (self.c.to_vec2() + Vec2::ONE * sizes[2]).to_point(), ) } @@ -279,6 +373,12 @@ impl Triangle { pub fn is_nan(&self) -> bool { self.a.is_nan() || self.b.is_nan() || self.c.is_nan() } + + /// [`Triangle`]'s vertices as an array of Points + #[inline] + pub fn as_array(&self) -> [Point; 3] { + [self.a, self.b, self.c] + } } impl From<(Point, Point, Point)> for Triangle { @@ -289,7 +389,7 @@ impl From<(Point, Point, Point)> for Triangle { impl From<(Point, f64)> for Triangle { fn from(params: (Point, f64)) -> Triangle { - Triangle::from_center_size(params.0, params.1) + Triangle::from_center_radius(params.0, params.1) } } @@ -398,4 +498,40 @@ impl Iterator for TrianglePathIter { } } -// TODO: tests +#[cfg(test)] +mod tests { + use crate::{Point, Triangle}; + + #[test] + fn from_centroid_distances() { + let test = Triangle::from_centroid_distances((0.6, 12.2), [11.9, 1.2, 12.3]); + let expected = Triangle::new((0.0, 0.0), (0.1882, 4.3293), (0.8494, 18.1853)); + + assert_eq!(test, expected); + } + + #[test] + fn from_center_radius() { + let test = Triangle::from_center_radius((0.2, 3.3), 10.1); + let expected = Triangle::new((20.4, 3.3), (-9.9, 20.779), (-9.9, -14.179)); + + assert_eq!(test, expected); + } + + #[test] + fn centroid() { + let test = Triangle::new((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); + let expected = Point::new(-24.939999999999998, 1.0999999999999996); + + assert_eq!(test, expected); + } + + // should only need one out of Triangle:: topmost, rightmost, leftmost, bottommost + #[test] + fn topmost() { + let test = Triangle::new((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).topmost(); + let expected = Point::new(12.3, 12.3); + + assert_eq!(test, expected); + } +} diff --git a/src/vec2.rs b/src/vec2.rs index 44e27d63..af6a76b3 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -32,7 +32,7 @@ impl Vec2 { pub const ZERO: Vec2 = Vec2::new(0., 0.); /// The vector (1, 1) - pub const IDENTITY: Vec2 = Vec2::new(1., 1.); + pub const ONE: Vec2 = Vec2::new(1., 1.); /// Create a new vector. #[inline] @@ -163,6 +163,14 @@ impl Vec2 { } } + /// A vector with magnitude `magnitude` with components in `direction` direction + /// + /// see [`Self::from_angle`] to get a unit vector from an angle + /// + /// NOTE: direction should be a unit vector, it is not normalized in this function + // #[inline] + // pub fn from_magnitude(magnitude: f64, direction: Vec2) -> Vec2 {} + /// Linearly interpolate between two vectors. #[inline] pub fn lerp(self, other: Vec2, t: f64) -> Vec2 { From b2eaa7aefcd8b1274bcdcf1bd819a004de25321b Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 1 Jun 2024 15:59:09 +1000 Subject: [PATCH 06/27] tests add all necessary tests --- src/point.rs | 25 ++++++++ src/triangle.rs | 150 +++++++++++++++++++++++++++++++++++------------- src/vec2.rs | 8 ++- 3 files changed, 139 insertions(+), 44 deletions(-) diff --git a/src/point.rs b/src/point.rs index 2216adc7..ee5ef31f 100644 --- a/src/point.rs +++ b/src/point.rs @@ -3,6 +3,7 @@ //! A 2D point. +use core::cmp::Ordering; use core::fmt; use core::ops::{Add, AddAssign, Sub, SubAssign}; @@ -202,6 +203,30 @@ impl Point { pub fn is_nan(self) -> bool { self.x.is_nan() || self.y.is_nan() } + + /// The topmost [`Point`] + #[inline] + pub fn topmost(&self, other: &Self) -> Ordering { + self.y.total_cmp(&other.y) + } + + /// The bottommost [`Point`] + #[inline] + pub fn bottommost(&self, other: &Self) -> Ordering { + other.y.total_cmp(&self.y) + } + + /// The rightmost [`Point`] + #[inline] + pub fn rightmost(&self, other: &Self) -> Ordering { + self.x.total_cmp(&other.x) + } + + /// The leftmost [`Point`] + #[inline] + pub fn leftmost(&self, other: &Self) -> Ordering { + other.x.total_cmp(&self.x) + } } impl From<(f32, f32)> for Point { diff --git a/src/triangle.rs b/src/triangle.rs index e2e4a124..d676bdbf 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -4,9 +4,9 @@ //! Triangle shape use crate::{Ellipse, PathEl, Point, Rect, Shape, Size, Vec2}; +use core::cmp::*; use core::f64::consts::FRAC_PI_3; use core::ops::{Add, Sub}; -use std::cmp::*; #[cfg(not(feature = "std"))] use crate::common::FloatFuncs; @@ -32,16 +32,25 @@ pub struct Triangle { impl Default for Triangle { fn default() -> Self { - Self::IDENTITY + Self::EQUILATERAL } } +macro_rules! most_ordering { + ($x: ident, $by: block) => { + *$x.as_array().iter().max_by($by).unwrap() + }; +} + impl Triangle { - /// The empty triangle at the origin + /// The empty [`Triangle`] at the origin pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); - /// Triangle identity - pub const IDENTITY: Self = Self::from_coords((3.0, 6.0), (0.0, 0.0), (6.0, 0.0)); + /// empty [`Triangle`] at (1.0, 1.0) + pub const ONE: Self = Self::from_coords((1.0, 1.0), (1.0, 1.0), (1.0, 1.0)); + + /// equilateral [`Triangle`] identity + pub const EQUILATERAL: Self = Self::from_coords((3.0, 6.0), (0.0, 0.0), (6.0, 0.0)); /// A new [`Triangle`] from three vertices ([`Points`]) #[inline] @@ -151,12 +160,18 @@ impl Triangle { let centroid = self.centroid().to_vec2(); [ - (centroid - self.a.to_vec2()).to_size(), - (centroid - self.b.to_vec2()).to_size(), - (centroid - self.c.to_vec2()).to_size(), + (self.a.to_vec2() - centroid).to_size(), + (self.b.to_vec2() - centroid).to_size(), + (self.c.to_vec2() - centroid).to_size(), ] } + /// The perimeter of the [`Triangle`] + #[inline] + fn perimeter(&self) -> f64 { + self.a.distance(self.b) + self.b.distance(self.c) + self.c.distance(self.a) + } + /// The area of the [`Triangle`] #[inline] pub fn area(&self) -> f64 { @@ -165,7 +180,7 @@ impl Triangle { let bc = self.b.distance(self.c); // cos rule - let theta = ((bc * bc - ab * ab - ac * ac) / -2.0 * ab * ac).acos(); + let theta = ((bc * bc - ab * ab - ac * ac) / (-2.0 * ab * ac)).acos(); // A = 1/2*a*b*sin(C) (ab * ac * theta.sin()) / 2.0 @@ -174,17 +189,21 @@ impl Triangle { // TODO: maybe make height functions for right and non right angled triangles /// The area of a right angled [`Triangle`] + /// + /// NOTE: assumed [`Triangle::organise`]d positions #[inline] pub fn right_angled_area(&self) -> f64 { // A = 1/2*b*h - (self.b.distance(self.c) * self.b.midpoint(self.c).distance(self.a)) / 2.0 + (self.b.distance(self.c) * (self.a.y - self.b.y)) / 2.0 } /// Updates [`Triangle`]'s vertice positions /// such that [`Triangle::a`] is topmost, [`Triangle::b`] is leftmost, and [`Triangle::c`] is rightmost #[inline] pub fn organise(self) -> Self { - Self::new(self.topmost(), self.leftmost(), self.rightmost()) + let t = Self::new(self.topmost(), self.leftmost(), self.rightmost()); + + t } /// Vertex coordinate ([`Point`]) of [`Triangle`] with `x` ordinate @@ -202,41 +221,25 @@ impl Triangle { /// The topmost vertex of `self` as a [`Point`] #[inline] pub fn topmost(&self) -> Point { - *self - .as_array() - .iter() - .max_by(|x, y| x.y.total_cmp(&y.y)) - .unwrap() + most_ordering!(self, { |x, y| x.topmost(y) }) } /// The bottommost vertex of `self` as a [`Point`] #[inline] pub fn bottommost(&self) -> Point { - *self - .as_array() - .iter() - .max_by(|x, y| y.y.total_cmp(&x.y)) - .unwrap() + most_ordering!(self, { |x, y| x.bottommost(y) }) } /// The rightmost vertex of `self` as a [`Point`] #[inline] pub fn rightmost(&self) -> Point { - *self - .as_array() - .iter() - .max_by(|x, y| x.x.total_cmp(&y.x)) - .unwrap() + most_ordering!(self, { |x, y| x.x.total_cmp(&y.x) }) } /// The leftmost vertex of `self` as a [`Point`] #[inline] pub fn leftmost(&self) -> Point { - *self - .as_array() - .iter() - .max_by(|x, y| y.x.total_cmp(&x.x)) - .unwrap() + most_ordering!(self, { |x, y| y.x.total_cmp(&x.x) }) } /// Maximum x-coordinate of the [`Triangle`]'s vertices @@ -300,11 +303,13 @@ impl Triangle { } /// Expand the triangle by a constant amount (`sizes`) in all directions - pub fn inflate(&self, sizes: [f64; 3]) -> Self { + pub fn inflate(&self, size: impl Into) -> Self { + let size = size.into(); + Self::new( - (self.a.to_vec2() + Vec2::ONE * sizes[0]).to_point(), - (self.b.to_vec2() + Vec2::ONE * sizes[1]).to_point(), - (self.c.to_vec2() + Vec2::ONE * sizes[2]).to_point(), + (self.a.to_vec2() + size.to_vec2()).to_point(), + (self.b.to_vec2() + size.to_vec2()).to_point(), + (self.c.to_vec2() + size.to_vec2()).to_point(), ) } @@ -442,7 +447,7 @@ impl Shape for Triangle { #[inline] fn perimeter(&self, _accuracy: f64) -> f64 { - self.a.distance(self.b) + self.b.distance(self.c) + self.c.distance(self.a) + self.perimeter() } #[inline] @@ -500,7 +505,7 @@ impl Iterator for TrianglePathIter { #[cfg(test)] mod tests { - use crate::{Point, Triangle}; + use crate::{Point, Size, Triangle}; #[test] fn from_centroid_distances() { @@ -526,11 +531,74 @@ mod tests { assert_eq!(test, expected); } - // should only need one out of Triangle:: topmost, rightmost, leftmost, bottommost #[test] - fn topmost() { - let test = Triangle::new((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).topmost(); - let expected = Point::new(12.3, 12.3); + fn sizes() { + let test = Triangle::new((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).sizes(); + let expected = [ + Size::new(-110.39999999999999, 86.8), + Size::new(-89.19999999999999, -93.39999999999999), + Size::new(199.60000000000002, 6.6000000000000085), + ]; + + assert_eq!(test, expected); + } + + #[test] + fn perimeter() { + let test = Triangle::new( + (781239.273894, 789234.234789), + (234897.823471, 378902.234789), + (789241.789234, 789234.234897), + ) + .perimeter(); + let expected = 1380963.0638877784; + + assert_eq!(test, expected); + } + + #[test] + fn area() { + let test = Triangle::new( + (12123.423, 2382.7834), + (7892.729, 238.459), + (7820.2, 712.23), + ) + .area(); + let expected = 1079952.91574081; + + assert_eq!(test, expected); + } + + #[test] + fn right_angled_area() { + let test = Triangle::new((1.2, 5.3), (1.2, 1.6), (10.0, 1.6)).right_angled_area(); + let expected = 16.28; + + assert_eq!(test, expected); + } + + #[test] + fn vertex_from_x() { + let test = Triangle::new((1.0, 3.2), (63.8, 30.2), (2.0, 2.0)) + .vertex_from_x(63.8) + .unwrap(); + let expected = Point::new(63.8, 30.2); + + assert_eq!(test, expected); + } + + #[test] + fn organise() { + let test = Triangle::new((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).organise(); + let expected = Triangle::new((12.3, 12.3), (-20.1, 3.2), (9.2, -100.8)); + + assert_eq!(test, expected); + } + + #[test] + fn radius() { + let test = Triangle::EQUILATERAL.radius(); + let expected = 1.8541019662496845; assert_eq!(test, expected); } diff --git a/src/vec2.rs b/src/vec2.rs index af6a76b3..e718de70 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -163,13 +163,15 @@ impl Vec2 { } } - /// A vector with magnitude `magnitude` with components in `direction` direction + /// A [`Vec2`] with magnitude `magnitude` with components in `direction` direction /// /// see [`Self::from_angle`] to get a unit vector from an angle /// - /// NOTE: direction should be a unit vector, it is not normalized in this function + /// NOTE: direction should be a unit vector because it is not [`Self::normalize`]d in this function // #[inline] - // pub fn from_magnitude(magnitude: f64, direction: Vec2) -> Vec2 {} + pub fn from_magnitude(magnitude: f64, direction: Vec2) -> Vec2 { + todo!() + } /// Linearly interpolate between two vectors. #[inline] From 0be14deb3db0b07a0b20e2cdd9d300c70c683ba8 Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 1 Jun 2024 19:28:19 +1000 Subject: [PATCH 07/27] bug fixes --- src/triangle.rs | 90 ++++++++++++++++++++++++++++++++++++------------- src/vec2.rs | 10 ------ 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index d676bdbf..8f78c078 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -5,7 +5,7 @@ use crate::{Ellipse, PathEl, Point, Rect, Shape, Size, Vec2}; use core::cmp::*; -use core::f64::consts::FRAC_PI_3; +use core::f64::consts::FRAC_PI_4; use core::ops::{Add, Sub}; #[cfg(not(feature = "std"))] @@ -94,30 +94,41 @@ impl Triangle { Self::new( centroid + (0.0, distances[0]), - centroid - Vec2::ONE * distances[1] / 2.0, - centroid + Vec2::ONE * distances[2] / 2.0, + centroid + distances[1] * Vec2::from_angle(5.0 * FRAC_PI_4), + centroid + distances[2] * Vec2::from_angle(7.0 * FRAC_PI_4), ) } /// A new equilateral [`Triangle`] /// takes the center and a point equidistant to the midpoints of the vertices (radius) #[inline] - pub fn from_center_radius(center: impl Into, radius: f64) -> Self { - const THETA: f64 = FRAC_PI_3; // equilateral triangle guarantee - let center = center.into().to_vec2(); + pub fn from_radius(radius: f64) -> Self { + // sqrt(3) (until https://github.com/rust-lang/rust/issues/103883) + pub const SQRT_3: f64 = 1.732050807568877293527446341505872367_f64; - let h = (radius / ((THETA / 2.0).tan()) * radius / ((THETA / 2.0).tan()) + radius * radius) - .sqrt(); - let a = center.y + radius; - let b = center + Vec2::ONE * h; - let c = center - Vec2::ONE * h; + let center = Point::new(0.0, 0.0); // self.centroid().to_vec2(); - Self::new((center.x, a), b.to_point(), c.to_point()) + let length = 2.0 * SQRT_3 * radius; + let height = length * SQRT_3 / 2.0; + let distance = height / 3.0; + + let sizes: [Size; 3] = [ + (0.0, 2.0 * distance).into(), + (-length / 2.0, -height / 3.0).into(), + (length / 2.0, -height / 3.0).into(), + ]; + + // let a = (center.x, center.y + 2.0 * distance); + // let b = (center + Vec2::ONE * h; + // let c = center - Vec2::ONE * h; + + // Self::new(a, b.to_point(), c.to_point()) + Self::from_centroid_sizes(center, sizes) } /// A new [`Triangle`] moved to `centroid` #[inline] - pub fn with_centroid(self, centroid: impl Into) -> Self { + pub fn with_centroid(&self, centroid: impl Into) -> Self { Self::from_centroid_sizes(centroid, self.sizes()) } @@ -294,6 +305,7 @@ impl Triangle { let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); + // semi perimeter let s = (ab + bc + ac) / 2.0; // Heron's formula @@ -394,7 +406,7 @@ impl From<(Point, Point, Point)> for Triangle { impl From<(Point, f64)> for Triangle { fn from(params: (Point, f64)) -> Triangle { - Triangle::from_center_radius(params.0, params.1) + Triangle::from_radius(params.1).with_centroid(params.0) } } @@ -424,6 +436,8 @@ impl Sub for Triangle { } } +// TODO: Insets + #[doc(hidden)] pub struct TrianglePathIter { triangle: Triangle, @@ -507,25 +521,53 @@ impl Iterator for TrianglePathIter { mod tests { use crate::{Point, Size, Triangle}; + #[test] + fn from_centroid_sizes() { + let test = Triangle::from_centroid_distances((0.06, 12.2), [11.9, 1.2, 12.3]); + let expected = Triangle::from_coords( + (0.06, 24.1), + (-0.7885281374238573, 11.351471862576142), + (8.757413408594534, 3.5025865914054624), + ); + + assert_eq!(test, expected); + } + #[test] fn from_centroid_distances() { let test = Triangle::from_centroid_distances((0.6, 12.2), [11.9, 1.2, 12.3]); - let expected = Triangle::new((0.0, 0.0), (0.1882, 4.3293), (0.8494, 18.1853)); + let expected = Triangle::from_coords( + (0.6, 24.1), + (-0.24852813742385726, 11.351471862576142), + (9.297413408594533, 3.5025865914054624), + ); + + assert_eq!(test, expected); + } + + #[test] + fn from_radius() { + let test = Triangle::from_radius(10.1); + let expected = Triangle::from_coords( + (0.0, 20.199999999999996), + (-17.493713156445658, -10.099999999999998), + (17.493713156445658, -10.099999999999998), + ); assert_eq!(test, expected); } #[test] - fn from_center_radius() { - let test = Triangle::from_center_radius((0.2, 3.3), 10.1); - let expected = Triangle::new((20.4, 3.3), (-9.9, 20.779), (-9.9, -14.179)); + fn with_centroid() { + let test = Triangle::EQUILATERAL.with_centroid((12.2, -2.3)); + let expected = Triangle::from_coords((12.2, 1.7000000000000002), (9.2, -4.3), (15.2, -4.3)); assert_eq!(test, expected); } #[test] fn centroid() { - let test = Triangle::new((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); + let test = Triangle::from_coords((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); let expected = Point::new(-24.939999999999998, 1.0999999999999996); assert_eq!(test, expected); @@ -533,7 +575,7 @@ mod tests { #[test] fn sizes() { - let test = Triangle::new((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).sizes(); + let test = Triangle::from_coords((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).sizes(); let expected = [ Size::new(-110.39999999999999, 86.8), Size::new(-89.19999999999999, -93.39999999999999), @@ -571,7 +613,7 @@ mod tests { #[test] fn right_angled_area() { - let test = Triangle::new((1.2, 5.3), (1.2, 1.6), (10.0, 1.6)).right_angled_area(); + let test = Triangle::from_coords((1.2, 5.3), (1.2, 1.6), (10.0, 1.6)).right_angled_area(); let expected = 16.28; assert_eq!(test, expected); @@ -579,7 +621,7 @@ mod tests { #[test] fn vertex_from_x() { - let test = Triangle::new((1.0, 3.2), (63.8, 30.2), (2.0, 2.0)) + let test = Triangle::from_coords((1.0, 3.2), (63.8, 30.2), (2.0, 2.0)) .vertex_from_x(63.8) .unwrap(); let expected = Point::new(63.8, 30.2); @@ -589,8 +631,8 @@ mod tests { #[test] fn organise() { - let test = Triangle::new((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).organise(); - let expected = Triangle::new((12.3, 12.3), (-20.1, 3.2), (9.2, -100.8)); + let test = Triangle::from_coords((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).organise(); + let expected = Triangle::from_coords((12.3, 12.3), (-20.1, 3.2), (9.2, -100.8)); assert_eq!(test, expected); } diff --git a/src/vec2.rs b/src/vec2.rs index e718de70..787f5898 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -163,16 +163,6 @@ impl Vec2 { } } - /// A [`Vec2`] with magnitude `magnitude` with components in `direction` direction - /// - /// see [`Self::from_angle`] to get a unit vector from an angle - /// - /// NOTE: direction should be a unit vector because it is not [`Self::normalize`]d in this function - // #[inline] - pub fn from_magnitude(magnitude: f64, direction: Vec2) -> Vec2 { - todo!() - } - /// Linearly interpolate between two vectors. #[inline] pub fn lerp(self, other: Vec2, t: f64) -> Vec2 { From 276076de8c443ad95fe2a2a62518da6105762321 Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 2 Jun 2024 08:44:04 +1000 Subject: [PATCH 08/27] working `organise`\(\) --- src/triangle.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 8f78c078..f153b3a1 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -212,9 +212,13 @@ impl Triangle { /// such that [`Triangle::a`] is topmost, [`Triangle::b`] is leftmost, and [`Triangle::c`] is rightmost #[inline] pub fn organise(self) -> Self { - let t = Self::new(self.topmost(), self.leftmost(), self.rightmost()); + let mut points = self.as_array(); + points.sort_by(|x, y| x.rightmost(y)); - t + let a = self.topmost(); + let b = *points.iter().find(|&&p| p != a).unwrap(); + let c = *points.iter().rev().find(|&&p| p != a).unwrap(); + Self::new(a, b, c) } /// Vertex coordinate ([`Point`]) of [`Triangle`] with `x` ordinate From 7358faaca070bd447e9d29a7bafa1e68c7dac066 Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 2 Jun 2024 09:02:27 +1000 Subject: [PATCH 09/27] proper ellipse size --- src/ellipse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ellipse.rs b/src/ellipse.rs index 7541e464..1fbe38db 100644 --- a/src/ellipse.rs +++ b/src/ellipse.rs @@ -70,8 +70,8 @@ impl Ellipse { let radius = triangle.radius(); Ellipse::private_new( triangle.centroid().to_vec2(), - radius / 2.0, - radius / 2.0, + radius, + radius, 0.0, ) } From 0532dc964802ae4e71e2daa87eae0f21110dcf7e Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 2 Jun 2024 11:04:56 +1000 Subject: [PATCH 10/27] fix ci --- src/ellipse.rs | 7 +------ src/triangle.rs | 13 +++++++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ellipse.rs b/src/ellipse.rs index 1fbe38db..635eaa21 100644 --- a/src/ellipse.rs +++ b/src/ellipse.rs @@ -68,12 +68,7 @@ impl Ellipse { #[inline] pub fn from_triangle(triangle: Triangle) -> Self { let radius = triangle.radius(); - Ellipse::private_new( - triangle.centroid().to_vec2(), - radius, - radius, - 0.0, - ) + Ellipse::private_new(triangle.centroid().to_vec2(), radius, radius, 0.0) } /// Create an ellipse from an affine transformation of the unit circle. diff --git a/src/triangle.rs b/src/triangle.rs index f153b3a1..b52e3326 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -525,6 +525,11 @@ impl Iterator for TrianglePathIter { mod tests { use crate::{Point, Size, Triangle}; + fn assert_approx_eq(x: f64, y: f64) { + assert!((x - y).abs() < 1e-7); + } + + #[test] fn from_centroid_sizes() { let test = Triangle::from_centroid_distances((0.06, 12.2), [11.9, 1.2, 12.3]); @@ -599,7 +604,7 @@ mod tests { .perimeter(); let expected = 1380963.0638877784; - assert_eq!(test, expected); + assert_approx_eq(test, expected); } #[test] @@ -612,7 +617,7 @@ mod tests { .area(); let expected = 1079952.91574081; - assert_eq!(test, expected); + assert_approx_eq(test, expected); } #[test] @@ -620,7 +625,7 @@ mod tests { let test = Triangle::from_coords((1.2, 5.3), (1.2, 1.6), (10.0, 1.6)).right_angled_area(); let expected = 16.28; - assert_eq!(test, expected); + assert_approx_eq(test, expected); } #[test] @@ -646,6 +651,6 @@ mod tests { let test = Triangle::EQUILATERAL.radius(); let expected = 1.8541019662496845; - assert_eq!(test, expected); + assert_approx_eq(test, expected); } } From 1126a0f9b0712b290e8a43ffe4ca090b61fd376a Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 2 Jun 2024 11:08:15 +1000 Subject: [PATCH 11/27] chore: format --- src/triangle.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/triangle.rs b/src/triangle.rs index b52e3326..5d4afdbd 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -529,7 +529,6 @@ mod tests { assert!((x - y).abs() < 1e-7); } - #[test] fn from_centroid_sizes() { let test = Triangle::from_centroid_distances((0.06, 12.2), [11.9, 1.2, 12.3]); From 8f0e1a86e41f9f58d40dbf568af492a96f10dabe Mon Sep 17 00:00:00 2001 From: julia Date: Thu, 15 Aug 2024 14:17:39 +1000 Subject: [PATCH 12/27] apply suggestions --- src/ellipse.rs | 15 +- src/size.rs | 10 -- src/triangle.rs | 386 +++++++----------------------------------------- src/vec2.rs | 3 - 4 files changed, 52 insertions(+), 362 deletions(-) diff --git a/src/ellipse.rs b/src/ellipse.rs index 635eaa21..6ded7dab 100644 --- a/src/ellipse.rs +++ b/src/ellipse.rs @@ -9,7 +9,7 @@ use core::{ ops::{Add, Mul, Sub}, }; -use crate::{Affine, Arc, ArcAppendIter, Circle, PathEl, Point, Rect, Shape, Size, Triangle, Vec2}; +use crate::{Affine, Arc, ArcAppendIter, Circle, PathEl, Point, Rect, Shape, Size, Vec2}; #[cfg(not(feature = "std"))] use crate::common::FloatFuncs; @@ -58,19 +58,6 @@ impl Ellipse { Ellipse::private_new(center, width, height, 0.0) } - // TODO: this returns the largest circle not ellipse - /// Returns the largest ellipse that can be bounded by this [`Triangle`]. - /// - /// This ellipse is always axis-aligned; to apply rotation you can call - /// [`with_rotation`] with the result. - /// - /// [`with_rotation`]: Ellipse::with_rotation - #[inline] - pub fn from_triangle(triangle: Triangle) -> Self { - let radius = triangle.radius(); - Ellipse::private_new(triangle.centroid().to_vec2(), radius, radius, 0.0) - } - /// Create an ellipse from an affine transformation of the unit circle. #[inline] pub fn from_affine(affine: Affine) -> Self { diff --git a/src/size.rs b/src/size.rs index 083afc96..3fe4b3a0 100644 --- a/src/size.rs +++ b/src/size.rs @@ -358,16 +358,6 @@ impl SubAssign for Size { } } -impl From for Size { - #[inline] - fn from(s: f64) -> Size { - Size { - width: s, - height: s, - } - } -} - impl From<(f64, f64)> for Size { #[inline] fn from(v: (f64, f64)) -> Size { diff --git a/src/triangle.rs b/src/triangle.rs index 5d4afdbd..d7c8ccf9 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{Ellipse, PathEl, Point, Rect, Shape, Size, Vec2}; +use crate::{PathEl, Point, Rect, Shape, Vec2}; use core::cmp::*; use core::f64::consts::FRAC_PI_4; @@ -30,18 +30,6 @@ pub struct Triangle { pub c: Point, } -impl Default for Triangle { - fn default() -> Self { - Self::EQUILATERAL - } -} - -macro_rules! most_ordering { - ($x: ident, $by: block) => { - *$x.as_array().iter().max_by($by).unwrap() - }; -} - impl Triangle { /// The empty [`Triangle`] at the origin pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); @@ -49,8 +37,15 @@ impl Triangle { /// empty [`Triangle`] at (1.0, 1.0) pub const ONE: Self = Self::from_coords((1.0, 1.0), (1.0, 1.0), (1.0, 1.0)); - /// equilateral [`Triangle`] identity - pub const EQUILATERAL: Self = Self::from_coords((3.0, 6.0), (0.0, 0.0), (6.0, 0.0)); + /// equilateral [`Triangle`] + pub const EQUILATERAL: Self = Self::from_coords( + ( + 1.0 / 2.0, + 1.732050807568877293527446341505872367_f64 / 2.0, /* (sqrt 3)/2 */ + ), + (0.0, 0.0), + (1.0, 0.0), + ); /// A new [`Triangle`] from three vertices ([`Points`]) #[inline] @@ -72,213 +67,41 @@ impl Triangle { } } - /// A new [`Triangle`] from centroid and sizes ([`Size`]) - offset of each vertex from the centroid + /// A new [`Triangle`] from centroid and offsets ([`Vec2`]) - offset of each vertex from the centroid #[inline] - pub fn from_centroid_sizes(centroid: impl Into, sizes: [impl Into; 3]) -> Self { + fn from_centroid_offsets(centroid: impl Into, offsets: [impl Into; 3]) -> Self { let centroid = centroid.into(); let mut points: [Point; 3] = Default::default(); - for (i, size) in sizes.into_iter().enumerate() { - points[i] = centroid + size.into().to_vec2(); + for (i, offset) in offsets.into_iter().enumerate() { + points[i] = centroid + offset.into(); } Self::new(points[0], points[1], points[2]) } - /// A new [`Triangle`] with vertex distances of `distances` from the `centroid` - /// - /// NOTE: the new [`Triangle`] does not keep `centroid` - #[inline] - pub fn from_centroid_distances(centroid: impl Into, distances: [f64; 3]) -> Self { - let centroid = centroid.into(); - - Self::new( - centroid + (0.0, distances[0]), - centroid + distances[1] * Vec2::from_angle(5.0 * FRAC_PI_4), - centroid + distances[2] * Vec2::from_angle(7.0 * FRAC_PI_4), - ) - } - - /// A new equilateral [`Triangle`] - /// takes the center and a point equidistant to the midpoints of the vertices (radius) - #[inline] - pub fn from_radius(radius: f64) -> Self { - // sqrt(3) (until https://github.com/rust-lang/rust/issues/103883) - pub const SQRT_3: f64 = 1.732050807568877293527446341505872367_f64; - - let center = Point::new(0.0, 0.0); // self.centroid().to_vec2(); - - let length = 2.0 * SQRT_3 * radius; - let height = length * SQRT_3 / 2.0; - let distance = height / 3.0; - - let sizes: [Size; 3] = [ - (0.0, 2.0 * distance).into(), - (-length / 2.0, -height / 3.0).into(), - (length / 2.0, -height / 3.0).into(), - ]; - - // let a = (center.x, center.y + 2.0 * distance); - // let b = (center + Vec2::ONE * h; - // let c = center - Vec2::ONE * h; - - // Self::new(a, b.to_point(), c.to_point()) - Self::from_centroid_sizes(center, sizes) - } - - /// A new [`Triangle`] moved to `centroid` - #[inline] - pub fn with_centroid(&self, centroid: impl Into) -> Self { - Self::from_centroid_sizes(centroid, self.sizes()) - } - - /// Creates a new [`Triangle`] with `sizes` - #[inline] - pub fn with_sizes(self, sizes: [impl Into; 3]) -> Self { - Self::from_centroid_sizes(self.centroid(), sizes) - } - - /// Creates a new [`Triangle`] with each vertice's `distances` offset from centroid - #[inline] - pub fn with_distances(self, distances: [f64; 3]) -> Self { - Self::from_centroid_distances(self.centroid(), distances) - } - /// The centroid of the [`Triangle`] #[inline] pub fn centroid(&self) -> Point { - Point::new( - (self.a.x + self.b.x + self.c.x) / 3.0, - (self.a.y + self.b.y + self.c.y) / 3.0, - ) - } - - /// The euclidean distance of each vertex from the centroid - #[inline] - pub fn distance(&self) -> [f64; 3] { - let centroid = self.centroid(); - - [ - self.a.distance(centroid), - self.b.distance(centroid), - self.c.distance(centroid), - ] + (1.0 / 3.0 * (self.a.to_vec2() + self.b.to_vec2() + self.c.to_vec2())).to_point() } /// The offset of each vertex from the centroid #[inline] - pub fn sizes(&self) -> [Size; 3] { + pub fn offsets(&self) -> [Vec2; 3] { let centroid = self.centroid().to_vec2(); [ - (self.a.to_vec2() - centroid).to_size(), - (self.b.to_vec2() - centroid).to_size(), - (self.c.to_vec2() - centroid).to_size(), + (self.a.to_vec2() - centroid), + (self.b.to_vec2() - centroid), + (self.c.to_vec2() - centroid), ] } - /// The perimeter of the [`Triangle`] - #[inline] - fn perimeter(&self) -> f64 { - self.a.distance(self.b) + self.b.distance(self.c) + self.c.distance(self.a) - } - /// The area of the [`Triangle`] #[inline] pub fn area(&self) -> f64 { - let ab = self.a.distance(self.b); - let ac = self.a.distance(self.c); - let bc = self.b.distance(self.c); - - // cos rule - let theta = ((bc * bc - ab * ab - ac * ac) / (-2.0 * ab * ac)).acos(); - - // A = 1/2*a*b*sin(C) - (ab * ac * theta.sin()) / 2.0 - } - - // TODO: maybe make height functions for right and non right angled triangles - - /// The area of a right angled [`Triangle`] - /// - /// NOTE: assumed [`Triangle::organise`]d positions - #[inline] - pub fn right_angled_area(&self) -> f64 { - // A = 1/2*b*h - (self.b.distance(self.c) * (self.a.y - self.b.y)) / 2.0 - } - - /// Updates [`Triangle`]'s vertice positions - /// such that [`Triangle::a`] is topmost, [`Triangle::b`] is leftmost, and [`Triangle::c`] is rightmost - #[inline] - pub fn organise(self) -> Self { - let mut points = self.as_array(); - points.sort_by(|x, y| x.rightmost(y)); - - let a = self.topmost(); - let b = *points.iter().find(|&&p| p != a).unwrap(); - let c = *points.iter().rev().find(|&&p| p != a).unwrap(); - Self::new(a, b, c) - } - - /// Vertex coordinate ([`Point`]) of [`Triangle`] with `x` ordinate - #[inline] - pub fn vertex_from_x(&self, x: f64) -> Option { - self.as_array().iter().find(|&v| v.x == x).copied() - } - - /// Vertex coordinate ([`Point`]) of [`Triangle`] with `y` ordinate - #[inline] - pub fn vertex_from_y(&self, y: f64) -> Option { - self.as_array().iter().find(|&v| v.y == y).copied() - } - - /// The topmost vertex of `self` as a [`Point`] - #[inline] - pub fn topmost(&self) -> Point { - most_ordering!(self, { |x, y| x.topmost(y) }) - } - - /// The bottommost vertex of `self` as a [`Point`] - #[inline] - pub fn bottommost(&self) -> Point { - most_ordering!(self, { |x, y| x.bottommost(y) }) - } - - /// The rightmost vertex of `self` as a [`Point`] - #[inline] - pub fn rightmost(&self) -> Point { - most_ordering!(self, { |x, y| x.x.total_cmp(&y.x) }) - } - - /// The leftmost vertex of `self` as a [`Point`] - #[inline] - pub fn leftmost(&self) -> Point { - most_ordering!(self, { |x, y| y.x.total_cmp(&x.x) }) - } - - /// Maximum x-coordinate of the [`Triangle`]'s vertices - #[inline] - pub fn max_x(&self) -> f64 { - self.a.x.max(self.b.x.max(self.c.x)) - } - - /// Minimum x-coordinate of the [`Triangle`]'s vertices - #[inline] - pub fn min_x(&self) -> f64 { - self.a.x.min(self.b.x.min(self.c.x)) - } - - /// Mayimum y-coordinate of the [`Triangle`]'s vertices - #[inline] - pub fn max_y(&self) -> f64 { - self.a.y.max(self.b.y.max(self.c.y)) - } - - /// Minimum y-coordinate of the [`Triangle`]'s vertices - #[inline] - pub fn min_y(&self) -> f64 { - self.a.y.min(self.b.y.min(self.c.y)) + 0.5 * (self.b - self.a).cross(self.c - self.a) } /// Whether this [`Triangle`] has no (zero) area @@ -287,102 +110,37 @@ impl Triangle { self.area() == 0.0 } - /// Whether this right angled [`Triangle`] has no (zero) area - #[inline] - pub fn is_empty_right_angled(&self) -> bool { - self.right_angled_area() == 0.0 - } - - /// `true` if `point` lies within `self` - #[inline] - pub fn contains(&self, point: Point) -> bool { - point.x >= self.min_x() - && point.x < self.max_x() - && point.y >= self.min_y() - && point.y < self.max_y() - } - /// The greatest radius of a circle that is within the [`Triangle`] #[inline] - pub fn radius(&self) -> f64 { + pub fn incircle(&self) -> f64 { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); - // semi perimeter - let s = (ab + bc + ac) / 2.0; - - // Heron's formula - let area = (s * (s - ab) * (s - bc) * (s - ac)).sqrt(); - - area / s + self.area() / (ab + bc + ac) / 2.0 } - /// Expand the triangle by a constant amount (`sizes`) in all directions - pub fn inflate(&self, size: impl Into) -> Self { - let size = size.into(); - - Self::new( - (self.a.to_vec2() + size.to_vec2()).to_point(), - (self.b.to_vec2() + size.to_vec2()).to_point(), - (self.c.to_vec2() + size.to_vec2()).to_point(), - ) - } - - /// A new [`Triangle`] with each vertecie's ordinates rounded to the nearest integer - #[inline] - pub fn round(self) -> Self { - Self::new(self.a.round(), self.b.round(), self.c.round()) - } - - /// A new [`Triangle`] with each vertecie's ordinates rounded up to the nearest integer + /// Circumcircle of the [`Triangle`] #[inline] - pub fn ceil(self) -> Self { - Self::new(self.a.ceil(), self.b.ceil(), self.c.ceil()) - } - - /// A new [`Triangle`] with each vertecie's ordinates rounded down to the nearest integer - #[inline] - pub fn floor(self) -> Self { - Self::new(self.a.floor(), self.b.floor(), self.c.floor()) - } + pub fn circumcircle(&self) -> f64 { + let ab = self.a.distance(self.b); + let bc = self.b.distance(self.c); + let ac = self.a.distance(self.c); - /// A new [`Triangle`], - /// with each coordinate value rounded towards the center of the [`Triangle`] - /// to the nearest integer, unless they are already an integer. - /// That is to say this function will return the biggest possible [`Triangle`] - /// with integer coordinates that is a subset of `self`. - #[inline] - pub fn expand(self) -> Self { - Self::new(self.a.expand(), self.b.expand(), self.c.expand()) + ab * bc * ac / (((ab + bc + ac) * (bc + ac - ab) * (ab + ac - bc) * (ab + bc - ac)).sqrt()) } - /// Returns a new [`Triangle`], - /// with each coordinate value rounded towards the center of the [`Triangle`] - /// to the nearest integer, unless they are already an integer. - /// That is to say this function will return the biggest possible [`Triangle`] - /// with integer coordinates that is a subset of `self`. - #[inline] - pub fn trunc(self) -> Self { - Self::new(self.a.trunc(), self.b.trunc(), self.c.trunc()) - } + /// Expand the triangle by a constant amount (`sizes`) in all directions + pub fn inflate(&self, scalar: f64) -> Self { + let centroid = self.centroid(); - /// Scales the [`Triangle`] by a `factor` - #[inline] - pub fn scale(self, factor: f64) -> Self { Self::new( - (self.a.to_vec2() * factor).to_point(), - (self.b.to_vec2() * factor).to_point(), - (self.c.to_vec2() * factor).to_point(), + centroid + (0.0, scalar), + centroid + scalar * Vec2::from_angle(5.0 * FRAC_PI_4), + centroid + scalar * Vec2::from_angle(7.0 * FRAC_PI_4), ) } - /// Returns the [`Ellipse`] that is bounded by this [`Triangle`]. - #[inline] - pub fn to_ellipse(self) -> Ellipse { - Ellipse::from_triangle(self) - } - /// Is this [`Triangle`] finite? #[inline] pub fn is_finite(&self) -> bool { @@ -408,12 +166,6 @@ impl From<(Point, Point, Point)> for Triangle { } } -impl From<(Point, f64)> for Triangle { - fn from(params: (Point, f64)) -> Triangle { - Triangle::from_radius(params.1).with_centroid(params.0) - } -} - impl Add for Triangle { type Output = Triangle; @@ -465,45 +217,33 @@ impl Shape for Triangle { #[inline] fn perimeter(&self, _accuracy: f64) -> f64 { - self.perimeter() + self.a.distance(self.b) + self.b.distance(self.c) + self.c.distance(self.a) } #[inline] fn winding(&self, pt: Point) -> i32 { - let xmin = self.min_x(); - let xmax = self.max_x(); - let ymin = self.min_y(); - let ymax = self.max_y(); - - // TODO: may not be correct - if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax { - if (self.c.x > self.b.x) ^ (self.a.y > self.b.y || self.a.y > self.c.y) { - -1 - } else { - 1 - } - } else { - 0 - } + let s0 = (self.b - self.a).cross(pt - self.a).signum(); + let s1 = (self.c - self.b).cross(pt - self.b).signum(); + let s2 = (self.a - self.c).cross(pt - self.c).signum(); + if s0 == s1 && s1 == s2 { s0 as i32 } else { 0 } } #[inline] fn bounding_box(&self) -> Rect { - Rect::new(self.min_x(), self.min_y(), self.max_x(), self.max_y()) + Rect::new( + self.a.x.min(self.b.x.min(self.c.x)), + self.a.y.min(self.b.y.min(self.c.y)), + self.a.x.max(self.b.x.max(self.c.x)), + self.a.y.max(self.b.y.max(self.c.y)), + ) } #[inline] fn as_rect(&self) -> Option { Some(self.bounding_box()) } - - #[inline] - fn contains(&self, pt: Point) -> bool { - self.contains(pt) - } } -// Anticlockwise direction from vertices a, b, c // NOTE: vertices a, b and c are not garunteed to be in order as described in the struct comments // (i.e. as "vertex a is topmost, vertex b is leftmost, and vertex c is rightmost") impl Iterator for TrianglePathIter { @@ -523,36 +263,12 @@ impl Iterator for TrianglePathIter { #[cfg(test)] mod tests { - use crate::{Point, Size, Triangle}; + use crate::{Point, Triangle, Vec2}; fn assert_approx_eq(x: f64, y: f64) { assert!((x - y).abs() < 1e-7); } - #[test] - fn from_centroid_sizes() { - let test = Triangle::from_centroid_distances((0.06, 12.2), [11.9, 1.2, 12.3]); - let expected = Triangle::from_coords( - (0.06, 24.1), - (-0.7885281374238573, 11.351471862576142), - (8.757413408594534, 3.5025865914054624), - ); - - assert_eq!(test, expected); - } - - #[test] - fn from_centroid_distances() { - let test = Triangle::from_centroid_distances((0.6, 12.2), [11.9, 1.2, 12.3]); - let expected = Triangle::from_coords( - (0.6, 24.1), - (-0.24852813742385726, 11.351471862576142), - (9.297413408594533, 3.5025865914054624), - ); - - assert_eq!(test, expected); - } - #[test] fn from_radius() { let test = Triangle::from_radius(10.1); @@ -582,12 +298,12 @@ mod tests { } #[test] - fn sizes() { - let test = Triangle::from_coords((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).sizes(); + fn offsets() { + let test = Triangle::from_coords((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).offsets(); let expected = [ - Size::new(-110.39999999999999, 86.8), - Size::new(-89.19999999999999, -93.39999999999999), - Size::new(199.60000000000002, 6.6000000000000085), + Vec2::new(-110.39999999999999, 86.8), + Vec2::new(-89.19999999999999, -93.39999999999999), + Vec2::new(199.60000000000002, 6.6000000000000085), ]; assert_eq!(test, expected); diff --git a/src/vec2.rs b/src/vec2.rs index 787f5898..b3d16f81 100644 --- a/src/vec2.rs +++ b/src/vec2.rs @@ -31,9 +31,6 @@ impl Vec2 { /// The vector (0, 0). pub const ZERO: Vec2 = Vec2::new(0., 0.); - /// The vector (1, 1) - pub const ONE: Vec2 = Vec2::new(1., 1.); - /// Create a new vector. #[inline] pub const fn new(x: f64, y: f64) -> Vec2 { From e1cce3d73afbe8ce3bbeb4e4a8434ff31d56a429 Mon Sep 17 00:00:00 2001 From: julia Date: Thu, 15 Aug 2024 18:03:56 +1000 Subject: [PATCH 13/27] tests fix old tests + add new circle tests --- src/triangle.rs | 105 +++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 60 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index d7c8ccf9..299af03d 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -86,6 +86,27 @@ impl Triangle { (1.0 / 3.0 * (self.a.to_vec2() + self.b.to_vec2() + self.c.to_vec2())).to_point() } + /// The circumcenter of the [`Triangle`] + #[inline] + pub fn circumcenter(&self) -> Point { + let d = 2.0 + * (self.a.x * (self.b.y - self.c.y) + + self.b.x * (self.c.y - self.a.y) + + self.c.x * (self.a.y - self.b.y)); + + let ux = ((self.a.x.powi(2) + self.a.y.powi(2)) * (self.b.y - self.c.y) + + (self.b.x.powi(2) + self.b.y.powi(2)) * (self.c.y - self.a.y) + + (self.c.x.powi(2) + self.c.y.powi(2)) * (self.a.y - self.b.y)) + / d; + + let uy = ((self.a.x.powi(2) + self.a.y.powi(2)) * (self.c.x - self.b.x) + + (self.b.x.powi(2) + self.b.y.powi(2)) * (self.a.x - self.c.x) + + (self.c.x.powi(2) + self.c.y.powi(2)) * (self.b.x - self.a.x)) + / d; + + Point::new(ux, uy) + } + /// The offset of each vertex from the centroid #[inline] pub fn offsets(&self) -> [Vec2; 3] { @@ -101,7 +122,7 @@ impl Triangle { /// The area of the [`Triangle`] #[inline] pub fn area(&self) -> f64 { - 0.5 * (self.b - self.a).cross(self.c - self.a) + 0.5 * (self.b - self.a).cross(self.c - self.a).abs() } /// Whether this [`Triangle`] has no (zero) area @@ -111,26 +132,29 @@ impl Triangle { } /// The greatest radius of a circle that is within the [`Triangle`] + /// with center [`Triangle::circumcenter`] #[inline] pub fn incircle(&self) -> f64 { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); - self.area() / (ab + bc + ac) / 2.0 + 2.0 * self.area() / (ab + bc + ac) } /// Circumcircle of the [`Triangle`] + /// with center [`Triangle::circumcenter`] #[inline] pub fn circumcircle(&self) -> f64 { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); - ab * bc * ac / (((ab + bc + ac) * (bc + ac - ab) * (ab + ac - bc) * (ab + bc - ac)).sqrt()) + (ab * bc * ac) / (4.0 * self.area()) + // ab * bc * ac / (((ab + bc + ac) * (bc + ac - ab) * (ab + ac - bc) * (ab + bc - ac)).sqrt()) } - /// Expand the triangle by a constant amount (`sizes`) in all directions + /// Expand the triangle by a constant amount (`scalar`) in all directions pub fn inflate(&self, scalar: f64) -> Self { let centroid = self.centroid(); @@ -192,8 +216,6 @@ impl Sub for Triangle { } } -// TODO: Insets - #[doc(hidden)] pub struct TrianglePathIter { triangle: Triangle, @@ -225,7 +247,12 @@ impl Shape for Triangle { let s0 = (self.b - self.a).cross(pt - self.a).signum(); let s1 = (self.c - self.b).cross(pt - self.b).signum(); let s2 = (self.a - self.c).cross(pt - self.c).signum(); - if s0 == s1 && s1 == s2 { s0 as i32 } else { 0 } + + if s0 == s1 && s1 == s2 { + s0 as i32 + } else { + 0 + } } #[inline] @@ -261,6 +288,7 @@ impl Iterator for TrianglePathIter { } } +// TODO: better and more tests #[cfg(test)] mod tests { use crate::{Point, Triangle, Vec2}; @@ -269,26 +297,6 @@ mod tests { assert!((x - y).abs() < 1e-7); } - #[test] - fn from_radius() { - let test = Triangle::from_radius(10.1); - let expected = Triangle::from_coords( - (0.0, 20.199999999999996), - (-17.493713156445658, -10.099999999999998), - (17.493713156445658, -10.099999999999998), - ); - - assert_eq!(test, expected); - } - - #[test] - fn with_centroid() { - let test = Triangle::EQUILATERAL.with_centroid((12.2, -2.3)); - let expected = Triangle::from_coords((12.2, 1.7000000000000002), (9.2, -4.3), (15.2, -4.3)); - - assert_eq!(test, expected); - } - #[test] fn centroid() { let test = Triangle::from_coords((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); @@ -309,19 +317,6 @@ mod tests { assert_eq!(test, expected); } - #[test] - fn perimeter() { - let test = Triangle::new( - (781239.273894, 789234.234789), - (234897.823471, 378902.234789), - (789241.789234, 789234.234897), - ) - .perimeter(); - let expected = 1380963.0638877784; - - assert_approx_eq(test, expected); - } - #[test] fn area() { let test = Triangle::new( @@ -336,35 +331,25 @@ mod tests { } #[test] - fn right_angled_area() { - let test = Triangle::from_coords((1.2, 5.3), (1.2, 1.6), (10.0, 1.6)).right_angled_area(); - let expected = 16.28; - - assert_approx_eq(test, expected); - } - - #[test] - fn vertex_from_x() { - let test = Triangle::from_coords((1.0, 3.2), (63.8, 30.2), (2.0, 2.0)) - .vertex_from_x(63.8) - .unwrap(); - let expected = Point::new(63.8, 30.2); + fn circumcenter() { + let test = Triangle::EQUILATERAL.circumcenter(); + let expected = Point::new(0.5, 0.2886751345948128); assert_eq!(test, expected); } #[test] - fn organise() { - let test = Triangle::from_coords((-20.1, 3.2), (12.3, 12.3), (9.2, -100.8)).organise(); - let expected = Triangle::from_coords((12.3, 12.3), (-20.1, 3.2), (9.2, -100.8)); + fn incircle() { + let test = Triangle::EQUILATERAL.incircle(); + let expected = 0.28867513459481287; - assert_eq!(test, expected); + assert_approx_eq(test, expected); } #[test] - fn radius() { - let test = Triangle::EQUILATERAL.radius(); - let expected = 1.8541019662496845; + fn circumcircle() { + let test = Triangle::EQUILATERAL.circumcircle(); + let expected = 0.5773502691896258; assert_approx_eq(test, expected); } From 0542c607b1fc204f66a4ea8e8036a9d6b24b38f9 Mon Sep 17 00:00:00 2001 From: julia Date: Tue, 27 Aug 2024 06:41:02 +1000 Subject: [PATCH 14/27] apply cleanup suggestions --- src/triangle.rs | 55 ++++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 299af03d..7e53e482 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{PathEl, Point, Rect, Shape, Vec2}; +use crate::{Line, PathEl, Point, Rect, Shape, Vec2}; use core::cmp::*; use core::f64::consts::FRAC_PI_4; @@ -67,19 +67,6 @@ impl Triangle { } } - /// A new [`Triangle`] from centroid and offsets ([`Vec2`]) - offset of each vertex from the centroid - #[inline] - fn from_centroid_offsets(centroid: impl Into, offsets: [impl Into; 3]) -> Self { - let centroid = centroid.into(); - - let mut points: [Point; 3] = Default::default(); - for (i, offset) in offsets.into_iter().enumerate() { - points[i] = centroid + offset.into(); - } - - Self::new(points[0], points[1], points[2]) - } - /// The centroid of the [`Triangle`] #[inline] pub fn centroid(&self) -> Point { @@ -122,7 +109,7 @@ impl Triangle { /// The area of the [`Triangle`] #[inline] pub fn area(&self) -> f64 { - 0.5 * (self.b - self.a).cross(self.c - self.a).abs() + 0.5 * (self.b - self.a).cross(self.c - self.a) } /// Whether this [`Triangle`] has no (zero) area @@ -131,10 +118,10 @@ impl Triangle { self.area() == 0.0 } - /// The greatest radius of a circle that is within the [`Triangle`] + /// Inradius of [`Triangle`] (the greatest radius of a circle that is within the [`Triangle`]) /// with center [`Triangle::circumcenter`] #[inline] - pub fn incircle(&self) -> f64 { + pub fn inradius(&self) -> f64 { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); @@ -142,16 +129,15 @@ impl Triangle { 2.0 * self.area() / (ab + bc + ac) } - /// Circumcircle of the [`Triangle`] + /// Circumradius of [`Triangle`] (the smallest radius of a circle such that it intercepts each vertex) /// with center [`Triangle::circumcenter`] #[inline] - pub fn circumcircle(&self) -> f64 { + pub fn circumradius(&self) -> f64 { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); (ab * bc * ac) / (4.0 * self.area()) - // ab * bc * ac / (((ab + bc + ac) * (bc + ac - ab) * (ab + ac - bc) * (ab + bc - ac)).sqrt()) } /// Expand the triangle by a constant amount (`scalar`) in all directions @@ -190,16 +176,18 @@ impl From<(Point, Point, Point)> for Triangle { } } +impl From<(Line, Line)> for Triangle { + fn from(lines: (Line, Line)) -> Triangle { + Triangle::new(lines.0.p0, lines.0.p1, lines.1.p0) + } +} + impl Add for Triangle { type Output = Triangle; #[inline] fn add(self, v: Vec2) -> Triangle { - Triangle::new( - (self.a.to_vec2() + v).to_point(), - (self.b.to_vec2() + v).to_point(), - (self.c.to_vec2() + v).to_point(), - ) + Triangle::new(self.a + v, self.b + v, self.c + v) } } @@ -208,11 +196,7 @@ impl Sub for Triangle { #[inline] fn sub(self, v: Vec2) -> Triangle { - Triangle::new( - (self.a.to_vec2() - v).to_point(), - (self.b.to_vec2() - v).to_point(), - (self.c.to_vec2() - v).to_point(), - ) + Triangle::new(self.a - v, self.b - v, self.c - v) } } @@ -335,20 +319,21 @@ mod tests { let test = Triangle::EQUILATERAL.circumcenter(); let expected = Point::new(0.5, 0.2886751345948128); - assert_eq!(test, expected); + assert_eq!(test.x, expected.x); + assert_approx_eq(test.y, expected.y); } #[test] - fn incircle() { - let test = Triangle::EQUILATERAL.incircle(); + fn inradius() { + let test = Triangle::EQUILATERAL.inradius(); let expected = 0.28867513459481287; assert_approx_eq(test, expected); } #[test] - fn circumcircle() { - let test = Triangle::EQUILATERAL.circumcircle(); + fn circumradius() { + let test = Triangle::EQUILATERAL.circumradius(); let expected = 0.5773502691896258; assert_approx_eq(test, expected); From 2124dcc2d07d7a8d76c087071efbf4a16ffb3333 Mon Sep 17 00:00:00 2001 From: julia Date: Tue, 27 Aug 2024 20:38:38 +1000 Subject: [PATCH 15/27] signed area test one with and without permutated vertices --- src/triangle.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 7e53e482..e515928d 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -307,11 +307,14 @@ mod tests { (12123.423, 2382.7834), (7892.729, 238.459), (7820.2, 712.23), - ) - .area(); + ); let expected = 1079952.91574081; - assert_approx_eq(test, expected); + // initial + assert_approx_eq(test.area(), -expected); + // permutate vertex + let test = Triangle::new(test.b, test.a, test.c); + assert_approx_eq(test.area(), expected); } #[test] From ff39ceef630e90a036ab55fc4a8d86cfc40756a6 Mon Sep 17 00:00:00 2001 From: julia Date: Fri, 30 Aug 2024 17:07:27 +1000 Subject: [PATCH 16/27] match new style `is_zero_area`, `is_nan`/`is_finite` docs, typo --- src/triangle.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index e515928d..65f00f15 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -112,9 +112,9 @@ impl Triangle { 0.5 * (self.b - self.a).cross(self.c - self.a) } - /// Whether this [`Triangle`] has no (zero) area + /// Whether this [`Triangle`] has zero area #[inline] - pub fn is_empty(&self) -> bool { + pub fn is_zero_area(&self) -> bool { self.area() == 0.0 } @@ -151,13 +151,17 @@ impl Triangle { ) } - /// Is this [`Triangle`] finite? + /// Is this [`Triangle`] [finite]? + /// + /// [finite]: f64::is_finite #[inline] pub fn is_finite(&self) -> bool { self.a.is_finite() && self.b.is_finite() && self.c.is_finite() } - /// Is this [`Triangle`] NaN? + /// Is this [`Triangle`] [NaN]? + /// + /// [NaN]: f64::is_nan #[inline] pub fn is_nan(&self) -> bool { self.a.is_nan() || self.b.is_nan() || self.c.is_nan() @@ -255,7 +259,7 @@ impl Shape for Triangle { } } -// NOTE: vertices a, b and c are not garunteed to be in order as described in the struct comments +// NOTE: vertices a, b and c are not guaranteed to be in order as described in the struct comments // (i.e. as "vertex a is topmost, vertex b is leftmost, and vertex c is rightmost") impl Iterator for TrianglePathIter { type Item = PathEl; From 7e07c0013bc4aa96a8a5289fb09f44bd1d634749 Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 31 Aug 2024 18:34:39 +1000 Subject: [PATCH 17/27] add `is_zero_area` doc alias see \#371 --- src/triangle.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/triangle.rs b/src/triangle.rs index 65f00f15..5a93d1a1 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -113,6 +113,7 @@ impl Triangle { } /// Whether this [`Triangle`] has zero area + #[doc(alias = "is_empty")] #[inline] pub fn is_zero_area(&self) -> bool { self.area() == 0.0 From 6b10318bde368a7fd47da37fdfb745706c1bc94e Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 31 Aug 2024 19:53:57 +1000 Subject: [PATCH 18/27] remove from `Line` wasnt supposed to be committed at all --- src/triangle.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 5a93d1a1..f0cf0293 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{Line, PathEl, Point, Rect, Shape, Vec2}; +use crate::{PathEl, Point, Rect, Shape, Vec2}; use core::cmp::*; use core::f64::consts::FRAC_PI_4; @@ -181,12 +181,6 @@ impl From<(Point, Point, Point)> for Triangle { } } -impl From<(Line, Line)> for Triangle { - fn from(lines: (Line, Line)) -> Triangle { - Triangle::new(lines.0.p0, lines.0.p1, lines.1.p0) - } -} - impl Add for Triangle { type Output = Triangle; From 913f0f4e7a702c9f29869ad26bd724547d3d2380 Mon Sep 17 00:00:00 2001 From: Julia <58243358+juliapaci@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:44:10 +1000 Subject: [PATCH 19/27] Apply explicit suggestions from derekdreery Co-authored-by: Richard Dodd --- src/triangle.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index f0cf0293..6733d8c3 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -119,7 +119,9 @@ impl Triangle { self.area() == 0.0 } - /// Inradius of [`Triangle`] (the greatest radius of a circle that is within the [`Triangle`]) + /// Inradius of [`Triangle`] + /// + /// This is defined as the greatest radius of a circle that is within the [`Triangle`] /// with center [`Triangle::circumcenter`] #[inline] pub fn inradius(&self) -> f64 { @@ -283,7 +285,7 @@ mod tests { #[test] fn centroid() { let test = Triangle::from_coords((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); - let expected = Point::new(-24.939999999999998, 1.0999999999999996); + let expected = Point::new(-24.94, 1.1); assert_eq!(test, expected); } @@ -292,9 +294,9 @@ mod tests { fn offsets() { let test = Triangle::from_coords((-20.0, 180.2), (1.2, 0.0), (290.0, 100.0)).offsets(); let expected = [ - Vec2::new(-110.39999999999999, 86.8), - Vec2::new(-89.19999999999999, -93.39999999999999), - Vec2::new(199.60000000000002, 6.6000000000000085), + Vec2::new(-110.4, 86.8), + Vec2::new(-89.2, -93.4), + Vec2::new(199.6, 6.6), ]; assert_eq!(test, expected); From 3670f6e11e11a4c1d52194e501b00ee46ed22758 Mon Sep 17 00:00:00 2001 From: Julia <58243358+juliapaci@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:46:43 +1000 Subject: [PATCH 20/27] remove point comparisons Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> --- src/point.rs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/point.rs b/src/point.rs index ee5ef31f..4cc03700 100644 --- a/src/point.rs +++ b/src/point.rs @@ -204,29 +204,6 @@ impl Point { self.x.is_nan() || self.y.is_nan() } - /// The topmost [`Point`] - #[inline] - pub fn topmost(&self, other: &Self) -> Ordering { - self.y.total_cmp(&other.y) - } - - /// The bottommost [`Point`] - #[inline] - pub fn bottommost(&self, other: &Self) -> Ordering { - other.y.total_cmp(&self.y) - } - - /// The rightmost [`Point`] - #[inline] - pub fn rightmost(&self, other: &Self) -> Ordering { - self.x.total_cmp(&other.x) - } - - /// The leftmost [`Point`] - #[inline] - pub fn leftmost(&self, other: &Self) -> Ordering { - other.x.total_cmp(&self.x) - } } impl From<(f32, f32)> for Point { From 84b0609bbd6a4d3edbe6d98dbddd722a5893fa0b Mon Sep 17 00:00:00 2001 From: julia Date: Mon, 23 Sep 2024 17:48:43 +1000 Subject: [PATCH 21/27] apply implicit suggestions private `from_coords` and instead of `*radius` use `*circle` --- src/triangle.rs | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 6733d8c3..b2a68a9e 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Triangle shape -use crate::{PathEl, Point, Rect, Shape, Vec2}; +use crate::{Circle, PathEl, Point, Rect, Shape, Vec2}; use core::cmp::*; use core::f64::consts::FRAC_PI_4; @@ -34,9 +34,6 @@ impl Triangle { /// The empty [`Triangle`] at the origin pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); - /// empty [`Triangle`] at (1.0, 1.0) - pub const ONE: Self = Self::from_coords((1.0, 1.0), (1.0, 1.0), (1.0, 1.0)); - /// equilateral [`Triangle`] pub const EQUILATERAL: Self = Self::from_coords( ( @@ -59,7 +56,7 @@ impl Triangle { /// A new [`Triangle`] from three float vertex coordinates #[inline] - pub const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { + const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { Self { a: Point::new(a.0, a.1), b: Point::new(b.0, b.1), @@ -75,7 +72,7 @@ impl Triangle { /// The circumcenter of the [`Triangle`] #[inline] - pub fn circumcenter(&self) -> Point { + fn circumcenter(&self) -> Point { let d = 2.0 * (self.a.x * (self.b.y - self.c.y) + self.b.x * (self.c.y - self.a.y) @@ -119,28 +116,24 @@ impl Triangle { self.area() == 0.0 } - /// Inradius of [`Triangle`] - /// - /// This is defined as the greatest radius of a circle that is within the [`Triangle`] - /// with center [`Triangle::circumcenter`] + /// Incircle of [`Triangle`] (the greatest [`Circle`] that lies within the [`Triangle`]) #[inline] - pub fn inradius(&self) -> f64 { + pub fn incircle(&self) -> Circle { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); - 2.0 * self.area() / (ab + bc + ac) + Circle::new(self.circumcenter(), 2.0 * self.area() / (ab + bc + ac)) } - /// Circumradius of [`Triangle`] (the smallest radius of a circle such that it intercepts each vertex) - /// with center [`Triangle::circumcenter`] + /// Circumcircle of [`Triangle`] (the smallest [`Circle`] such that it intercepts each vertex) #[inline] - pub fn circumradius(&self) -> f64 { + pub fn circumcircle(&self) -> Circle { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); - (ab * bc * ac) / (4.0 * self.area()) + Circle::new(self.circumcenter(), (ab * bc * ac) / (4.0 * self.area())) } /// Expand the triangle by a constant amount (`scalar`) in all directions @@ -249,14 +242,9 @@ impl Shape for Triangle { self.a.y.max(self.b.y.max(self.c.y)), ) } - - #[inline] - fn as_rect(&self) -> Option { - Some(self.bounding_box()) - } } -// NOTE: vertices a, b and c are not guaranteed to be in order as described in the struct comments +// Note: vertices a, b and c are not guaranteed to be in order as described in the struct comments // (i.e. as "vertex a is topmost, vertex b is leftmost, and vertex c is rightmost") impl Iterator for TrianglePathIter { type Item = PathEl; @@ -329,17 +317,17 @@ mod tests { #[test] fn inradius() { - let test = Triangle::EQUILATERAL.inradius(); + let test = Triangle::EQUILATERAL.incircle(); let expected = 0.28867513459481287; - assert_approx_eq(test, expected); + assert_approx_eq(test.radius, expected); } #[test] fn circumradius() { - let test = Triangle::EQUILATERAL.circumradius(); + let test = Triangle::EQUILATERAL.circumcircle(); let expected = 0.5773502691896258; - assert_approx_eq(test, expected); + assert_approx_eq(test.radius, expected); } } From 1f4e1ad250a0c34bf84c6ddce995e0c8a635457c Mon Sep 17 00:00:00 2001 From: julia Date: Tue, 24 Sep 2024 16:27:15 +1000 Subject: [PATCH 22/27] meta cleanup name and doc updates + public `from_coords` --- src/triangle.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index b2a68a9e..f3b819c3 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -44,7 +44,7 @@ impl Triangle { (1.0, 0.0), ); - /// A new [`Triangle`] from three vertices ([`Points`]) + /// A new [`Triangle`] from three vertices ([`Point`]s) #[inline] pub fn new(a: impl Into, b: impl Into, c: impl Into) -> Self { Self { @@ -55,8 +55,10 @@ impl Triangle { } /// A new [`Triangle`] from three float vertex coordinates + /// + /// Works as a constant [`Triangle::new`] #[inline] - const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { + pub const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { Self { a: Point::new(a.0, a.1), b: Point::new(b.0, b.1), @@ -116,9 +118,10 @@ impl Triangle { self.area() == 0.0 } - /// Incircle of [`Triangle`] (the greatest [`Circle`] that lies within the [`Triangle`]) + /// The inscribed circle of [`Triangle`] (the greatest [`Circle`] that lies within the [`Triangle`]) + #[doc(alias = "incircle")] #[inline] - pub fn incircle(&self) -> Circle { + pub fn inscribed_circle(&self) -> Circle { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); @@ -126,9 +129,10 @@ impl Triangle { Circle::new(self.circumcenter(), 2.0 * self.area() / (ab + bc + ac)) } - /// Circumcircle of [`Triangle`] (the smallest [`Circle`] such that it intercepts each vertex) + /// The circumscribed circle of [`Triangle`] (the smallest [`Circle`] which intercepts each vertex of the [`Triangle`]) + #[doc(alias = "circumcircle")] #[inline] - pub fn circumcircle(&self) -> Circle { + pub fn circumscribed_circle(&self) -> Circle { let ab = self.a.distance(self.b); let bc = self.b.distance(self.c); let ac = self.a.distance(self.c); @@ -317,7 +321,7 @@ mod tests { #[test] fn inradius() { - let test = Triangle::EQUILATERAL.incircle(); + let test = Triangle::EQUILATERAL.inscribed_circle(); let expected = 0.28867513459481287; assert_approx_eq(test.radius, expected); @@ -325,7 +329,7 @@ mod tests { #[test] fn circumradius() { - let test = Triangle::EQUILATERAL.circumcircle(); + let test = Triangle::EQUILATERAL.circumscribed_circle(); let expected = 0.5773502691896258; assert_approx_eq(test.radius, expected); From c5dc9fba66255b1968832b2833a51171ae893793 Mon Sep 17 00:00:00 2001 From: julia Date: Fri, 27 Sep 2024 12:11:33 +1000 Subject: [PATCH 23/27] cleanup remove `as_array` which hasnt been used since `organise`, add doc alias to `inflate` --- src/point.rs | 1 - src/triangle.rs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/point.rs b/src/point.rs index 4cc03700..e3c92fa9 100644 --- a/src/point.rs +++ b/src/point.rs @@ -3,7 +3,6 @@ //! A 2D point. -use core::cmp::Ordering; use core::fmt; use core::ops::{Add, AddAssign, Sub, SubAssign}; diff --git a/src/triangle.rs b/src/triangle.rs index f3b819c3..89d079c0 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -141,6 +141,7 @@ impl Triangle { } /// Expand the triangle by a constant amount (`scalar`) in all directions + #[doc(alias = "offset")] pub fn inflate(&self, scalar: f64) -> Self { let centroid = self.centroid(); @@ -166,12 +167,6 @@ impl Triangle { pub fn is_nan(&self) -> bool { self.a.is_nan() || self.b.is_nan() || self.c.is_nan() } - - /// [`Triangle`]'s vertices as an array of Points - #[inline] - pub fn as_array(&self) -> [Point; 3] { - [self.a, self.b, self.c] - } } impl From<(Point, Point, Point)> for Triangle { From 452b2964cf9b1bfd4e7bc6afe450386a4369d8cf Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 28 Sep 2024 09:16:04 +1000 Subject: [PATCH 24/27] little doc cleanup --- src/point.rs | 1 - src/triangle.rs | 18 +++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/point.rs b/src/point.rs index e3c92fa9..2216adc7 100644 --- a/src/point.rs +++ b/src/point.rs @@ -202,7 +202,6 @@ impl Point { pub fn is_nan(self) -> bool { self.x.is_nan() || self.y.is_nan() } - } impl From<(f32, f32)> for Point { diff --git a/src/triangle.rs b/src/triangle.rs index 89d079c0..477c78d5 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -34,7 +34,7 @@ impl Triangle { /// The empty [`Triangle`] at the origin pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); - /// equilateral [`Triangle`] + /// Equilateral [`Triangle`] with the x-axis unit vector as its base. pub const EQUILATERAL: Self = Self::from_coords( ( 1.0 / 2.0, @@ -118,7 +118,9 @@ impl Triangle { self.area() == 0.0 } - /// The inscribed circle of [`Triangle`] (the greatest [`Circle`] that lies within the [`Triangle`]) + /// The inscribed circle of [`Triangle`] + /// + /// This is defined as the greatest [`Circle`] that lies within the [`Triangle`] #[doc(alias = "incircle")] #[inline] pub fn inscribed_circle(&self) -> Circle { @@ -129,7 +131,9 @@ impl Triangle { Circle::new(self.circumcenter(), 2.0 * self.area() / (ab + bc + ac)) } - /// The circumscribed circle of [`Triangle`] (the smallest [`Circle`] which intercepts each vertex of the [`Triangle`]) + /// The circumscribed circle of [`Triangle`] + /// + /// This is defined as the smallest [`Circle`] which intercepts each vertex of the [`Triangle`] #[doc(alias = "circumcircle")] #[inline] pub fn circumscribed_circle(&self) -> Circle { @@ -316,17 +320,17 @@ mod tests { #[test] fn inradius() { - let test = Triangle::EQUILATERAL.inscribed_circle(); + let test = Triangle::EQUILATERAL.inscribed_circle().radius; let expected = 0.28867513459481287; - assert_approx_eq(test.radius, expected); + assert_approx_eq(test, expected); } #[test] fn circumradius() { - let test = Triangle::EQUILATERAL.circumscribed_circle(); + let test = Triangle::EQUILATERAL.circumscribed_circle().radius; let expected = 0.5773502691896258; - assert_approx_eq(test.radius, expected); + assert_approx_eq(test, expected); } } From bebbb784d49e5dc563bb7f9668a7e6ffd86f6a1e Mon Sep 17 00:00:00 2001 From: julia Date: Sun, 29 Sep 2024 09:24:58 +1000 Subject: [PATCH 25/27] end comments with a period --- src/triangle.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 477c78d5..420d0129 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -22,16 +22,16 @@ use crate::common::FloatFuncs; #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Triangle { - /// vertex a + /// vertex a. pub a: Point, - /// vertex b + /// vertex b. pub b: Point, - /// vertex c + /// vertex c. pub c: Point, } impl Triangle { - /// The empty [`Triangle`] at the origin + /// The empty [`Triangle`] at the origin. pub const ZERO: Self = Self::from_coords((0., 0.), (0., 0.), (0., 0.)); /// Equilateral [`Triangle`] with the x-axis unit vector as its base. @@ -44,7 +44,7 @@ impl Triangle { (1.0, 0.0), ); - /// A new [`Triangle`] from three vertices ([`Point`]s) + /// A new [`Triangle`] from three vertices ([`Point`]s). #[inline] pub fn new(a: impl Into, b: impl Into, c: impl Into) -> Self { Self { @@ -54,9 +54,9 @@ impl Triangle { } } - /// A new [`Triangle`] from three float vertex coordinates + /// A new [`Triangle`] from three float vertex coordinates. /// - /// Works as a constant [`Triangle::new`] + /// Works as a constant [`Triangle::new`]. #[inline] pub const fn from_coords(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> Self { Self { @@ -66,13 +66,13 @@ impl Triangle { } } - /// The centroid of the [`Triangle`] + /// The centroid of the [`Triangle`]. #[inline] pub fn centroid(&self) -> Point { (1.0 / 3.0 * (self.a.to_vec2() + self.b.to_vec2() + self.c.to_vec2())).to_point() } - /// The circumcenter of the [`Triangle`] + /// The circumcenter of the [`Triangle`]. #[inline] fn circumcenter(&self) -> Point { let d = 2.0 @@ -93,7 +93,7 @@ impl Triangle { Point::new(ux, uy) } - /// The offset of each vertex from the centroid + /// The offset of each vertex from the centroid. #[inline] pub fn offsets(&self) -> [Vec2; 3] { let centroid = self.centroid().to_vec2(); @@ -105,22 +105,22 @@ impl Triangle { ] } - /// The area of the [`Triangle`] + /// The area of the [`Triangle`]. #[inline] pub fn area(&self) -> f64 { 0.5 * (self.b - self.a).cross(self.c - self.a) } - /// Whether this [`Triangle`] has zero area + /// Whether this [`Triangle`] has zero area. #[doc(alias = "is_empty")] #[inline] pub fn is_zero_area(&self) -> bool { self.area() == 0.0 } - /// The inscribed circle of [`Triangle`] + /// The inscribed circle of [`Triangle`]. /// - /// This is defined as the greatest [`Circle`] that lies within the [`Triangle`] + /// This is defined as the greatest [`Circle`] that lies within the [`Triangle`]. #[doc(alias = "incircle")] #[inline] pub fn inscribed_circle(&self) -> Circle { @@ -131,9 +131,9 @@ impl Triangle { Circle::new(self.circumcenter(), 2.0 * self.area() / (ab + bc + ac)) } - /// The circumscribed circle of [`Triangle`] + /// The circumscribed circle of [`Triangle`]. /// - /// This is defined as the smallest [`Circle`] which intercepts each vertex of the [`Triangle`] + /// This is defined as the smallest [`Circle`] which intercepts each vertex of the [`Triangle`]. #[doc(alias = "circumcircle")] #[inline] pub fn circumscribed_circle(&self) -> Circle { @@ -144,7 +144,7 @@ impl Triangle { Circle::new(self.circumcenter(), (ab * bc * ac) / (4.0 * self.area())) } - /// Expand the triangle by a constant amount (`scalar`) in all directions + /// Expand the triangle by a constant amount (`scalar`) in all directions. #[doc(alias = "offset")] pub fn inflate(&self, scalar: f64) -> Self { let centroid = self.centroid(); From 99d9c91cae994b009b09c8961bbcef991e516fba Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 5 Oct 2024 08:57:40 +1000 Subject: [PATCH 26/27] fix tests --- src/triangle.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 420d0129..13ac324e 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -273,12 +273,17 @@ mod tests { assert!((x - y).abs() < 1e-7); } + fn assert_approx_eq_point(x: Point, y: Point) { + assert!((x.x - y.x).abs() < 1e-7); + assert!((x.y - y.y).abs() < 1e-7); + } + #[test] fn centroid() { let test = Triangle::from_coords((-90.02, 3.5), (7.2, -9.3), (8.0, 9.1)).centroid(); let expected = Point::new(-24.94, 1.1); - assert_eq!(test, expected); + assert_approx_eq_point(test, expected); } #[test] @@ -290,7 +295,9 @@ mod tests { Vec2::new(199.6, 6.6), ]; - assert_eq!(test, expected); + test.iter() + .zip(expected.iter()) + .for_each(|(t, e)| assert_approx_eq_point(t.to_point(), e.to_point())); } #[test] @@ -314,8 +321,7 @@ mod tests { let test = Triangle::EQUILATERAL.circumcenter(); let expected = Point::new(0.5, 0.2886751345948128); - assert_eq!(test.x, expected.x); - assert_approx_eq(test.y, expected.y); + assert_approx_eq_point(test, expected); } #[test] From ca336fc938cdcf1bf102efabcd3ae8cf1e04510f Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 5 Oct 2024 12:45:30 +1000 Subject: [PATCH 27/27] use `assert_approx_eq` for point test oops. for when we change the bound --- src/triangle.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/triangle.rs b/src/triangle.rs index 13ac324e..8c1d677f 100644 --- a/src/triangle.rs +++ b/src/triangle.rs @@ -274,8 +274,8 @@ mod tests { } fn assert_approx_eq_point(x: Point, y: Point) { - assert!((x.x - y.x).abs() < 1e-7); - assert!((x.y - y.y).abs() < 1e-7); + assert_approx_eq(x.x, y.x); + assert_approx_eq(x.y, x.y); } #[test]