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    };
133    let issues = solid.validate();
134    if !issues.is_empty() {
135        return Err(format!(
136            "Rust box builder produced invalid topology: {:?}",
137            issues
138        ));
139    }
140    Ok(solid)
141}
142
143pub fn make_pyramid_brep(
144    corner: Vec3,
145    side_length: f64,
146    height: f64,
147    sides: usize,
148) -> Result<BrepSolid, String> {
149    if side_length <= 0.0 || height <= 0.0 {
150        return Err("makePyramidSolid: sizes must be positive".into());
151    }
152    let side_count = sides.max(3);
153    let center = corner.add(Vec3::new(side_length / 2.0, side_length / 2.0, 0.0));
154    let radius = side_length / (2.0 * (std::f64::consts::PI / side_count as f64).sin());
155    let start_angle = -std::f64::consts::FRAC_PI_2 - std::f64::consts::PI / side_count as f64;
156    let mut vertices = Vec::with_capacity(side_count + 1);
157    for index in 0..side_count {
158        let angle = start_angle + index as f64 * std::f64::consts::TAU / side_count as f64;
159        vertices.push(VertexRecord {
160            id: index as u64 + 1,
161            point: center.add(Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)),
162        });
163    }
164    let apex_index = vertices.len();
165    vertices.push(VertexRecord {
166        id: apex_index as u64 + 1,
167        point: center.add(Vec3::new(0.0, 0.0, height)),
168    });
169
170    let mut edges = Vec::<EdgeRecord>::new();
171    let mut edge_indices = HashMap::<(usize, usize), usize>::default();
172    let mut faces = Vec::with_capacity(side_count + 1);
173    let mut next_topology_id = 100u64;
174    let mut make_face = |corners: &[usize]| -> Result<FaceRecord, String> {
175        if corners.len() < 3 {
176            return Err("makePyramidSolid: face needs at least three corners".into());
177        }
178        let p0 = vertices[corners[0]].point;
179        let p1 = vertices[corners[1]].point;
180        let p2 = vertices[corners[2]].point;
181        let x_axis = p1.sub(p0).normalized()?;
182        let normal = p1.sub(p0).cross(p2.sub(p0)).normalized()?;
183        let y_axis = normal.cross(x_axis).normalized()?;
184        let mut minimum_u = f64::INFINITY;
185        let mut minimum_v = f64::INFINITY;
186        let mut maximum_u = f64::NEG_INFINITY;
187        let mut maximum_v = f64::NEG_INFINITY;
188        let projected = corners
189            .iter()
190            .map(|&index| {
191                let delta = vertices[index].point.sub(p0);
192                let u = delta.dot(x_axis);
193                let v = delta.dot(y_axis);
194                minimum_u = minimum_u.min(u);
195                minimum_v = minimum_v.min(v);
196                maximum_u = maximum_u.max(u);
197                maximum_v = maximum_v.max(v);
198                (u, v)
199            })
200            .collect::<Vec<_>>();
201        let origin = p0.add(x_axis.scale(minimum_u)).add(y_axis.scale(minimum_v));
202        let surface = make_plane(
203            origin,
204            x_axis,
205            y_axis,
206            maximum_u - minimum_u,
207            maximum_v - minimum_v,
208        )?;
209        let mut coedges = Vec::with_capacity(corners.len());
210        for index in 0..corners.len() {
211            let start_index = corners[index];
212            let end_index = corners[(index + 1) % corners.len()];
213            let key = if start_index < end_index {
214                (start_index, end_index)
215            } else {
216                (end_index, start_index)
217            };
218            let edge_index = if let Some(edge_index) = edge_indices.get(&key) {
219                *edge_index
220            } else {
221                let edge_index = edges.len();
222                edges.push(EdgeRecord {
223                    id: 10 + edge_index as u64,
224                    curve: make_line(vertices[key.0].point, vertices[key.1].point)?,
225                    t0: 0.0,
226                    t1: 1.0,
227                    start_vertex_id: vertices[key.0].id,
228                    end_vertex_id: vertices[key.1].id,
229                    degenerate: false,
230                    name: None,
231                });
232                edge_indices.insert(key, edge_index);
233                edge_index
234            };
235            let edge = &edges[edge_index];
236            let start_uv = projected[index];
237            let end_uv = projected[(index + 1) % corners.len()];
238            coedges.push(CoedgeRecord {
239                id: next_topology_id,
240                edge_id: edge.id,
241                forward: edge.start_vertex_id == vertices[start_index].id,
242                pcurve: parameter_line(
243                    start_uv.0 - minimum_u,
244                    start_uv.1 - minimum_v,
245                    end_uv.0 - minimum_u,
246                    end_uv.1 - minimum_v,
247                )?,
248            });
249            next_topology_id += 1;
250        }
251        let loop_record = LoopRecord {
252            id: next_topology_id,
253            coedges,
254        };
255        next_topology_id += 1;
256        let face = FaceRecord {
257            id: next_topology_id,
258            surface,
259            same_sense: true,
260            loops: vec![loop_record],
261            name: None,
262        };
263        next_topology_id += 1;
264        Ok(face)
265    };
266
267    let base = (0..side_count).rev().collect::<Vec<_>>();
268    faces.push(make_face(&base)?);
269    for index in 0..side_count {
270        faces.push(make_face(&[index, (index + 1) % side_count, apex_index])?);
271    }
272    drop(make_face);
273    let solid = BrepSolid {
274        mass_properties_cache: Default::default(),
275        id: next_topology_id + 1,
276        vertices,
277        edges,
278        shells: vec![ShellRecord {
279            id: next_topology_id,
280            faces,
281        }],
282        genus: 0,
283    };
284    let issues = solid.validate();
285    if !issues.is_empty() {
286        return Err(format!(
287            "Rust pyramid builder produced invalid topology: {issues:?}"
288        ));
289    }
290    Ok(solid)
291}
292
293fn curve_to_plane_parameters(
294    curve: &NurbsCurve,
295    origin: Vec3,
296    x_axis: Vec3,
297    y_axis: Vec3,
298) -> Result<NurbsCurve, String> {
299    let mut points = Vec::with_capacity(curve.control_points.len());
300    for control_point in &curve.control_points {
301        let point = control_point.point()?;
302        let delta = point.sub(origin);
303        points.push(Vec4 {
304            x: delta.dot(x_axis) * control_point.w,
305            y: delta.dot(y_axis) * control_point.w,
306            z: 0.0,
307            w: control_point.w,
308        });
309    }
310    NurbsCurve::new(curve.degree, curve.knots.clone(), points)
311}
312
313pub fn make_cylinder_brep(
314    base: Vec3,
315    axis_direction: Vec3,
316    radius: f64,
317    height: f64,
318) -> Result<BrepSolid, String> {
319    if radius <= 0.0 || height <= 0.0 {
320        return Err("makeCylinderSolid: radius and height must be positive".into());
321    }
322    let axis = axis_direction.normalized()?;
323    let side = make_cylinder_surface(base, axis, radius, height)?;
324    let bottom_point = side.evaluate(0.0, 0.0)?;
325    let top_point = side.evaluate(0.0, 1.0)?;
326    let vertices = vec![
327        VertexRecord {
328            id: 1,
329            point: bottom_point,
330        },
331        VertexRecord {
332            id: 2,
333            point: top_point,
334        },
335    ];
336    let bottom_circle = side.iso_curve_v(0.0)?;
337    let top_circle = side.iso_curve_v(1.0)?;
338    let seam_line = side.iso_curve_u(0.0)?;
339    let edges = vec![
340        EdgeRecord {
341            id: 10,
342            curve: bottom_circle.clone(),
343            t0: 0.0,
344            t1: 1.0,
345            start_vertex_id: 1,
346            end_vertex_id: 1,
347            degenerate: false,
348            name: None,
349        },
350        EdgeRecord {
351            id: 11,
352            curve: top_circle.clone(),
353            t0: 0.0,
354            t1: 1.0,
355            start_vertex_id: 2,
356            end_vertex_id: 2,
357            degenerate: false,
358            name: None,
359        },
360        EdgeRecord {
361            id: 12,
362            curve: seam_line,
363            t0: 0.0,
364            t1: 1.0,
365            start_vertex_id: 1,
366            end_vertex_id: 2,
367            degenerate: false,
368            name: None,
369        },
370    ];
371
372    let side_loop = LoopRecord {
373        id: 100,
374        coedges: vec![
375            CoedgeRecord {
376                id: 101,
377                edge_id: 10,
378                forward: true,
379                pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
380            },
381            CoedgeRecord {
382                id: 102,
383                edge_id: 12,
384                forward: true,
385                pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
386            },
387            CoedgeRecord {
388                id: 103,
389                edge_id: 11,
390                forward: false,
391                pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
392            },
393            CoedgeRecord {
394                id: 104,
395                edge_id: 12,
396                forward: false,
397                pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
398            },
399        ],
400    };
401    let side_face = FaceRecord {
402        id: 105,
403        surface: side,
404        same_sense: true,
405        loops: vec![side_loop],
406        name: None,
407    };
408
409    let x_axis = axis.perpendicular()?;
410    let y_axis = axis.cross(x_axis).normalized()?;
411    let bottom_origin = base.add(x_axis.scale(-radius)).add(y_axis.scale(-radius));
412    let bottom_plane = make_plane(bottom_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
413    let bottom_pcurve =
414        curve_to_plane_parameters(&bottom_circle, bottom_origin, x_axis, y_axis)?.reversed()?;
415    let bottom_face = FaceRecord {
416        id: 108,
417        surface: bottom_plane,
418        same_sense: false,
419        loops: vec![LoopRecord {
420            id: 107,
421            coedges: vec![CoedgeRecord {
422                id: 106,
423                edge_id: 10,
424                forward: false,
425                pcurve: bottom_pcurve,
426            }],
427        }],
428        name: None,
429    };
430
431    let top_origin = base
432        .add(axis.scale(height))
433        .add(x_axis.scale(-radius))
434        .add(y_axis.scale(-radius));
435    let top_plane = make_plane(top_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
436    let top_pcurve = curve_to_plane_parameters(&top_circle, top_origin, x_axis, y_axis)?;
437    let top_face = FaceRecord {
438        id: 111,
439        surface: top_plane,
440        same_sense: true,
441        loops: vec![LoopRecord {
442            id: 110,
443            coedges: vec![CoedgeRecord {
444                id: 109,
445                edge_id: 11,
446                forward: true,
447                pcurve: top_pcurve,
448            }],
449        }],
450        name: None,
451    };
452
453    let solid = BrepSolid {
454        mass_properties_cache: Default::default(),
455        id: 114,
456        vertices,
457        edges,
458        shells: vec![ShellRecord {
459            id: 113,
460            faces: vec![side_face, bottom_face, top_face],
461        }],
462        genus: 0,
463    };
464    let issues = solid.validate();
465    if !issues.is_empty() {
466        return Err(format!(
467            "Rust cylinder builder produced invalid topology: {:?}",
468            issues
469        ));
470    }
471    Ok(solid)
472}