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}