use crate::{
core::{
math::{Vector2, dist_squared},
traits::Real,
},
polyline::seg_split_at_point,
};
use super::{PlineSource, PlineVertex, Polyline, seg_closest_point};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy)]
pub struct PlineView<'a, P>
where
P: PlineSource + ?Sized,
{
pub source: &'a P,
pub data: PlineViewData<P::Num>,
}
impl<'a, P> PlineView<'a, P>
where
P: PlineSource + ?Sized,
{
#[inline]
pub fn new(source: &'a P, data: PlineViewData<P::Num>) -> Self {
Self { source, data }
}
#[inline]
pub fn detach(self) -> PlineViewData<P::Num> {
self.data
}
}
impl<P> PlineSource for PlineView<'_, P>
where
P: PlineSource + ?Sized,
{
type Num = P::Num;
type OutputPolyline = Polyline<P::Num>;
#[inline]
fn get_userdata_count(&self) -> usize {
self.source.get_userdata_count()
}
#[inline]
fn get_userdata_values(&self) -> impl Iterator<Item = u64> + '_ {
self.source.get_userdata_values()
}
#[inline]
fn vertex_count(&self) -> usize {
self.data.vertex_count()
}
#[inline]
fn is_closed(&self) -> bool {
false
}
#[inline]
fn get(&self, index: usize) -> Option<PlineVertex<Self::Num>> {
self.data.get_vertex(self.source, index)
}
#[inline]
fn at(&self, index: usize) -> PlineVertex<Self::Num> {
self.data.get_vertex(self.source, index).unwrap()
}
}
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(rename_all = "camelCase")
)]
#[derive(Debug, Clone, Copy)]
pub struct PlineViewData<T = f64> {
pub start_index: usize,
pub end_index_offset: usize,
pub updated_start: PlineVertex<T>,
pub updated_end_bulge: T,
pub end_point: Vector2<T>,
pub inverted_direction: bool,
}
impl<T> PlineViewData<T>
where
T: Real,
{
#[inline]
pub fn view<'a, P>(&self, source: &'a P) -> PlineView<'a, P>
where
P: PlineSource<Num = T> + ?Sized,
{
debug_assert_eq!(
self.validate_for_source(source),
ViewDataValidation::IsValid
);
PlineView {
source,
data: *self,
}
}
#[inline]
fn vertex_count(&self) -> usize {
self.end_index_offset + 2
}
fn get_vertex<P>(&self, source: &P, index: usize) -> Option<PlineVertex<T>>
where
P: PlineSource<Num = T> + ?Sized,
T: Real,
{
if index >= self.vertex_count() {
return None;
}
if self.inverted_direction {
if index == 0 {
let v = PlineVertex::from_vector2(self.end_point, -self.updated_end_bulge);
return Some(v);
}
if index < self.end_index_offset {
let bulge_i =
source.fwd_wrapping_index(self.start_index, self.end_index_offset - index);
let i = source.next_wrapping_index(bulge_i);
return Some(source.at(i).with_bulge(-source.at(bulge_i).bulge));
}
if index == self.end_index_offset {
let i =
source.fwd_wrapping_index(self.start_index, self.end_index_offset - index + 1);
let v = source.at(i);
return Some(v.with_bulge(-self.updated_start.bulge));
}
if index == self.end_index_offset + 1 {
return Some(self.updated_start.with_bulge(T::zero()));
}
} else {
if index == 0 {
return Some(self.updated_start);
}
if index < self.end_index_offset {
let i = source.fwd_wrapping_index(self.start_index, index);
return Some(source.at(i));
}
if index == self.end_index_offset {
let i = source.fwd_wrapping_index(self.start_index, self.end_index_offset);
let v = source.at(i);
return Some(v.with_bulge(self.updated_end_bulge));
}
if index == self.end_index_offset + 1 {
return Some(PlineVertex::from_vector2(self.end_point, T::zero()));
}
}
None
}
pub fn create_on_single_segment<P>(
source: &P,
start_index: usize,
updated_start: PlineVertex<T>,
end_intersect: Vector2<T>,
pos_equal_eps: T,
) -> Option<Self>
where
P: PlineSource<Num = T> + ?Sized,
{
if updated_start
.pos()
.fuzzy_eq_eps(end_intersect, pos_equal_eps)
{
return None;
}
let view_data = Self {
start_index,
end_index_offset: 0,
updated_start,
updated_end_bulge: updated_start.bulge,
end_point: end_intersect,
inverted_direction: false,
};
debug_assert_eq!(
view_data.validate_for_source(source),
ViewDataValidation::IsValid
);
Some(view_data)
}
pub fn create<P>(
source: &P,
start_index: usize,
end_intersect: Vector2<T>,
intersect_index: usize,
updated_start: PlineVertex<T>,
traverse_count: usize,
pos_equal_eps: T,
) -> Self
where
P: PlineSource<Num = T> + ?Sized,
{
assert!(
traverse_count != 0,
"traverse_count must be greater than 1, use different constructor if view is all on one segment"
);
let current_vertex = source.at(intersect_index);
let (end_index_offset, updated_end_bulge) =
if end_intersect.fuzzy_eq_eps(current_vertex.pos(), pos_equal_eps) {
let offset = traverse_count - 1;
let updated_end_bulge = if offset != 0 {
source.at(source.prev_wrapping_index(intersect_index)).bulge
} else {
updated_start.bulge
};
(offset, updated_end_bulge)
} else {
let next_index = source.next_wrapping_index(intersect_index);
let split = seg_split_at_point(
current_vertex,
source.at(next_index),
end_intersect,
pos_equal_eps,
);
(traverse_count, split.updated_start.bulge)
};
let view_data = Self {
start_index,
end_index_offset,
updated_start,
updated_end_bulge,
end_point: end_intersect,
inverted_direction: false,
};
debug_assert_eq!(
view_data.validate_for_source(source),
ViewDataValidation::IsValid
);
view_data
}
pub fn from_entire_pline<P>(source: &P) -> Self
where
P: PlineSource<Num = T> + ?Sized,
{
let vc = source.vertex_count();
assert!(
vc >= 2,
"source must have at least 2 vertexes to form view data"
);
let view_data = if source.is_closed() {
Self {
start_index: 0,
end_index_offset: vc - 1,
updated_start: source.at(0),
updated_end_bulge: source.last().unwrap().bulge,
end_point: source.at(0).pos(),
inverted_direction: false,
}
} else {
Self {
start_index: 0,
end_index_offset: vc - 2,
updated_start: source.at(0),
updated_end_bulge: source.at(vc - 2).bulge,
end_point: source.at(vc - 1).pos(),
inverted_direction: false,
}
};
debug_assert_eq!(
view_data.validate_for_source(source),
ViewDataValidation::IsValid
);
view_data
}
pub fn from_new_start<P>(
source: &P,
start_point: Vector2<T>,
start_index: usize,
pos_equal_eps: T,
) -> Option<Self>
where
P: PlineSource<Num = T> + ?Sized,
{
if !source.is_closed() {
return Self::from_slice_points(
source,
start_point,
start_index,
source.last()?.pos(),
source.vertex_count() - 1,
pos_equal_eps,
);
}
let vc = source.vertex_count();
assert!(
vc >= 2,
"source must have at least 2 vertexes to form view data"
);
let start_index = {
let next_index = source.next_wrapping_index(start_index);
if source
.at(next_index)
.pos()
.fuzzy_eq_eps(start_point, pos_equal_eps)
{
next_index
} else {
start_index
}
};
let start_v1 = source.at(start_index);
let start_v2 = source.at(source.next_wrapping_index(start_index));
let split = seg_split_at_point(start_v1, start_v2, start_point, pos_equal_eps);
let (end_index_offset, updated_end_bulge) =
if start_v1.pos().fuzzy_eq_eps(start_point, pos_equal_eps) {
(
vc - 1,
source.at(source.prev_wrapping_index(start_index)).bulge,
)
} else {
(vc, split.updated_start.bulge)
};
let view_data = Self {
start_index,
end_index_offset,
updated_start: split.split_vertex,
updated_end_bulge,
end_point: start_point,
inverted_direction: false,
};
debug_assert_eq!(
view_data.validate_for_source(source),
ViewDataValidation::IsValid
);
Some(view_data)
}
pub fn from_slice_points<P>(
source: &P,
start_point: Vector2<T>,
start_index: usize,
end_point: Vector2<T>,
end_index: usize,
pos_equal_eps: T,
) -> Option<Self>
where
P: PlineSource<Num = T> + ?Sized,
{
debug_assert!(
start_index <= end_index || source.is_closed(),
"start index should be less than or equal to end index if polyline is open"
);
let (start_index, start_point_at_seg_end) = {
if !source.is_closed() && start_index >= end_index {
(start_index, false)
} else {
let next_index = source.next_wrapping_index(start_index);
if source
.at(next_index)
.pos()
.fuzzy_eq_eps(start_point, pos_equal_eps)
{
(next_index, true)
} else {
(start_index, false)
}
}
};
let traverse_count = {
let index_dist = source.fwd_wrapping_dist(start_index, end_index);
if index_dist == 0
&& source.is_closed()
&& !start_point.fuzzy_eq_eps(end_point, pos_equal_eps)
{
let seg_start = source.at(start_index).pos();
let dist1 = dist_squared(seg_start, start_point);
let dist2 = dist_squared(seg_start, end_point);
if dist1 < dist2 {
0
} else {
source.vertex_count()
}
} else {
index_dist
}
};
let updated_start = {
let start_v1 = source.at(start_index);
let start_v2 = source.at(source.next_wrapping_index(start_index));
if start_point_at_seg_end {
if traverse_count == 0 {
let split = seg_split_at_point(start_v1, start_v2, end_point, pos_equal_eps);
split.updated_start
} else {
start_v1
}
} else {
let start_split =
seg_split_at_point(start_v1, start_v2, start_point, pos_equal_eps);
let updated_for_start = start_split.split_vertex;
if traverse_count == 0 {
let split =
seg_split_at_point(updated_for_start, start_v2, end_point, pos_equal_eps);
split.updated_start
} else {
updated_for_start
}
}
};
if traverse_count == 0 {
Self::create_on_single_segment(
source,
start_index,
updated_start,
end_point,
pos_equal_eps,
)
} else {
Some(Self::create(
source,
start_index,
end_point,
end_index,
updated_start,
traverse_count,
pos_equal_eps,
))
}
}
const VALIDATION_EPS: f64 = 1e-5;
const VALIDATION_POINT_ON_SEG_EPS: f64 = 1e-3;
pub fn validate_for_source<P>(&self, source: &P) -> ViewDataValidation<T>
where
P: PlineSource<Num = T> + ?Sized,
{
if source.vertex_count() < 2 {
return ViewDataValidation::SourceHasNoSegments;
}
if self.end_index_offset > source.vertex_count() {
return ViewDataValidation::OffsetOutOfRange {
offset: self.end_index_offset,
source_length: source.vertex_count(),
};
}
let validation_eps = T::from(Self::VALIDATION_EPS).unwrap();
let point_is_on_segment = |seg_index, point: Vector2<T>| {
let on_seg_eps = T::from(Self::VALIDATION_POINT_ON_SEG_EPS).unwrap();
let v1 = source.at(seg_index);
let v2 = source.at(source.next_wrapping_index(seg_index));
if point.fuzzy_eq_eps(v1.pos(), on_seg_eps) || point.fuzzy_eq_eps(v2.pos(), on_seg_eps)
{
return true;
}
let closest_point = seg_closest_point(v1, v2, point, validation_eps);
closest_point.fuzzy_eq_eps(point, on_seg_eps)
};
if !point_is_on_segment(self.start_index, self.updated_start.pos()) {
return ViewDataValidation::UpdatedStartNotOnSegment {
start_point: self.updated_start.pos(),
};
}
let end_index = source.fwd_wrapping_index(self.start_index, self.end_index_offset);
if !point_is_on_segment(end_index, self.end_point) {
return ViewDataValidation::EndPointNotOnSegment {
end_point: self.end_point,
};
}
if self
.end_point
.fuzzy_eq_eps(source.at(end_index).pos(), validation_eps)
{
return ViewDataValidation::EndPointOnFinalOffsetVertex {
end_point: self.end_point,
final_offset_vertex: source.at(end_index),
};
}
if self.end_index_offset == 0 {
if !self
.updated_end_bulge
.fuzzy_eq_eps(self.updated_start.bulge, validation_eps)
{
return ViewDataValidation::UpdatedBulgeDoesNotMatch {
updated_bulge: self.updated_end_bulge,
expected: self.updated_start.bulge,
};
}
}
ViewDataValidation::IsValid
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ViewDataValidation<T> {
SourceHasNoSegments,
OffsetOutOfRange {
offset: usize,
source_length: usize,
},
UpdatedStartNotOnSegment {
start_point: Vector2<T>,
},
EndPointNotOnSegment {
end_point: Vector2<T>,
},
EndPointOnFinalOffsetVertex {
end_point: Vector2<T>,
final_offset_vertex: PlineVertex<T>,
},
UpdatedBulgeDoesNotMatch {
updated_bulge: T,
expected: T,
},
IsValid,
}