use crate::{fragment::Bounds, util, Cell, Point};
use nalgebra::Point2;
use parry2d::shape::{ConvexPolygon, Polyline};
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
};
use sauron::{
html::attributes::*,
svg::{attributes::*, *},
Node,
};
#[derive(Debug, Clone)]
pub struct Circle {
pub radius: f32,
pub center: Point,
pub is_filled: bool,
}
impl Hash for Circle {
fn hash<H: Hasher>(&self, state: &mut H) {
((self.radius * 2.0) as i32).hash(state);
}
}
impl Circle {
pub(crate) fn new(center: Point, radius: f32, is_filled: bool) -> Self {
Circle {
center,
radius,
is_filled,
}
}
fn top_left_bound(&self) -> Point {
Point::new(self.center.x - self.radius, self.center.y - self.radius)
}
fn top_right_bound(&self) -> Point {
Point::new(self.center.x + self.radius, self.center.y - self.radius)
}
fn bottom_right_bound(&self) -> Point {
Point::new(self.center.x + self.radius, self.center.y + self.radius)
}
fn bottom_left_bound(&self) -> Point {
Point::new(self.center.x - self.radius, self.center.y + self.radius)
}
pub(crate) fn absolute_position(&self, cell: Cell) -> Self {
Circle {
center: cell.absolute_position(self.center),
..*self
}
}
pub fn scale(&self, scale: f32) -> Self {
Circle {
center: self.center.scale(scale),
radius: self.radius * scale,
..*self
}
}
}
impl Bounds for Circle {
fn bounds(&self) -> (Point, Point) {
(self.top_left_bound(), self.bottom_right_bound())
}
}
impl fmt::Display for Circle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "C {} {}", self.center, self.radius)
}
}
impl<MSG> From<Circle> for Node<MSG> {
fn from(c: Circle) -> Node<MSG> {
circle(
[
cx(c.center.x),
cy(c.center.y),
r(c.radius),
classes_flag([
("filled", c.is_filled),
("nofill", !c.is_filled),
]),
],
[],
)
}
}
impl Eq for Circle {}
impl Ord for Circle {
fn cmp(&self, other: &Self) -> Ordering {
self.mins()
.cmp(&other.mins())
.then(self.maxs().cmp(&other.maxs()))
.then(util::ord(self.radius, other.radius))
.then(self.is_filled.cmp(&other.is_filled))
}
}
impl PartialOrd for Circle {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Circle {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == Ordering::Equal
}
}
impl From<Circle> for Polyline {
fn from(c: Circle) -> Polyline {
let points: Vec<Point2<f32>> = extract_circle_points(c.radius, 64)
.into_iter()
.map(|p| Point2::new(p.x + c.center.x, p.y + c.center.y))
.collect();
Polyline::new(points, None)
}
}
impl From<Circle> for ConvexPolygon {
fn from(c: Circle) -> ConvexPolygon {
let points: Vec<Point2<f32>> = extract_circle_points(c.radius, 64)
.into_iter()
.map(|p| Point2::new(p.x + c.center.x, p.y + c.center.y))
.collect();
ConvexPolygon::from_convex_polyline(points)
.expect("must create a convex polygon")
}
}
fn extract_circle_points(radius: f32, nsubdivs: u32) -> Vec<Point> {
let two_pi = std::f32::consts::TAU;
let dtheta = two_pi / nsubdivs as f32;
push_xy_arc(radius, nsubdivs, dtheta)
}
fn push_xy_arc(radius: f32, nsubdiv: u32, dtheta: f32) -> Vec<Point> {
let mut out: Vec<Point> = vec![];
let mut curr_theta: f32 = 0.0;
for _ in 0..nsubdiv {
let x = curr_theta.cos() * radius;
let y = curr_theta.sin() * radius;
out.push(Point::new(x, y));
curr_theta += dtheta;
}
out
}