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 id: next_topology_id + 1,
124 vertices,
125 edges,
126 shells: vec![ShellRecord {
127 id: next_topology_id,
128 faces,
129 }],
130 genus: 0,
131 };
132 let issues = solid.validate();
133 if !issues.is_empty() {
134 return Err(format!(
135 "Rust box builder produced invalid topology: {:?}",
136 issues
137 ));
138 }
139 Ok(solid)
140}
141
142pub fn make_pyramid_brep(
143 corner: Vec3,
144 side_length: f64,
145 height: f64,
146 sides: usize,
147) -> Result<BrepSolid, String> {
148 if side_length <= 0.0 || height <= 0.0 {
149 return Err("makePyramidSolid: sizes must be positive".into());
150 }
151 let side_count = sides.max(3);
152 let center = corner.add(Vec3::new(side_length / 2.0, side_length / 2.0, 0.0));
153 let radius = side_length / (2.0 * (std::f64::consts::PI / side_count as f64).sin());
154 let start_angle = -std::f64::consts::FRAC_PI_2 - std::f64::consts::PI / side_count as f64;
155 let mut vertices = Vec::with_capacity(side_count + 1);
156 for index in 0..side_count {
157 let angle = start_angle + index as f64 * std::f64::consts::TAU / side_count as f64;
158 vertices.push(VertexRecord {
159 id: index as u64 + 1,
160 point: center.add(Vec3::new(radius * angle.cos(), radius * angle.sin(), 0.0)),
161 });
162 }
163 let apex_index = vertices.len();
164 vertices.push(VertexRecord {
165 id: apex_index as u64 + 1,
166 point: center.add(Vec3::new(0.0, 0.0, height)),
167 });
168
169 let mut edges = Vec::<EdgeRecord>::new();
170 let mut edge_indices = HashMap::<(usize, usize), usize>::default();
171 let mut faces = Vec::with_capacity(side_count + 1);
172 let mut next_topology_id = 100u64;
173 let mut make_face = |corners: &[usize]| -> Result<FaceRecord, String> {
174 if corners.len() < 3 {
175 return Err("makePyramidSolid: face needs at least three corners".into());
176 }
177 let p0 = vertices[corners[0]].point;
178 let p1 = vertices[corners[1]].point;
179 let p2 = vertices[corners[2]].point;
180 let x_axis = p1.sub(p0).normalized()?;
181 let normal = p1.sub(p0).cross(p2.sub(p0)).normalized()?;
182 let y_axis = normal.cross(x_axis).normalized()?;
183 let mut minimum_u = f64::INFINITY;
184 let mut minimum_v = f64::INFINITY;
185 let mut maximum_u = f64::NEG_INFINITY;
186 let mut maximum_v = f64::NEG_INFINITY;
187 let projected = corners
188 .iter()
189 .map(|&index| {
190 let delta = vertices[index].point.sub(p0);
191 let u = delta.dot(x_axis);
192 let v = delta.dot(y_axis);
193 minimum_u = minimum_u.min(u);
194 minimum_v = minimum_v.min(v);
195 maximum_u = maximum_u.max(u);
196 maximum_v = maximum_v.max(v);
197 (u, v)
198 })
199 .collect::<Vec<_>>();
200 let origin = p0.add(x_axis.scale(minimum_u)).add(y_axis.scale(minimum_v));
201 let surface = make_plane(
202 origin,
203 x_axis,
204 y_axis,
205 maximum_u - minimum_u,
206 maximum_v - minimum_v,
207 )?;
208 let mut coedges = Vec::with_capacity(corners.len());
209 for index in 0..corners.len() {
210 let start_index = corners[index];
211 let end_index = corners[(index + 1) % corners.len()];
212 let key = if start_index < end_index {
213 (start_index, end_index)
214 } else {
215 (end_index, start_index)
216 };
217 let edge_index = if let Some(edge_index) = edge_indices.get(&key) {
218 *edge_index
219 } else {
220 let edge_index = edges.len();
221 edges.push(EdgeRecord {
222 id: 10 + edge_index as u64,
223 curve: make_line(vertices[key.0].point, vertices[key.1].point)?,
224 t0: 0.0,
225 t1: 1.0,
226 start_vertex_id: vertices[key.0].id,
227 end_vertex_id: vertices[key.1].id,
228 degenerate: false,
229 name: None,
230 });
231 edge_indices.insert(key, edge_index);
232 edge_index
233 };
234 let edge = &edges[edge_index];
235 let start_uv = projected[index];
236 let end_uv = projected[(index + 1) % corners.len()];
237 coedges.push(CoedgeRecord {
238 id: next_topology_id,
239 edge_id: edge.id,
240 forward: edge.start_vertex_id == vertices[start_index].id,
241 pcurve: parameter_line(
242 start_uv.0 - minimum_u,
243 start_uv.1 - minimum_v,
244 end_uv.0 - minimum_u,
245 end_uv.1 - minimum_v,
246 )?,
247 });
248 next_topology_id += 1;
249 }
250 let loop_record = LoopRecord {
251 id: next_topology_id,
252 coedges,
253 };
254 next_topology_id += 1;
255 let face = FaceRecord {
256 id: next_topology_id,
257 surface,
258 same_sense: true,
259 loops: vec![loop_record],
260 name: None,
261 };
262 next_topology_id += 1;
263 Ok(face)
264 };
265
266 let base = (0..side_count).rev().collect::<Vec<_>>();
267 faces.push(make_face(&base)?);
268 for index in 0..side_count {
269 faces.push(make_face(&[index, (index + 1) % side_count, apex_index])?);
270 }
271 drop(make_face);
272 let solid = BrepSolid {
273 id: next_topology_id + 1,
274 vertices,
275 edges,
276 shells: vec![ShellRecord {
277 id: next_topology_id,
278 faces,
279 }],
280 genus: 0,
281 };
282 let issues = solid.validate();
283 if !issues.is_empty() {
284 return Err(format!(
285 "Rust pyramid builder produced invalid topology: {issues:?}"
286 ));
287 }
288 Ok(solid)
289}
290
291fn curve_to_plane_parameters(
292 curve: &NurbsCurve,
293 origin: Vec3,
294 x_axis: Vec3,
295 y_axis: Vec3,
296) -> Result<NurbsCurve, String> {
297 let mut points = Vec::with_capacity(curve.control_points.len());
298 for control_point in &curve.control_points {
299 let point = control_point.point()?;
300 let delta = point.sub(origin);
301 points.push(Vec4 {
302 x: delta.dot(x_axis) * control_point.w,
303 y: delta.dot(y_axis) * control_point.w,
304 z: 0.0,
305 w: control_point.w,
306 });
307 }
308 NurbsCurve::new(curve.degree, curve.knots.clone(), points)
309}
310
311pub fn make_cylinder_brep(
312 base: Vec3,
313 axis_direction: Vec3,
314 radius: f64,
315 height: f64,
316) -> Result<BrepSolid, String> {
317 if radius <= 0.0 || height <= 0.0 {
318 return Err("makeCylinderSolid: radius and height must be positive".into());
319 }
320 let axis = axis_direction.normalized()?;
321 let side = make_cylinder_surface(base, axis, radius, height)?;
322 let bottom_point = side.evaluate(0.0, 0.0)?;
323 let top_point = side.evaluate(0.0, 1.0)?;
324 let vertices = vec![
325 VertexRecord {
326 id: 1,
327 point: bottom_point,
328 },
329 VertexRecord {
330 id: 2,
331 point: top_point,
332 },
333 ];
334 let bottom_circle = side.iso_curve_v(0.0)?;
335 let top_circle = side.iso_curve_v(1.0)?;
336 let seam_line = side.iso_curve_u(0.0)?;
337 let edges = vec![
338 EdgeRecord {
339 id: 10,
340 curve: bottom_circle.clone(),
341 t0: 0.0,
342 t1: 1.0,
343 start_vertex_id: 1,
344 end_vertex_id: 1,
345 degenerate: false,
346 name: None,
347 },
348 EdgeRecord {
349 id: 11,
350 curve: top_circle.clone(),
351 t0: 0.0,
352 t1: 1.0,
353 start_vertex_id: 2,
354 end_vertex_id: 2,
355 degenerate: false,
356 name: None,
357 },
358 EdgeRecord {
359 id: 12,
360 curve: seam_line,
361 t0: 0.0,
362 t1: 1.0,
363 start_vertex_id: 1,
364 end_vertex_id: 2,
365 degenerate: false,
366 name: None,
367 },
368 ];
369
370 let side_loop = LoopRecord {
371 id: 100,
372 coedges: vec![
373 CoedgeRecord {
374 id: 101,
375 edge_id: 10,
376 forward: true,
377 pcurve: parameter_line(0.0, 0.0, 1.0, 0.0)?,
378 },
379 CoedgeRecord {
380 id: 102,
381 edge_id: 12,
382 forward: true,
383 pcurve: parameter_line(1.0, 0.0, 1.0, 1.0)?,
384 },
385 CoedgeRecord {
386 id: 103,
387 edge_id: 11,
388 forward: false,
389 pcurve: parameter_line(1.0, 1.0, 0.0, 1.0)?,
390 },
391 CoedgeRecord {
392 id: 104,
393 edge_id: 12,
394 forward: false,
395 pcurve: parameter_line(0.0, 1.0, 0.0, 0.0)?,
396 },
397 ],
398 };
399 let side_face = FaceRecord {
400 id: 105,
401 surface: side,
402 same_sense: true,
403 loops: vec![side_loop],
404 name: None,
405 };
406
407 let x_axis = axis.perpendicular()?;
408 let y_axis = axis.cross(x_axis).normalized()?;
409 let bottom_origin = base.add(x_axis.scale(-radius)).add(y_axis.scale(-radius));
410 let bottom_plane = make_plane(bottom_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
411 let bottom_pcurve =
412 curve_to_plane_parameters(&bottom_circle, bottom_origin, x_axis, y_axis)?.reversed()?;
413 let bottom_face = FaceRecord {
414 id: 108,
415 surface: bottom_plane,
416 same_sense: false,
417 loops: vec![LoopRecord {
418 id: 107,
419 coedges: vec![CoedgeRecord {
420 id: 106,
421 edge_id: 10,
422 forward: false,
423 pcurve: bottom_pcurve,
424 }],
425 }],
426 name: None,
427 };
428
429 let top_origin = base
430 .add(axis.scale(height))
431 .add(x_axis.scale(-radius))
432 .add(y_axis.scale(-radius));
433 let top_plane = make_plane(top_origin, x_axis, y_axis, 2.0 * radius, 2.0 * radius)?;
434 let top_pcurve = curve_to_plane_parameters(&top_circle, top_origin, x_axis, y_axis)?;
435 let top_face = FaceRecord {
436 id: 111,
437 surface: top_plane,
438 same_sense: true,
439 loops: vec![LoopRecord {
440 id: 110,
441 coedges: vec![CoedgeRecord {
442 id: 109,
443 edge_id: 11,
444 forward: true,
445 pcurve: top_pcurve,
446 }],
447 }],
448 name: None,
449 };
450
451 let solid = BrepSolid {
452 id: 114,
453 vertices,
454 edges,
455 shells: vec![ShellRecord {
456 id: 113,
457 faces: vec![side_face, bottom_face, top_face],
458 }],
459 genus: 0,
460 };
461 let issues = solid.validate();
462 if !issues.is_empty() {
463 return Err(format!(
464 "Rust cylinder builder produced invalid topology: {:?}",
465 issues
466 ));
467 }
468 Ok(solid)
469}