Skip to main content

pfx_geom/
mark.rs

1use std::collections::BTreeMap;
2
3use i_overlay::core::fill_rule::FillRule as OverlayFill;
4use i_overlay::core::overlay_rule::OverlayRule;
5use i_overlay::float::simplify::SimplifyShape;
6use i_overlay::float::single::SingleFloatOverlay;
7use i_overlay::mesh::float::outline::offset::OutlineOffset;
8use i_overlay::mesh::float::style::{LineJoin, OutlineStyle};
9use lyon_tessellation::math::point;
10use lyon_tessellation::path::Path as LyonPath;
11use lyon_tessellation::{BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers};
12
13use crate::field::Surface;
14use crate::icon::join;
15use crate::math::normalize;
16use crate::mesh::Mesh;
17use crate::svg::{Drawing, Element, FillRule, Paint};
18
19pub type Region = Vec<Vec<Vec<[f64; 2]>>>;
20
21pub const DEPTH: f32 = 0.12;
22pub const BEVEL: f32 = 0.05;
23pub const SEGMENTS: u32 = 8;
24pub const LAYER: f32 = 0.25;
25pub const SMOOTH_ANGLE: f32 = 40.0;
26pub const TOLERANCE: f32 = 2e-4;
27pub const BEVEL_SHARE: f32 = 0.2;
28pub const CORE: f32 = 0.15;
29pub const THIN_STROKE: f32 = 0.1;
30pub const VOXEL_SPAN: f32 = 1400.0;
31pub const RELAX: u32 = 2;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub enum Profile {
35    Round,
36    Chamfer,
37}
38
39#[derive(Clone, Debug, PartialEq)]
40pub struct Each {
41    pub all: f32,
42    pub by_element: BTreeMap<usize, f32>,
43}
44
45impl Each {
46    pub fn new(all: f32) -> Self {
47        Self {
48            all,
49            by_element: BTreeMap::new(),
50        }
51    }
52
53    pub fn at(&self, element: usize) -> f32 {
54        self.by_element.get(&element).copied().unwrap_or(self.all)
55    }
56}
57
58#[derive(Clone, Debug, PartialEq)]
59pub struct Form {
60    pub depth: f32,
61    pub bevel: f32,
62    pub segments: u32,
63    pub profile: Profile,
64    pub layer: f32,
65    pub inset: Each,
66    pub element_depth: Each,
67    pub element_z: Each,
68    pub element_material: BTreeMap<usize, String>,
69    pub merge: bool,
70    pub smooth_angle: f32,
71    pub tolerance: f32,
72    pub relax: u32,
73}
74
75impl Default for Form {
76    fn default() -> Self {
77        Self {
78            depth: DEPTH,
79            bevel: BEVEL,
80            segments: SEGMENTS,
81            profile: Profile::Round,
82            layer: LAYER,
83            inset: Each::new(0.0),
84            element_depth: Each::new(1.0),
85            element_z: Each::new(0.0),
86            element_material: BTreeMap::new(),
87            merge: true,
88            smooth_angle: SMOOTH_ANGLE,
89            tolerance: TOLERANCE,
90            relax: RELAX,
91        }
92    }
93}
94
95#[derive(Clone, Debug)]
96pub struct Solid {
97    pub paint: Paint,
98    pub material: Option<String>,
99    pub order: usize,
100    pub elements: Vec<usize>,
101    pub span: Option<[f32; 2]>,
102    pub mesh: Mesh,
103}
104
105#[derive(Clone, Debug)]
106pub struct Mark {
107    pub view: [f32; 4],
108    pub scale: f32,
109    pub ground: Option<Paint>,
110    pub solids: Vec<Solid>,
111}
112
113enum Geometry {
114    Region { region: Region, bevel: f64 },
115    Field(Mesh),
116}
117
118struct Piece {
119    paint: Paint,
120    material: Option<String>,
121    order: usize,
122    element: usize,
123    key: (usize, Option<String>, Option<usize>),
124    mul: f32,
125    base: f32,
126    geometry: Geometry,
127}
128
129struct World {
130    center: [f64; 2],
131    size: f64,
132    view: [f64; 4],
133}
134
135impl World {
136    fn point(&self, p: [f64; 2]) -> [f64; 2] {
137        [
138            (p[0] - self.center[0]) / self.size,
139            (self.center[1] - p[1]) / self.size,
140        ]
141    }
142
143    fn uv(&self, x: f32, y: f32) -> [f32; 2] {
144        let w = self.view[2] / self.size;
145        let h = self.view[3] / self.size;
146        [
147            ((x as f64 + w * 0.5) / w) as f32,
148            ((h * 0.5 - y as f64) / h) as f32,
149        ]
150    }
151}
152
153impl Mark {
154    pub fn build(svg: &str, form: &Form) -> Result<Mark, String> {
155        check(form)?;
156        let drawing = Drawing::parse(svg, form.tolerance as f64)?;
157        let view = drawing.view;
158        let world = World {
159            center: [view[0] + view[2] * 0.5, view[1] + view[3] * 0.5],
160            size: view[2].max(view[3]),
161            view,
162        };
163        let mut ground = None;
164        let mut painted: Vec<Paint> = Vec::new();
165        let mut pieces: Vec<Piece> = Vec::new();
166        let mut n = 0usize;
167        for element in &drawing.elements {
168            if element.ground {
169                ground = element.fill.clone();
170                continue;
171            }
172            let made = element_pieces(element, n, form, &world)?;
173            for (paint, geometry) in made {
174                let order = match painted.iter().position(|p| *p == paint) {
175                    Some(order) => order,
176                    None => {
177                        painted.push(paint.clone());
178                        painted.len() - 1
179                    }
180                };
181                let material = form.element_material.get(&n).cloned();
182                let key = (
183                    order,
184                    material.clone(),
185                    if form.merge { None } else { Some(n) },
186                );
187                let base =
188                    order as f32 * form.layer * form.depth + form.element_z.at(n) * form.depth;
189                pieces.push(Piece {
190                    paint,
191                    material,
192                    order,
193                    element: n,
194                    key,
195                    mul: form.element_depth.at(n).max(1e-3),
196                    base,
197                    geometry,
198                });
199            }
200            n += 1;
201        }
202        let mut keys: Vec<(usize, Option<String>, Option<usize>)> = Vec::new();
203        for piece in &pieces {
204            if !keys.contains(&piece.key) {
205                keys.push(piece.key.clone());
206            }
207        }
208        let mut solids = Vec::new();
209        for key in keys {
210            let members = pieces.iter().filter(|p| p.key == key).collect::<Vec<_>>();
211            let first = members[0];
212            let mut meshes = Vec::new();
213            let mut merged: Vec<(f32, f32, Vec<&Region>, f64)> = Vec::new();
214            for piece in &members {
215                match &piece.geometry {
216                    Geometry::Region { region, bevel } => {
217                        match merged.iter_mut().find(|(mul, base, _, own)| {
218                            mul.to_bits() == piece.mul.to_bits()
219                                && base.to_bits() == piece.base.to_bits()
220                                && (*own - *bevel).abs() <= 1e-9
221                        }) {
222                            Some((_, _, regions, _)) => regions.push(region),
223                            None => merged.push((piece.mul, piece.base, vec![region], *bevel)),
224                        }
225                    }
226                    Geometry::Field(mesh) => meshes.push(lift(mesh, piece.mul, piece.base)),
227                }
228            }
229            for (mul, base, regions, bevel) in merged {
230                let region = if regions.len() == 1 {
231                    regions[0].clone()
232                } else {
233                    let contours = regions
234                        .iter()
235                        .flat_map(|r| r.iter().flatten().cloned())
236                        .collect::<Vec<_>>();
237                    contours.simplify_shape_as::<i64>(OverlayFill::NonZero)
238                };
239                meshes.push(extrude(&region, form, bevel, mul, base, &world));
240            }
241            let mesh = if meshes.len() == 1 {
242                meshes.pop().unwrap()
243            } else {
244                join(&meshes)
245            };
246            if mesh.indices.is_empty() {
247                continue;
248            }
249            let span = match &first.paint {
250                Paint::Gradient(g) => Some([
251                    world.point([g.x1 as f64, 0.0])[0] as f32,
252                    world.point([g.x2 as f64, 0.0])[0] as f32,
253                ]),
254                Paint::Color(_) => None,
255            };
256            let mut elements = members.iter().map(|p| p.element).collect::<Vec<_>>();
257            elements.dedup();
258            solids.push(Solid {
259                paint: first.paint.clone(),
260                material: first.material.clone(),
261                order: first.order,
262                elements,
263                span,
264                mesh,
265            });
266        }
267        Ok(Mark {
268            view: view.map(|v| v as f32),
269            scale: (1.0 / world.size) as f32,
270            ground,
271            solids,
272        })
273    }
274}
275
276fn check(form: &Form) -> Result<(), String> {
277    let finite = |name: &str, v: f32| {
278        if v.is_finite() && v >= 0.0 {
279            Ok(())
280        } else {
281            Err(format!("form: {name} must be 0 or more, got {v}"))
282        }
283    };
284    finite("depth", form.depth)?;
285    finite("bevel", form.bevel)?;
286    finite("layer", form.layer)?;
287    finite("smooth_angle", form.smooth_angle)?;
288    if !(form.tolerance > 0.0 && form.tolerance.is_finite()) {
289        return Err(format!(
290            "form: tolerance must be above 0, got {}",
291            form.tolerance
292        ));
293    }
294    if form.depth <= 0.0 && form.bevel <= 0.0 {
295        return Err("form: depth and bevel are both 0, so there is no solid".to_string());
296    }
297    Ok(())
298}
299
300fn element_pieces(
301    element: &Element,
302    n: usize,
303    form: &Form,
304    world: &World,
305) -> Result<Vec<(Paint, Geometry)>, String> {
306    let inset = form.inset.at(n) as f64 / world.size;
307    let width = element.stroke_width / world.size;
308    let holes = element
309        .holes
310        .iter()
311        .map(|h| h.iter().map(|p| world.point(*p)).collect::<Vec<_>>())
312        .collect::<Vec<_>>();
313    let rule = match element.fill_rule {
314        FillRule::NonZero => OverlayFill::NonZero,
315        FillRule::EvenOdd => OverlayFill::EvenOdd,
316    };
317    let closed = element
318        .paths
319        .iter()
320        .filter(|p| p.points.len() > 2)
321        .map(|p| p.points.iter().map(|q| world.point(*q)).collect::<Vec<_>>())
322        .collect::<Vec<_>>();
323    let mut out = Vec::new();
324    if element.joined {
325        let fill = element.fill.clone().unwrap();
326        let region = cut(closed.simplify_shape_as::<i64>(rule), &holes);
327        let grow = (width * 0.5 - inset).max(width * 0.5 * 0.2);
328        if !region.is_empty() {
329            out.push((fill, Geometry::Field(pillow(&region, grow, &holes, form))));
330        }
331        return Ok(out);
332    }
333    if let Some(fill) = &element.fill {
334        let size = extent(&closed);
335        let mut region = closed.simplify_shape_as::<i64>(rule);
336        if inset > 0.0 && !region.is_empty() {
337            let style = OutlineStyle::new(-inset).line_join(LineJoin::Miter(0.05));
338            region = region.outline(&style);
339        }
340        let region = cut(region, &holes);
341        if !region.is_empty() {
342            let bevel = (form.bevel as f64).min(size * BEVEL_SHARE as f64);
343            out.push((fill.clone(), Geometry::Region { region, bevel }));
344        }
345    }
346    if let Some(stroke) = &element.stroke
347        && width > 0.0
348    {
349        let r = (width * 0.5 - inset).max(width * THIN_STROKE as f64);
350        let segments = element
351            .paths
352            .iter()
353            .flat_map(|path| {
354                let points = path
355                    .points
356                    .iter()
357                    .map(|q| world.point(*q))
358                    .collect::<Vec<_>>();
359                let mut pairs = points.windows(2).map(|w| [w[0], w[1]]).collect::<Vec<_>>();
360                if path.closed && points.len() > 2 {
361                    pairs.push([*points.last().unwrap(), points[0]]);
362                }
363                pairs
364            })
365            .collect::<Vec<_>>();
366        if !segments.is_empty() {
367            out.push((
368                stroke.clone(),
369                Geometry::Field(tube(&segments, r, &holes, form)),
370            ));
371        }
372    }
373    Ok(out)
374}
375
376fn cut(region: Region, holes: &[Vec<[f64; 2]>]) -> Region {
377    if holes.is_empty() || region.is_empty() {
378        return region;
379    }
380    region.overlay_as::<i64>(
381        &holes.to_vec(),
382        OverlayRule::Difference,
383        OverlayFill::NonZero,
384    )
385}
386
387fn extent(contours: &[Vec<[f64; 2]>]) -> f64 {
388    let mut lo = [f64::INFINITY; 2];
389    let mut hi = [f64::NEG_INFINITY; 2];
390    for p in contours.iter().flatten() {
391        for i in 0..2 {
392            lo[i] = lo[i].min(p[i]);
393            hi[i] = hi[i].max(p[i]);
394        }
395    }
396    if !lo[0].is_finite() {
397        return 0.0;
398    }
399    (hi[0] - lo[0]).max(hi[1] - lo[1])
400}
401
402fn lift(mesh: &Mesh, mul: f32, base: f32) -> Mesh {
403    if mul == 1.0 && base == 0.0 {
404        return mesh.clone();
405    }
406    let positions = mesh
407        .positions
408        .iter()
409        .map(|p| [p[0], p[1], p[2] * mul + base])
410        .collect();
411    let normals = mesh
412        .normals
413        .iter()
414        .map(|n| normalize([n[0], n[1], n[2] / mul]))
415        .collect();
416    Mesh::new(
417        positions,
418        normals,
419        Vec::new(),
420        mesh.uvs.clone(),
421        mesh.indices.clone(),
422    )
423}
424
425#[derive(Clone, Copy, Debug)]
426struct Step {
427    o: f32,
428    z: f32,
429    below: [f32; 2],
430    above: [f32; 2],
431    smooth: Option<[f32; 2]>,
432}
433
434fn profile(depth: f32, bevel: f32, form: &Form) -> Vec<Step> {
435    let mut top: Vec<(f32, f32, Option<[f32; 2]>)> = Vec::new();
436    if bevel > 1e-9 {
437        let steps = match form.profile {
438            Profile::Round => form.segments + 1,
439            Profile::Chamfer => 1,
440        };
441        for k in 0..=steps {
442            let g = std::f32::consts::FRAC_PI_2 * k as f32 / steps as f32;
443            let (o, z) = if k == 0 {
444                (bevel, depth)
445            } else if k == steps {
446                (0.0, depth + bevel)
447            } else {
448                (bevel * g.cos(), depth + bevel * g.sin())
449            };
450            let arc = (form.profile == Profile::Round).then_some(if k == steps {
451                [0.0, 1.0]
452            } else {
453                [g.cos(), g.sin()]
454            });
455            top.push((o, z, arc));
456        }
457    } else {
458        top.push((0.0, depth, None));
459    }
460    let mut points: Vec<(f32, f32, Option<[f32; 2]>)> = top
461        .iter()
462        .rev()
463        .map(|(o, z, arc)| (*o, -*z, arc.map(|a| [a[0], -a[1]])))
464        .collect();
465    let skip = usize::from(depth <= 1e-9);
466    points.extend(top.iter().skip(skip).copied());
467    let facets = points
468        .windows(2)
469        .map(|w| {
470            let (to, tz) = (w[1].0 - w[0].0, w[1].1 - w[0].1);
471            let len = (to * to + tz * tz).sqrt().max(1e-12);
472            [tz / len, -to / len]
473        })
474        .collect::<Vec<_>>();
475    let limit = form.smooth_angle.to_radians() + 1e-4;
476    points
477        .iter()
478        .enumerate()
479        .map(|(j, &(o, z, arc))| {
480            let below = if j == 0 { [0.0, -1.0] } else { facets[j - 1] };
481            let above = if j == points.len() - 1 {
482                [0.0, 1.0]
483            } else {
484                facets[j]
485            };
486            let angle = (below[0] * above[0] + below[1] * above[1])
487                .clamp(-1.0, 1.0)
488                .acos();
489            let smooth = (angle <= limit).then(|| {
490                arc.unwrap_or_else(|| {
491                    let s = [below[0] + above[0], below[1] + above[1]];
492                    let len = (s[0] * s[0] + s[1] * s[1]).sqrt().max(1e-12);
493                    [s[0] / len, s[1] / len]
494                })
495            });
496            Step {
497                o,
498                z,
499                below,
500                above,
501                smooth,
502            }
503        })
504        .collect()
505}
506
507struct Column {
508    index: usize,
509    point: [f64; 2],
510    miter: [f64; 2],
511    before: [f64; 2],
512    after: [f64; 2],
513    smooth: bool,
514}
515
516fn columns(contour: &[[f64; 2]], limit: f64) -> Vec<Column> {
517    let mut points = contour.to_vec();
518    points.dedup_by(|a, b| (a[0] - b[0]).abs() <= 1e-12 && (a[1] - b[1]).abs() <= 1e-12);
519    while points.len() > 1 {
520        let (a, b) = (points[0], *points.last().unwrap());
521        if (a[0] - b[0]).abs() <= 1e-12 && (a[1] - b[1]).abs() <= 1e-12 {
522            points.pop();
523        } else {
524            break;
525        }
526    }
527    let count = points.len();
528    if count < 3 {
529        return Vec::new();
530    }
531    let normal = |a: [f64; 2], b: [f64; 2]| {
532        let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
533        let len = (dx * dx + dy * dy).sqrt().max(1e-300);
534        [dy / len, -dx / len]
535    };
536    (0..count)
537        .map(|i| {
538            let prev = points[(i + count - 1) % count];
539            let here = points[i];
540            let next = points[(i + 1) % count];
541            let before = normal(prev, here);
542            let after = normal(here, next);
543            let cos = before[0] * after[0] + before[1] * after[1];
544            let sum = [before[0] + after[0], before[1] + after[1]];
545            let sum_len = (sum[0] * sum[0] + sum[1] * sum[1]).sqrt();
546            let miter = if 1.0 + cos > 0.125 {
547                [sum[0] / (1.0 + cos), sum[1] / (1.0 + cos)]
548            } else if sum_len > 1e-9 {
549                [sum[0] / sum_len * 4.0, sum[1] / sum_len * 4.0]
550            } else {
551                [before[0] * 4.0, before[1] * 4.0]
552            };
553            Column {
554                index: i,
555                point: here,
556                miter,
557                before,
558                after,
559                smooth: cos.clamp(-1.0, 1.0).acos() <= limit,
560            }
561        })
562        .collect()
563}
564
565struct Builder<'a> {
566    world: &'a World,
567    steps: &'a [Step],
568    mul: f32,
569    base: f32,
570    positions: Vec<[f32; 3]>,
571    normals: Vec<[f32; 3]>,
572    uvs: Vec<[f32; 2]>,
573    indices: Vec<u32>,
574    made: BTreeMap<(usize, usize, u8, usize, u8), u32>,
575}
576
577impl Builder<'_> {
578    fn vertex(&mut self, contour: usize, column: &Column, side: u8, j: usize, half: u8) -> u32 {
579        let step = self.steps[j];
580        let (normal2, half) = match step.smooth {
581            Some(n) => (n, 0),
582            None => (if half == 0 { step.below } else { step.above }, half),
583        };
584        let flat = normal2[0].abs() <= 1e-6;
585        let side = if column.smooth || flat { 0 } else { side };
586        let key = (contour, column.index, side, j, half);
587        if let Some(found) = self.made.get(&key) {
588            return *found;
589        }
590        let horizontal = if column.smooth {
591            let s = [
592                column.before[0] + column.after[0],
593                column.before[1] + column.after[1],
594            ];
595            let len = (s[0] * s[0] + s[1] * s[1]).sqrt().max(1e-300);
596            [s[0] / len, s[1] / len]
597        } else if side == 0 {
598            column.before
599        } else {
600            column.after
601        };
602        let x = (column.point[0] + column.miter[0] * step.o as f64) as f32;
603        let y = (column.point[1] + column.miter[1] * step.o as f64) as f32;
604        let z = step.z * self.mul + self.base;
605        let normal = normalize([
606            horizontal[0] as f32 * normal2[0],
607            horizontal[1] as f32 * normal2[0],
608            normal2[1] / self.mul,
609        ]);
610        let index = self.positions.len() as u32;
611        self.positions.push([x, y, z]);
612        self.normals.push(normal);
613        self.uvs.push(self.world.uv(x, y));
614        self.made.insert(key, index);
615        index
616    }
617
618    fn tri(&mut self, a: u32, b: u32, c: u32) {
619        if a == b || b == c || a == c {
620            return;
621        }
622        self.indices.extend([a, b, c]);
623    }
624}
625
626fn extrude(region: &Region, form: &Form, bevel: f64, mul: f32, base: f32, world: &World) -> Mesh {
627    let steps = profile(form.depth, bevel as f32, form);
628    let limit = (form.smooth_angle as f64).to_radians() + 1e-4;
629    let mut builder = Builder {
630        world,
631        steps: &steps,
632        mul,
633        base,
634        positions: Vec::new(),
635        normals: Vec::new(),
636        uvs: Vec::new(),
637        indices: Vec::new(),
638        made: BTreeMap::new(),
639    };
640    let last = steps.len() - 1;
641    let mut contour_id = 0usize;
642    for shape in region {
643        let mut caps: BTreeMap<(u32, u32), (usize, usize)> = BTreeMap::new();
644        let mut rings: Vec<(usize, Vec<Column>)> = Vec::new();
645        for contour in shape {
646            let cols = columns(contour, limit);
647            if cols.is_empty() {
648                continue;
649            }
650            let id = contour_id;
651            contour_id += 1;
652            let count = cols.len();
653            for i in 0..count {
654                let next = (i + 1) % count;
655                for j in 0..last {
656                    let a = builder.vertex(id, &cols[i], 1, j, 1);
657                    let b = builder.vertex(id, &cols[next], 0, j, 1);
658                    let c = builder.vertex(id, &cols[next], 0, j + 1, 0);
659                    let d = builder.vertex(id, &cols[i], 1, j + 1, 0);
660                    builder.tri(a, b, c);
661                    builder.tri(a, c, d);
662                }
663            }
664            for (i, col) in cols.iter().enumerate() {
665                let key = (
666                    (col.point[0] as f32).to_bits(),
667                    (col.point[1] as f32).to_bits(),
668                );
669                caps.entry(key).or_insert((id, i));
670            }
671            rings.push((id, cols));
672        }
673        if rings.is_empty() {
674            continue;
675        }
676        let mut path = LyonPath::builder();
677        for (_, cols) in &rings {
678            path.begin(point(cols[0].point[0] as f32, cols[0].point[1] as f32));
679            for col in &cols[1..] {
680                path.line_to(point(col.point[0] as f32, col.point[1] as f32));
681            }
682            path.end(true);
683        }
684        let path = path.build();
685        let mut buffers: VertexBuffers<[f32; 2], u32> = VertexBuffers::new();
686        let options = FillOptions::default().with_fill_rule(lyon_tessellation::FillRule::EvenOdd);
687        let result = FillTessellator::new().tessellate_path(
688            &path,
689            &options,
690            &mut BuffersBuilder::new(&mut buffers, |v: FillVertex| v.position().to_array()),
691        );
692        if result.is_err() {
693            continue;
694        }
695        let lookup = |p: [f32; 2]| caps.get(&(p[0].to_bits(), p[1].to_bits())).copied();
696        for (j, half, up) in [(last, 1u8, true), (0usize, 0u8, false)] {
697            let mut map: Vec<u32> = Vec::with_capacity(buffers.vertices.len());
698            for v in &buffers.vertices {
699                let index = match lookup(*v) {
700                    Some((id, i)) => {
701                        let cols = &rings.iter().find(|(c, _)| *c == id).unwrap().1;
702                        builder.vertex(id, &cols[i], 0, j, half)
703                    }
704                    None => {
705                        let z = steps[j].z * mul + base;
706                        let index = builder.positions.len() as u32;
707                        builder.positions.push([v[0], v[1], z]);
708                        builder
709                            .normals
710                            .push([0.0, 0.0, if up { 1.0 } else { -1.0 }]);
711                        builder.uvs.push(world.uv(v[0], v[1]));
712                        index
713                    }
714                };
715                map.push(index);
716            }
717            for tri in buffers.indices.chunks_exact(3) {
718                let [a, b, c] = [tri[0], tri[1], tri[2]].map(|i| buffers.vertices[i as usize]);
719                let area = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
720                let (i0, i1, i2) = (
721                    map[tri[0] as usize],
722                    map[tri[1] as usize],
723                    map[tri[2] as usize],
724                );
725                if (area > 0.0) == up {
726                    builder.tri(i0, i1, i2);
727                } else {
728                    builder.tri(i0, i2, i1);
729                }
730            }
731        }
732    }
733    Mesh::new(
734        builder.positions,
735        builder.normals,
736        Vec::new(),
737        builder.uvs,
738        builder.indices,
739    )
740}
741
742struct Raster {
743    origin: [f64; 2],
744    cell: f64,
745    width: usize,
746    height: usize,
747    values: Vec<f32>,
748}
749
750const TILE: usize = 16;
751
752impl Raster {
753    fn new(lo: [f64; 2], hi: [f64; 2], cell: f64) -> Raster {
754        let width = (((hi[0] - lo[0]) / cell).ceil() as usize + 2).max(2);
755        let height = (((hi[1] - lo[1]) / cell).ceil() as usize + 2).max(2);
756        Raster {
757            origin: lo,
758            cell,
759            width,
760            height,
761            values: vec![0.0; width * height],
762        }
763    }
764
765    fn at(&self, x: usize, y: usize) -> [f64; 2] {
766        [
767            self.origin[0] + x as f64 * self.cell,
768            self.origin[1] + y as f64 * self.cell,
769        ]
770    }
771
772    fn distances(&mut self, segments: &[[[f64; 2]; 2]], band: f64) {
773        let reach = self.cell * TILE as f64 * std::f64::consts::SQRT_2 * 0.5 + band;
774        for ty in (0..self.height).step_by(TILE) {
775            for tx in (0..self.width).step_by(TILE) {
776                let center = self.at(tx + TILE / 2, ty + TILE / 2);
777                let near = segments
778                    .iter()
779                    .filter(|s| box_distance(s, center) <= reach)
780                    .collect::<Vec<_>>();
781                for y in ty..(ty + TILE).min(self.height) {
782                    for x in tx..(tx + TILE).min(self.width) {
783                        let p = self.at(x, y);
784                        let d = near
785                            .iter()
786                            .map(|s| segment_distance(p, s[0], s[1]))
787                            .fold(band, f64::min);
788                        self.values[y * self.width + x] = d as f32;
789                    }
790                }
791            }
792        }
793    }
794
795    fn sign_inside(&mut self, segments: &[[[f64; 2]; 2]]) {
796        let mut crossings = Vec::new();
797        for y in 0..self.height {
798            let row = self.origin[1] + y as f64 * self.cell;
799            crossings.clear();
800            for [a, b] in segments {
801                if (a[1] <= row) != (b[1] <= row) {
802                    crossings.push(a[0] + (row - a[1]) * (b[0] - a[0]) / (b[1] - a[1]));
803                }
804            }
805            crossings.sort_by(f64::total_cmp);
806            for pair in crossings.chunks_exact(2) {
807                let from = ((pair[0] - self.origin[0]) / self.cell).ceil().max(0.0) as usize;
808                let to = ((pair[1] - self.origin[0]) / self.cell).floor();
809                if to < 0.0 {
810                    continue;
811                }
812                let to = (to as usize).min(self.width - 1);
813                for x in from..=to {
814                    let v = &mut self.values[y * self.width + x];
815                    *v = -*v;
816                }
817            }
818        }
819    }
820
821    fn sample(&self, x: f32, y: f32) -> f32 {
822        let fx = ((x as f64 - self.origin[0]) / self.cell).clamp(0.0, (self.width - 1) as f64);
823        let fy = ((y as f64 - self.origin[1]) / self.cell).clamp(0.0, (self.height - 1) as f64);
824        let x0 = (fx.floor() as usize).min(self.width - 2);
825        let y0 = (fy.floor() as usize).min(self.height - 2);
826        let (tx, ty) = ((fx - x0 as f64) as f32, (fy - y0 as f64) as f32);
827        let v = |x: usize, y: usize| self.values[y * self.width + x];
828        let top = v(x0, y0) * (1.0 - tx) + v(x0 + 1, y0) * tx;
829        let bottom = v(x0, y0 + 1) * (1.0 - tx) + v(x0 + 1, y0 + 1) * tx;
830        top * (1.0 - ty) + bottom * ty
831    }
832}
833
834fn box_distance(s: &[[f64; 2]; 2], p: [f64; 2]) -> f64 {
835    let lo = [s[0][0].min(s[1][0]), s[0][1].min(s[1][1])];
836    let hi = [s[0][0].max(s[1][0]), s[0][1].max(s[1][1])];
837    let dx = (lo[0] - p[0]).max(p[0] - hi[0]).max(0.0);
838    let dy = (lo[1] - p[1]).max(p[1] - hi[1]).max(0.0);
839    (dx * dx + dy * dy).sqrt()
840}
841
842fn segment_distance(p: [f64; 2], a: [f64; 2], b: [f64; 2]) -> f64 {
843    let ab = [b[0] - a[0], b[1] - a[1]];
844    let ap = [p[0] - a[0], p[1] - a[1]];
845    let l2 = ab[0] * ab[0] + ab[1] * ab[1];
846    let t = if l2 <= 1e-300 {
847        0.0
848    } else {
849        ((ap[0] * ab[0] + ap[1] * ab[1]) / l2).clamp(0.0, 1.0)
850    };
851    let d = [ap[0] - ab[0] * t, ap[1] - ab[1] * t];
852    (d[0] * d[0] + d[1] * d[1]).sqrt()
853}
854
855fn edges(region: &[Vec<[f64; 2]>]) -> Vec<[[f64; 2]; 2]> {
856    region
857        .iter()
858        .filter(|c| c.len() > 2)
859        .flat_map(|c| (0..c.len()).map(move |i| [c[i], c[(i + 1) % c.len()]]))
860        .collect()
861}
862
863fn bounds2(segments: &[[[f64; 2]; 2]]) -> ([f64; 2], [f64; 2]) {
864    let mut lo = [f64::INFINITY; 2];
865    let mut hi = [f64::NEG_INFINITY; 2];
866    for p in segments.iter().flatten() {
867        for i in 0..2 {
868            lo[i] = lo[i].min(p[i]);
869            hi[i] = hi[i].max(p[i]);
870        }
871    }
872    (lo, hi)
873}
874
875fn signed(segments: &[[[f64; 2]; 2]], lo: [f64; 2], hi: [f64; 2], cell: f64, band: f64) -> Raster {
876    let mut raster = Raster::new(lo, hi, cell);
877    raster.distances(segments, band);
878    raster.sign_inside(segments);
879    raster
880}
881
882fn hole_raster(
883    holes: &[Vec<[f64; 2]>],
884    lo: [f64; 2],
885    hi: [f64; 2],
886    cell: f64,
887    band: f64,
888) -> Option<Raster> {
889    if holes.is_empty() {
890        return None;
891    }
892    let region = holes.simplify_shape_as::<i64>(OverlayFill::NonZero);
893    let contours = region.into_iter().flatten().collect::<Vec<_>>();
894    Some(signed(&edges(&contours), lo, hi, cell, band))
895}
896
897fn tube(segments: &[[[f64; 2]; 2]], r: f64, holes: &[Vec<[f64; 2]>], form: &Form) -> Mesh {
898    let voxel = (r / 10.0).max(1.0 / VOXEL_SPAN as f64);
899    let (lo, hi) = bounds2(segments);
900    let pad = r + 4.0 * voxel;
901    let lo = [lo[0] - pad, lo[1] - pad];
902    let hi = [hi[0] + pad, hi[1] + pad];
903    let cell = voxel * 0.5;
904    let band = r + 6.0 * voxel;
905    let mut raster = Raster::new(lo, hi, cell);
906    raster.distances(segments, band);
907    let cut = hole_raster(holes, lo, hi, cell, band);
908    let r = r as f32;
909    let field = |p: [f32; 3]| {
910        let u = raster.sample(p[0], p[1]);
911        let d = (u * u + p[2] * p[2]).sqrt() - r;
912        match &cut {
913            Some(h) => d.max(-h.sample(p[0], p[1])),
914            None => d,
915        }
916    };
917    let low = [
918        (lo[0] + 3.0 * voxel) as f32,
919        (lo[1] + 3.0 * voxel) as f32,
920        -r,
921    ];
922    let high = [
923        (hi[0] - 3.0 * voxel) as f32,
924        (hi[1] - 3.0 * voxel) as f32,
925        r,
926    ];
927    Surface::new(voxel as f32, form.relax).mesh(&field, low, high)
928}
929
930fn pillow(region: &Region, grow: f64, holes: &[Vec<[f64; 2]>], form: &Form) -> Mesh {
931    let voxel = (grow / 10.0).max(1.0 / VOXEL_SPAN as f64);
932    let contours = region.iter().flatten().cloned().collect::<Vec<_>>();
933    let segments = edges(&contours);
934    let (lo, hi) = bounds2(&segments);
935    let pad = grow + 4.0 * voxel;
936    let lo = [lo[0] - pad, lo[1] - pad];
937    let hi = [hi[0] + pad, hi[1] + pad];
938    let cell = voxel * 0.5;
939    let band = grow + 6.0 * voxel;
940    let raster = signed(&segments, lo, hi, cell, band);
941    let cut = hole_raster(holes, lo, hi, cell, band);
942    let core = (grow * CORE as f64) as f32;
943    let reach = grow as f32 - core;
944    let field = |p: [f32; 3]| {
945        let w = [raster.sample(p[0], p[1]), p[2].abs() - core];
946        let d = w[0].max(w[1]).min(0.0) + (w[0].max(0.0).powi(2) + w[1].max(0.0).powi(2)).sqrt()
947            - reach;
948        match &cut {
949            Some(h) => d.max(-h.sample(p[0], p[1])),
950            None => d,
951        }
952    };
953    let g = grow as f32;
954    let low = [
955        (lo[0] + 3.0 * voxel) as f32,
956        (lo[1] + 3.0 * voxel) as f32,
957        -g,
958    ];
959    let high = [
960        (hi[0] - 3.0 * voxel) as f32,
961        (hi[1] - 3.0 * voxel) as f32,
962        g,
963    ];
964    Surface::new(voxel as f32, form.relax).mesh(&field, low, high)
965}