1const NEWTON_ITERATIONS: usize = 20;
5
6use crate::{Coedge, CoedgeGeometry, Edge, EdgeId, Model, Sense, VertexId};
7use geop_core_geometry::contains::{curve::curve_could_contain, surface::surface_could_contain};
8use geop_core_math::{
9 geop_error::{GeopError, GeopResult, WithContext},
10 scalars::Scalar,
11 vector::Vector2,
12};
13
14impl<S: Scalar> Model<S> {
15 pub fn split_edge_at_vertex(
25 &mut self,
26 edge_id: EdgeId,
27 edge_t: S,
28 vertex_id: VertexId,
29 max_nodes: usize,
30 min_subdivision_size: S,
31 ) -> GeopResult<EdgeId> {
32 let ctx = |e: GeopError| {
33 e.with_context(format!(
34 "Model::split_edge_at_vertex(edge_id={edge_id}, edge_t={edge_t}, vertex_id={vertex_id}, max_nodes={max_nodes}, min_subdivision_size={min_subdivision_size})"
35 ))
36 };
37
38 let edge = self.get_edge(edge_id).with_context(&ctx)?.clone();
39 let vertex_point = self.get_vertex(vertex_id).with_context(&ctx)?.point;
40
41 let curve_point = edge.curve.evaluate(edge_t).with_context(&ctx)?;
42 if !curve_point.could_be_equal(&vertex_point) {
43 return Err(ctx(GeopError::new(format!(
44 "edge {edge_id} at t={edge_t} evaluates to {curve_point}, which does not coincide with vertex {vertex_id} at {vertex_point}"
45 ))));
46 }
47
48 let (curve_lo, curve_hi) = edge.curve.domain();
72 let edge_t = edge
73 .curve
74 .refine_parameter_at_point(edge_t, &vertex_point)
75 .with_context(&ctx)?;
76 if !edge_t.definitely_greater(curve_lo) || !edge_t.definitely_less(curve_hi) {
77 return Err(ctx(GeopError::new(format!(
78 "edge_t={edge_t:?} is not strictly inside the curve domain ({curve_lo:?}, {curve_hi:?}), so vertex {vertex_id} is an endpoint of edge {edge_id} rather than interior to it — there is nothing to split"
79 ))));
80 }
81 let mut pcurve_splits = Vec::new();
82 for coedge_id in self.coedges_of_edge(edge_id) {
83 let coedge = self.get_coedge(coedge_id).with_context(&ctx)?.clone();
84 let coedge_ctx = |e: GeopError| {
85 let (t0, t1) = coedge.pcurve.domain();
86 e.with_context(format!(
87 "coedge_id={coedge_id}, face={}, sense={:?}, pcurve_domain=({t0}, {t1})",
88 coedge.face, coedge.sense,
89 ))
90 };
91 let surface = &self.get_face(coedge.face).with_context(&ctx)?.surface;
92 let (u, v) = surface_could_contain(
97 surface,
98 &vertex_point,
99 max_nodes,
100 min_subdivision_size,
101 )
102 .with_context(&ctx)
103 .with_context(&coedge_ctx)?
104 .ok_or_else(|| {
105 ctx(coedge_ctx(GeopError::new(format!(
106 "could not locate vertex {vertex_id} (at {vertex_point}) on face {}'s surface",
107 coedge.face
108 ))))
109 })?;
110 let refined = surface
121 .project(vertex_point, u.sharpen(), v.sharpen(), NEWTON_ITERATIONS)
122 .with_context(&ctx)
123 .with_context(&coedge_ctx)?;
124 let (u, v) = if refined.0.could_be_equal(u) && refined.1.could_be_equal(v) {
125 (u.intersect(refined.0), v.intersect(refined.1))
126 } else {
127 (u, v)
128 };
129 let uv = Vector2::from_array([u, v]);
130 let uv_ctx = |e: GeopError| e.with_context(format!("found uv=({u}, {v})"));
131 let pcurve_t =
132 curve_could_contain(&coedge.pcurve, &uv, max_nodes, min_subdivision_size)
133 .with_context(&ctx)
134 .with_context(&coedge_ctx)
135 .with_context(&uv_ctx)?
136 .ok_or_else(|| {
137 ctx(coedge_ctx(uv_ctx(GeopError::new(format!(
138 "could not locate vertex {vertex_id} on coedge {coedge_id}'s pcurve"
139 )))))
140 })?;
141 let (pcurve_lo, pcurve_hi) = coedge.pcurve.domain();
142 let pcurve_t = coedge
143 .pcurve
144 .refine_parameter_at_point(pcurve_t, &uv)
145 .with_context(&ctx)
146 .with_context(&coedge_ctx)?;
147 if !pcurve_t.definitely_greater(pcurve_lo) || !pcurve_t.definitely_less(pcurve_hi) {
148 return Err(ctx(coedge_ctx(GeopError::new(format!(
149 "pcurve_t={pcurve_t:?} is not strictly inside coedge {coedge_id}'s pcurve domain ({pcurve_lo:?}, {pcurve_hi:?}), so vertex {vertex_id} is an endpoint of that pcurve rather than interior to it — there is nothing to split"
150 )))));
151 }
152 pcurve_splits.push((coedge_id, pcurve_t));
153 }
154
155 let (new_curve_1, new_curve_2) = edge.curve.split(edge_t).with_context(&ctx)?;
156 let existing_edge = self.get_edge_mut(edge_id).with_context(&ctx)?;
159 existing_edge.curve = new_curve_1;
160 existing_edge.end_vertex = vertex_id;
161 let edge_1 = edge_id;
162 let edge_2 = self.insert_edge(Edge {
163 curve: new_curve_2,
164 start_vertex: vertex_id,
165 end_vertex: edge.end_vertex,
166 });
167
168 for (coedge_id, pcurve_t) in pcurve_splits {
169 let coedge = self.get_coedge(coedge_id).with_context(&ctx)?.clone();
170
171 let coedge_ctx = |e: GeopError| {
172 let (t0, t1) = coedge.pcurve.domain();
173 e.with_context(format!(
174 "coedge_id={coedge_id}, face={}, sense={:?}, pcurve_domain=({t0}, {t1})",
175 coedge.face, coedge.sense,
176 ))
177 };
178
179 let (pcurve_left, pcurve_right) = coedge
180 .pcurve
181 .split(pcurve_t)
182 .with_context(&ctx)
183 .with_context(&coedge_ctx)?;
184 let (first_edge, first_pcurve, second_edge, second_pcurve) = match coedge.sense {
188 Sense::Forward => (edge_1, pcurve_left, edge_2, pcurve_right),
189 Sense::Reversed => (edge_2, pcurve_left, edge_1, pcurve_right),
190 };
191
192 let new_coedge_id = self.insert_coedge(Coedge {
193 geometry: CoedgeGeometry::Edge(second_edge),
194 sense: coedge.sense,
195 pcurve: second_pcurve,
196 next: coedge.next,
197 prev: coedge_id,
198 face: coedge.face,
199 });
200
201 let existing = self.get_coedge_mut(coedge_id).with_context(&ctx)?;
202 existing.geometry = CoedgeGeometry::Edge(first_edge);
203 existing.pcurve = first_pcurve;
204 existing.next = new_coedge_id;
205
206 self.get_coedge_mut(coedge.next).with_context(&ctx)?.prev = new_coedge_id;
207 }
208
209 Ok(edge_2)
210 }
211}