use std::collections::HashMap;
use std::f64::consts::{FRAC_PI_2, PI, TAU};
use brepkit_math::tolerance::Tolerance;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::edge::{Edge, EdgeCurve};
use brepkit_topology::face::{Face, FaceSurface};
use brepkit_topology::shell::Shell;
use brepkit_topology::solid::{Solid, SolidId};
use brepkit_topology::vertex::Vertex;
use brepkit_topology::wire::{OrientedEdge, Wire};
pub fn make_box(
topo: &mut Topology,
dx: f64,
dy: f64,
dz: f64,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if dx <= tol.linear || dy <= tol.linear || dz <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("box dimensions must be positive, got ({dx}, {dy}, {dz})"),
});
}
let v = [
topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, 0.0), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(dx, 0.0, 0.0), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(dx, dy, 0.0), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(0.0, dy, 0.0), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, dz), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(dx, 0.0, dz), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(dx, dy, dz), tol.linear)),
topo.add_vertex(Vertex::new(Point3::new(0.0, dy, dz), tol.linear)),
];
let eb0 = topo.add_edge(Edge::new(v[0], v[1], EdgeCurve::Line));
let eb1 = topo.add_edge(Edge::new(v[1], v[2], EdgeCurve::Line));
let eb2 = topo.add_edge(Edge::new(v[2], v[3], EdgeCurve::Line));
let eb3 = topo.add_edge(Edge::new(v[3], v[0], EdgeCurve::Line));
let et0 = topo.add_edge(Edge::new(v[4], v[5], EdgeCurve::Line));
let et1 = topo.add_edge(Edge::new(v[5], v[6], EdgeCurve::Line));
let et2 = topo.add_edge(Edge::new(v[6], v[7], EdgeCurve::Line));
let et3 = topo.add_edge(Edge::new(v[7], v[4], EdgeCurve::Line));
let ev0 = topo.add_edge(Edge::new(v[0], v[4], EdgeCurve::Line));
let ev1 = topo.add_edge(Edge::new(v[1], v[5], EdgeCurve::Line));
let ev2 = topo.add_edge(Edge::new(v[2], v[6], EdgeCurve::Line));
let ev3 = topo.add_edge(Edge::new(v[3], v[7], EdgeCurve::Line));
let mk_face = |topo: &mut Topology,
edges: [(brepkit_topology::edge::EdgeId, bool); 4],
normal: Vec3,
d: f64|
-> Result<brepkit_topology::face::FaceId, crate::OperationsError> {
let wire = Wire::new(
edges
.iter()
.map(|&(eid, fwd)| OrientedEdge::new(eid, fwd))
.collect(),
true,
)
.map_err(crate::OperationsError::Topology)?;
let wid = topo.add_wire(wire);
Ok(topo.add_face(Face::new(wid, vec![], FaceSurface::Plane { normal, d })))
};
let bottom = mk_face(
topo,
[(eb0, false), (eb3, false), (eb2, false), (eb1, false)],
Vec3::new(0.0, 0.0, -1.0),
0.0,
)?;
let top = mk_face(
topo,
[(et0, true), (et1, true), (et2, true), (et3, true)],
Vec3::new(0.0, 0.0, 1.0),
dz,
)?;
let front = mk_face(
topo,
[(eb0, true), (ev1, true), (et0, false), (ev0, false)],
Vec3::new(0.0, -1.0, 0.0),
0.0,
)?;
let back = mk_face(
topo,
[(eb2, true), (ev3, true), (et2, false), (ev2, false)],
Vec3::new(0.0, 1.0, 0.0),
dy,
)?;
let left = mk_face(
topo,
[(eb3, true), (ev0, true), (et3, false), (ev3, false)],
Vec3::new(-1.0, 0.0, 0.0),
0.0,
)?;
let right = mk_face(
topo,
[(eb1, true), (ev2, true), (et1, false), (ev1, false)],
Vec3::new(1.0, 0.0, 0.0),
dx,
)?;
let shell = Shell::new(vec![bottom, top, front, back, left, right])
.map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
Ok(topo.add_solid(Solid::new(shell_id, vec![])))
}
pub fn make_cylinder(
topo: &mut Topology,
radius: f64,
height: f64,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if radius <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("cylinder radius must be positive, got {radius}"),
});
}
if height <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("cylinder height must be positive, got {height}"),
});
}
let cyl_surface = brepkit_math::surfaces::CylindricalSurface::new(
Point3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
radius,
)
.map_err(crate::OperationsError::Math)?;
let v_bot = topo.add_vertex(Vertex::new(Point3::new(radius, 0.0, 0.0), tol.linear));
let v_top = topo.add_vertex(Vertex::new(Point3::new(radius, 0.0, height), tol.linear));
let bot_circle = brepkit_math::curves::Circle3D::new(
Point3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
radius,
)
.map_err(crate::OperationsError::Math)?;
let top_circle = brepkit_math::curves::Circle3D::new(
Point3::new(0.0, 0.0, height),
Vec3::new(0.0, 0.0, 1.0),
radius,
)
.map_err(crate::OperationsError::Math)?;
let e_bot_circle = topo.add_edge(Edge::new(v_bot, v_bot, EdgeCurve::Circle(bot_circle)));
let e_top_circle = topo.add_edge(Edge::new(v_top, v_top, EdgeCurve::Circle(top_circle)));
let e_seam = topo.add_edge(Edge::new(v_bot, v_top, EdgeCurve::Line));
let lateral_wire = Wire::new(
vec![
OrientedEdge::new(e_bot_circle, true),
OrientedEdge::new(e_seam, true),
OrientedEdge::new(e_top_circle, false),
OrientedEdge::new(e_seam, false),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let lateral_wid = topo.add_wire(lateral_wire);
let lateral_face = topo.add_face(Face::new(
lateral_wid,
vec![],
FaceSurface::Cylinder(cyl_surface),
));
let bot_cap_wire = Wire::new(vec![OrientedEdge::new(e_bot_circle, false)], true)
.map_err(crate::OperationsError::Topology)?;
let bot_wid = topo.add_wire(bot_cap_wire);
let bot_face = topo.add_face(Face::new(
bot_wid,
vec![],
FaceSurface::Plane {
normal: Vec3::new(0.0, 0.0, -1.0),
d: 0.0,
},
));
let top_cap_wire = Wire::new(vec![OrientedEdge::new(e_top_circle, true)], true)
.map_err(crate::OperationsError::Topology)?;
let top_wid = topo.add_wire(top_cap_wire);
let top_face = topo.add_face(Face::new(
top_wid,
vec![],
FaceSurface::Plane {
normal: Vec3::new(0.0, 0.0, 1.0),
d: height,
},
));
let shell = Shell::new(vec![lateral_face, bot_face, top_face])
.map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
Ok(topo.add_solid(Solid::new(shell_id, vec![])))
}
#[allow(clippy::too_many_lines)]
pub fn make_cone(
topo: &mut Topology,
bottom_radius: f64,
top_radius: f64,
height: f64,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if height <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("cone height must be positive, got {height}"),
});
}
if bottom_radius < 0.0 || top_radius < 0.0 {
return Err(crate::OperationsError::InvalidInput {
reason: format!(
"cone radii must be non-negative, got bottom={bottom_radius}, top={top_radius}"
),
});
}
if bottom_radius <= tol.linear && top_radius <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: "cone must have at least one non-zero radius".into(),
});
}
let (r_big, r_small, big_z, small_z, axis_sign) = if bottom_radius >= top_radius {
(bottom_radius, top_radius, 0.0, height, -1.0_f64)
} else {
(top_radius, bottom_radius, height, 0.0, 1.0_f64)
};
let half_angle = if r_small <= tol.linear {
height.atan2(r_big)
} else {
let axial_to_apex = r_small * height / (r_big - r_small);
(axial_to_apex + height).atan2(r_big)
};
if half_angle <= tol.angular || half_angle >= FRAC_PI_2 {
let face_id = make_trapezoid_xz_face(topo, bottom_radius, top_radius, 0.0, height)?;
return crate::revolve::revolve(
topo,
face_id,
Point3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
2.0 * PI,
);
}
let apex_pos = if r_small <= tol.linear {
Point3::new(0.0, 0.0, small_z)
} else {
let axial_to_apex = r_small * height / (r_big - r_small);
Point3::new(0.0, 0.0, small_z - axis_sign * axial_to_apex)
};
let axis_dir = Vec3::new(0.0, 0.0, axis_sign);
let cone_surface = brepkit_math::surfaces::ConicalSurface::new(apex_pos, axis_dir, half_angle)
.map_err(crate::OperationsError::Math)?;
let mut faces = Vec::new();
if r_small <= tol.linear {
let v_apex = topo.add_vertex(Vertex::new(apex_pos, tol.linear));
let v_base = topo.add_vertex(Vertex::new(Point3::new(r_big, 0.0, big_z), tol.linear));
let base_circle = brepkit_math::curves::Circle3D::new(
Point3::new(0.0, 0.0, big_z),
Vec3::new(0.0, 0.0, 1.0),
r_big,
)
.map_err(crate::OperationsError::Math)?;
let e_circle = topo.add_edge(Edge::new(v_base, v_base, EdgeCurve::Circle(base_circle)));
let e_seam = topo.add_edge(Edge::new(v_base, v_apex, EdgeCurve::Line));
let lateral_wire = Wire::new(
vec![
OrientedEdge::new(e_circle, true),
OrientedEdge::new(e_seam, true),
OrientedEdge::new(e_seam, false),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let lateral_wid = topo.add_wire(lateral_wire);
faces.push(topo.add_face(Face::new(
lateral_wid,
vec![],
FaceSurface::Cone(cone_surface),
)));
let cap_forward = axis_sign > 0.0;
let cap_wire = Wire::new(vec![OrientedEdge::new(e_circle, cap_forward)], true)
.map_err(crate::OperationsError::Topology)?;
let cap_wid = topo.add_wire(cap_wire);
let cap_normal = Vec3::new(0.0, 0.0, axis_sign);
faces.push(topo.add_face(Face::new(
cap_wid,
vec![],
FaceSurface::Plane {
normal: cap_normal,
d: big_z,
},
)));
} else {
let v_bot = topo.add_vertex(Vertex::new(
Point3::new(bottom_radius, 0.0, 0.0),
tol.linear,
));
let v_top = topo.add_vertex(Vertex::new(
Point3::new(top_radius, 0.0, height),
tol.linear,
));
let bot_circle = brepkit_math::curves::Circle3D::new(
Point3::new(0.0, 0.0, 0.0),
Vec3::new(0.0, 0.0, 1.0),
bottom_radius,
)
.map_err(crate::OperationsError::Math)?;
let top_circle = brepkit_math::curves::Circle3D::new(
Point3::new(0.0, 0.0, height),
Vec3::new(0.0, 0.0, 1.0),
top_radius,
)
.map_err(crate::OperationsError::Math)?;
let e_bot = topo.add_edge(Edge::new(v_bot, v_bot, EdgeCurve::Circle(bot_circle)));
let e_top = topo.add_edge(Edge::new(v_top, v_top, EdgeCurve::Circle(top_circle)));
let e_seam = topo.add_edge(Edge::new(v_bot, v_top, EdgeCurve::Line));
let lateral_wire = Wire::new(
vec![
OrientedEdge::new(e_bot, true),
OrientedEdge::new(e_seam, true),
OrientedEdge::new(e_top, false),
OrientedEdge::new(e_seam, false),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let lateral_wid = topo.add_wire(lateral_wire);
faces.push(topo.add_face(Face::new(
lateral_wid,
vec![],
FaceSurface::Cone(cone_surface),
)));
let bot_cap_wire = Wire::new(vec![OrientedEdge::new(e_bot, false)], true)
.map_err(crate::OperationsError::Topology)?;
let bot_wid = topo.add_wire(bot_cap_wire);
faces.push(topo.add_face(Face::new(
bot_wid,
vec![],
FaceSurface::Plane {
normal: Vec3::new(0.0, 0.0, -1.0),
d: 0.0,
},
)));
let top_cap_wire = Wire::new(vec![OrientedEdge::new(e_top, true)], true)
.map_err(crate::OperationsError::Topology)?;
let top_wid = topo.add_wire(top_cap_wire);
faces.push(topo.add_face(Face::new(
top_wid,
vec![],
FaceSurface::Plane {
normal: Vec3::new(0.0, 0.0, 1.0),
d: height,
},
)));
}
let shell = Shell::new(faces).map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
Ok(topo.add_solid(Solid::new(shell_id, vec![])))
}
#[allow(clippy::too_many_lines)]
pub fn make_sphere(
topo: &mut Topology,
radius: f64,
segments: usize,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if radius <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("sphere radius must be positive, got {radius}"),
});
}
if segments < 4 {
return Err(crate::OperationsError::InvalidInput {
reason: format!("sphere needs at least 4 segments, got {segments}"),
});
}
let surface_n =
brepkit_math::surfaces::SphericalSurface::new(Point3::new(0.0, 0.0, 0.0), radius)
.map_err(crate::OperationsError::Math)?;
let surface_s =
brepkit_math::surfaces::SphericalSurface::new(Point3::new(0.0, 0.0, 0.0), radius)
.map_err(crate::OperationsError::Math)?;
let eq_verts: Vec<_> = (0..segments)
.map(|i| {
let theta = TAU * i as f64 / segments as f64;
let pt = Point3::new(radius * theta.cos(), radius * theta.sin(), 0.0);
topo.add_vertex(Vertex::new(pt, tol.linear))
})
.collect();
let eq_edges: Vec<_> = (0..segments)
.map(|i| {
let j = (i + 1) % segments;
topo.add_edge(Edge::new(eq_verts[i], eq_verts[j], EdgeCurve::Line))
})
.collect();
let north_wire = Wire::new(
eq_edges
.iter()
.map(|&eid| OrientedEdge::new(eid, true))
.collect(),
true,
)
.map_err(crate::OperationsError::Topology)?;
let north_wid = topo.add_wire(north_wire);
let north_face = topo.add_face(Face::new(north_wid, vec![], FaceSurface::Sphere(surface_n)));
let south_wire = Wire::new(
eq_edges
.iter()
.rev()
.map(|&eid| OrientedEdge::new(eid, false))
.collect(),
true,
)
.map_err(crate::OperationsError::Topology)?;
let south_wid = topo.add_wire(south_wire);
let south_face = topo.add_face(Face::new(south_wid, vec![], FaceSurface::Sphere(surface_s)));
let shell =
Shell::new(vec![north_face, south_face]).map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
Ok(topo.add_solid(Solid::new(shell_id, vec![])))
}
pub fn make_torus(
topo: &mut Topology,
major_radius: f64,
minor_radius: f64,
segments: usize,
) -> Result<SolidId, crate::OperationsError> {
let tol = Tolerance::new();
if major_radius <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("torus major radius must be positive, got {major_radius}"),
});
}
if minor_radius <= tol.linear {
return Err(crate::OperationsError::InvalidInput {
reason: format!("torus minor radius must be positive, got {minor_radius}"),
});
}
if minor_radius >= major_radius {
return Err(crate::OperationsError::InvalidInput {
reason: format!(
"torus minor radius ({minor_radius}) must be less than major radius ({major_radius})"
),
});
}
if segments < 4 {
return Err(crate::OperationsError::InvalidInput {
reason: format!("torus needs at least 4 segments, got {segments}"),
});
}
let surface = brepkit_math::surfaces::ToroidalSurface::new(
Point3::new(0.0, 0.0, 0.0),
major_radius,
minor_radius,
)
.map_err(crate::OperationsError::Math)?;
let v0 = topo.add_vertex(Vertex::new(
Point3::new(major_radius + minor_radius, 0.0, 0.0),
tol.linear,
));
let ea = topo.add_edge(Edge::new(v0, v0, EdgeCurve::Line));
let eb = topo.add_edge(Edge::new(v0, v0, EdgeCurve::Line));
let wire = Wire::new(
vec![
OrientedEdge::new(ea, true),
OrientedEdge::new(eb, true),
OrientedEdge::new(ea, false),
OrientedEdge::new(eb, false),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let wid = topo.add_wire(wire);
let face_id = topo.add_face(Face::new(wid, vec![], FaceSurface::Torus(surface)));
let shell = Shell::new(vec![face_id]).map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
Ok(topo.add_solid(Solid::new(shell_id, vec![])))
}
fn make_trapezoid_xz_face(
topo: &mut Topology,
bottom_radius: f64,
top_radius: f64,
z_bottom: f64,
z_top: f64,
) -> Result<brepkit_topology::face::FaceId, crate::OperationsError> {
let tol = Tolerance::new();
let v0 = topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, z_bottom), tol.linear));
let v1 = topo.add_vertex(Vertex::new(
Point3::new(bottom_radius, 0.0, z_bottom),
tol.linear,
));
let v2 = topo.add_vertex(Vertex::new(Point3::new(top_radius, 0.0, z_top), tol.linear));
let v3 = topo.add_vertex(Vertex::new(Point3::new(0.0, 0.0, z_top), tol.linear));
let e0 = topo.add_edge(Edge::new(v0, v1, EdgeCurve::Line));
let e1 = topo.add_edge(Edge::new(v1, v2, EdgeCurve::Line));
let e2 = topo.add_edge(Edge::new(v2, v3, EdgeCurve::Line));
let e3 = topo.add_edge(Edge::new(v3, v0, EdgeCurve::Line));
let wire = Wire::new(
vec![
OrientedEdge::new(e0, true),
OrientedEdge::new(e1, true),
OrientedEdge::new(e2, true),
OrientedEdge::new(e3, true),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let wid = topo.add_wire(wire);
let normal = Vec3::new(0.0, -1.0, 0.0);
Ok(topo.add_face(Face::new(
wid,
vec![],
FaceSurface::Plane { normal, d: 0.0 },
)))
}
#[allow(dead_code)]
fn make_rect_xz_face(
topo: &mut Topology,
x0: f64,
z0: f64,
x1: f64,
z1: f64,
) -> Result<brepkit_topology::face::FaceId, crate::OperationsError> {
let tol = Tolerance::new();
let v0 = topo.add_vertex(Vertex::new(Point3::new(x0, 0.0, z0), tol.linear));
let v1 = topo.add_vertex(Vertex::new(Point3::new(x1, 0.0, z0), tol.linear));
let v2 = topo.add_vertex(Vertex::new(Point3::new(x1, 0.0, z1), tol.linear));
let v3 = topo.add_vertex(Vertex::new(Point3::new(x0, 0.0, z1), tol.linear));
let e0 = topo.add_edge(Edge::new(v0, v1, EdgeCurve::Line));
let e1 = topo.add_edge(Edge::new(v1, v2, EdgeCurve::Line));
let e2 = topo.add_edge(Edge::new(v2, v3, EdgeCurve::Line));
let e3 = topo.add_edge(Edge::new(v3, v0, EdgeCurve::Line));
let wire = Wire::new(
vec![
OrientedEdge::new(e0, true),
OrientedEdge::new(e1, true),
OrientedEdge::new(e2, true),
OrientedEdge::new(e3, true),
],
true,
)
.map_err(crate::OperationsError::Topology)?;
let wid = topo.add_wire(wire);
let normal = Vec3::new(0.0, -1.0, 0.0);
let d = 0.0;
Ok(topo.add_face(Face::new(wid, vec![], FaceSurface::Plane { normal, d })))
}
pub fn make_convex_hull(
topo: &mut Topology,
points: &[Point3],
) -> Result<SolidId, crate::OperationsError> {
let hull = brepkit_math::convex_hull::convex_hull_3d(points).ok_or_else(|| {
crate::OperationsError::InvalidInput {
reason: "points are coplanar or degenerate — cannot form a 3D convex hull".into(),
}
})?;
let tol = Tolerance::new();
let vertex_ids: Vec<_> = hull
.vertices
.iter()
.map(|p| topo.add_vertex(Vertex::new(*p, tol.linear)))
.collect();
let mut edge_map: HashMap<(usize, usize), brepkit_topology::edge::EdgeId> = HashMap::new();
let mut face_ids = Vec::with_capacity(hull.faces.len());
for &[a, b, c] in &hull.faces {
let va = vertex_ids[a];
let vb = vertex_ids[b];
let vc = vertex_ids[c];
let pairs = [(a, b, va, vb), (b, c, vb, vc), (c, a, vc, va)];
let mut oriented_edges = Vec::with_capacity(3);
for (idx_a, idx_b, v_a, v_b) in pairs {
let key = (idx_a.min(idx_b), idx_a.max(idx_b));
let (eid, forward) = if let Some(&existing) = edge_map.get(&key) {
(existing, false)
} else {
let eid = topo.add_edge(Edge::new(v_a, v_b, EdgeCurve::Line));
edge_map.insert(key, eid);
(eid, true)
};
oriented_edges.push(OrientedEdge::new(eid, forward));
}
let wire = Wire::new(oriented_edges, true).map_err(crate::OperationsError::Topology)?;
let wid = topo.add_wire(wire);
let pa = hull.vertices[a];
let pb = hull.vertices[b];
let pc = hull.vertices[c];
let ab = pb - pa;
let ac = pc - pa;
let normal = ab.cross(ac).normalize().unwrap_or(Vec3::new(0.0, 0.0, 1.0));
let d = normal
.x()
.mul_add(pa.x(), normal.y().mul_add(pa.y(), normal.z() * pa.z()));
let fid = topo.add_face(Face::new(wid, vec![], FaceSurface::Plane { normal, d }));
face_ids.push(fid);
}
let shell = Shell::new(face_ids).map_err(crate::OperationsError::Topology)?;
let shell_id = topo.add_shell(shell);
let solid = Solid::new(shell_id, vec![]);
Ok(topo.add_solid(solid))
}
pub fn make_minkowski_sum(
topo: &mut Topology,
a: SolidId,
b: SolidId,
) -> Result<SolidId, crate::OperationsError> {
use brepkit_topology::explorer::solid_vertices;
let point_of = |topo: &Topology, ids: &[brepkit_topology::vertex::VertexId]| {
ids.iter()
.map(|&v| topo.vertex(v).map(brepkit_topology::vertex::Vertex::point))
.collect::<Result<Vec<Point3>, _>>()
};
let a_pts = point_of(topo, &solid_vertices(topo, a)?)?;
let b_pts = point_of(topo, &solid_vertices(topo, b)?)?;
if a_pts.is_empty() || b_pts.is_empty() {
return Err(crate::OperationsError::InvalidInput {
reason: "minkowski sum requires two non-empty solids".into(),
});
}
let capacity = a_pts.len().checked_mul(b_pts.len()).ok_or_else(|| {
crate::OperationsError::InvalidInput {
reason: "minkowski sum input too large: vertex-count product overflows".into(),
}
})?;
let mut sums = Vec::with_capacity(capacity);
for &p in &a_pts {
for &q in &b_pts {
sums.push(Point3::new(p.x() + q.x(), p.y() + q.y(), p.z() + q.z()));
}
}
make_convex_hull(topo, &sums)
}
#[cfg(test)]
mod tests;