#![allow(dead_code)]
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::edge::EdgeId;
use crate::BlendError;
#[derive(Debug, Clone)]
pub struct Spine {
edges: Vec<EdgeId>,
params: Vec<f64>,
length: f64,
is_closed: bool,
}
fn edge_endpoints(topo: &Topology, edge_id: EdgeId) -> Result<(Point3, Point3), BlendError> {
let edge = topo.edge(edge_id)?;
let p_start = topo.vertex(edge.start())?.point();
let p_end = topo.vertex(edge.end())?.point();
Ok((p_start, p_end))
}
impl Spine {
pub fn from_single_edge(topo: &Topology, edge_id: EdgeId) -> Result<Self, BlendError> {
let (p_start, p_end) = edge_endpoints(topo, edge_id)?;
let length = (p_end - p_start).length();
Ok(Self {
edges: vec![edge_id],
params: vec![0.0, length],
length,
is_closed: false,
})
}
pub fn from_chain(topo: &Topology, edges: Vec<EdgeId>) -> Result<Self, BlendError> {
let mut params = Vec::with_capacity(edges.len() + 1);
params.push(0.0);
let mut cumulative = 0.0;
for &eid in &edges {
let (p_start, p_end) = edge_endpoints(topo, eid)?;
cumulative += (p_end - p_start).length();
params.push(cumulative);
}
let is_closed = if edges.len() >= 2 {
let first = topo.edge(edges[0])?;
let last = topo.edge(edges[edges.len() - 1])?;
first.start() == last.end()
} else {
false
};
Ok(Self {
edges,
params,
length: cumulative,
is_closed,
})
}
#[must_use]
pub fn length(&self) -> f64 {
self.length
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.edges.len()
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.is_closed
}
#[must_use]
pub fn edges(&self) -> &[EdgeId] {
&self.edges
}
#[must_use]
pub fn locate(&self, s: f64) -> (usize, f64) {
let s_clamped = s.clamp(0.0, self.length);
for i in 0..self.edges.len() {
let s0 = self.params[i];
let s1 = self.params[i + 1];
if s_clamped <= s1 || i == self.edges.len() - 1 {
let edge_len = s1 - s0;
let t = if edge_len > f64::EPSILON {
(s_clamped - s0) / edge_len
} else {
0.0
};
return (i, t.clamp(0.0, 1.0));
}
}
(self.edges.len() - 1, 1.0)
}
pub fn evaluate(&self, topo: &Topology, s: f64) -> Result<Point3, BlendError> {
let (idx, t) = self.locate(s);
let edge = topo.edge(self.edges[idx])?;
let p_start = topo.vertex(edge.start())?.point();
let p_end = topo.vertex(edge.end())?.point();
let curve = edge.curve();
let (t0, t1) = curve.domain_with_endpoints(p_start, p_end);
let param = t0 + (t1 - t0) * t;
Ok(curve.evaluate_with_endpoints(param, p_start, p_end))
}
pub fn tangent(&self, topo: &Topology, s: f64) -> Result<Vec3, BlendError> {
let (idx, t) = self.locate(s);
let edge = topo.edge(self.edges[idx])?;
let p_start = topo.vertex(edge.start())?.point();
let p_end = topo.vertex(edge.end())?.point();
let curve = edge.curve();
let (t0, t1) = curve.domain_with_endpoints(p_start, p_end);
let param = t0 + (t1 - t0) * t;
let tan = curve.tangent_with_endpoints(param, p_start, p_end);
Ok(tan.normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0)))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use brepkit_topology::Topology;
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::vertex::Vertex;
fn make_line_edge(topo: &mut Topology, a: Point3, b: Point3) -> EdgeId {
let v0 = topo.add_vertex(Vertex::new(a, 1e-7));
let v1 = topo.add_vertex(Vertex::new(b, 1e-7));
topo.add_edge(Edge::new(v0, v1, EdgeCurve::Line))
}
#[test]
fn single_edge_spine_length() {
let mut topo = Topology::new();
let eid = make_line_edge(
&mut topo,
Point3::new(0.0, 0.0, 0.0),
Point3::new(10.0, 0.0, 0.0),
);
let spine = Spine::from_single_edge(&topo, eid).unwrap();
assert!((spine.length() - 10.0).abs() < 1e-10);
assert_eq!(spine.edge_count(), 1);
assert!(!spine.is_closed());
}
#[test]
fn locate_maps_parameter_correctly() {
let mut topo = Topology::new();
let eid = make_line_edge(
&mut topo,
Point3::new(0.0, 0.0, 0.0),
Point3::new(10.0, 0.0, 0.0),
);
let spine = Spine::from_single_edge(&topo, eid).unwrap();
let (idx, t) = spine.locate(5.0);
assert_eq!(idx, 0);
assert!((t - 0.5).abs() < 1e-10);
}
#[test]
fn evaluate_midpoint() {
let mut topo = Topology::new();
let eid = make_line_edge(
&mut topo,
Point3::new(0.0, 0.0, 0.0),
Point3::new(10.0, 0.0, 0.0),
);
let spine = Spine::from_single_edge(&topo, eid).unwrap();
let mid = spine.evaluate(&topo, 5.0).unwrap();
assert!((mid - Point3::new(5.0, 0.0, 0.0)).length() < 1e-10);
}
}