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 std::env::var("BREP_DEBUG_SUBRANGE").is_ok() && second < domain[1] - epsilon {
31 eprintln!("abnormal subrange skip in edge_split subcurve");
32 }
33 Ok(result)
34}
35
36fn claim_vertex(
60 vertices: &mut Vec<VertexRecord>,
61 imprint: &ImprintResultRecord,
62 point: Vec3,
63 entity_band: f64,
64 next_id: &mut u64,
65) -> u64 {
66 let floor = 1e-5 * (1.0 + point.length());
71 let tolerance = floor.max(if entity_band.is_finite() && entity_band > 0.0 {
72 entity_band
73 } else {
74 0.0
75 });
76 let debug = std::env::var("BREP_DEBUG_SPLIT_VERTEX").is_ok();
77 if let Some(vertex) = vertices
78 .iter()
79 .find(|vertex| vertex.point.sub(point).length() <= tolerance)
80 {
81 if debug {
82 eprintln!(
83 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: reuse v{}",
84 point.x, point.y, point.z, vertex.id
85 );
86 }
87 return vertex.id;
88 }
89 let nearest = imprint
90 .vertices
91 .iter()
92 .map(|vertex| (vertex.point, vertex.point.sub(point).length()))
93 .min_by(|a, b| a.1.total_cmp(&b.1));
94 let canonical = nearest
95 .filter(|(_, distance)| *distance <= tolerance)
96 .map(|(candidate, _)| candidate)
97 .unwrap_or(point);
98 if debug {
99 eprintln!(
100 "split-vertex ({:.7},{:.7},{:.7}) band={tolerance:.3e} entity={entity_band:.3e}: \
101 nearest imprint junction {:.3e} -> {}",
102 point.x,
103 point.y,
104 point.z,
105 nearest.map(|(_, d)| d).unwrap_or(f64::INFINITY),
106 if canonical.sub(point).length() > 0.0 {
107 "SNAPPED"
108 } else {
109 "kept"
110 }
111 );
112 }
113 let id = *next_id;
114 *next_id += 1;
115 vertices.push(VertexRecord {
116 id,
117 point: canonical,
118 });
119 id
120}
121
122pub fn apply_edge_splits(
127 solid: &BrepSolid,
128 operand: u8,
129 imprint: &ImprintResultRecord,
130) -> Result<BrepSolid, KernelRefusal> {
131 apply_edge_splits_with_map(solid, operand, imprint).map(|(result, _)| result)
132}
133
134pub fn apply_edge_splits_with_map(
139 solid: &BrepSolid,
140 operand: u8,
141 imprint: &ImprintResultRecord,
142) -> Result<(BrepSolid, HashMap<u64, Vec<u64>>), KernelRefusal> {
143 let requested = imprint
144 .edge_splits
145 .iter()
146 .filter(|split| split.operand == operand)
147 .map(|split| (split.edge_id, split.parameters.as_slice()))
148 .collect::<HashMap<_, _>>();
149 if requested.is_empty() {
150 return Ok((solid.clone(), HashMap::default()));
151 }
152
153 let mut result = solid.clone();
154 let mut next_vertex_id = result
155 .vertices
156 .iter()
157 .map(|vertex| vertex.id)
158 .max()
159 .unwrap_or(0)
160 + 1;
161 let mut next_edge_id = result.edges.iter().map(|edge| edge.id).max().unwrap_or(0) + 1;
162 let mut next_coedge_id = result
163 .shells
164 .iter()
165 .flat_map(|shell| &shell.faces)
166 .flat_map(|face| &face.loops)
167 .flat_map(|loop_record| &loop_record.coedges)
168 .map(|coedge| coedge.id)
169 .max()
170 .unwrap_or(0)
171 + 1;
172 let mut pieces: HashMap<u64, Vec<EdgeRecord>> = HashMap::default();
173 let mut entity = EntityTolerances::with_floor(solid, 0.0);
183
184 for edge in &solid.edges {
185 let Some(parameters) = requested.get(&edge.id) else {
186 continue;
187 };
188 let parameter_tolerance = (edge.t1 - edge.t0).abs() * 1e-10;
189 let mut partition = parameters
190 .iter()
191 .copied()
192 .filter(|parameter| {
193 *parameter > edge.t0 + parameter_tolerance
194 && *parameter < edge.t1 - parameter_tolerance
195 })
196 .collect::<Vec<_>>();
197 partition.sort_by(f64::total_cmp);
198 partition.dedup_by(|a, b| (*a - *b).abs() <= parameter_tolerance);
199 partition.insert(0, edge.t0);
200 partition.push(edge.t1);
201 if partition.len() <= 2 {
202 continue;
203 }
204 let entity_band = entity.edge(edge.id);
205 let mut vertex_ids = vec![edge.start_vertex_id];
206 for parameter in &partition[1..partition.len() - 1] {
207 let point = edge
208 .curve
209 .evaluate(*parameter)
210 .or_refuse(KernelStage::Refine, "evaluate")?;
211 vertex_ids.push(claim_vertex(
212 &mut result.vertices,
213 imprint,
214 point,
215 entity_band,
216 &mut next_vertex_id,
217 ));
218 }
219 vertex_ids.push(edge.end_vertex_id);
220 let mut edge_pieces = Vec::new();
221 for index in 0..partition.len() - 1 {
222 let name = edge.name.as_ref().map(|name| {
225 if index == 0 {
226 name.clone()
227 } else {
228 format!("{name}_{index}")
229 }
230 });
231 edge_pieces.push(EdgeRecord {
232 id: next_edge_id,
233 curve: edge.curve.clone(),
234 t0: partition[index],
235 t1: partition[index + 1],
236 start_vertex_id: vertex_ids[index],
237 end_vertex_id: vertex_ids[index + 1],
238 degenerate: false,
239 name,
240 });
241 next_edge_id += 1;
242 }
243 pieces.insert(edge.id, edge_pieces);
244 }
245
246 result.edges.retain(|edge| !pieces.contains_key(&edge.id));
247 for edge_pieces in pieces.values() {
248 result.edges.extend(edge_pieces.iter().cloned());
249 }
250
251 let source_edges: HashMap<u64, &EdgeRecord> =
255 solid.edges.iter().map(|edge| (edge.id, edge)).collect();
256
257 for face in result
258 .shells
259 .iter_mut()
260 .flat_map(|shell| shell.faces.iter_mut())
261 {
262 for loop_record in &mut face.loops {
263 let mut rebuilt = Vec::new();
264 for coedge in &loop_record.coedges {
265 let Some(edge_pieces) = pieces.get(&coedge.edge_id) else {
266 rebuilt.push(coedge.clone());
267 continue;
268 };
269 let original = *source_edges.get(&coedge.edge_id).ok_or_else(|| {
270 KernelRefusal::internal(
271 KernelStage::Refine,
272 "edge_split",
273 "apply_edge_splits: missing source edge",
274 )
275 })?;
276 let span = original.t1 - original.t0;
277 let indices: Box<dyn Iterator<Item = usize>> = if coedge.forward {
278 Box::new(0..edge_pieces.len())
279 } else {
280 Box::new((0..edge_pieces.len()).rev())
281 };
282 for index in indices {
283 let piece = &edge_pieces[index];
284 let start_fraction = if coedge.forward {
285 (piece.t0 - original.t0) / span
286 } else {
287 (original.t1 - piece.t1) / span
288 };
289 let end_fraction = if coedge.forward {
290 (piece.t1 - original.t0) / span
291 } else {
292 (original.t1 - piece.t0) / span
293 };
294 rebuilt.push(CoedgeRecord {
295 id: next_coedge_id,
296 edge_id: piece.id,
297 forward: coedge.forward,
298 pcurve: subcurve_by_fraction(&coedge.pcurve, start_fraction, end_fraction)?,
299 });
300 next_coedge_id += 1;
301 }
302 }
303 loop_record.coedges = rebuilt;
304 }
305 }
306 let map = pieces
307 .iter()
308 .map(|(source_id, edge_pieces)| {
309 (
310 *source_id,
311 edge_pieces.iter().map(|piece| piece.id).collect(),
312 )
313 })
314 .collect();
315 Ok((result, map))
316}
317
318