Skip to main content

brep_kernel/brep/topology/
primitives.rs

1use crate::curve::parameter_line;
2use super::*;
3
4// Module-private, owned evidence from the fixed primitive builders below.
5// Consuming it cannot confer trust on a later mutation or on a caller's body.
6// Primitive certificate type: begin
7struct CertifiedPrimitive { solid: BrepSolid }
8// Primitive certificate type: end
9impl CertifiedPrimitive {
10    fn accept(self) -> BrepSolid {
11        crate::analytic_topology::primitive_check_count(true);
12        self.solid
13    }
14}
15
16pub fn make_box_brep(
17    corner: Vec3,
18    size_x: f64,
19    size_y: f64,
20    size_z: f64,
21) -> Result<BrepSolid, String> {
22    let mut profile = crate::loft_topology::ConstructionProfile::new("box");
23    if size_x <= 0.0 || size_y <= 0.0 || size_z <= 0.0 {
24        return Err("makeBoxSolid: sizes must be positive".into());
25    }
26    let mut vertices = Vec::with_capacity(8);
27    for index in 0..8_u64 {
28        vertices.push(VertexRecord {
29            id: index + 1,
30            point: corner.add(Vec3::new(
31                if index & 1 != 0 { size_x } else { 0.0 },
32                if index & 2 != 0 { size_y } else { 0.0 },
33                if index & 4 != 0 { size_z } else { 0.0 },
34            )),
35        });
36    }
37    let specifications = [
38        [0_usize, 2, 3, 1],
39        [4, 5, 7, 6],
40        [0, 1, 5, 4],
41        [2, 6, 7, 3],
42        [0, 4, 6, 2],
43        [1, 3, 7, 5],
44    ];
45    let mut edges = Vec::<EdgeRecord>::new();
46    let mut edge_indices = HashMap::<(usize, usize), usize>::default();
47    let mut faces = Vec::with_capacity(6);
48    let mut next_topology_id = 100_u64;
49
50    for corners in specifications {
51        let p0 = vertices[corners[0]].point;
52        let p1 = vertices[corners[1]].point;
53        let p3 = vertices[corners[3]].point;
54        let u_vector = p1.sub(p0);
55        let v_vector = p3.sub(p0);
56        let u_length = u_vector.length();
57        let v_length = v_vector.length();
58        let u_direction = u_vector.normalized()?;
59        let v_direction = v_vector.normalized()?;
60        let p11 = p0
61            .add(u_direction.scale(u_length))
62            .add(v_direction.scale(v_length));
63        let surface = NurbsSurface::new(
64            1,
65            1,
66            vec![0.0, 0.0, u_length, u_length],
67            vec![0.0, 0.0, v_length, v_length],
68            vec![
69                vec![Vec4::from_point(p0, 1.0), Vec4::from_point(p3, 1.0)],
70                vec![Vec4::from_point(p1, 1.0), Vec4::from_point(p11, 1.0)],
71            ],
72        )?;
73        let uv = [
74            [0.0, 0.0],
75            [u_length, 0.0],
76            [u_length, v_length],
77            [0.0, v_length],
78        ];
79        let mut coedges = Vec::with_capacity(4);
80        for side in 0..4 {
81            let start_index = corners[side];
82            let end_index = corners[(side + 1) % 4];
83            let key = if start_index < end_index {
84                (start_index, end_index)
85            } else {
86                (end_index, start_index)
87            };
88            let edge_index = match edge_indices.get(&key) {
89                Some(index) => *index,
90                None => {
91                    let id = 10 + edges.len() as u64;
92                    let start = vertices[key.0].point;
93                    let end = vertices[key.1].point;
94                    edges.push(EdgeRecord {
95                        id,
96                        curve: make_line(start, end)?,
97                        t0: 0.0,
98                        t1: 1.0,
99                        start_vertex_id: vertices[key.0].id,
100                        end_vertex_id: vertices[key.1].id,
101                        degenerate: false,
102                        name: None,
103                    });
104                    let index = edges.len() - 1;
105                    edge_indices.insert(key, index);
106                    index
107                }
108            };
109            let edge = &edges[edge_index];
110            coedges.push(CoedgeRecord {
111                id: next_topology_id,
112                edge_id: edge.id,
113                forward: edge.start_vertex_id == vertices[start_index].id,
114                pcurve: make_line(
115                    Vec3::new(uv[side][0], uv[side][1], 0.0),
116                    Vec3::new(uv[(side + 1) % 4][0], uv[(side + 1) % 4][1], 0.0),
117                )?,
118            });
119            next_topology_id += 1;
120        }
121        let loop_record = LoopRecord {
122            id: next_topology_id,
123            coedges,
124        };
125        next_topology_id += 1;
126        faces.push(FaceRecord {
127            id: next_topology_id,
128            surface,
129            same_sense: true,
130            loops: vec![loop_record],
131            name: None,
132        });
133        next_topology_id += 1;
134    }
135    let solid = BrepSolid {
136        mass_properties_cache: Default::default(),
137        cells: Default::default(),
138        id: next_topology_id + 1,
139        vertices,
140        edges,
141        shells: vec![ShellRecord {
142            id: next_topology_id,
143            faces,
144        }],
145        genus: 0,
146        kind: crate::BodyKind::Solid,
147    };
148    profile.lap("construct");
149    if crate::analytic_topology::primitive_certificate_enabled()
150        && crate::analytic_topology::primitive_domain(corner, &[size_x, size_y, size_z], None) {
151        return Ok(CertifiedPrimitive { solid }.accept());
152    }
153    crate::analytic_topology::primitive_check_count(false);
154    let issues = solid.validate();
155    profile.lap("validation");
156    if !issues.is_empty() {
157        return Err(format!(
158            "Rust box builder produced invalid topology: {:?}",
159            issues
160        ));
161    }
162    Ok(solid)
163}
164
165pub fn make_pyramid_brep(
166    corner: Vec3,
167    side_length: f64,
168    height: f64,
169    sides: usize,
170) -> Result<BrepSolid, String> {
171    if side_length <= 0.0 || height <= 0.0 {
172        return Err("makePyramidSolid: sizes must be positive".into());
173    }
174    let side_count = sides.max(3);
175    let center = corner.add(Vec3::new(side_length / 2.0, side_length / 2.0, 0.0));
176    let radius = side_length / (2.0 * (std::f64::consts::PI / side_count as f64).sin());
177    let start_angle = -std::f64::consts::FRAC_PI_2 - std::f64::consts::PI / side_count as f64;
178    let mut vertices = Vec::with_capacity(side_count + 1);
179    for index in 0..side_count {
180        let angle = start_angle + index as f64 * std::f64::consts::TAU / side_count as f64;
181        vertices.push(VertexRecord {
182            id: index as u64 + 1,
183            point: center.add(Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)),
184        });
185    }
186    let apex_index = vertices.len();
187    vertices.push(VertexRecord {
188        id: apex_index as u64 + 1,
189        point: center.add(Vec3::new(0.0, 0.0, height)),
190    });
191
192    let mut edges = Vec::<EdgeRecord>::new();
193    let mut edge_indices = HashMap::<(usize, usize), usize>::default();
194    let mut faces = Vec::with_capacity(side_count + 1);
195    let mut next_topology_id = 100u64;
196    let mut make_face = |corners: &[usize]| -> Result<FaceRecord, String> {
197        if corners.len() < 3 {
198            return Err("makePyramidSolid: face needs at least three corners".into());
199        }
200        let p0 = vertices[corners[0]].point;
201        let p1 = vertices[corners[1]].point;
202        let p2 = vertices[corners[2]].point;
203        let x_axis = p1.sub(p0).normalized()?;
204        let normal = p1.sub(p0).cross(p2.sub(p0)).normalized()?;
205        let y_axis = normal.cross(x_axis).normalized()?;
206        let mut minimum_u = f64::INFINITY;
207        let mut minimum_v = f64::INFINITY;
208        let mut maximum_u = f64::NEG_INFINITY;
209        let mut maximum_v = f64::NEG_INFINITY;
210        let projected = corners
211            .iter()
212            .map(|&index| {
213                let delta = vertices[index].point.sub(p0);
214                let u = delta.dot(x_axis);
215                let v = delta.dot(y_axis);
216                minimum_u = minimum_u.min(u);
217                minimum_v = minimum_v.min(v);
218                maximum_u = maximum_u.max(u);
219                maximum_v = maximum_v.max(v);
220                (u, v)
221            })
222            .collect::<Vec<_>>();
223        let origin = p0.add(x_axis.scale(minimum_u)).add(y_axis.scale(minimum_v));
224        let surface = make_plane(
225            origin,
226            x_axis,
227            y_axis,
228            maximum_u - minimum_u,
229            maximum_v - minimum_v,
230        )?;
231        let mut coedges = Vec::with_capacity(corners.len());
232        for index in 0..corners.len() {
233            let start_index = corners[index];
234            let end_index = corners[(index + 1) % corners.len()];
235            let key = if start_index < end_index {
236                (start_index, end_index)
237            } else {
238                (end_index, start_index)
239            };
240            let edge_index = if let Some(edge_index) = edge_indices.get(&key) {
241                *edge_index
242            } else {
243                let edge_index = edges.len();
244                edges.push(EdgeRecord {
245                    id: 10 + edge_index as u64,
246                    curve: make_line(vertices[key.0].point, vertices[key.1].point)?,
247                    t0: 0.0,
248                    t1: 1.0,
249                    start_vertex_id: vertices[key.0].id,
250                    end_vertex_id: vertices[key.1].id,
251                    degenerate: false,
252                    name: None,
253                });
254                edge_indices.insert(key, edge_index);
255                edge_index
256            };
257            let edge = &edges[edge_index];
258            let start_uv = projected[index];
259            let end_uv = projected[(index + 1) % corners.len()];
260            coedges.push(CoedgeRecord {
261                id: next_topology_id,
262                edge_id: edge.id,
263                forward: edge.start_vertex_id == vertices[start_index].id,
264                pcurve: parameter_line(
265                    start_uv.0 - minimum_u,
266                    start_uv.1 - minimum_v,
267                    end_uv.0 - minimum_u,
268                    end_uv.1 - minimum_v,
269                )?,
270            });
271            next_topology_id += 1;
272        }
273        let loop_record = LoopRecord {
274            id: next_topology_id,
275            coedges,
276        };
277        next_topology_id += 1;
278        let face = FaceRecord {
279            id: next_topology_id,
280            surface,
281            same_sense: true,
282            loops: vec![loop_record],
283            name: None,
284        };
285        next_topology_id += 1;
286        Ok(face)
287    };
288
289    let base = (0..side_count).rev().collect::<Vec<_>>();
290    faces.push(make_face(&base)?);
291    for index in 0..side_count {
292        faces.push(make_face(&[index, (index + 1) % side_count, apex_index])?);
293    }
294    drop(make_face);
295    let solid = BrepSolid {
296        mass_properties_cache: Default::default(),
297        cells: Default::default(),
298        id: next_topology_id + 1,
299        vertices,
300        edges,
301        shells: vec![ShellRecord {
302            id: next_topology_id,
303            faces,
304        }],
305        genus: 0,
306        kind: crate::BodyKind::Solid,
307    };
308    let issues = solid.validate();
309    if !issues.is_empty() {
310        return Err(format!(
311            "Rust pyramid builder produced invalid topology: {issues:?}"
312        ));
313    }
314    Ok(solid)
315}
316
317fn curve_to_plane_parameters(
318    curve: &NurbsCurve,
319    origin: Vec3,
320    x_axis: Vec3,
321    y_axis: Vec3,
322) -> Result<NurbsCurve, String> {
323    let mut points = Vec::with_capacity(curve.control_points.len());
324    for control_point in &curve.control_points {
325        let point = control_point.point()?;
326        let delta = point.sub(origin);
327        points.push(Vec4 {
328            x: delta.dot(x_axis) * control_point.w,
329            y: delta.dot(y_axis) * control_point.w,
330            z: 0.0,
331            w: control_point.w,
332        });
333    }
334    NurbsCurve::new(curve.degree, curve.knots.clone(), points)
335}
336
337pub fn make_cylinder_brep(
338    base: Vec3,
339    axis_direction: Vec3,
340    radius: f64,
341    height: f64,
342) -> Result<BrepSolid, String> {
343    let mut profile = crate::loft_topology::ConstructionProfile::new("cylinder");
344    if radius <= 0.0 || height <= 0.0 {
345        return Err("makeCylinderSolid: radius and height must be positive".into());
346    }
347    let axis = axis_direction.normalized()?;
348    let side = make_cylinder_surface(base, axis, radius, height)?;
349    let bottom_point = side.evaluate(0.0, 0.0)?;
350    let top_point = side.evaluate(0.0, 1.0)?;
351    let vertices = vec![
352        VertexRecord {
353            id: 1,
354            point: bottom_point,
355        },
356        VertexRecord {
357            id: 2,
358            point: top_point,
359        },
360    ];
361    let bottom_circle = side.iso_curve_v(0.0)?;
362    let top_circle = side.iso_curve_v(1.0)?;
363    let seam_line = side.iso_curve_u(0.0)?;
364    let edges = vec![
365        EdgeRecord {
366            id: 10,
367            curve: bottom_circle.clone(),
368            t0: 0.0,
369            t1: 1.0,
370            start_vertex_id: 1,
371            end_vertex_id: 1,
372            degenerate: false,
373            name: None,
374        },
375        EdgeRecord {
376            id: 11,
377            curve: top_circle.clone(),
378            t0: 0.0,
379            t1: 1.0,
380            start_vertex_id: 2,
381            end_vertex_id: 2,
382            degenerate: false,
383            name: None,
384        },
385        EdgeRecord {
386            id: 12,
387            curve: seam_line,
388            t0: 0.0,
389            t1: 1.0,
390            start_vertex_id: 1,
391            end_vertex_id: 2,
392            degenerate: false,
393            name: None,
394        },
395    ];
396
397    let side_loop = LoopRecord {
398        id: 100,
399        coedges: vec![
400            CoedgeRecord {
401                id: 101,
402                edge_id: 10,
403                forward: true,
404                pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
405            },
406            CoedgeRecord {
407                id: 102,
408                edge_id: 12,
409                forward: true,
410                pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
411            },
412            CoedgeRecord {
413                id: 103,
414                edge_id: 11,
415                forward: false,
416                pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
417            },
418            CoedgeRecord {
419                id: 104,
420                edge_id: 12,
421                forward: false,
422                pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
423            },
424        ],
425    };
426    let side_face = FaceRecord {
427        id: 105,
428        surface: side,
429        same_sense: true,
430        loops: vec![side_loop],
431        name: None,
432    };
433
434    let x_axis = axis.perpendicular()?;
435    let y_axis = axis.cross(x_axis).normalized()?;
436    let bottom_origin = base.add(x_axis.scale(-radius)).add(y_axis.scale(-radius));
437    let bottom_plane = make_plane(bottom_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
438    let bottom_pcurve =
439        curve_to_plane_parameters(&bottom_circle, bottom_origin, x_axis, y_axis)?.reversed()?;
440    let bottom_face = FaceRecord {
441        id: 108,
442        surface: bottom_plane,
443        same_sense: false,
444        loops: vec![LoopRecord {
445            id: 107,
446            coedges: vec![CoedgeRecord {
447                id: 106,
448                edge_id: 10,
449                forward: false,
450                pcurve: bottom_pcurve,
451            }],
452        }],
453        name: None,
454    };
455
456    let top_origin = base
457        .add(axis.scale(height))
458        .add(x_axis.scale(-radius))
459        .add(y_axis.scale(-radius));
460    let top_plane = make_plane(top_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
461    let top_pcurve = curve_to_plane_parameters(&top_circle, top_origin, x_axis, y_axis)?;
462    let top_face = FaceRecord {
463        id: 111,
464        surface: top_plane,
465        same_sense: true,
466        loops: vec![LoopRecord {
467            id: 110,
468            coedges: vec![CoedgeRecord {
469                id: 109,
470                edge_id: 11,
471                forward: true,
472                pcurve: top_pcurve,
473            }],
474        }],
475        name: None,
476    };
477
478    let solid = BrepSolid {
479        mass_properties_cache: Default::default(),
480        cells: Default::default(),
481        id: 114,
482        vertices,
483        edges,
484        shells: vec![ShellRecord {
485            id: 113,
486            faces: vec![side_face, bottom_face, top_face],
487        }],
488        genus: 0,
489        kind: crate::BodyKind::Solid,
490    };
491    profile.lap("construct");
492    if crate::analytic_topology::primitive_certificate_enabled()
493        && crate::analytic_topology::primitive_domain(base, &[radius, height], Some(axis_direction)) {
494        return Ok(CertifiedPrimitive { solid }.accept());
495    }
496    crate::analytic_topology::primitive_check_count(false);
497    let issues = solid.validate();
498    profile.lap("validation");
499    if !issues.is_empty() {
500        return Err(format!(
501            "Rust cylinder builder produced invalid topology: {:?}",
502            issues
503        ));
504    }
505    Ok(solid)
506}