Zebra: flat faces keep one stripe colour while the view turns (#2153) (#9761)

* claim: #2153

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Zebra: flat faces keep one stripe colour while the view turns (#2153)

A face whose normal lies in the upright plane through the view (every
horizontal face) reflects straight ahead, where the zebra's sideways
coordinate is zero: exactly on the edge between a black and a white
stripe. Rounding then picked one or the other from frame to frame, so
the face flickered while the camera orbited. Straight ahead is now white.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
PaddleStroke
2026-10-31 00:03:59 +00:00
committed by GitHub
parent 045cfed6a7
commit 526cdfd247
2 changed files with 29 additions and 2 deletions
+7 -2
View File
@@ -423,8 +423,13 @@ fn analysis(i: TOut, k: f32) -> vec4<f32> {
let r = reflect(-v, n);
var side = cross(v, vec3<f32>(0.0, 0.0, 1.0));
if (length(side) < 1e-3) { side = vec3<f32>(1.0, 0.0, 0.0); }
let s = sin(dot(r, normalize(side)) * u.ana.y * 3.14159265);
let c = select(0.08, 0.95, s > 0.0);
let x = dot(r, normalize(side));
let s = sin(x * u.ana.y * 3.14159265);
// A face whose normal lies in the upright plane through the view (every horizontal
// face) reflects straight ahead: its sideways part is zero, on the edge between a black
// and a white stripe, and rounding picked one or the other from frame to frame while
// the view turned (#2153). Straight ahead is white.
let c = select(0.08, 0.95, s > 0.0 || abs(x) < 1e-4);
return out_color(vec4<f32>(vec3<f32>(c), 1.0));
}
if (u.ana.x > 4.5) {
+22
View File
@@ -319,6 +319,28 @@ fn pictures_show_the_surface_analysis() {
assert!(reachable > 1000 && under > 1000, "accessibility: {reachable} reachable, {under} blocked pixels");
}
/// A flat face of a box keeps its zebra colour while the view turns a little at a time (#2153):
/// a horizontal face reflected straight ahead, on the edge between a black and a white stripe,
/// and rounding flipped it from one step to the next.
#[test]
fn zebra_on_flat_faces_holds_still_while_the_view_turns() {
let Some(c) = capturer() else { return };
let mut s = session(&[("solid.box", json!({"length": 60, "width": 40, "height": 20}))]);
s.execute("inspect.zebra", &json!({})).unwrap();
let home = view::home_camera(&s);
let whites: Vec<usize> = (0..24)
.map(|step| {
let mut cam = home;
cam.yaw += f64::from(step) * 0.11_f64.to_radians();
let img = c.rgba(&view::scene_with_guides(&s, &cam, None, Guides { grid: false, axes: false }), &cam, 320, 200).unwrap();
pixels(&img).filter(|p| p[0] > 225 && p[1] > 225 && p[2] > 225).count()
})
.collect();
let (least, most) = (whites.iter().min().copied().unwrap_or(0), whites.iter().max().copied().unwrap_or(0));
assert!(most > 1000, "white stripes: {whites:?}");
assert!((most - least) * 10 < most, "white pixels jump while the view turns by 0.11 degree steps: {whites:?}");
}
fn colors_of(c: [[f32; 3]; 2]) -> [[u8; 3]; 2] {
c.map(|rgb| rgb.map(|x| (x * 255.0).round() as u8))
}