#[derive(Clone)]
pub struct Wireframe<P>
{
points: Vec<P>,
lines: Vec<[usize; 2]>
}
impl<P> Default for Wireframe<P>
{
fn default() -> Self
{
Self {
points: Vec::new(),
lines: Vec::new()
}
}
}
impl<P> Wireframe<P>
{
pub fn new() -> Self
{
Self::default()
}
pub fn from_path(path: impl IntoIterator<Item = P>) -> Self
where
P: Copy + PartialEq
{
Self::from_lines(crate::path::lines(path))
}
pub fn from_lines(lines: impl IntoIterator<Item = [P; 2]>) -> Self
where
P: PartialEq
{
Self::new()
.with_lines(lines)
}
pub fn with_lines(mut self, lines: impl IntoIterator<Item = [P; 2]>) -> Self
where
P: PartialEq
{
self.add_lines(lines);
self
}
pub fn add_lines(&mut self, lines: impl IntoIterator<Item = [P; 2]>)
where
P: PartialEq
{
self.lines.extend(
lines.into_iter()
.map(|line| line.map(|point| {
if let Some(index) = self.points.iter()
.position(|p| *p == point)
{
index
}
else
{
let index = self.points.len();
self.points.push(point);
index
}
}))
);
self.lines.sort();
self.lines.dedup_by(|l1, l2| crate::line::equals(l1, l2));
}
pub fn map<U>(self, mapper: impl Fn(P) -> U) -> Wireframe<U>
where
U: Copy + PartialEq
{
let Self { points, lines } = self;
let mut wireframe = Wireframe {
points: points.into_iter()
.map(mapper)
.collect(),
lines
};
wireframe.dedup();
wireframe
}
pub fn dedup(&mut self)
where
P: Copy + PartialEq
{
*self = Self::from_lines(core::mem::take(self).into_lines());
}
pub fn into_lines<'a>(self) -> impl Iterator<Item = [P; 2]> + 'a
where
P: Copy + 'a
{
let Self { points, lines } = self;
lines.into_iter()
.map(move |[a, b]| [points[a], points[b]])
}
pub fn corner_indices<'a>(&'a self) -> impl Iterator<Item = [usize; 3]> + 'a
where
P: Copy + PartialEq
{
self.lines.iter()
.enumerate()
.flat_map(move |(i, l1 @ [a1, b1])| self.lines[..i].iter()
.filter_map(move |l2 @ [a2, b2]| {
assert!(!crate::line::equals(l1, l2), "Lines cannot overlap exactly");
assert!(
!crate::line::equals(&[self.points[*a1], self.points[*b1]], &[self.points[*a2], self.points[*b2]]),
"Lines cannot overlap"
);
crate::line::corner(*l1, *l2)
})
)
}
pub fn corners<'a>(&'a self) -> impl Iterator<Item = [P; 3]> + 'a
where
P: Copy + PartialEq
{
self.corner_indices()
.map(|[a, p, b]| [self.points[a], self.points[p], self.points[b]])
}
}
impl Wireframe<[f64; 3]>
{
#[cfg(feature = "artwork")]
pub fn mesh(&self) -> ratatui_3d::Mesh
{
use ratatui_3d::{Mesh, Vertex, math::Vec3};
let vertices = self.corners()
.flat_map(|[a, p, b]| {
let as_vec = |[x, y, z]: [f64; 3]| {
Vec3::new(x as f32, y as f32, z as f32)
};
let [mut a, p, mut b] = [
as_vec(a),
as_vec(p),
as_vec(b)
];
let mut normal = (a - p).cross(b - p);
if normal.z > 0.0
{
normal = -normal;
core::mem::swap(&mut a, &mut b)
}
[
Vertex::new(a, normal).with_uv(0.0, 0.0),
Vertex::new(p, normal).with_uv(0.5, 1.0),
Vertex::new(b, normal).with_uv(1.0, 0.0)
]
})
.collect::<Vec<_>>();
assert_eq!(vertices.len() % 3, 0);
let indices = (0..vertices.len() as u32).collect();
Mesh::new(
vertices,
indices
)
}
}
impl Wireframe<[f64; 2]>
{
pub fn from_line(line: [[f64; 2]; 2], thickness: f64) -> Self
{
let [[ax, ay], [bx, by]] = line;
let [vx, vy] = [bx - ax, by - ay];
let v = (vx*vx + vy*vy).sqrt();
let [wx, wy] = [vy/v*thickness, -vx/v*thickness];
Wireframe::from_path([
[ax + wx, ay + wy],
[bx + wx, by + wy],
[ax - wx, ay - wy],
[bx - wx, by - wy]
])
}
pub fn extrude(mut self, z: f64) -> Wireframe<[f64; 3]>
{
self.fragment();
self.map(|[x, y]| [x, y, z + (1.0 - x*x - y*y).sqrt()])
}
pub fn fragment(&mut self) -> bool
{
let mut changed = false;
let mut i = 0;
'lp1:
while i < self.lines.len()
{
let mut j = 0;
while j < i
{
let l1 @ [a1, b1] = self.lines[i];
let l2 @ [a2, b2] = self.lines[j];
if crate::line::equals(&l1, &l2)
{
self.lines.remove(i);
continue 'lp1
}
if crate::line::corner(l1, l2).is_some()
{
j += 1;
continue
}
let [a1x, a1y] = self.points[a1];
let [b1x, b1y] = self.points[b1];
let [a2x, a2y] = self.points[a2];
let [b2x, b2y] = self.points[b2];
let d1x = b1x - a1x;
let d1y = b1y - a1y;
let d2x = b2x - a2x;
let d2y = b2y - a2y;
let x = (d1x*d2y*a2x - (a2y - a1y)*d1x*d2x - d2x*d1y*a1x)/(d1x*d2y - d2x*d1y);
let y = (d1x*d2y*a1y + (a2x - a1x)*d1y*d2y - d2x*d1y*a2y)/(d1x*d2y - d2x*d1y);
if a1x.min(b1x).max(a2x.min(b2x)) < x
&& x < a1x.max(b1x).min(a2x.max(b2x))
{
changed = true;
let p = if let Some(p) = self.points.iter()
.position(|point| *point == [x, y])
{
p
}
else
{
let p = self.points.len();
self.points.push([x, y]);
p
};
self.lines[i] = [a1, p];
self.lines[j] = [a2, p];
self.lines.extend([[p, b1], [p, b2]]);
i = i.min(j);
continue 'lp1
}
j += 1
}
i += 1
}
if changed
{
self.dedup();
}
changed
}
}