use cellular_raza_concepts::*;
use nalgebra::SVector;
use serde::{Deserialize, Serialize};
#[cfg(feature = "pyo3")]
use pyo3::prelude::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass)]
pub struct NoInteraction;
impl<Pos, Vel, For> Interaction<Pos, Vel, For> for NoInteraction {
fn calculate_force_between(
&self,
_: &Pos,
_: &Vel,
_: &Pos,
_: &Vel,
_ext_information: &(),
) -> Option<Result<For, CalcError>> {
return None;
}
fn get_interaction_information(&self) -> () {}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
pub struct BoundLennardJones {
pub epsilon: f64,
pub sigma: f64,
pub bound: f64,
pub cutoff: f64,
}
impl<const D: usize> Interaction<SVector<f64, D>, SVector<f64, D>, SVector<f64, D>>
for BoundLennardJones
{
fn calculate_force_between(
&self,
own_pos: &SVector<f64, D>,
_own_vel: &SVector<f64, D>,
ext_pos: &SVector<f64, D>,
_ext_vel: &SVector<f64, D>,
_ext_information: &(),
) -> Option<Result<SVector<f64, D>, CalcError>> {
let z = own_pos - ext_pos;
let r = z.norm();
let dir = z / r;
let val = 4.0 * self.epsilon / r
* (12.0 * (self.sigma / r).powf(11.0) - 6.0 * (self.sigma / r).powf(5.0));
let max = self.bound / r;
let q = if self.cutoff >= r { 1.0 } else { 0.0 };
return Some(Ok(dir * q * max.min(val)));
}
fn get_interaction_information(&self) -> () {}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "pyo3", pyclass(get_all, set_all))]
pub struct BoundLennardJonesF32 {
pub epsilon: f32,
pub sigma: f32,
pub bound: f32,
pub cutoff: f32,
}
impl<const D: usize> Interaction<SVector<f32, D>, SVector<f32, D>, SVector<f32, D>>
for BoundLennardJonesF32
{
fn calculate_force_between(
&self,
own_pos: &SVector<f32, D>,
_own_vel: &SVector<f32, D>,
ext_pos: &SVector<f32, D>,
_ext_vel: &SVector<f32, D>,
_ext_information: &(),
) -> Option<Result<SVector<f32, D>, CalcError>> {
let z = own_pos - ext_pos;
let r = z.norm();
let dir = z / r;
let val = 4.0 * self.epsilon / r
* (12.0 * (self.sigma / r).powf(11.0) - 6.0 * (self.sigma / r).powf(5.0));
let max = self.bound / r;
let q = if self.cutoff >= r { 1.0 } else { 0.0 };
return Some(Ok(dir * q * max.min(val)));
}
fn get_interaction_information(&self) -> () {}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct VertexDerivedInteraction<A, R, I1 = (), I2 = ()> {
pub outside_interaction: A,
pub inside_interaction: R,
phantom_inf_1: core::marker::PhantomData<I1>,
phantom_inf_2: core::marker::PhantomData<I2>,
}
impl<A, R, I1, I2> VertexDerivedInteraction<A, R, I1, I2> {
pub fn from_two_forces(attracting_force: A, repelling_force: R) -> Self
where
A: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I1>,
R: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I2>,
{
VertexDerivedInteraction {
outside_interaction: attracting_force,
inside_interaction: repelling_force,
phantom_inf_1: core::marker::PhantomData::<I1>,
phantom_inf_2: core::marker::PhantomData::<I2>,
}
}
}
use itertools::Itertools;
use nalgebra::Vector2;
fn nearest_point_from_point_to_line(
point: &Vector2<f64>,
line: (Vector2<f64>, Vector2<f64>),
) -> (f64, Vector2<f64>) {
let ab = line.1 - line.0;
let ap = point - line.0;
let t = (ab.dot(&ap) / ab.norm_squared()).clamp(0.0, 1.0);
let nearest_point = (1.0 - t) * line.0 + t * line.1;
((point - nearest_point).norm(), nearest_point)
}
fn nearest_point_from_point_to_multiple_lines(
point: &Vector2<f64>,
polygon_lines: &[(Vector2<f64>, Vector2<f64>)],
) -> Option<(f64, Vector2<f64>)> {
polygon_lines
.iter()
.map(|&line| nearest_point_from_point_to_line(point, line))
.min_by(|(distance1, _), (distance2, _)| distance1.total_cmp(&distance2))
}
fn ray_intersects_line_segment(
ray: &(Vector2<f64>, Vector2<f64>),
line_segment: &(Vector2<f64>, Vector2<f64>),
) -> bool {
let (r1, r2) = ray;
let (l1, l2) = line_segment;
let t_enum = (l1 - r1).perp(&(l2 - l1));
let u_enum = (r1 - l1).perp(&(r2 - r1));
let t_denom = (r2 - r1).perp(&(l2 - l1));
let u_denom = -t_denom;
if t_denom == 0.0 || u_denom == 0.0 {
let d = (r1 - l1).dot(&(l2 - l1));
let e = (l2 - l1).norm_squared();
return 0.0 <= d && d <= e;
}
let t = t_enum / t_denom;
let u = u_enum / u_denom;
return 0.0 <= u && u < 1.0 && 0.0 <= t;
}
impl<A, R, I1, I2, const D: usize>
Interaction<
super::mechanics::VertexVector2<D>,
super::mechanics::VertexVector2<D>,
super::mechanics::VertexVector2<D>,
(I1, I2),
> for VertexDerivedInteraction<A, R, I1, I2>
where
A: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I1>,
R: Interaction<Vector2<f64>, Vector2<f64>, Vector2<f64>, I2>,
{
fn get_interaction_information(&self) -> (I1, I2) {
let i1 = self.outside_interaction.get_interaction_information();
let i2 = self.inside_interaction.get_interaction_information();
(i1, i2)
}
fn calculate_force_between(
&self,
own_pos: &super::mechanics::VertexVector2<D>,
own_vel: &super::mechanics::VertexVector2<D>,
ext_pos: &super::mechanics::VertexVector2<D>,
ext_vel: &super::mechanics::VertexVector2<D>,
ext_information: &(I1, I2),
) -> Option<Result<super::mechanics::VertexVector2<D>, CalcError>> {
let middle_own: Vector2<f64> = own_pos
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ own_pos.shape().0 as f64;
let average_vel_own: Vector2<f64> = own_vel
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ own_vel.shape().0 as f64;
let middle_ext: Vector2<f64> = ext_pos
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ ext_pos.shape().0 as f64;
let average_vel_ext: Vector2<f64> = ext_vel
.row_iter()
.map(|v| v.transpose())
.sum::<Vector2<f64>>()
/ ext_vel.shape().0 as f64;
let own_polygon_lines = own_pos
.row_iter()
.map(|vec| vec.transpose())
.circular_tuple_windows()
.collect::<Vec<(_, _)>>();
let vec_on_edge =
0.5 * (own_pos.view_range(0..1, 0..2) + own_pos.view_range(1..2, 0..2)).transpose();
let point_outside_polygon = 2.0 * vec_on_edge - middle_own;
let mut total_force = ext_pos.clone() * 0.0;
let (inf1, inf2) = ext_information;
for (point, mut force) in ext_pos
.row_iter()
.map(|vec| vec.transpose())
.zip(total_force.row_iter_mut())
{
let bounding_box: [[f64; 2]; 2] = own_pos.row_iter().map(|v| v.transpose()).fold(
[[std::f64::INFINITY, -std::f64::INFINITY]; 2],
|mut accumulator, polygon_edge| {
accumulator[0][0] = accumulator[0][0].min(polygon_edge.x);
accumulator[0][1] = accumulator[0][1].max(polygon_edge.x);
accumulator[1][0] = accumulator[1][0].min(polygon_edge.y);
accumulator[1][1] = accumulator[1][1].max(polygon_edge.y);
accumulator
},
);
let point_is_out_of_bounding_box = point.x < bounding_box[0][0]
|| point.x > bounding_box[0][1]
|| point.y < bounding_box[1][0]
|| point.y > bounding_box[1][1];
let external_point_is_in_polygon = match point_is_out_of_bounding_box {
true => false,
false => {
let n_intersections: usize = own_polygon_lines
.iter()
.map(|line| {
ray_intersects_line_segment(&(point, point_outside_polygon), line)
as usize
})
.sum();
n_intersections % 2 == 1
}
};
let calc;
if external_point_is_in_polygon {
calc = self.inside_interaction.calculate_force_between(
&middle_own,
&average_vel_own,
&middle_ext,
&average_vel_ext,
&inf2,
);
} else {
let (_, nearest_point) =
match nearest_point_from_point_to_multiple_lines(&point, &own_polygon_lines) {
Some(point) => point,
None => return None,
};
calc = self.outside_interaction.calculate_force_between(
&nearest_point,
&average_vel_own,
&point,
&average_vel_ext,
&inf1,
);
}
match calc {
Some(Ok(calculated_force)) => force += calculated_force.transpose(),
Some(Err(error)) => return Some(Err(error)),
None => (),
}
}
Some(Ok(total_force))
}
}
mod test {
#[test]
fn test_closest_points() {
let p1 = nalgebra::Vector2::from([0.0, 0.0]);
let p2 = nalgebra::Vector2::from([2.0, 0.0]);
let mut test_points = Vec::new();
test_points.push((
nalgebra::Vector2::from([0.5, 1.0]),
nalgebra::Vector2::from([0.5, 0.0]),
1.0,
));
test_points.push((nalgebra::Vector2::from([-1.0, 2.0]), p1, 5.0_f64.sqrt()));
test_points.push((nalgebra::Vector2::from([3.0, -2.0]), p2, 5.0_f64.sqrt()));
for (q, r, d) in test_points.iter() {
let (dist, nearest_point) = super::nearest_point_from_point_to_line(&q, (p1, p2));
assert_eq!(dist, *d);
assert_eq!(nearest_point, *r);
}
}
#[test]
fn test_point_is_in_regular_polygon() {
use itertools::Itertools;
let polygon = [
nalgebra::Vector2::from([-1.0, 0.0]),
nalgebra::Vector2::from([0.0, 1.0]),
nalgebra::Vector2::from([1.0, 0.0]),
nalgebra::Vector2::from([0.0, -1.0]),
];
let point_outside_polygon = nalgebra::Vector::from([-3.0, 0.0]);
let points_inside = [
nalgebra::Vector2::from([0.0, 0.0]),
nalgebra::Vector2::from([0.0, 0.1]),
nalgebra::Vector2::from([0.0, 0.99999]),
nalgebra::Vector2::from([0.99999, 0.0]),
nalgebra::Vector2::from([0.0, 1.0]),
nalgebra::Vector2::from([0.99999, 0.0]),
nalgebra::Vector2::from([-1.0, 0.0]),
nalgebra::Vector2::from([0.0, -1.0]),
];
for p in points_inside.iter() {
let n_intersections: usize = polygon
.clone()
.into_iter()
.circular_tuple_windows::<(_, _)>()
.map(|line| {
super::ray_intersects_line_segment(&(*p, point_outside_polygon), &line) as usize
})
.sum();
assert_eq!(n_intersections % 2 == 1, true);
}
let points_outside = [
nalgebra::Vector2::from([2.0, 0.0]),
nalgebra::Vector2::from([-1.5, 0.0]),
nalgebra::Vector2::from([0.5, 1.2]),
nalgebra::Vector2::from([1.3, -1.0001]),
nalgebra::Vector2::from([1.0000000000001, 0.0]),
nalgebra::Vector2::from([0.0, -1.000000000001]),
];
for q in points_outside.iter() {
let n_intersections: usize = polygon
.clone()
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, point_outside_polygon), &line) as usize
})
.sum();
assert_eq!(n_intersections % 2 == 0, true);
}
let new_polygon = [
nalgebra::Vector2::from([89.8169131069576, 105.21635977300497]),
nalgebra::Vector2::from([88.08135232199689, 107.60515425930363]),
nalgebra::Vector2::from([85.27315598238903, 106.69271595767589]),
nalgebra::Vector2::from([85.27315598238903, 103.74000358833405]),
nalgebra::Vector2::from([88.08135232199689, 102.8275652867063]),
];
let new_point_outside_polygon = nalgebra::Vector2::from([80.0, 90.0]);
let points_inside_2 = [nalgebra::Vector2::from([
88.08135232199689,
102.8275652867063,
])];
for q in points_inside_2.iter() {
let n_intersections: usize = new_polygon
.clone()
.into_iter()
.circular_tuple_windows()
.map(|line| {
super::ray_intersects_line_segment(&(*q, new_point_outside_polygon), &line)
as usize
})
.sum();
assert_eq!(n_intersections % 2 == 0, false);
}
}
}