Skip to main content

brep_kernel/brep/topology/
primitives.rs

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