Skip to main content

brep_kernel/brep/topology/
primitives.rs

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