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