mod impls;
use crate::{LinestringError, linestring_2d};
use itertools::Itertools;
use std::{collections, fs, io, io::Write, path::Path};
use vector_traits::{
approx::{AbsDiffEq, UlpsEq},
num_traits::{AsPrimitive, real::Real},
prelude::{GenericVector3, *},
};
pub(crate) mod simplify;
#[cfg(test)]
mod tests;
#[derive(PartialEq, Copy, Clone)]
pub struct Line3<T: GenericVector3> {
pub start: T,
pub end: T,
}
impl<T: GenericVector3> Line3<T> {
pub fn new(start: T, end: T) -> Self {
Self { start, end }
}
fn triangle_area_squared_times_4(p1: &T, p2: &T, p3: &T) -> T::Scalar {
let v1_x = p1.x() - p2.x();
let v1_y = p1.y() - p2.y();
let v1_z = p1.z() - p2.z();
let v2_x = p3.x() - p2.x();
let v2_y = p3.y() - p2.y();
let v2_z = p3.z() - p2.z();
let x = v1_y * v2_z - v2_y * v1_z;
let y = v1_x * v2_z - v2_x * v1_z;
let z = v1_x * v2_y - v2_x * v1_y;
x * x + y * y + z * z
}
pub fn copy_to_2d(&self, plane: Plane) -> linestring_2d::Line2<T::Vector2> {
linestring_2d::Line2::new(
plane.point_to_2d::<T>(self.start),
plane.point_to_2d::<T>(self.end),
)
}
pub fn aabb(&self) -> T::Aabb {
let mut aabb = T::Aabb::from_point(self.start);
aabb.add_point(self.end);
aabb
}
pub fn apply<F>(&mut self, f: &F)
where
F: Fn(T) -> T,
{
self.start = f(self.start);
self.end = f(self.end);
}
pub fn apply_new<F: Fn(T) -> T>(self, f: F) -> Self {
Self {
start: f(self.start),
end: f(self.end),
}
}
}
struct PriorityDistance<T: GenericVector3> {
key: T::Scalar,
index: usize,
}
#[doc(hidden)]
pub struct WindowIterator<'a, T: GenericVector3>(std::slice::Windows<'a, T>);
impl<T: GenericVector3> WindowIterator<'_, T> {
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
}
#[doc(hidden)]
pub struct ChunkIterator<'a, T: GenericVector3>(std::slice::ChunksExact<'a, T>);
impl<'a, T: GenericVector3> ChunkIterator<'a, T> {
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.0.len() == 0
}
#[inline(always)]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
#[inline(always)]
pub fn remainder(&self) -> &'a [T] {
self.0.remainder()
}
}
pub trait LineString3<T: GenericVector3> {
fn copy_to_2d(&self, plane: Plane) -> Vec<T::Vector2>;
fn is_connected(&self) -> bool;
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
fn window_iter(&self) -> WindowIterator<'_, T>;
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
fn chunk_iter(&self) -> ChunkIterator<'_, T>;
fn simplify_rdp(&self, distance_predicate: T::Scalar) -> Self;
fn simplify_vw(&self, points_to_delete: usize) -> Self;
fn apply<F>(&mut self, f: &F)
where
F: Fn(T) -> T;
fn discretize(&self, distance: T::Scalar) -> DiscreteLineSegmentIterator<'_, T>;
}
impl<T: GenericVector3> LineString3<T> for Vec<T> {
fn copy_to_2d(&self, plane: Plane) -> Vec<T::Vector2> {
plane.points_to_2d(self.iter()).collect()
}
fn is_connected(&self) -> bool {
self.is_empty() || self.first().unwrap() == self.last().unwrap()
}
fn window_iter(&self) -> WindowIterator<'_, T> {
WindowIterator(self.windows(2))
}
fn chunk_iter(&self) -> ChunkIterator<'_, T> {
ChunkIterator(self.chunks_exact(2))
}
#[inline(always)]
fn simplify_rdp(&self, distance_predicate: T::Scalar) -> Self {
simplify::simplify_rdp(self, distance_predicate)
}
#[inline(always)]
fn simplify_vw(&self, points_to_delete: usize) -> Self {
simplify::simplify_vw(self, points_to_delete)
}
fn apply<F: Fn(T) -> T>(&mut self, f: &F) {
self.iter_mut().for_each(|v| *v = f(*v));
}
fn discretize(&self, distance: T::Scalar) -> DiscreteLineSegmentIterator<'_, T> {
DiscreteLineSegmentIterator::new(self, distance)
}
}
pub trait Approx<T: GenericVector3> {
fn ulps_eq(
&self,
other: &Self,
epsilon: <T::Scalar as AbsDiffEq>::Epsilon,
max_ulps: u32,
) -> bool;
fn abs_diff_eq(&self, other: &Self, epsilon: <T::Scalar as AbsDiffEq>::Epsilon) -> bool;
}
impl<T: GenericVector3> Approx<T> for Vec<T> {
fn ulps_eq(
&self,
other: &Self,
epsilon: <T::Scalar as AbsDiffEq>::Epsilon,
max_ulps: u32,
) -> bool {
self.len() == other.len()
&& self
.iter()
.zip(other.iter())
.all(|(a, b)| a.is_ulps_eq(*b, epsilon, max_ulps))
}
fn abs_diff_eq(&self, other: &Self, epsilon: <T::Scalar as AbsDiffEq>::Epsilon) -> bool {
self.len() == other.len()
&& self
.iter()
.zip(other.iter())
.all(|(a, b)| a.is_abs_diff_eq(*b, epsilon))
}
}
#[allow(clippy::many_single_char_names)]
#[inline(always)]
fn cross_abs_squared<T: GenericVector3>(a: T, b: T) -> T::Scalar {
let x = a.y() * b.z() - a.z() * b.y();
let y = a.z() * b.x() - a.x() * b.z();
let z = a.x() * b.y() - a.y() * b.x();
x * x + y * y + z * z
}
#[inline(always)]
pub fn distance_to_line_squared<T: GenericVector3>(l0: T, l1: T, p: T) -> T::Scalar {
let l0_sub_l1 = l0 - l1;
let l0_sub_p = l0 - p;
cross_abs_squared(l0_sub_p, l0_sub_l1)
/ (l0_sub_l1.x() * l0_sub_l1.x()
+ l0_sub_l1.y() * l0_sub_l1.y()
+ l0_sub_l1.z() * l0_sub_l1.z())
}
#[inline(always)]
pub fn distance_to_line_squared_safe<T: GenericVector3>(l0: T, l1: T, p: T) -> T::Scalar {
if l0.is_ulps_eq(
l1,
T::Scalar::default_epsilon(),
T::Scalar::default_max_ulps(),
) {
l0.distance_sq(p)
} else {
let l0_sub_l1 = l0 - l1;
let l0_sub_p = l0 - p;
cross_abs_squared(l0_sub_p, l0_sub_l1)
/ (l0_sub_l1.x() * l0_sub_l1.x()
+ l0_sub_l1.y() * l0_sub_l1.y()
+ l0_sub_l1.z() * l0_sub_l1.z())
}
}
pub fn save_to_obj_file<T: GenericVector3>(
filename: impl AsRef<Path>,
object_name: &str,
lines: &[Vec<Line3<T>>],
) -> Result<(), LinestringError> {
let mut point_set = collections::HashMap::<String, usize>::new();
for i in lines.iter() {
for j in i.iter() {
let a_str = format!("v {:+e} {:+e} {:+e}", j.start.x(), j.start.y(), j.start.z());
let len = point_set.len();
let _ = point_set.entry(a_str).or_insert(len);
let a_str = format!("v {:+e} {:+e} {:+e}", j.end.x(), j.end.y(), j.end.z());
let len = point_set.len();
let _ = point_set.entry(a_str).or_insert(len);
}
}
let mut file = io::BufWriter::new(fs::File::create(filename)?);
writeln!(file, "o {object_name}")?;
for (k, v) in point_set.iter().sorted_unstable_by(|a, b| a.1.cmp(b.1)) {
writeln!(file, "{} #{}", k, v + 1)?;
}
for i in lines.iter() {
for j in i.iter() {
let str0 = format!("v {:+e} {:+e} {:+e}", j.start.x(), j.start.y(), j.start.z());
if let Some(p0) = point_set.get(&str0) {
let str1 = format!("v {:+e} {:+e} {:+e}", j.end.x(), j.end.y(), j.end.z());
if let Some(p1) = point_set.get(&str1) {
writeln!(file, "l {} {}", p0 + 1, p1 + 1)?;
} else {
return Err(LinestringError::InternalError(format!(
"HashMap can't find the key:{str1}"
)));
}
} else {
return Err(LinestringError::InternalError(format!(
"HashMap can't find the key:{str0}"
)));
}
}
}
file.flush()?;
Ok(())
}
pub struct DiscreteLineSegmentIterator<'a, T: GenericVector3> {
points: &'a Vec<T>,
distance: T::Scalar,
current_index: usize,
substep_index: usize,
num_substeps: usize,
step: T,
}
impl<'a, T: GenericVector3> DiscreteLineSegmentIterator<'a, T> {
fn new(points: &'a Vec<T>, distance: T::Scalar) -> Self {
DiscreteLineSegmentIterator {
points,
distance,
current_index: 0,
substep_index: 0,
num_substeps: 0,
step: T::new_3d(T::Scalar::ZERO, T::Scalar::ZERO, T::Scalar::ZERO),
}
}
fn initialize_step(&mut self) {
let start_point = self.points[self.current_index];
let end_point = self.points[self.current_index + 1];
let direction = end_point - start_point;
let segment_length = direction.magnitude();
if segment_length > T::Scalar::ZERO {
let num_substeps = (segment_length / self.distance).ceil();
self.num_substeps = num_substeps.as_();
self.step = direction / num_substeps;
} else {
self.num_substeps = 1;
self.step = T::new_3d(T::Scalar::ZERO, T::Scalar::ZERO, T::Scalar::ZERO);
}
}
}
impl<T: GenericVector3> Iterator for DiscreteLineSegmentIterator<'_, T>
where
usize: AsPrimitive<<T as HasXY>::Scalar>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.points.is_empty() {
return None;
}
match self.points.len() - 1 {
n if self.current_index < n => {
if self.substep_index == 0 {
self.initialize_step();
}
if self.substep_index < self.num_substeps {
let start_point = self.points[self.current_index];
let result = start_point + self.step * (self.substep_index.as_());
self.substep_index += 1;
return Some(result);
}
self.current_index += 1;
self.substep_index = 0;
self.next()
}
n if self.current_index == n => {
self.current_index += 1;
self.substep_index = 0;
self.points.last().copied()
}
_ => None,
}
}
}