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}