1use crate::curve::parameter_line;
2use super::*;
3
4struct CertifiedPrimitive { solid: BrepSolid }
8impl 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}