use std::f32::consts::{FRAC_PI_2, PI, TAU};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MarkerType {
Circle,
Cross,
Diamond,
Square,
Star,
Triangle,
Wye,
}
impl MarkerType {
pub fn vertices(&self, radius: f32) -> Vec<(f32, f32)> {
match self {
MarkerType::Circle => circle_vertices(radius, 64),
MarkerType::Cross => cross_vertices(radius),
MarkerType::Diamond => diamond_vertices(radius),
MarkerType::Square => square_vertices(radius),
MarkerType::Star => star_vertices(radius),
MarkerType::Triangle => triangle_vertices(radius),
MarkerType::Wye => wye_vertices(radius),
}
}
}
fn circle_vertices(radius: f32, sides: usize) -> Vec<(f32, f32)> {
let step = TAU / sides as f32;
let mut angle = -FRAC_PI_2;
(0..sides)
.map(|_| {
let v = (radius * angle.cos(), radius * angle.sin());
angle += step;
v
})
.collect()
}
fn cross_vertices(radius: f32) -> Vec<(f32, f32)> {
let a = radius * 3.0 / 10.0_f32.sqrt(); let t = a / 3.0; vec![
(-t, -a),
( t, -a),
( t, -t),
( a, -t),
( a, t),
( t, t),
( t, a),
(-t, a),
(-t, t),
(-a, t),
(-a, -t),
(-t, -t),
]
}
fn diamond_vertices(radius: f32) -> Vec<(f32, f32)> {
let half_w = radius / 3.0_f32.sqrt();
vec![
(0.0, -radius),
(half_w, 0.0),
(0.0, radius),
(-half_w, 0.0),
]
}
fn square_vertices(radius: f32) -> Vec<(f32, f32)> {
let h = radius * std::f32::consts::FRAC_1_SQRT_2;
vec![(-h, -h), (h, -h), (h, h), (-h, h)]
}
fn star_vertices(radius: f32) -> Vec<(f32, f32)> {
let inner = radius * 0.382;
let step = PI / 5.0;
let mut angle = -FRAC_PI_2;
let mut verts = Vec::with_capacity(10);
for i in 0..10 {
let r = if i % 2 == 0 { radius } else { inner };
verts.push((r * angle.cos(), r * angle.sin()));
angle += step;
}
verts
}
fn triangle_vertices(radius: f32) -> Vec<(f32, f32)> {
let mut angle = -FRAC_PI_2;
let step = TAU / 3.0;
let mut verts = Vec::with_capacity(3);
for _ in 0..3 {
verts.push((radius * angle.cos(), radius * angle.sin()));
angle += step;
}
verts
}
fn wye_vertices(radius: f32) -> Vec<(f32, f32)> {
let arm = radius * 3.0 / 10.0_f32.sqrt();
let t = arm / 3.0;
let angles = [0.0, TAU / 3.0, 2.0 * TAU / 3.0];
let rotate = |x: f32, y: f32, a: f32| -> (f32, f32) {
let (sin, cos) = a.sin_cos();
(x * cos - y * sin, x * sin + y * cos)
};
let mut verts = Vec::with_capacity(12);
for i in 0..3 {
let next = (i + 1) % 3;
verts.push(rotate(-t, -arm, angles[i]));
verts.push(rotate( t, -arm, angles[i]));
verts.push(rotate( t, -t, angles[i]));
verts.push(rotate(-t, -t, angles[next]));
}
verts
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn circle_vertex_count() {
assert_eq!(MarkerType::Circle.vertices(10.0).len(), 64);
}
#[test]
fn cross_vertex_count() {
assert_eq!(MarkerType::Cross.vertices(10.0).len(), 12);
}
#[test]
fn diamond_vertex_count() {
assert_eq!(MarkerType::Diamond.vertices(10.0).len(), 4);
}
#[test]
fn square_vertex_count() {
assert_eq!(MarkerType::Square.vertices(10.0).len(), 4);
}
#[test]
fn star_vertex_count() {
assert_eq!(MarkerType::Star.vertices(10.0).len(), 10);
}
#[test]
fn triangle_vertex_count() {
assert_eq!(MarkerType::Triangle.vertices(10.0).len(), 3);
}
#[test]
fn wye_vertex_count() {
assert_eq!(MarkerType::Wye.vertices(10.0).len(), 12);
}
#[test]
fn vertices_within_bounding_radius() {
let radius = 15.0;
for symbol in [
MarkerType::Circle,
MarkerType::Cross,
MarkerType::Diamond,
MarkerType::Square,
MarkerType::Star,
MarkerType::Triangle,
MarkerType::Wye,
] {
for (x, y) in symbol.vertices(radius) {
let dist = (x * x + y * y).sqrt();
assert!(
dist <= radius + 1e-4,
"{:?} vertex ({}, {}) at distance {} exceeds radius {}",
symbol, x, y, dist, radius
);
}
}
}
#[test]
fn vertices_touch_bounding_radius() {
let radius = 20.0;
for symbol in [
MarkerType::Circle,
MarkerType::Cross,
MarkerType::Diamond,
MarkerType::Square,
MarkerType::Star,
MarkerType::Triangle,
MarkerType::Wye,
] {
let max_dist = symbol
.vertices(radius)
.iter()
.map(|(x, y)| (x * x + y * y).sqrt())
.fold(0.0f32, f32::max);
assert!(
(max_dist - radius).abs() < 0.5,
"{:?} max vertex distance {} doesn't touch radius {}",
symbol, max_dist, radius
);
}
}
}