Kernel: pole-free six-patch spheres; vendored truck-shapeops with robust meshing and nearest-parameter loop projection (sphere and box booleans work); least-squares sphere classification
Oracle: 25/29 (14 and 15 now match Fusion). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
7ef3426cb3
commit
043a542e29
34 files changed
+6228
-43
No files matched your search
@@ -37,17 +37,16 @@ pub struct FaceInfo {
|
||||
type MeshedShell = truck_topology::Shell<mt::Point3, PolylineCurve<mt::Point3>, Option<PolygonMesh>>;
|
||||
|
||||
/// Triangulate each shell (not the solid: truck's solid constructor panics on the open or
|
||||
/// non-manifold shells an import can produce). Faces whose boundary is not exactly on the
|
||||
/// surface (imported geometry within the file's tolerance) fail the exact parameter search;
|
||||
/// such shells are meshed again projecting onto the surface.
|
||||
/// non-manifold shells an import can produce). Boundaries that lie on their surface only within
|
||||
/// tolerance (imports, boolean results) are projected onto it.
|
||||
fn mesh_shells(solid: &Solid, tol: f64) -> Vec<MeshedShell> {
|
||||
solid
|
||||
.boundaries()
|
||||
.iter()
|
||||
.map(|sh| {
|
||||
let m = sh.triangulation(tol);
|
||||
if m.face_iter().any(|f| f.surface().is_none()) { sh.robust_triangulation(tol) } else { m }
|
||||
})
|
||||
// The robust search tries the exact one first, so exact geometry costs nothing extra; a
|
||||
// face can also come back as an empty mesh rather than none (boolean results with
|
||||
// approximate intersection curves), so always use it.
|
||||
.map(|sh| sh.robust_triangulation(tol))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
+100
-13
@@ -140,6 +140,14 @@ pub fn revolve(plane: &Plane, regions: &[Region2], axis_origin: Vec2, axis_dir:
|
||||
.collect(),
|
||||
};
|
||||
let r = &Region2 { outer: snap_loop(&r.outer), holes: r.holes.iter().map(snap_loop).collect() };
|
||||
// A half disc on the axis revolved fully is a sphere: build it without poles.
|
||||
if angle.abs() >= std::f64::consts::TAU - 1e-9
|
||||
&& r.holes.is_empty()
|
||||
&& let Some((c, rad)) = half_disc(&r.outer, axis_origin, d)
|
||||
{
|
||||
out.push(sphere_patches(plane.to_world(c), rad)?);
|
||||
continue;
|
||||
}
|
||||
// A full revolution of a profile with one edge on the axis: sweep the rest of the loop
|
||||
// as a cone-like shell (no zero-area faces, which booleans can't handle).
|
||||
if angle.abs() >= std::f64::consts::TAU - 1e-9
|
||||
@@ -191,19 +199,7 @@ pub fn cylinder(base: Vec3, axis: Vec3, radius: f64, height: f64) -> Result<Body
|
||||
|
||||
/// Sphere: revolve a half disc.
|
||||
pub fn sphere(center: Vec3, radius: f64) -> Result<Body> {
|
||||
let radius = finite(radius, "radius")?;
|
||||
if radius <= 1e-6 {
|
||||
return Err(KernelError::Invalid("radius must be positive".into()));
|
||||
}
|
||||
let plane = Plane { origin: center, ..Plane::XZ };
|
||||
let half = Loop2 {
|
||||
segs: vec![
|
||||
Seg2::Arc { center: Vec2::ZERO, radius, start: -std::f64::consts::FRAC_PI_2, sweep: std::f64::consts::PI },
|
||||
Seg2::Line { a: Vec2::new(0.0, radius), b: Vec2::new(0.0, -radius) },
|
||||
],
|
||||
};
|
||||
let mut v = revolve(&plane, &[Region2 { outer: half, holes: vec![] }], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?;
|
||||
v.pop().ok_or_else(|| KernelError::Failed("sphere".into()))
|
||||
sphere_patches(center, radius)
|
||||
}
|
||||
|
||||
/// Torus about the Z axis through `center`.
|
||||
@@ -313,6 +309,23 @@ pub fn extrude_tapered(plane: &Plane, region: &Region2, length: f64, dir_sign: f
|
||||
}
|
||||
|
||||
/// Revolve 360° a loop that has exactly one line segment on the axis, without degenerate faces.
|
||||
/// A loop made of a half circle and its diameter on the axis: (centre, radius).
|
||||
fn half_disc(lp: &Loop2, axis_origin: Vec2, d: Vec2) -> Option<(Vec2, f64)> {
|
||||
let on_axis = |p: Vec2| d.cross(p - axis_origin).abs() < 1e-7 * (1.0 + p.len());
|
||||
let mut arcs = Vec::new();
|
||||
for s in &lp.segs {
|
||||
match *s {
|
||||
Seg2::Line { a, b } if on_axis(a) && on_axis(b) => {}
|
||||
Seg2::Arc { center, radius, sweep, .. } if on_axis(center) => arcs.push((center, radius, sweep)),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
let (c, r, _) = *arcs.first()?;
|
||||
let total: f64 = arcs.iter().map(|a| a.2.abs()).sum();
|
||||
let same = arcs.iter().all(|(c2, r2, _)| c2.dist(c) < 1e-7 * (1.0 + r) && (r2 - r).abs() < 1e-7 * (1.0 + r));
|
||||
(same && (total - std::f64::consts::PI).abs() < 1e-6).then_some((c, r))
|
||||
}
|
||||
|
||||
fn revolve_touching_axis(plane: &Plane, lp: &Loop2, axis_origin: Vec2, d: Vec2) -> Result<Option<Body>> {
|
||||
let on_axis = |p: Vec2| d.cross(p - axis_origin).abs() < 1e-9;
|
||||
let n = lp.segs.len();
|
||||
@@ -654,3 +667,77 @@ fn uniform_wire(plane: &Plane, lp: &Loop2) -> Result<mt::Wire> {
|
||||
}
|
||||
Ok(edges.into())
|
||||
}
|
||||
|
||||
/// A sphere as six patches (the cube's faces projected onto it) on revolved surfaces whose poles
|
||||
/// and seams lie outside each patch. No face has a singular point, which the boolean needs; the
|
||||
/// patches are one analytic sphere, so measured topology counts it as one face.
|
||||
pub fn sphere_patches(center: Vec3, radius: f64) -> Result<Body> {
|
||||
sphere_patches_rotated(center, radius, [Vec3::X, Vec3::Y, Vec3::Z])
|
||||
}
|
||||
|
||||
/// [`sphere_patches`] with the patch layout turned to the frame `rot` (orthonormal columns).
|
||||
pub fn sphere_patches_rotated(center: Vec3, radius: f64, rot: [Vec3; 3]) -> Result<Body> {
|
||||
let radius = finite(radius, "radius")?;
|
||||
let turn = |v: Vec3| rot[0] * v.x + rot[1] * v.y + rot[2] * v.z;
|
||||
if radius <= 1e-6 || !center.is_finite() {
|
||||
return Err(KernelError::Invalid("radius must be positive".into()));
|
||||
}
|
||||
let k = radius / 3f64.sqrt();
|
||||
let corner = |sx: f64, sy: f64, sz: f64| center + turn(Vec3::new(sx * k, sy * k, sz * k));
|
||||
// Cube faces: outward direction, rotation axis for the patch's surface (perpendicular to it),
|
||||
// and the four corner sign triples counter-clockwise seen from outside.
|
||||
let faces: [(Vec3, Vec3, [[f64; 3]; 4]); 6] = [
|
||||
(Vec3::X, Vec3::Z, [[1.0, -1.0, -1.0], [1.0, 1.0, -1.0], [1.0, 1.0, 1.0], [1.0, -1.0, 1.0]]),
|
||||
(-Vec3::X, Vec3::Z, [[-1.0, 1.0, -1.0], [-1.0, -1.0, -1.0], [-1.0, -1.0, 1.0], [-1.0, 1.0, 1.0]]),
|
||||
(Vec3::Y, Vec3::Z, [[1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], [-1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]),
|
||||
(-Vec3::Y, Vec3::Z, [[-1.0, -1.0, -1.0], [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], [-1.0, -1.0, 1.0]]),
|
||||
(Vec3::Z, Vec3::X, [[-1.0, -1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 1.0, 1.0]]),
|
||||
(-Vec3::Z, Vec3::X, [[-1.0, 1.0, -1.0], [1.0, 1.0, -1.0], [1.0, -1.0, -1.0], [-1.0, -1.0, -1.0]]),
|
||||
];
|
||||
guard("sphere", || {
|
||||
let key = |s: [f64; 3]| ((s[0] > 0.0) as usize) | (((s[1] > 0.0) as usize) << 1) | (((s[2] > 0.0) as usize) << 2);
|
||||
let mut verts: std::collections::HashMap<usize, mt::Vertex> = std::collections::HashMap::new();
|
||||
let mut vert = |s: [f64; 3]| verts.entry(key(s)).or_insert_with(|| builder::vertex(p3(corner(s[0], s[1], s[2])))).clone();
|
||||
let mut edges: std::collections::HashMap<(usize, usize), mt::Edge> = std::collections::HashMap::new();
|
||||
let mut out: Vec<mt::Face> = Vec::new();
|
||||
for (dir, axis, cs) in faces {
|
||||
let (dir, axis) = (turn(dir), turn(axis));
|
||||
let mut wire: Vec<mt::Edge> = Vec::new();
|
||||
for i in 0..4 {
|
||||
let (a, b) = (cs[i], cs[(i + 1) % 4]);
|
||||
let (ka, kb) = (key(a), key(b));
|
||||
let e = match edges.get(&(ka.min(kb), ka.max(kb))) {
|
||||
Some(e) => e.clone(),
|
||||
None => {
|
||||
let (lo, hi) = if ka < kb { (a, b) } else { (b, a) };
|
||||
let (va, vb) = (vert(lo), vert(hi));
|
||||
let mid = (corner(lo[0], lo[1], lo[2]) + corner(hi[0], hi[1], hi[2])) * 0.5 - center;
|
||||
let transit = center + mid.normalized().unwrap_or(dir) * radius;
|
||||
let e = builder::circle_arc(&va, &vb, p3(transit));
|
||||
edges.insert((ka.min(kb), ka.max(kb)), e.clone());
|
||||
e
|
||||
}
|
||||
};
|
||||
wire.push(if ka < kb { e } else { e.inverse() });
|
||||
}
|
||||
// The meridian runs pole to pole through the side away from the patch.
|
||||
let (np, sp, back) = (center + axis * radius, center - axis * radius, center - dir * radius);
|
||||
let meridian = builder::circle_arc(&builder::vertex(p3(np)), &builder::vertex(p3(sp)), p3(back));
|
||||
let mut surface =
|
||||
mt::Surface::RevolutedCurve(mt::Processor::new(mt::RevolutedCurve::by_revolution(meridian.oriented_curve(), p3(center), v3(axis))));
|
||||
// Outward normal at the patch centre.
|
||||
let probe = p3(center + dir * radius);
|
||||
use mt::{ParametricSurface3D, SearchParameter};
|
||||
if let Some((u, v)) = surface.search_parameter(probe, mt::SPHint2D::None, 100) {
|
||||
let n = surface.normal(u, v);
|
||||
if mt::InnerSpace::dot(n, v3(dir)) < 0.0 {
|
||||
mt::Invertible::invert(&mut surface);
|
||||
}
|
||||
}
|
||||
out.push(mt::Face::new(vec![wire.into()], surface));
|
||||
}
|
||||
let shell: mt::Shell = out.into();
|
||||
let solid = Solid::try_new(vec![shell]).map_err(|e| KernelError::Failed(format!("sphere: {e}")))?;
|
||||
Body::new(solid)
|
||||
})
|
||||
}
|
||||
@@ -246,6 +246,20 @@ fn planar_booleans_with_coincident_faces() {
|
||||
assert!(rel(measure(&i).unwrap().volume, 250.0) < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sphere_booleans() {
|
||||
let s = sphere(Vec3::ZERO, 15.0).unwrap();
|
||||
let m = measure(&s).unwrap();
|
||||
assert!(rel(m.volume, 4.0 / 3.0 * PI * 3375.0) < 1e-3, "{}", m.volume);
|
||||
assert_eq!((m.merged.faces, m.merged.edges), (1, 0));
|
||||
// A ball cut out of the top face of a slab (oracle 14).
|
||||
let b = box_solid(Vec3::new(-25.0, -25.0, -20.0), Vec3::new(25.0, 25.0, 0.0)).unwrap();
|
||||
let c = boolean(&b, &s, BoolOp::Cut).unwrap().unwrap();
|
||||
let m = measure(&c).unwrap();
|
||||
assert!(rel(m.volume, 50.0 * 50.0 * 20.0 - 2.0 / 3.0 * PI * 3375.0) < 1e-3, "{}", m.volume);
|
||||
assert_eq!((m.merged.faces, m.merged.edges, m.merged.vertices), (7, 13, 9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_convergence() {
|
||||
@@ -456,3 +470,132 @@ fn debug_cbore_seq() {
|
||||
println!(" swapped: {:?}", boolean(&c2, &t1, BoolOp::Cut).map(|o| o.map(|x| measure(&x).unwrap().volume)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_truck_sphere() {
|
||||
use truck_modeling::{self as mt, builder};
|
||||
let mk = |c: Vec3, r: f64, axis: Vec3| -> Body {
|
||||
let ax = axis.normalized().unwrap();
|
||||
let side = ax.any_perp();
|
||||
let v0 = builder::vertex(crate::body::p3(c + ax * r));
|
||||
let arc = builder::rsweep(&v0, crate::body::p3(c), crate::body::v3(side), mt::Rad(PI));
|
||||
let shell = builder::rsweep(&arc, crate::body::p3(c), crate::body::v3(ax), mt::Rad(2.0 * PI));
|
||||
Body::new(mt::Solid::new(vec![shell])).unwrap()
|
||||
};
|
||||
let b = box_solid(Vec3::new(-25.0, -25.0, -20.0), Vec3::new(25.0, 25.0, 0.0)).unwrap();
|
||||
for (name, axis) in [("z", Vec3::Z), ("x", Vec3::X), ("diag", Vec3::new(0.3, 0.5, 0.8))] {
|
||||
let sp = mk(Vec3::ZERO, 15.0, axis);
|
||||
println!("{name}: faces {} vol {}", sp.face_count(), measure(&sp).unwrap().volume);
|
||||
for op in [BoolOp::Cut, BoolOp::Intersect, BoolOp::Union] {
|
||||
let r = guard("t", || {
|
||||
let (sa, mut sb) = (b.deep_copy(), sp.deep_copy());
|
||||
let res = match op {
|
||||
BoolOp::Cut => {
|
||||
sb.not();
|
||||
truck_shapeops::and(&sa, &sb, 0.01)
|
||||
}
|
||||
BoolOp::Intersect => truck_shapeops::and(&sa, &sb, 0.01),
|
||||
BoolOp::Union => truck_shapeops::or(&sa, &sb, 0.01),
|
||||
};
|
||||
Ok(res.map(|s| s.face_iter().count()))
|
||||
});
|
||||
println!(" {op:?}: {r:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_sphere_patches() {
|
||||
let sp = crate::build::sphere_patches(Vec3::ZERO, 15.0).unwrap();
|
||||
let m = measure(&sp).unwrap();
|
||||
println!("vol {} want {} area {} merged {:?}", m.volume, 4.0 / 3.0 * PI * 3375.0, m.area, m.merged);
|
||||
let b = box_solid(Vec3::new(-25.0, -25.0, -20.0), Vec3::new(25.0, 25.0, 0.0)).unwrap();
|
||||
for op in [BoolOp::Cut, BoolOp::Intersect, BoolOp::Union] {
|
||||
let r = boolean(&b, &sp, op).map(|o| {
|
||||
o.map(|x| {
|
||||
let m = measure(&x).unwrap();
|
||||
(m.volume, m.merged)
|
||||
})
|
||||
});
|
||||
println!("{op:?}: {r:?}");
|
||||
}
|
||||
let c = box_solid(Vec3::new(-20.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 20.0)).unwrap();
|
||||
let a = 0.3f64;
|
||||
let (ca, sa) = (a.cos(), a.sin());
|
||||
let b2 = 0.5f64;
|
||||
let (cb, sb) = (b2.cos(), b2.sin());
|
||||
// Rz(a) * Rx(b)
|
||||
let rot = [Vec3::new(ca, sa, 0.0), Vec3::new(-sa * cb, ca * cb, sb), Vec3::new(sa * sb, -ca * sb, cb)];
|
||||
for (name, s2) in [
|
||||
("plain", crate::build::sphere_patches(Vec3::ZERO, 25.0).unwrap()),
|
||||
("turned", crate::build::sphere_patches_rotated(Vec3::ZERO, 25.0, rot).unwrap()),
|
||||
] {
|
||||
println!("{name} vol {}", measure(&s2).unwrap().volume);
|
||||
println!(
|
||||
"{name} cube and sphere: {:?}",
|
||||
boolean(&c, &s2, BoolOp::Intersect).map(|o| o.map(|x| {
|
||||
let m = measure(&x).unwrap();
|
||||
(m.volume, m.merged)
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_sphere_cases() {
|
||||
let s = crate::build::sphere_patches(Vec3::ZERO, 25.0).unwrap();
|
||||
let cases = [
|
||||
("one plane", Vec3::new(-100.0, -100.0, -100.0), Vec3::new(100.0, 100.0, 20.0)),
|
||||
("two planes", Vec3::new(-100.0, -100.0, -20.0), Vec3::new(100.0, 100.0, 20.0)),
|
||||
("three", Vec3::new(-100.0, -100.0, -20.0), Vec3::new(20.0, 100.0, 20.0)),
|
||||
("four", Vec3::new(-100.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 20.0)),
|
||||
("five", Vec3::new(-20.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 100.0)),
|
||||
("cube", Vec3::new(-20.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 20.0)),
|
||||
("cube big sphere-ish", Vec3::new(-24.0, -24.0, -24.0), Vec3::new(24.0, 24.0, 24.0)),
|
||||
("cube off", Vec3::new(-20.3, -19.7, -20.1), Vec3::new(19.9, 20.2, 19.8)),
|
||||
];
|
||||
for (name, lo, hi) in cases {
|
||||
let b = box_solid(lo, hi).unwrap();
|
||||
for op in [BoolOp::Intersect, BoolOp::Cut] {
|
||||
let r = boolean(&b, &s, op).map(|o| o.map(|x| measure(&x).unwrap().volume));
|
||||
println!("{name} {op:?}: {:?}", r.map_err(|e| e.to_string().chars().take(60).collect::<String>()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_sphere_rotations() {
|
||||
let b = box_solid(Vec3::new(-20.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 20.0)).unwrap();
|
||||
for k in 0..8 {
|
||||
let (a, c) = (0.37 * k as f64 + 0.1, 0.61 * k as f64 + 0.2);
|
||||
let (ca, sa, cc, sc) = (a.cos(), a.sin(), c.cos(), c.sin());
|
||||
let rot = [Vec3::new(ca, sa, 0.0), Vec3::new(-sa * cc, ca * cc, sc), Vec3::new(sa * sc, -ca * sc, cc)];
|
||||
let s = crate::build::sphere_patches_rotated(Vec3::ZERO, 25.0, rot).unwrap();
|
||||
let r = guard("t", || {
|
||||
let res = truck_shapeops::and(&b.deep_copy(), &s.deep_copy(), 0.01);
|
||||
Ok(res.map(|x| x.face_iter().count()))
|
||||
});
|
||||
println!("rot {k}: {r:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn debug_cube_sphere_measure() {
|
||||
let b = box_solid(Vec3::new(-20.0, -20.0, -20.0), Vec3::new(20.0, 20.0, 20.0)).unwrap();
|
||||
let s = crate::build::sphere_patches(Vec3::ZERO, 25.0).unwrap();
|
||||
let r = guard("t", || Ok(truck_shapeops::and(&b.deep_copy(), &s.deep_copy(), 0.05))).unwrap().unwrap();
|
||||
let body = Body::new(r).unwrap();
|
||||
for tol in [0.5, 0.1, 0.02] {
|
||||
let m = body.tessellate(tol).unwrap();
|
||||
let mm = m.measure();
|
||||
println!("tol {tol}: vol {} area {} tris {}", mm.volume, mm.area, m.triangles.len());
|
||||
}
|
||||
for f in body.faces(0.05).unwrap() {
|
||||
println!("face {} area {:.2} c {:?} planar {:?}", f.index, f.area, f.centroid, f.plane_normal.is_some());
|
||||
}
|
||||
}
|
||||
+45
-17
@@ -135,23 +135,10 @@ fn classify(pts: &[Vec3], nrm: &[Vec3], tol: f64, id: usize) -> Surf {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sphere: points equidistant from the point where normals meet.
|
||||
let c_est = {
|
||||
let mut acc = Vec3::ZERO;
|
||||
let mut k = 0.0;
|
||||
for (p, n) in pts.iter().zip(nrm) {
|
||||
// Assume radius from the first pair.
|
||||
let r0 = (p0 - *p).len() / ((n0 - *n).len().max(1e-12));
|
||||
if r0.is_finite() && r0 > 0.0 && (n0 - *n).len() > 0.1 {
|
||||
acc += *p - *n * r0;
|
||||
k += 1.0;
|
||||
}
|
||||
}
|
||||
(k > 0.0).then(|| acc / k)
|
||||
};
|
||||
if let Some(c) = c_est {
|
||||
let r = pts.iter().map(|p| p.dist(c)).sum::<f64>() / pts.len().max(1) as f64;
|
||||
if pts.iter().all(|p| (p.dist(c) - r).abs() < tol.max(r * 2e-3)) {
|
||||
// Sphere: algebraic least-squares fit |p|² = 2c·p + k, then every point at the same distance.
|
||||
if let Some((c, r)) = fit_sphere(pts) {
|
||||
let spread = pts.iter().map(|p| p.dist(p0)).fold(0.0, f64::max);
|
||||
if spread > tol && pts.iter().all(|p| (p.dist(c) - r).abs() < tol.max(r * 2e-3)) && r < spread * 1e4 {
|
||||
return Surf::Sphere { c, r };
|
||||
}
|
||||
}
|
||||
@@ -278,6 +265,47 @@ pub fn seam_flags(b: &Body, mesh: &Mesh) -> Vec<bool> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Least-squares sphere through points: centre and radius.
|
||||
fn fit_sphere(pts: &[Vec3]) -> Option<(Vec3, f64)> {
|
||||
if pts.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
// Centre the data for conditioning.
|
||||
let m = pts.iter().fold(Vec3::ZERO, |a, p| a + *p) / pts.len() as f64;
|
||||
let mut a = [[0.0f64; 5]; 4];
|
||||
for p in pts {
|
||||
let q = *p - m;
|
||||
let row = [2.0 * q.x, 2.0 * q.y, 2.0 * q.z, 1.0];
|
||||
let rhs = q.dot(q);
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
a[i][j] += row[i] * row[j];
|
||||
}
|
||||
a[i][4] += row[i] * rhs;
|
||||
}
|
||||
}
|
||||
for col in 0..4 {
|
||||
let piv = (col..4).max_by(|x, y| a[*x][col].abs().total_cmp(&a[*y][col].abs()))?;
|
||||
if a[piv][col].abs() < 1e-18 {
|
||||
return None;
|
||||
}
|
||||
a.swap(col, piv);
|
||||
for r in 0..4 {
|
||||
if r != col {
|
||||
let f = a[r][col] / a[col][col];
|
||||
let prow = a[col];
|
||||
for (x, v) in a[r].iter_mut().zip(prow) {
|
||||
*x -= f * v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let c = Vec3::new(a[0][4] / a[0][0], a[1][4] / a[1][1], a[2][4] / a[2][2]);
|
||||
let k = a[3][4] / a[3][3];
|
||||
let r2 = k + c.dot(c);
|
||||
(r2 > 0.0 && r2.is_finite()).then(|| (c + m, r2.sqrt()))
|
||||
}
|
||||
|
||||
fn classify_faces(mesh: &Mesh, nf: usize, tol: f64) -> Vec<Surf> {
|
||||
let mut pts: Vec<Vec<Vec3>> = vec![Vec::new(); nf];
|
||||
let mut nrm: Vec<Vec<Vec3>> = vec![Vec::new(); nf];
|
||||
|
||||
Reference in new issue
Block a user