1use geop_core_geometry::nurb_curve::{NurbCurve, NurbCurve2D, NurbCurve3D};
6use geop_core_math::{
7 geop_error::{GeopError, GeopResult},
8 primitives::CoordinateSystem,
9 scalars::Scalar,
10 vector::{Vector2, Vector3},
11};
12
13#[derive(Clone, Debug)]
22pub struct Chain<C> {
23 pub name: String,
26 pub curves: Vec<C>,
27 pub curve_names: Vec<String>,
28 pub joint_names: Vec<String>,
29}
30
31pub type Profile = Chain<NurbCurve2D>;
33
34pub trait ChainCurve: Clone {
38 type Point;
39
40 fn start(&self) -> GeopResult<Self::Point>;
42
43 fn end(&self) -> GeopResult<Self::Point>;
45}
46
47impl ChainCurve for NurbCurve2D {
48 type Point = Vector2;
49
50 fn start(&self) -> GeopResult<Vector2> {
51 NurbCurve2D::start(self)
52 }
53
54 fn end(&self) -> GeopResult<Vector2> {
55 NurbCurve2D::end(self)
56 }
57}
58
59impl ChainCurve for NurbCurve3D {
60 type Point = Vector3;
61
62 fn start(&self) -> GeopResult<Vector3> {
63 NurbCurve3D::start(self)
64 }
65
66 fn end(&self) -> GeopResult<Vector3> {
67 NurbCurve3D::end(self)
68 }
69}
70
71impl<const D: usize> Chain<NurbCurve<D>>
72where
73 NurbCurve<D>: ChainCurve,
74{
75 pub fn closed(curves: Vec<NurbCurve<D>>) -> Self {
78 let n = curves.len();
79 Self::numbered(curves, n)
80 }
81
82 pub fn open(curves: Vec<NurbCurve<D>>) -> Self {
84 let n = curves.len();
85 Self::numbered(curves, n + 1)
86 }
87
88 fn numbered(curves: Vec<NurbCurve<D>>, joints: usize) -> Self {
89 Self {
90 name: String::new(),
91 curve_names: (0..curves.len()).map(|i| format!("c{i}")).collect(),
92 joint_names: (0..joints).map(|i| format!("p{i}")).collect(),
93 curves,
94 }
95 }
96
97 pub fn with_prefix(mut self, prefix: &str) -> Self {
100 for name in self.curve_names.iter_mut().chain(&mut self.joint_names) {
101 *name = format!("{prefix}{name}");
102 }
103 self
104 }
105
106 pub fn is_closed(&self) -> bool {
107 self.joint_names.len() == self.curves.len()
108 }
109
110 pub fn check_names(&self) -> GeopResult<()> {
112 let n = self.curves.len();
113 if self.curve_names.len() != n
114 || !(self.joint_names.len() == n || self.joint_names.len() == n + 1)
115 {
116 return Err(GeopError::new(format!(
117 "chain of {n} curves has {} curve names and {} joint names",
118 self.curve_names.len(),
119 self.joint_names.len()
120 )));
121 }
122 Ok(())
123 }
124
125 pub fn reversed(&self) -> Self {
128 let n = self.curves.len();
129 let joint_names = if self.is_closed() {
130 (0..n)
133 .map(|m| self.joint_names[(n - m) % n].clone())
134 .collect()
135 } else {
136 self.joint_names.iter().rev().cloned().collect()
137 };
138 Self {
139 name: self.name.clone(),
140 curves: self.curves.iter().rev().map(|c| c.reverse()).collect(),
141 curve_names: self.curve_names.iter().rev().cloned().collect(),
142 joint_names,
143 }
144 }
145
146 pub fn starting_at(&self, k: usize) -> Self {
148 let mut out = self.clone();
149 out.curves.rotate_left(k);
150 out.curve_names.rotate_left(k);
151 out.joint_names.rotate_left(k);
152 out
153 }
154
155 pub fn map_curves(&self, f: impl Fn(&NurbCurve<D>) -> NurbCurve<D>) -> Self {
157 Self {
158 curves: self.curves.iter().map(f).collect(),
159 ..self.clone()
160 }
161 }
162
163 pub fn joint(&self, i: usize) -> GeopResult<<NurbCurve<D> as ChainCurve>::Point> {
165 if i < self.curves.len() {
166 self.curves[i].start()
167 } else {
168 self.curves[i - 1].end()
169 }
170 }
171
172 pub fn joints(&self) -> GeopResult<Vec<<NurbCurve<D> as ChainCurve>::Point>> {
174 (0..self.joint_names.len()).map(|i| self.joint(i)).collect()
175 }
176}
177
178impl Chain<NurbCurve2D> {
179 pub fn corners(&self) -> GeopResult<Vec<usize>> {
188 let n = self.curves.len();
189 let mut corners = Vec::new();
190 for i in 0..n {
191 let before = &self.curves[(i + n - 1) % n];
192 let after = &self.curves[i];
193 let arriving = before.tangent(before.domain().1)?;
194 let leaving = after.tangent(after.domain().0)?;
195 let smooth = arriving.prod_cross(&leaving).could_be_equal(Scalar::ZERO)
196 && arriving.prod_dot(&leaving).definitely_greater(Scalar::ZERO);
197 if !smooth {
198 corners.push(i);
199 }
200 }
201 Ok(corners)
202 }
203}
204
205impl Chain<NurbCurve3D> {
206 pub fn starting_on(&self, plane: &CoordinateSystem) -> GeopResult<Option<Self>> {
210 let on_plane = |p: &Vector3| plane.to_uvw(p)[2].could_be_equal(Scalar::ZERO);
211 let joints = self.joints()?;
212 Ok(if on_plane(&joints[0]) {
213 Some(self.clone())
214 } else if on_plane(&joints[joints.len() - 1]) {
215 Some(self.reversed())
216 } else {
217 None
218 })
219 }
220}