1use geop_core_editor::{
28 edit_form::{EditForm, FormState},
29 editable::{Context, OperationGroup, OperationInfo, OperationTier},
30 part_operation,
31 part_operation::PartOperation,
32};
33use geop_core_geometry::{
34 intersection::{Intersections, curve_curve_intersect, refine_curve_curve_crossing},
35 nurb_curve::NurbCurve3D,
36 nurb_surface::NurbSurface3D,
37 search::{MAX_NODES, min_subdivision_size},
38};
39use geop_core_math::{
40 geop_error::{GeopError, GeopResult},
41 scalars::Scalar,
42};
43use geop_core_program::{part::StepContext, reader::Reader};
44use geop_core_topology::{
45 Sense,
46 build::{BodySpec, CoedgeOn, EdgeSpec, FaceSpec},
47 distance::{Feature, closest_points},
48};
49use geop_ops_model::sketch3d::CurveSource;
50use geop_ops_model::{BodyNames, Chain, ModelOperation, NamedModel, Namer, build_model};
51use serde::{Deserialize, Serialize};
52
53use crate::{NamedCurve, SheetSpec};
54
55#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
64pub struct NetworkSurface;
65
66#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
67pub struct NetworkSurfaceArgs {
68 pub u_curves: Vec<CurveSource>,
70 pub v_curves: Vec<CurveSource>,
72}
73
74part_operation!(NetworkSurface(NetworkSurfaceArgs));
75
76impl PartOperation for NetworkSurface {
77 const KIND: &'static str = "network_surface";
78 const INFO: OperationInfo = OperationInfo::new(
79 "UV surface",
80 "Span a face standing on its own through a network of curves — u curves crossing v curves — running along every one of them.",
81 OperationGroup::Surface,
82 OperationTier::Small,
83 )
84 .experimental()
85 .icon(crate::icons::NETWORK_SURFACE);
86 type Args = NetworkSurfaceArgs;
87 type Session = ();
88
89 fn new_args(_: &Context) -> NetworkSurfaceArgs {
91 NetworkSurfaceArgs::default()
92 }
93
94 fn run(ctx: &mut StepContext, args: &NetworkSurfaceArgs) -> GeopResult<()> {
95 let _step = ctx.step();
96 build_model::<Self>(ctx, args)
97 }
98
99 fn form<'a>(
101 context: &'a Context<'a>,
102 args: &NetworkSurfaceArgs,
103 _: &FormState<()>,
104 ) -> EditForm<'a, NetworkSurfaceArgs> {
105 let before = &context.before;
106 let mut f = EditForm::<NetworkSurfaceArgs>::default();
107 f.pick(
108 "u_curves",
109 "u curves",
110 before,
111 &args.u_curves,
112 true,
113 |edit, picked| edit.args.u_curves = picked,
114 );
115 f.pick(
116 "v_curves",
117 "v curves",
118 before,
119 &args.v_curves,
120 true,
121 |edit, picked| edit.args.v_curves = picked,
122 );
123 f
124 }
125}
126
127impl ModelOperation for NetworkSurface {
128 fn build(
129 model: &mut NamedModel,
130 reader: &Reader,
131 step: &str,
132 args: &NetworkSurfaceArgs,
133 ) -> GeopResult<()> {
134 let namer = Namer::new("network", step)?;
135 let resolve = |curves: &[CurveSource]| {
136 (curves.iter())
137 .map(|c| c.resolve(reader))
138 .collect::<GeopResult<Vec<_>>>()
139 };
140 let (u, v) = (resolve(&args.u_curves)?, resolve(&args.v_curves)?);
141 let (spec, names) = network_surface(&namer, &u, &v)?;
142 model.build_body(spec, names)?;
143 Ok(())
144 }
145}
146
147#[derive(Clone, Debug)]
150struct Strand {
151 label: String,
153 name: String,
154 curve: NurbCurve3D,
155 start: String,
156 end: String,
157}
158
159impl Strand {
160 fn of(chain: &Chain<NurbCurve3D>, direction: &str) -> GeopResult<Self> {
161 let named = NamedCurve::of(chain)?;
162 let label = format!("{direction} curve {}", named.name);
163 if named.start.0 == named.end.0 {
164 return Err(GeopError::new(format!(
165 "{label} is closed: a network surface spans curves with two ends"
166 )));
167 }
168 Ok(Self {
169 label,
170 name: named.name,
171 curve: named.curve,
172 start: named.start.0,
173 end: named.end.0,
174 })
175 }
176
177 fn turned(&self) -> (Self, impl Fn(Scalar) -> Scalar) {
180 let (t0, t1) = self.curve.domain();
181 let turned = Self {
182 curve: self.curve.reverse(),
183 start: self.end.clone(),
184 end: self.start.clone(),
185 ..self.clone()
186 };
187 (turned, move |t: Scalar| t0.add(t1).sub(t))
188 }
189}
190
191fn crossing(a: &Strand, b: &Strand) -> GeopResult<(Scalar, Scalar)> {
195 let ends = |s: &Strand| {
196 let (t0, t1) = s.curve.domain();
197 [(s.start.clone(), t0), (s.end.clone(), t1)]
198 };
199 let shared: Vec<(Scalar, Scalar)> = ends(a)
200 .into_iter()
201 .flat_map(|(na, ta)| {
202 ends(b)
203 .into_iter()
204 .filter(move |(nb, _)| *nb == na)
205 .map(move |(_, tb)| (ta, tb))
206 })
207 .collect();
208 let (la, lb) = (&a.label, &b.label);
209 match shared.as_slice() {
210 [one] => return Ok(*one),
211 [] => {}
212 _ => {
213 return Err(GeopError::new(format!(
214 "{la} and {lb} meet at both their ends: each u curve must cross each v curve once"
215 )));
216 }
217 }
218 let found = curve_curve_intersect(&a.curve, &b.curve, 3, MAX_NODES, min_subdivision_size())?;
219 match found {
220 Intersections::Coincident(_) => Err(GeopError::new(format!(
221 "{la} and {lb} run along each other: each u curve must cross each v curve once"
222 ))),
223 Intersections::Found(found) => match found.as_slice() {
224 [] => Err(GeopError::new(format!(
225 "{la} and {lb} do not cross: they pass {:.3e} apart",
226 closest_points(
227 &Feature::Curve(a.curve.clone()),
228 &Feature::Curve(b.curve.clone())
229 )?
230 .distance
231 .to_f64()
232 ))),
233 [(ta, tb)] => Ok(refine_curve_curve_crossing(&a.curve, &b.curve, *ta, *tb)),
234 more => Err(GeopError::new(format!(
235 "{la} and {lb} cross {} times: each u curve must cross each v curve once",
236 more.len()
237 ))),
238 },
239 }
240}
241
242fn order(params: &[Scalar]) -> Vec<usize> {
245 let mut order: Vec<usize> = (0..params.len()).collect();
246 order.sort_by(|&i, &j| params[i].to_f64().total_cmp(¶ms[j].to_f64()));
247 order
248}
249
250type Aligned = (Vec<Strand>, Vec<Vec<Scalar>>);
252
253fn aligned(
258 strands: Vec<Strand>,
259 params: Vec<Vec<Scalar>>,
260 across: &[usize],
261 first: &str,
262) -> GeopResult<Aligned> {
263 let mut out = (Vec::new(), Vec::new());
264 for (strand, params) in strands.into_iter().zip(params) {
265 let along: Vec<Scalar> = across.iter().map(|&i| params[i]).collect();
266 let rising = along.windows(2).all(|w| w[0].definitely_less(w[1]));
267 let falling = along.windows(2).all(|w| w[0].definitely_greater(w[1]));
268 let (strand, along) = if rising {
269 (strand, along)
270 } else if falling {
271 let (turned, at) = strand.turned();
272 (turned, along.into_iter().map(at).collect())
273 } else {
274 return Err(GeopError::new(format!(
275 "the curves crossing {} cross it in another order than they cross {first}: the curves must make a grid",
276 strand.label
277 )));
278 };
279 out.0.push(strand);
280 out.1.push(along);
281 }
282 Ok(out)
283}
284
285pub fn network_surface(
289 namer: &Namer,
290 u_curves: &[Chain<NurbCurve3D>],
291 v_curves: &[Chain<NurbCurve3D>],
292) -> GeopResult<SheetSpec> {
293 if u_curves.len() < 2 || v_curves.len() < 2 {
294 return Err(GeopError::new(format!(
295 "a network surface needs two curves at least in each direction, not {} u curves and {} v curves",
296 u_curves.len(),
297 v_curves.len()
298 )));
299 }
300 let picked: Vec<&str> = (u_curves.iter().chain(v_curves))
301 .map(|c| c.name.as_str())
302 .collect();
303 if let Some((_, twice)) = picked
304 .iter()
305 .enumerate()
306 .find(|(k, e)| picked[..*k].contains(e))
307 {
308 return Err(GeopError::new(format!(
309 "{twice} is picked twice: each curve of a network is in one direction, once"
310 )));
311 }
312 let u = u_curves
313 .iter()
314 .map(|c| Strand::of(c, "u"))
315 .collect::<GeopResult<Vec<_>>>()?;
316 let v = v_curves
317 .iter()
318 .map(|c| Strand::of(c, "v"))
319 .collect::<GeopResult<Vec<_>>>()?;
320 let (m, n) = (u.len(), v.len());
321 let mut on_u = vec![Vec::with_capacity(n); m];
323 let mut on_v = vec![Vec::with_capacity(m); n];
324 for (j, uj) in u.iter().enumerate() {
325 for (i, vi) in v.iter().enumerate() {
326 let (tu, tv) = crossing(uj, vi)?;
327 on_u[j].push(tu);
328 on_v[i].push(tv);
329 }
330 }
331 let v_order = order(&on_u[0]);
334 let u_order = order(&on_v[0]);
335 let (u_label, v_label) = (u[0].label.clone(), v[0].label.clone());
336 let (u, on_u) = aligned(u, on_u, &v_order, &u_label)?;
337 let (v, on_v) = aligned(v, on_v, &u_order, &v_label)?;
338 let pick = |items: &[Strand], params: &[Vec<Scalar>], order: &[usize]| {
339 order
340 .iter()
341 .map(|&k| (items[k].clone(), params[k].clone()))
342 .unzip::<_, _, Vec<_>, Vec<_>>()
343 };
344 let (u, on_u): (Vec<Strand>, Vec<Vec<Scalar>>) = pick(&u, &on_u, &u_order);
345 let (v, on_v): (Vec<Strand>, Vec<Vec<Scalar>>) = pick(&v, &on_v, &v_order);
346
347 let cut =
351 |s: &Strand, params: &[Scalar]| s.curve.sub_curve(params[0], params[params.len() - 1]);
352 for (strands, params) in [(&u, &on_u), (&v, &on_v)] {
353 let pieces = strands
354 .iter()
355 .zip(params.iter())
356 .map(|(s, p)| cut(s, p))
357 .collect::<GeopResult<Vec<_>>>()?;
358 for a in 0..pieces.len() {
359 for b in a + 1..pieces.len() {
360 let met = curve_curve_intersect(
361 &pieces[a],
362 &pieces[b],
363 1,
364 MAX_NODES,
365 min_subdivision_size(),
366 )?;
367 if !met.as_slice().is_empty() {
368 return Err(GeopError::new(format!(
369 "{} and {} meet between the curves they cross: the curves of one direction must not meet, so that they make a grid with the others",
370 strands[a].label, strands[b].label
371 )));
372 }
373 }
374 }
375 }
376
377 let curves = |s: &[Strand]| s.iter().map(|s| s.curve.clone()).collect::<Vec<_>>();
378 let surface = NurbSurface3D::gordon(&curves(&u), &curves(&v), &on_u, &on_v)?;
379
380 let corners = [(0, 0), (0, n - 1), (m - 1, n - 1), (m - 1, 0)];
383 let vertices = corners
384 .iter()
385 .map(|&(j, i)| {
386 let a = u[j].curve.evaluate(on_u[j][i])?;
387 let b = v[i].curve.evaluate(on_v[i][j])?;
388 Ok(a.union(&b))
389 })
390 .collect::<GeopResult<Vec<_>>>()?;
391 let edges = vec![
392 EdgeSpec {
393 curve: cut(&u[0], &on_u[0])?,
394 start: 0,
395 end: 1,
396 },
397 EdgeSpec {
398 curve: cut(&v[n - 1], &on_v[n - 1])?,
399 start: 1,
400 end: 2,
401 },
402 EdgeSpec {
403 curve: cut(&u[m - 1], &on_u[m - 1])?,
404 start: 3,
405 end: 2,
406 },
407 EdgeSpec {
408 curve: cut(&v[0], &on_v[0])?,
409 start: 0,
410 end: 3,
411 },
412 ];
413 let face = FaceSpec::on_unit_square(
414 surface,
415 [
416 CoedgeOn::Edge(0, Sense::Forward),
417 CoedgeOn::Edge(1, Sense::Forward),
418 CoedgeOn::Edge(2, Sense::Reversed),
419 CoedgeOn::Edge(3, Sense::Reversed),
420 ],
421 )?;
422 let names = BodyNames {
423 vertices: corners
424 .iter()
425 .map(|&(j, i)| namer.name(&[&u[j].name, &v[i].name]))
426 .collect(),
427 edges: [&u[0], &v[n - 1], &u[m - 1], &v[0]]
428 .iter()
429 .map(|s| namer.name(&[&s.name]))
430 .collect(),
431 faces: vec![namer.root()],
432 solid: None,
433 };
434 let spec = BodySpec {
435 vertices,
436 edges,
437 faces: vec![face],
438 shells: vec![vec![0]],
439 solid: false,
440 };
441 Ok((spec, names))
442}
443
444#[cfg(test)]
445mod tests;