1mod clip;
18pub mod debug;
19mod grid;
20mod mesh;
21pub mod stl;
22
23pub use grid::triangulate_polygon;
24
25pub type Triangle2 = (Vector2, Vector2, Vector2);
27
28use std::collections::BTreeMap;
29
30use geop_core_geometry::nurb_surface::NurbSurface3D;
31use geop_core_math::{
32 geop_error::{GeopError, GeopResult},
33 plain::distance,
34 primitives::TriangleFace,
35 scalars::Scalar,
36 solvers::refine::{Refine, Refined, refine},
37 vector::{Vector2, Vector3},
38};
39use geop_core_topology::{
40 CoedgeGeometry, CoedgeId, Curve2, Edge, EdgeId, Face, FaceId, Model, Sense, VertexId,
41 boundary::BoundaryType,
42};
43use grid::dist_point_to_segment;
44
45pub fn face_triangles_uv(
51 model: &Model,
52 face: &Face,
53 n: usize,
54) -> GeopResult<(Vec<Triangle2>, Vec<Vec<Vector2>>)> {
55 let mut edges = BTreeMap::new();
56 for anchor in loops(face) {
57 for c in model.iterate_loop_coedges(anchor) {
58 if let CoedgeGeometry::Edge(e) = model.get_coedge(c)?.geometry {
59 edges.insert(e, sample_edge(model, e, n)?);
60 }
61 }
62 }
63 let mut samples = grid::Samples::new(face);
64 let (triangles, boundaries) = face_mesh(model, face, n, &edges, &mut samples)?;
65 let at = |p: &clip::Point| Vector2::from_array(p.map(Scalar::from_f64));
66 Ok((
67 triangles
68 .into_iter()
69 .map(|[a, b, c]| (samples.uv(a), samples.uv(b), samples.uv(c)))
70 .collect(),
71 boundaries
72 .iter()
73 .map(|b| b.uv.iter().map(at).collect())
74 .collect(),
75 ))
76}
77
78fn loops(face: &Face) -> Vec<geop_core_topology::CoedgeId> {
80 std::iter::once(&face.outer)
81 .chain(&face.holes)
82 .filter_map(|b| match b {
83 BoundaryType::Loop(anchor) => Some(*anchor),
84 BoundaryType::Vertex(_) => None,
85 })
86 .collect()
87}
88
89fn face_mesh(
92 model: &Model,
93 face: &Face,
94 n: usize,
95 edges: &BTreeMap<EdgeId, Vec<Vector3>>,
96 samples: &mut grid::Samples,
97) -> GeopResult<(Vec<[clip::Point; 3]>, Vec<mesh::Boundary>)> {
98 let BoundaryType::Loop(_) = face.outer else {
99 return Ok((Vec::new(), Vec::new()));
100 };
101 let boundaries = loops(face)
102 .into_iter()
103 .map(|anchor| mesh::boundary(model, face, anchor, n, edges))
104 .collect::<GeopResult<Vec<_>>>()?;
105 let holes: Vec<Vec<clip::Point>> = boundaries[1..].iter().map(|b| b.uv.clone()).collect();
106 let triangles = grid::triangulate_face(&boundaries[0].uv, &holes, n, samples)?;
107 Ok((triangles, boundaries))
108}
109
110fn sample_edge(model: &Model, e: EdgeId, n: usize) -> GeopResult<Vec<Vector3>> {
113 let edge = model.get_edge(e)?;
114 let mut points = sample_curve(&edge.curve, n)?;
115 let last = points.len() - 1;
116 points[0] = model.get_vertex(edge.start_vertex)?.point;
117 points[last] = model.get_vertex(edge.end_vertex)?.point;
118 Ok(points)
119}
120
121pub struct RasterizedModel {
131 pub vertices: BTreeMap<VertexId, Vector3>,
132 pub edges: BTreeMap<EdgeId, Vec<Vector3>>,
134 pub faces: BTreeMap<FaceId, Vec<TriangleFace>>,
138}
139
140const MIN_EDGE_SEGMENTS: usize = 1;
142const MAX_EDGE_DOUBLINGS: usize = 10;
144
145fn sample_curve(
151 curve: &geop_core_geometry::nurb_curve::NurbCurve3D,
152 quality: usize,
153) -> GeopResult<Vec<Vector3>> {
154 let (t0, t1) = curve.domain();
155 let at = |frac: (usize, usize)| -> GeopResult<Vector3> {
156 let f = Scalar::from_ratio(frac.0 as i64, frac.1 as i64)?;
157 curve.evaluate(t0.add(t1.sub(t0).mul(f)))
158 };
159 let points_at = |segments: usize| -> GeopResult<Vec<Vector3>> {
160 (0..=segments).map(|i| at((i, segments))).collect()
161 };
162
163 let first = points_at(MIN_EDGE_SEGMENTS)?;
164 let size = first
165 .iter()
166 .flat_map(|p| {
167 first
168 .iter()
169 .map(move |q| distance(p.to_f64_array(), q.to_f64_array()))
170 })
171 .fold(0.0f64, f64::max)
172 .max(1e-9);
173 let tolerance = size / (4.0 * (quality * quality) as f64);
174
175 let refined = refine(
178 (MIN_EDGE_SEGMENTS, first),
179 MAX_EDGE_DOUBLINGS,
180 |(segments, points)| -> GeopResult<_> {
181 let mut worst = 0.0f64;
182 for i in 0..segments {
183 let mid = at((2 * i + 1, 2 * segments))?;
184 worst = worst.max(dist_point_to_segment(
185 mid.to_f64_array(),
186 points[i].to_f64_array(),
187 points[i + 1].to_f64_array(),
188 ));
189 }
190 if worst <= tolerance {
191 return Ok(Refine::Done(points));
192 }
193 Ok(Refine::Finer((2 * segments, points_at(2 * segments)?)))
194 },
195 )?;
196 Ok(match refined {
197 Refined::Done(points) | Refined::Exhausted((_, points)) => points,
198 })
199}
200
201pub fn rasterize(model: &Model, n: usize) -> GeopResult<RasterizedModel> {
206 Rasterizer::default().rasterize(model, n)
207}
208
209const KEPT_MODELS: u64 = 4;
213
214#[derive(PartialEq)]
220struct FaceSource {
221 n: usize,
222 surface: NurbSurface3D,
223 loops: Vec<Vec<CoedgeSource>>,
224}
225
226type CoedgeSource = (CoedgeGeometry, Sense, Curve2, Option<(Edge, [Vector3; 2])>);
229
230impl FaceSource {
231 fn of(model: &Model, face: &Face, n: usize) -> GeopResult<Self> {
232 let coedge = |c: CoedgeId| -> GeopResult<CoedgeSource> {
233 let coedge = model.get_coedge(c)?;
234 let edge = match coedge.geometry {
235 CoedgeGeometry::Edge(e) => {
236 let edge = model.get_edge(e)?;
237 let start = model.get_vertex(edge.start_vertex)?.point;
238 let end = model.get_vertex(edge.end_vertex)?.point;
239 Some((edge.clone(), [start, end]))
240 }
241 CoedgeGeometry::Vertex(_) => None,
242 };
243 Ok((coedge.geometry, coedge.sense, coedge.pcurve.clone(), edge))
244 };
245 let loops = (loops(face).into_iter())
246 .map(|anchor| model.iterate_loop_coedges(anchor).map(coedge).collect())
247 .collect::<GeopResult<_>>()?;
248 Ok(Self {
249 n,
250 surface: face.surface.clone(),
251 loops,
252 })
253 }
254}
255
256struct Meshed {
260 triangles: Vec<[clip::Point; 3]>,
261 boundaries: Vec<mesh::Boundary>,
262 samples: grid::Samples,
263}
264
265#[derive(Default)]
271pub struct Rasterizer {
272 models: u64,
274 faces: BTreeMap<FaceId, Vec<(FaceSource, Meshed, u64)>>,
277}
278
279thread_local! {
280 static FACES_MESHED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
281}
282
283pub fn faces_meshed() -> usize {
287 FACES_MESHED.get()
288}
289
290impl Rasterizer {
291 pub fn rasterize(&mut self, model: &Model, n: usize) -> GeopResult<RasterizedModel> {
294 if n < 2 {
295 return Err(GeopError::new("rasterize: n must be >= 2"));
296 }
297 self.models += 1;
298 let now = self.models;
299
300 let vertices = model
301 .vertices
302 .iter()
303 .map(|(&id, vertex)| (id, vertex.point))
304 .collect();
305
306 let mut edges = BTreeMap::new();
307 for &id in model.edges.keys() {
308 edges.insert(id, sample_edge(model, id, n)?);
309 }
310 let last: BTreeMap<EdgeId, usize> = edges.iter().map(|(&e, p)| (e, p.len() - 1)).collect();
311
312 let mut ids = Vec::new();
315 let mut sources = Vec::new();
316 let mut all_samples = Vec::new();
317 let mut all_boundaries = Vec::new();
318 let mut triangles = Vec::new();
319 for (&id, face) in model.faces.iter() {
320 let source = FaceSource::of(model, face, n)?;
321 let kept = self.faces.entry(id).or_default();
322 let meshed = match kept.iter().position(|(meshed, ..)| *meshed == source) {
323 Some(k) => kept.swap_remove(k).1,
324 None => {
325 FACES_MESHED.set(FACES_MESHED.get() + 1);
326 let mut samples = grid::Samples::new(face);
327 let (triangles, boundaries) = face_mesh(model, face, n, &edges, &mut samples)?;
328 Meshed {
329 triangles,
330 boundaries,
331 samples,
332 }
333 }
334 };
335 ids.push(id);
336 sources.push(source);
337 all_samples.push(meshed.samples);
338 all_boundaries.push(meshed.boundaries);
339 triangles.push(meshed.triangles);
340 }
341 let trims: Vec<mesh::Trim> = all_boundaries
342 .iter()
343 .map(|boundaries| mesh::Trim {
344 model,
345 boundaries,
346 last: &last,
347 })
348 .collect();
349 let mut meshes: Vec<mesh::Mesh> = triangles
350 .iter()
351 .zip(&trims)
352 .map(|(tris, trim)| mesh::Mesh::new(tris, trim))
353 .collect::<GeopResult<_>>()?;
354 mesh::zip(&mut meshes, &trims);
355
356 let mut faces = BTreeMap::new();
357 for ((&id, m), samples) in ids.iter().zip(&meshes).zip(&mut all_samples) {
358 let points = (0..m.uv.len())
359 .map(|i| match mesh::placed(model, m.on[i], &edges)? {
360 Some(p) => Ok(p),
361 None => samples.point(m.uv[i]),
362 })
363 .collect::<GeopResult<Vec<_>>>()?;
364 let mut tris = Vec::new();
365 for &[a, b, c] in &m.triangles {
366 let Ok(t) = TriangleFace::try_new(points[a], points[b], points[c]) else {
370 continue;
371 };
372 let mut normal = |i: usize| samples.normal(m.uv[i]);
376 tris.push(match (normal(a), normal(b), normal(c)) {
377 (Some(na), Some(nb), Some(nc)) => t.with_vertex_normals([na, nb, nc]),
378 _ => t,
379 });
380 }
381 faces.insert(id, tris);
382 }
383
384 let meshed = (triangles.into_iter().zip(all_boundaries).zip(all_samples)).map(
387 |((triangles, boundaries), samples)| Meshed {
388 triangles,
389 boundaries,
390 samples,
391 },
392 );
393 for ((id, source), meshed) in ids.into_iter().zip(sources).zip(meshed) {
394 self.faces
395 .entry(id)
396 .or_default()
397 .push((source, meshed, now));
398 }
399 self.faces.retain(|_, kept| {
400 kept.retain(|(.., used)| used + KEPT_MODELS > now);
401 !kept.is_empty()
402 });
403
404 Ok(RasterizedModel {
405 vertices,
406 edges,
407 faces,
408 })
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415 use crate::debug::Color10;
416 use geop_core_topology::test_fixtures::test_cube_model;
417
418 fn rasterize_and_save(model: &Model, name: &str) {
421 let scene = rasterize(model, 8).unwrap().scene(|_| Color10::Blue);
422 assert!(!scene.points.is_empty());
423 assert!(!scene.lines.is_empty());
424 assert!(!scene.triangles.is_empty());
425 std::fs::create_dir_all("outputs").unwrap();
426 scene.save_to_file(&format!("outputs/{name}.html")).unwrap();
427 }
428
429 #[test]
430 fn rasterize_cube() {
431 rasterize_and_save(&test_cube_model(), "cube");
432 }
433}