mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
3D and Materials: twisted Extrude & Bevel walls keep their faces
A twist bends the strip between two slices into a saddle; its normal came from one corner's triangle, which leaned so far back that from about 60° of twist every wall was taken for facing away and dropped, leaving the front cap alone. Each strip now takes the whole quad's average normal (Newell's), as Inflate's facets do. A test checks the faces kept against each strip's average normal oriented away from the axis, for twists up to 300° and four views.
This commit is contained in:
1 parent
e750891912
commit
431dc43e43
2 files changed
+54
-4
No files matched your search
@@ -464,10 +464,9 @@ impl Builder<'_> {
|
||||
for i in 0..n {
|
||||
let j = (i + 1) % n;
|
||||
let quad = vec![a[i], a[j], b[j], b[i]];
|
||||
let mut normal = quad[1].sub(quad[0]).cross(quad[2].sub(quad[0]));
|
||||
if normal.dot(normal) < 1e-18 {
|
||||
normal = quad[2].sub(quad[0]).cross(quad[3].sub(quad[0]));
|
||||
}
|
||||
// A twist bends the strip between two slices into a saddle: its normal is the whole
|
||||
// quad's average (Newell's), not one corner's triangle, which leans far to one side.
|
||||
let mut normal = newell(std::slice::from_ref(&quad));
|
||||
if normal.dot(normal) < 1e-18 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -373,3 +373,54 @@ fn twist_holds_between_0_and_360_degrees() {
|
||||
assert_eq!(at(500.0), at(360.0));
|
||||
assert_ne!(at(45.0), at(0.0));
|
||||
}
|
||||
|
||||
/// A twisted extrusion shows every wall facing the viewer: a twist bends each strip between two
|
||||
/// slices into a saddle, and its normal from one corner's triangle leaned so far back that from
|
||||
/// 60° on every wall was dropped. Checked against each strip's average normal, oriented away from
|
||||
/// the square's axis.
|
||||
#[test]
|
||||
fn twisted_walls_face_the_way_they_turn() {
|
||||
let (p, b) = shape(&square(0.0, 0.0, 100.0));
|
||||
let c = b.center();
|
||||
for twist in [60.0, 120.0, 180.0, 300.0] {
|
||||
for (x, y) in [(-20.0, 30.0), (0.0, 90.0), (30.0, -60.0), (-45.0, 150.0)] {
|
||||
let v = View { rotation_x: x, rotation_y: y, ..front() };
|
||||
let o = Extrude { twist, ..Extrude::default() };
|
||||
let got = extrude(&p, b, o, v).unwrap();
|
||||
// Expected: walls (and caps) whose outward normal faces the viewer.
|
||||
let o = o.sanitized();
|
||||
let camera = Camera::new(v, b, o.depth);
|
||||
let ring = &rings(&p, b.width().max(b.height()), MAX_SOLID_POINTS).unwrap()[0];
|
||||
let slices = ((twist / 6.0).ceil() as usize).clamp(8, 64);
|
||||
let z = |t: f64| o.depth / 2.0 - o.depth * t;
|
||||
let mut expected = 0;
|
||||
for k in 0..slices {
|
||||
let (t0, t1) = (k as f64 / slices as f64, (k + 1) as f64 / slices as f64);
|
||||
let (r0, r1) = (section(ring, b, &o, t0), section(ring, b, &o, t1));
|
||||
for i in 0..ring.len() {
|
||||
let j = (i + 1) % ring.len();
|
||||
let q = [camera.place(r0[i], z(t0)), camera.place(r0[j], z(t0)), camera.place(r1[j], z(t1)), camera.place(r1[i], z(t1))];
|
||||
let mut n = newell(&[q.to_vec()]).unit();
|
||||
let mid = r0[i].midpoint(r1[j]);
|
||||
let away = camera.rotate(V3(mid.x - c.x, mid.y - c.y, 0.0));
|
||||
if n.dot(away) < 0.0 {
|
||||
n = V3(-n.0, -n.1, -n.2);
|
||||
}
|
||||
let centre = V3((q[0].0 + q[2].0) / 2.0, (q[0].1 + q[2].1) / 2.0, (q[0].2 + q[2].2) / 2.0);
|
||||
// Kept as the builder keeps them: facing, or seen almost edge-on, with an area.
|
||||
let projected: Vec<Point> = q.iter().map(|v| camera.project(*v)).collect();
|
||||
if camera.facing(centre, n) > -0.02 && signed_area(&projected).abs() >= 1e-10 {
|
||||
expected += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (n, t) in [(1.0, 0.0), (-1.0, 1.0)] {
|
||||
let cap: Vec<Point> = section(ring, b, &o, t).iter().map(|p| camera.project(camera.place(*p, z(t)))).collect();
|
||||
if camera.facing(V3::default(), camera.rotate(V3(0.0, 0.0, n))) > -0.02 && signed_area(&cap).abs() >= 1e-10 {
|
||||
expected += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(got.len(), expected, "twist {twist}, view ({x}, {y})");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user