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}