Skip to main content

geop_ops_subd/
examples.rs

1//! The examples of subdivision surfaces an editor offers: a computer mouse,
2//! and a mug with a handle.
3
4use geop_core_editor::registry::ExampleSet;
5use geop_core_program::program::Program;
6
7use geop_core_math::plain::centroid;
8
9use crate::{Cage, Mirror, SubdArgs};
10
11/// The examples of this crate, by name.
12pub fn set() -> ExampleSet {
13    ExampleSet {
14        programs: vec![("subd_mouse", subd_mouse), ("subd_mug", subd_mug)],
15        workspaces: Vec::new(),
16    }
17}
18
19/// A computer mouse, shaped as a subdivision surface: half a 1.2 x 2 x 0.8
20/// box cage, mirrored in `x = 0`, its back raised into a hump and the rim
21/// of its bottom creased, so that it stands on a flat sole with a sharp
22/// edge while everything above rounds off. The body is `subd(mouse)`.
23#[allow(clippy::expect_used, reason = "the cage is the example's own")]
24pub fn subd_mouse() -> Program {
25    let mut cage = Cage::cuboid([1.2, 2.0, 0.8]);
26    cage.halve(0).expect("the box reaches across x = 0");
27    let bottom = cage.face(8).expect("the box's bottom").vertices.clone();
28    let on_plane = |v: u32| cage.vertex(v).is_ok_and(|x| x.at[0] == 0.0);
29    let rim: Vec<[u32; 2]> = (0..bottom.len())
30        .map(|k| [bottom[k], bottom[(k + 1) % bottom.len()]])
31        .filter(|&[a, b]| !(on_plane(a) && on_plane(b)))
32        .collect();
33    cage.set_sharpness(&rim, 1.0);
34    let back = cage
35        .vertices
36        .iter()
37        .filter(|v| v.at[1] > 0.0 && v.at[2] > 0.0)
38        .map(|v| v.id)
39        .collect();
40    cage.transform(&back, Mirror::X, |p| [p[0], p[1], p[2] + 0.3]);
41    let mut program = Program::new();
42    program.push(
43        "mouse",
44        SubdArgs {
45            cage,
46            mirror: Mirror::X,
47        },
48    );
49    program
50}
51
52/// The face of `cage` whose centre is nearest `point`, by id.
53#[allow(clippy::expect_used, reason = "the cage is the example's own")]
54fn face_near(cage: &Cage, point: [f64; 3]) -> u32 {
55    let centre = |ids: &[u32]| {
56        centroid(ids.iter().map(|&v| cage.vertex(v).expect("a corner").at)).expect("corners")
57    };
58    let distance = |c: [f64; 3]| (0..3).map(|k| (c[k] - point[k]).powi(2)).sum::<f64>();
59    (cage.faces.iter())
60        .min_by(|a, b| distance(centre(&a.vertices)).total_cmp(&distance(centre(&b.vertices))))
61        .expect("a face")
62        .id
63}
64
65/// A mug, shaped as a subdivision surface with the box modelling tools:
66/// half an eight-sided cylinder mirrored in `y = 0`, cut into five rows,
67/// its handle two side faces extruded out and bridged, its inside the top
68/// inset and extruded down to a floor; the bottom's rim creased sharp to
69/// stand on, the lip creased soft to round off. The body is `subd(mug)`.
70#[allow(clippy::expect_used, reason = "the cage is the example's own")]
71pub fn subd_mug() -> Program {
72    let mirror = Mirror::Y;
73    let mut cage = Cage::cylinder(1.0, 2.4, 8);
74    cage.halve(1).expect("the cylinder reaches across y = 0");
75    // Three rows into five: the vertical edges of the lower and the upper
76    // row cut half way.
77    cage.insert_loop(0, 8, 0.5).expect("a ring of quads");
78    cage.insert_loop(8, 16, 0.5).expect("a ring of quads");
79    // The handle: the lowest and the highest face on the `+x` side, pulled
80    // out and joined.
81    let lower = face_near(&cage, [0.95, 0.2, -0.9]);
82    let upper = face_near(&cage, [0.95, 0.2, 0.9]);
83    cage.extrude(&[lower].into(), 0.8, mirror).expect("a face");
84    cage.extrude(&[upper].into(), 0.8, mirror).expect("a face");
85    cage.bridge(lower, upper, mirror).expect("two quads");
86    // The inside: the top inset for the wall, then sunk to the floor.
87    let top = face_near(&cage, [0.0, 0.5, 1.2]);
88    let lip: Vec<[u32; 2]> = cage
89        .edges()
90        .into_iter()
91        .filter(|&[a, b]| {
92            let (p, q) = (cage.vertex(a).expect("a").at, cage.vertex(b).expect("b").at);
93            p[2] == 1.2 && q[2] == 1.2 && !(p[1] == 0.0 && q[1] == 0.0)
94        })
95        .collect();
96    cage.inset(&[top].into(), 0.15, mirror).expect("a face");
97    cage.extrude(&[top].into(), -2.0, mirror).expect("a face");
98    cage.set_sharpness(&lip, 0.5);
99    let bottom: Vec<[u32; 2]> = cage
100        .edges()
101        .into_iter()
102        .filter(|&[a, b]| {
103            let (p, q) = (cage.vertex(a).expect("a").at, cage.vertex(b).expect("b").at);
104            p[2] == -1.2 && q[2] == -1.2 && !(p[1] == 0.0 && q[1] == 0.0)
105        })
106        .collect();
107    cage.set_sharpness(&bottom, 1.0);
108    let mut program = Program::new();
109    program.push("mug", SubdArgs { cage, mirror });
110    program
111}