use super::line_field::sheet_field;
use crate::magnets::GetField;
use crate::{
points::{Point2, PointVec2, Points, Points2},
utils::conversions::Angle,
MagnetError,
};
use crate::{FP_CUTOFF, M2_PI, PI};
use serde_derive::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Line {
pub length: f64,
pub center: Point2,
pub beta: Angle,
pub kr: f64,
}
pub type LineVec = Vec<Line>;
impl Default for Line {
fn default() -> Self {
Line {
length: 1.0,
center: Point2::default(),
beta: Angle::Radians(0.0),
kr: 1.0,
}
}
}
impl Line {
pub fn new(length: f64, center: Point2, beta: Angle, kr: f64) -> Line {
Line {
length,
center,
beta,
kr,
}
}
}
pub fn generate_line_array(vertices: &PointVec2, jx: &f64, jy: &f64) -> (LineVec, f64, Point2) {
let num_points = vertices.x.len();
let mut line_array = LineVec::with_capacity(num_points / 2);
let mut area = 0.0;
let mut centroid = Point2::zero();
for i in 0..num_points {
let mut j = i + 1;
j %= num_points;
let weight = (vertices.x[j] + vertices.x[i]) * (vertices.y[j] - vertices.y[i]);
centroid.x += (vertices.x[j] + vertices.x[i]) * weight;
centroid.y += (vertices.y[j] + vertices.y[i]) * weight;
area += weight;
let start_point = Point2::new(vertices.x[i], vertices.y[i]);
let end_point = Point2::new(vertices.x[j], vertices.y[j]);
let (unit_norm, length) = unit_norm_vector(&start_point, &end_point);
let center: Point2 = line_center(&start_point, &end_point);
let beta = get_line_beta(&unit_norm);
let kr = jx * unit_norm.y - jy * unit_norm.x;
line_array.push(Line::new(length, center, beta, kr));
}
centroid = centroid.scale(1.0 / area / 3.0);
(line_array, area * 0.5, centroid)
}
fn unit_norm_vector(start_point: &Point2, end_point: &Point2) -> (Point2, f64) {
let delta = *end_point - *start_point;
let norm = Point2::new(-delta.y, delta.x);
let length = delta.magnitude();
(norm.scale(1.0 / length), length)
}
fn line_center(vertex_1: &Point2, vertex_2: &Point2) -> Point2 {
(*vertex_1 + *vertex_2).scale(0.5)
}
fn get_line_beta(unit_norm: &Point2) -> Angle {
Angle::Radians(unit_norm.y.atan2(unit_norm.x))
}
fn get_field_line(magnet: &Line, point: &Point2) -> Result<Point2, MagnetError> {
let mut field = Point2::zero();
let mut local_point = *point - magnet.center;
let mut rotation_flag = false;
if (magnet.beta.to_radians() % PI).abs() > FP_CUTOFF && magnet.kr.abs() > FP_CUTOFF {
let reverse_angle = M2_PI - magnet.beta.to_radians();
local_point = local_point.rotate(&reverse_angle);
rotation_flag = true;
}
field += if magnet.kr.abs() > FP_CUTOFF {
let local_field = sheet_field(
&local_point.x,
&local_point.y,
&(magnet.length / 2.0),
&magnet.kr,
);
match local_field {
Ok(value) => value,
Err(_e) => Point2 { x: 0.0, y: 0.0 },
}
} else {
Point2 { x: 0.0, y: 0.0 }
};
if rotation_flag {
let reverse_alpha = magnet.beta.to_radians();
field = field.rotate(&reverse_alpha);
}
Ok(field)
}
impl GetField<&Point2, Result<Point2, MagnetError>> for Line {
fn field(&self, point: &Point2) -> Result<Point2, MagnetError> {
get_field_line(self, point)
}
}
impl GetField<&(f64, f64), Result<(f64, f64), MagnetError>> for Line {
fn field(&self, point: &(f64, f64)) -> Result<(f64, f64), MagnetError> {
let field_vec = get_field_line(
self,
&Point2 {
x: point.0,
y: point.1,
},
)?;
Ok((field_vec.x, field_vec.y))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::magnets::magnet2d::polygon::{generate_vertices_wrapper, PolyDimension, Vertices};
use crate::utils::comparison::nearly_equal;
#[test]
fn test_get_line_center_vertical() {
let start_point = Point2::new(0.5, 0.5);
let end_point = Point2::new(0.5, -0.5);
let center = line_center(&start_point, &end_point);
assert_eq!(center, Point2::new(0.5, 0.0));
}
#[test]
fn test_get_line_center_horizontal() {
let start_point = Point2::new(-0.5, 0.5);
let end_point = Point2::new(0.5, 0.5);
let center = line_center(&start_point, &end_point);
assert_eq!(center, Point2::new(0.0, 0.5));
}
#[test]
fn test_default_get_field() {
let line = Line::default();
let point = Point2::new(0.5, 0.0);
let field = line.field(&point).unwrap();
assert_eq!(Point2::new(0.0, 0.25), field);
}
#[test]
fn test_generate_line_array() {
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 (_line_array, area, centroid) = generate_line_array(&vertices, &0.0, &1.0);
let comp_area = -4.0;
let mut comp_line_array = LineVec::with_capacity(4);
let comp_line = Line::new(1.0, Point2::new(0.0, 1.0), Angle::Degrees(90.0), 1.0);
comp_line_array.push(comp_line);
assert!(nearly_equal(area, comp_area), "Compare areas");
let centroid_comp = nearly_equal(0.0, centroid.x) && nearly_equal(0.0, centroid.y);
assert!(centroid_comp, "Centroid should be zero");
}
}