1use crate::entity_tolerance::EntityTolerances;
2use crate::imprint::ImprintResultRecord;
3use crate::topology::{BrepSolid, CoedgeRecord, EdgeRecord, VertexRecord};
4use crate::{KernelRefusal, KernelStage, OrRefuse};
5use crate::{NurbsCurve, Vec3};
6use rustc_hash::FxHashMap as HashMap;
7
8fn subcurve_by_fraction(
9 curve: &NurbsCurve,
10 start_fraction: f64,
11 end_fraction: f64,
12) -> Result<NurbsCurve, KernelRefusal> {
13 let [start, end] = curve.domain().or_refuse(KernelStage::Refine, "domain")?;
14 let first = start + (end - start) * start_fraction;
15 let second = start + (end - start) * end_fraction;
16 let epsilon = (1e-9 * (end - start)).max(2e-9);
17 let mut result = curve.clone();
18 if first > start + epsilon && first < end - epsilon {
19 result = result
20 .split(first)
21 .or_refuse(KernelStage::Refine, "split")?
22 .1;
23 }
24 let domain = result.domain().or_refuse(KernelStage::Refine, "domain")?;
25 if second < domain[1] - epsilon && second > domain[0] + epsilon {
26 result = result
27 .split(second)
28 .or_refuse(KernelStage::Refine, "split")?
29 .0;
30 } else if env_read!("BREP_DEBUG_SUBRANGE").is_some() && second < domain[1] - epsilon {
31 eprintln!("abnormal subrange skip in edge_split subcurve");
32 }
33 Ok(result)
34}
35
36const SPLIT_VERTEX_FLOOR: f64 = 1e-7;
38
39fn claim_vertex(
61 vertices: &mut Vec<VertexRecord>,
62 imprint: &ImprintResultRecord,
63 edge: &EdgeRecord,
64 point: Vec3,
65 entity_band: f64,
66 next_id: &mut u64,
67) -> u64 {
68 let floor: f64 = SPLIT_VERTEX_FLOOR;
73 let tolerance = floor.max(if entity_band.is_finite() && entity_band > 0.0 {
74 entity_band
75 } else {
76 0.0
77 });
78 let debug = std::env::var("BREP_DEBUG_SPLIT_VERTEX").is_ok();
79 if let Some(vertex) = vertices
80 .iter()
81 .find(|vertex| vertex.point.sub(point).length() <= tolerance)
82 {
83 if debug {
84 eprintln!(
85 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: reuse v{}",
86 point.x, point.y, point.z, vertex.id
87 );
88 }
89 return vertex.id;
90 }
91 let nearest = imprint
92 .vertices
93 .iter()
94 .map(|vertex| (vertex.point, vertex.point.sub(point).length()))
95 .min_by(|a, b| a.1.total_cmp(&b.1));
96 let canonical = imprint.vertices.iter()
102 .map(|vertex| (vertex.point, vertex.point.sub(point).length()))
103 .filter(|(candidate, distance)| {
104 if *distance <= tolerance {
105 return true;
106 }
107 if *distance > tolerance.max(crate::tolerance::WELD_FLOOR) {
108 return false;
109 }
110 crate::project_point_to_curve(&edge.curve, *candidate).is_ok_and(|projection| {
111 projection.distance <= tolerance
112 && projection.u >= edge.t0.min(edge.t1)
113 && projection.u <= edge.t0.max(edge.t1)
114 })
115 })
116 .min_by(|a, b| a.1.total_cmp(&b.1))
117 .map(|(candidate, _)| candidate)
118 .unwrap_or(point);
119 if let Some(vertex) = vertices.iter()
120 .find(|vertex| vertex.point.sub(canonical).length() <= tolerance)
121 {
122 return vertex.id;
123 }
124 if debug {
125 eprintln!(
126 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: \
127 nearest imprint junction {:.3e} -> {}",
128 point.x,
129 point.y,
130 point.z,
131 nearest.map(|(_, d)| d).unwrap_or(f64::INFINITY),
132 if canonical.sub(point).length() > 0.0 {
133 "SNAPPED"
134 } else {
135 "kept"
136 }
137 );
138 }
139 let id = *next_id;
140 *next_id += 1;
141 vertices.push(VertexRecord {
142 id,
143 point: canonical,
144 });
145 id
146}
147
148pub fn apply_edge_splits(
153 solid: &BrepSolid,
154 operand: u8,
155 imprint: &ImprintResultRecord,
156) -> Result<BrepSolid, KernelRefusal> {
157 apply_edge_splits_with_map(solid, operand, imprint).map(|(result, _)| result)
158}
159
160pub fn apply_edge_splits_with_map(
165 solid: &BrepSolid,
166 operand: u8,
167 imprint: &ImprintResultRecord,
168) -> Result<(BrepSolid, HashMap<u64, Vec<u64>>), KernelRefusal> {
169 let requested = imprint
170 .edge_splits
171 .iter()
172 .filter(|split| split.operand == operand)
173 .map(|split| (split.edge_id, split.parameters.as_slice()))
174 .collect::<HashMap<_, _>>();
175 if requested.is_empty() {
176 return Ok((solid.clone(), HashMap::default()));
177 }
178
179 let mut result = solid.clone();
180 let mut next_vertex_id = result
181 .vertices
182 .iter()
183 .map(|vertex| vertex.id)
184 .max()
185 .unwrap_or(0)
186 + 1;
187 let mut next_edge_id = result.edges.iter().map(|edge| edge.id).max().unwrap_or(0) + 1;
188 let mut next_coedge_id = result
189 .shells
190 .iter()
191 .flat_map(|shell| &shell.faces)
192 .flat_map(|face| &face.loops)
193 .flat_map(|loop_record| &loop_record.coedges)
194 .map(|coedge| coedge.id)
195 .max()
196 .unwrap_or(0)
197 + 1;
198 let mut pieces: HashMap<u64, Vec<EdgeRecord>> = HashMap::default();
199 let mut entity = EntityTolerances::with_floor(solid, 0.0);
208
209 for edge in &solid.edges {
210 let Some(parameters) = requested.get(&edge.id) else {
211 continue;
212 };
213 let parameter_tolerance = (edge.t1 - edge.t0).abs() * 1e-10;
214 let mut partition = parameters
215 .iter()
216 .copied()
217 .filter(|parameter| {
218 *parameter > edge.t0 + parameter_tolerance
219 && *parameter < edge.t1 - parameter_tolerance
220 })
221 .collect::<Vec<_>>();
222 partition.sort_by(f64::total_cmp);
223 partition.dedup_by(|a, b| (*a - *b).abs() <= parameter_tolerance);
224 partition.insert(0, edge.t0);
225 partition.push(edge.t1);
226 if partition.len() <= 2 {
227 continue;
228 }
229 let entity_band = if entity.edge_bound_under(edge.id, 0.5 * SPLIT_VERTEX_FLOOR) {
233 0.0
234 } else {
235 entity.edge(edge.id)
236 };
237 let mut vertex_ids = vec![edge.start_vertex_id];
238 for parameter in &partition[1..partition.len() - 1] {
239 let point = edge
240 .curve
241 .evaluate(*parameter)
242 .or_refuse(KernelStage::Refine, "evaluate")?;
243 vertex_ids.push(claim_vertex(
244 &mut result.vertices,
245 imprint,
246 edge,
247 point,
248 entity_band,
249 &mut next_vertex_id,
250 ));
251 }
252 vertex_ids.push(edge.end_vertex_id);
253 let mut edge_pieces = Vec::new();
254 for index in 0..partition.len() - 1 {
255 let name = edge.name.as_ref().map(|name| {
258 if index == 0 {
259 name.clone()
260 } else {
261 format!("{name}_{index}")
262 }
263 });
264 edge_pieces.push(EdgeRecord {
265 id: next_edge_id,
266 curve: edge.curve.clone(),
267 t0: partition[index],
268 t1: partition[index + 1],
269 start_vertex_id: vertex_ids[index],
270 end_vertex_id: vertex_ids[index + 1],
271 degenerate: false,
272 name,
273 });
274 next_edge_id += 1;
275 }
276 pieces.insert(edge.id, edge_pieces);
277 }
278
279 result.edges.retain(|edge| !pieces.contains_key(&edge.id));
280 for edge_pieces in pieces.values() {
281 result.edges.extend(edge_pieces.iter().cloned());
282 }
283
284 let source_edges: HashMap<u64, &EdgeRecord> =
288 solid.edges.iter().map(|edge| (edge.id, edge)).collect();
289
290 for face in result
291 .shells
292 .iter_mut()
293 .flat_map(|shell| shell.faces.iter_mut())
294 {
295 for loop_record in &mut face.loops {
296 let mut rebuilt = Vec::new();
297 for coedge in &loop_record.coedges {
298 let Some(edge_pieces) = pieces.get(&coedge.edge_id) else {
299 rebuilt.push(coedge.clone());
300 continue;
301 };
302 let original = *source_edges.get(&coedge.edge_id).ok_or_else(|| {
303 KernelRefusal::internal(
304 KernelStage::Refine,
305 "edge_split",
306 "apply_edge_splits: missing source edge",
307 )
308 })?;
309 let span = original.t1 - original.t0;
310 let indices: Box<dyn Iterator<Item = usize>> = if coedge.forward {
311 Box::new(0..edge_pieces.len())
312 } else {
313 Box::new((0..edge_pieces.len()).rev())
314 };
315 for index in indices {
316 let piece = &edge_pieces[index];
317 let start_fraction = if coedge.forward {
318 (piece.t0 - original.t0) / span
319 } else {
320 (original.t1 - piece.t1) / span
321 };
322 let end_fraction = if coedge.forward {
323 (piece.t1 - original.t0) / span
324 } else {
325 (original.t1 - piece.t0) / span
326 };
327 rebuilt.push(CoedgeRecord {
328 id: next_coedge_id,
329 edge_id: piece.id,
330 forward: coedge.forward,
331 pcurve: subcurve_by_fraction(&coedge.pcurve, start_fraction, end_fraction)?,
332 });
333 next_coedge_id += 1;
334 }
335 }
336 loop_record.coedges = rebuilt;
337 }
338 }
339 let map = pieces
340 .iter()
341 .map(|(source_id, edge_pieces)| {
342 (
343 *source_id,
344 edge_pieces.iter().map(|piece| piece.id).collect(),
345 )
346 })
347 .collect();
348 Ok((result, map))
349}
350