use crate::magnets::magnet2d::{generate_line_array, LineVec};
use crate::magnets::{GetCenter, GetField};
use crate::utils::conversions::Angle;
use crate::points::{Point2, PointVec2, Points2};
use crate::{MagnetError, PI};
use serde_derive::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Polygon {
pub center: Point2,
pub alpha: Angle,
pub jr: f64,
pub phi: Angle,
pub jx: f64,
pub jy: f64,
pub vertices: PointVec2,
pub num_vertices: usize,
pub line_array: LineVec,
}
impl Default for Polygon {
fn default() -> Self {
let vertices = PointVec2::new(vec![1.0, 1.0, -1.0, -1.0], vec![1.0, -1.0, -1.0, 1.0]);
let (line_array, _, _) = generate_line_array(&vertices, &0.0, &1.0);
Polygon {
center: Point2::new(0.0, 0.0),
alpha: Angle::Degrees(0.0),
jr: 1.0,
phi: Angle::Degrees(90.0),
jx: 0.0,
jy: 1.0,
vertices,
num_vertices: 4,
line_array,
}
}
}
impl Polygon {
pub fn new<C>(center: C, alpha: Angle, jr: f64, phi: Angle, vertices: Vertices) -> Polygon
where
C: GetCenter<Point2>,
{
let phi_rad = phi.to_radians();
let returned_vert = generate_vertices_wrapper(vertices, ¢er.center(), &alpha).unwrap();
let num_vertices = returned_vert.x.len();
let jx = jr * phi_rad.cos();
let jy = jr * phi_rad.sin();
let (line_array, _, _) = generate_line_array(&returned_vert, &jx, &jy);
Polygon {
center: center.center(),
alpha,
jr,
phi,
jx,
jy,
vertices: returned_vert,
num_vertices,
line_array,
}
}
}
impl fmt::Display for Polygon {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[c: {},\talpha:{}\tJ ({:.3}, {:.3})\nNo. vertices: {}]",
self.center,
self.alpha.to_degrees(),
self.jr,
self.phi.to_degrees(),
self.num_vertices,
)
}
}
pub enum PolyDimension {
Apothem(f64),
Radius(f64),
Side(f64),
}
pub enum Vertices {
Regular(usize, PolyDimension),
Custom(PointVec2),
}
fn get_radius(num_vertices: &usize, param: &PolyDimension) -> f64 {
match param {
PolyDimension::Apothem(v) => v / (PI / *num_vertices as f64).cos(),
PolyDimension::Side(v) => v / 2.0 / (PI / *num_vertices as f64).sin(),
PolyDimension::Radius(v) => *v,
}
}
fn offset_angle(num_vertices: &usize, alpha: &Angle) -> Angle {
if num_vertices % 2 == 0 {
Angle::Radians((PI / *num_vertices as f64) + alpha.to_radians())
} else {
Angle::Radians((PI / *num_vertices as f64) + PI + alpha.to_radians())
}
}
fn regular_vertices(
num_vertices: &usize,
center: &Point2,
param: &PolyDimension,
alpha: &Angle,
) -> Result<PointVec2, MagnetError> {
if *num_vertices < 3 {
Err(MagnetError::PolygonSideError())
} else {
let offset = offset_angle(num_vertices, alpha).to_radians();
let radius = get_radius(num_vertices, param);
let xv: Vec<f64> = (0..*num_vertices)
.into_iter()
.map(|k| {
center.x + radius * ((2.0 * PI * k as f64 / *num_vertices as f64) + offset).sin()
})
.collect();
let yv: Vec<f64> = (0..*num_vertices)
.into_iter()
.map(|k| {
center.y + radius * ((2.0 * PI * k as f64 / *num_vertices as f64) + offset).cos()
})
.collect();
Ok(PointVec2::new(xv, yv))
}
}
pub fn generate_vertices_wrapper(
vertex_wrapper: Vertices,
center: &Point2,
alpha: &Angle,
) -> Result<PointVec2, MagnetError> {
match vertex_wrapper {
Vertices::Regular(val, param) => Ok(regular_vertices(&val, center, ¶m, alpha)?),
Vertices::Custom(val) => Ok(val),
}
}
fn get_field_polygon(magnet: &Polygon, point: &Point2) -> Result<Point2, MagnetError> {
let mut field = Point2::zero();
for line in &magnet.line_array {
field += line.field(point)?;
}
Ok(field)
}
impl GetField<&Point2, Result<Point2, MagnetError>> for Polygon {
fn field(&self, point: &Point2) -> Result<Point2, MagnetError> {
get_field_polygon(self, point)
}
}
impl GetField<&(f64, f64), Result<(f64, f64), MagnetError>> for Polygon {
fn field(&self, point: &(f64, f64)) -> Result<(f64, f64), MagnetError> {
let field_vec = get_field_polygon(
self,
&Point2 {
x: point.0,
y: point.1,
},
)?;
Ok((field_vec.x, field_vec.y))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
utils::comparison::{nearly_equal, nearly_equal_array},
PI_2,
};
#[test]
fn test_regular_vertices_fail() {
let vertex = regular_vertices(
&2,
&Point2::zero(),
&PolyDimension::Side(1.0),
&Angle::Degrees(0.0),
);
assert!(vertex.is_err());
}
#[test]
fn test_radius_apothem_square() {
let param = PolyDimension::Apothem(2.0);
let num_vertices = 4;
let radius = get_radius(&num_vertices, ¶m);
assert!(nearly_equal(radius, 2.0 / (PI / 4.0).cos()));
}
#[test]
fn test_radius_side_hexagon() {
let param = PolyDimension::Side(1.0);
let num_vertices = 6;
let radius = get_radius(&num_vertices, ¶m);
assert!(nearly_equal(radius, 1.0 / 2.0 / (PI / 6.0).sin()));
}
#[test]
fn test_radius_radius_septagon() {
let param = PolyDimension::Radius(2.0);
let num_vertices = 7;
let radius = get_radius(&num_vertices, ¶m);
assert!(nearly_equal(radius, 2.0));
}
#[test]
fn test_gen_poly_square() {
let param = PolyDimension::Side(2.0);
let vertex_wrapper = Vertices::Regular(4, param);
let vertices =
generate_vertices_wrapper(vertex_wrapper, &Point2::default(), &Angle::Radians(0.0))
.unwrap();
let num_vertices = vertices.x.len();
let comp_vert = PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
};
assert!(nearly_equal_array(&vertices.x, &comp_vert.x));
assert!(nearly_equal_array(&vertices.y, &comp_vert.y));
assert_eq!(num_vertices, 4)
}
#[test]
fn test_gen_poly_hex() {
let param = PolyDimension::Side(3.0);
let vertex_wrapper = Vertices::Regular(6, param);
let vertices =
generate_vertices_wrapper(vertex_wrapper, &Point2::default(), &Angle::Radians(0.0))
.unwrap();
let num_vertices = vertices.x.len();
let comp_vert = PointVec2 {
x: vec![1.5, 3.0, 1.5, -1.5, -3.0, -1.5],
y: vec![
2.5980762113533165,
0.0,
-2.598076211353316,
-2.598076211353317,
-0.0,
2.598076211353317,
],
};
assert!(nearly_equal_array(&vertices.x, &comp_vert.x));
assert!(nearly_equal_array(&vertices.y, &comp_vert.y));
assert_eq!(num_vertices, 6)
}
#[test]
fn test_custom_poly() {
let vertex_wrapper = Vertices::Custom(PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
});
let vertices =
generate_vertices_wrapper(vertex_wrapper, &Point2::default(), &Angle::Radians(0.0))
.unwrap();
let num_vertices = vertices.x.len();
let comp_vert = PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
};
assert!(nearly_equal_array(&vertices.x, &comp_vert.x));
assert!(nearly_equal_array(&vertices.y, &comp_vert.y));
assert_eq!(num_vertices, comp_vert.x.len())
}
#[test]
fn test_polygon_new_square() {
let magnet = Polygon::new(
(0.0, 0.0),
Angle::Radians(0.0),
1.0,
Angle::Radians(PI_2),
Vertices::Regular(4, PolyDimension::Side(2.0)),
);
println!("{}", magnet);
let comp_vert = PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
};
assert!(nearly_equal_array(&magnet.vertices.x, &comp_vert.x));
assert!(nearly_equal_array(&magnet.vertices.y, &comp_vert.y));
assert!(nearly_equal(magnet.jr, 1.0));
}
#[test]
fn test_polygon_custom() {
let vertex_wrapper = Vertices::Custom(PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
});
let magnet = Polygon::new(
(0.0, 0.0),
Angle::Radians(0.0),
1.0,
Angle::Radians(PI_2),
vertex_wrapper,
);
println!("{}", magnet);
let comp_vert = PointVec2 {
x: vec![1.0, 1.0, -1.0, -1.0],
y: vec![1.0, -1.0, -1.0, 1.0],
};
assert_eq!(magnet.vertices, comp_vert);
assert!(nearly_equal(magnet.jr, 1.0));
}
}