Display tessellation plans (3/4): display meshes from plans, GPU evaluation in the viewport (#805)
Display meshes are built from tessellation plans, with GPU evaluation in the viewport.
This commit is contained in:
1 parent
534393ed5a
commit
ff7a347013
19 files changed
+1474
-77
No files matched your search
+46
-16
@@ -396,29 +396,59 @@ impl Mesh {
|
||||
}
|
||||
|
||||
/// Closest intersection of a ray with the mesh: (distance along `dir`, triangle index).
|
||||
///
|
||||
/// Watertight (Woop, Benthin & Wald, "Watertight Ray/Triangle Intersection", JCGT 2(1),
|
||||
/// 2013): the vertices are moved into a frame where the ray runs along +z through the
|
||||
/// origin, and each edge's side test is computed from those per-vertex values alone, so the
|
||||
/// two triangles that share an edge get exactly opposite values there. A ray through a
|
||||
/// shared edge or vertex hits at least one of them; it never slips between them.
|
||||
pub fn raycast(&self, o: Vec3, dir: Vec3) -> Option<(f64, usize)> {
|
||||
let comp = |v: Vec3, k: usize| match k {
|
||||
0 => v.x,
|
||||
1 => v.y,
|
||||
_ => v.z,
|
||||
};
|
||||
// The ray's dominant axis becomes z; x and y follow it cyclically, swapped to keep
|
||||
// the frame right-handed when the ray runs towards −z.
|
||||
let a = [dir.x.abs(), dir.y.abs(), dir.z.abs()];
|
||||
let kz = if a[0] >= a[1] && a[0] >= a[2] {
|
||||
0
|
||||
} else if a[1] >= a[2] {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
};
|
||||
let dz = comp(dir, kz);
|
||||
if !(dz.is_finite() && dz != 0.0) {
|
||||
return None;
|
||||
}
|
||||
let (mut kx, mut ky) = ((kz + 1) % 3, (kz + 2) % 3);
|
||||
if dz < 0.0 {
|
||||
std::mem::swap(&mut kx, &mut ky);
|
||||
}
|
||||
let (sx, sy, sz) = (comp(dir, kx) / dz, comp(dir, ky) / dz, 1.0 / dz);
|
||||
// A vertex in the ray's frame: (x, y) across the ray, z scaled along it.
|
||||
let frame = |p: Vec3| {
|
||||
let q = p - o;
|
||||
let z = comp(q, kz);
|
||||
(comp(q, kx) - sx * z, comp(q, ky) - sy * z, sz * z)
|
||||
};
|
||||
let mut best: Option<(f64, usize)> = None;
|
||||
for (i, t) in self.triangles.iter().enumerate() {
|
||||
let Some([a, b, c]) = self.tri(t) else { continue };
|
||||
let (e1, e2) = (b - a, c - a);
|
||||
let p = dir.cross(e2);
|
||||
let det = e1.dot(p);
|
||||
if det.abs() < 1e-14 {
|
||||
let Some([pa, pb, pc]) = self.tri(t) else { continue };
|
||||
let ((ax, ay, az), (bx, by, bz), (cx, cy, cz)) = (frame(pa), frame(pb), frame(pc));
|
||||
let u = cx * by - cy * bx;
|
||||
let v = ax * cy - ay * cx;
|
||||
let w = bx * ay - by * ax;
|
||||
if (u < 0.0 || v < 0.0 || w < 0.0) && (u > 0.0 || v > 0.0 || w > 0.0) {
|
||||
continue;
|
||||
}
|
||||
let inv = 1.0 / det;
|
||||
let s = o - a;
|
||||
let u = s.dot(p) * inv;
|
||||
if !(0.0..=1.0).contains(&u) {
|
||||
let det = u + v + w;
|
||||
if det == 0.0 {
|
||||
continue;
|
||||
}
|
||||
let q = s.cross(e1);
|
||||
let v = dir.dot(q) * inv;
|
||||
if v < 0.0 || u + v > 1.0 {
|
||||
continue;
|
||||
}
|
||||
let t = e2.dot(q) * inv;
|
||||
if t > 1e-9 && best.is_none_or(|(bt, _)| t < bt) {
|
||||
let t = (u * az + v * bz + w * cz) / det;
|
||||
if t.is_finite() && t > 1e-9 && best.is_none_or(|(bt, _)| t < bt) {
|
||||
best = Some((t, i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,40 @@ fn raycast_hits_front_face() {
|
||||
assert!(m.raycast(Vec3::new(5.0, 5.0, 10.0), Vec3::new(0.0, 0.0, -1.0)).is_none());
|
||||
}
|
||||
|
||||
/// A ray through a vertex or edge that triangles share never slips between them: a grid of
|
||||
/// triangles on a square, hit at every inner vertex and edge midpoint from oblique directions
|
||||
/// (on the square's own border nothing is shared, and a ray grazing it may miss).
|
||||
#[test]
|
||||
fn raycast_never_slips_between_triangles() {
|
||||
let n = 6;
|
||||
let positions: Vec<Vec3> =
|
||||
(0..=n).flat_map(|j| (0..=n).map(move |i| Vec3::new(f64::from(i) * 40.0 / 6.0 + 100.0, f64::from(j) * 5.0, 20.0))).collect();
|
||||
let id = |i: u32, j: u32| j * (n + 1) + i;
|
||||
let mut triangles = Vec::new();
|
||||
for j in 0..n {
|
||||
for i in 0..n {
|
||||
triangles.push([id(i, j), id(i + 1, j), id(i + 1, j + 1)]);
|
||||
triangles.push([id(i, j), id(i + 1, j + 1), id(i, j + 1)]);
|
||||
}
|
||||
}
|
||||
let tri_face = vec![0; triangles.len()];
|
||||
let m = Mesh { positions, triangles, tri_face, ..Default::default() };
|
||||
let dirs = [Vec3::new(-0.57735, 0.57735, -0.57735), Vec3::new(0.3, -0.7, -0.648), Vec3::new(-0.123456789, -0.2, -1.0), Vec3::new(0.0, 0.0, -1.0)];
|
||||
let mut targets = Vec::new();
|
||||
for j in 1..2 * n {
|
||||
for i in 1..2 * n {
|
||||
targets.push(Vec3::new(f64::from(i) * 20.0 / 6.0 + 100.0, f64::from(j) * 2.5, 20.0));
|
||||
}
|
||||
}
|
||||
for d in dirs {
|
||||
for p in &targets {
|
||||
let o = *p - d * 50.0;
|
||||
let hit = m.raycast(o, d);
|
||||
assert!(hit.is_some_and(|(t, _)| (t - 50.0).abs() < 1e-6), "{p:?} along {d:?}: {hit:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planes() {
|
||||
assert_eq!(Plane::XY.normal(), Vec3::Z);
|
||||
|
||||
Reference in new issue
Block a user