fix(hatch): delay complex pattern LOD
Keep every pattern family visible until all projected spacings become sub-pixel, then switch the hatch to solid. Refs #569
This commit is contained in:
parent
fcc6e1f8e1
commit
8e2b67bfa5
3 changed files with 71 additions and 23 deletions
|
|
@ -319,27 +319,31 @@ fn check_family(
|
||||||
return mix(inst.color2, inst.color, t);
|
return mix(inst.color2, inst.color, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Pattern LOD: when the densest family's spacing projects below
|
// 3. Pattern LOD. Keep every family visible until all family spacings
|
||||||
// 2 px, lines blur into a solid fill — return color instead of
|
// project below 2 px, then substitute one solid fill. A single dense
|
||||||
// iterating every family (mirrors Phase 3.3 LOD in hatch.wgsl).
|
// family must neither hide itself nor turn a complex hatch solid.
|
||||||
if u.world_per_pixel > 0.0 && inst.family_count > 0u {
|
var all_families_subpixel = inst.family_count > 0u && u.world_per_pixel > 0.0;
|
||||||
var min_spacing_world: f32 = 1.0e30;
|
if all_families_subpixel {
|
||||||
for (var i = 0u; i < inst.family_count; i++) {
|
for (var i = 0u; i < inst.family_count; i++) {
|
||||||
let s = abs(families[inst.family_offset + i].perp_step) * inst.scale;
|
let spacing_world =
|
||||||
if s > 0.0 && s < min_spacing_world {
|
abs(families[inst.family_offset + i].perp_step) * inst.scale;
|
||||||
min_spacing_world = s;
|
if spacing_world <= 0.0 {
|
||||||
|
all_families_subpixel = false;
|
||||||
|
} else if spacing_world / u.world_per_pixel >= 2.0 {
|
||||||
|
all_families_subpixel = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if min_spacing_world / u.world_per_pixel < 2.0 {
|
}
|
||||||
return inst.color;
|
if all_families_subpixel {
|
||||||
}
|
return inst.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Pattern evaluation.
|
// 4. Pattern evaluation.
|
||||||
let cos_off = cos(inst.angle_offset);
|
let cos_off = cos(inst.angle_offset);
|
||||||
let sin_off = sin(inst.angle_offset);
|
let sin_off = sin(inst.angle_offset);
|
||||||
for (var i = 0u; i < inst.family_count; i++) {
|
for (var i = 0u; i < inst.family_count; i++) {
|
||||||
if check_family(v.xz, ddx_xz, ddy_xz, families[inst.family_offset + i], cos_off, sin_off, inst.scale) {
|
let fam = families[inst.family_offset + i];
|
||||||
|
if check_family(v.xz, ddx_xz, ddy_xz, fam, cos_off, sin_off, inst.scale) {
|
||||||
return inst.color;
|
return inst.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -272,25 +272,29 @@ fn check_family(
|
||||||
return mix(h.color2, h.color, t);
|
return mix(h.color2, h.color, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern LOD: when the densest family projects below 2 px, lines blur into
|
// Keep every family visible until all family spacings project below 2 px,
|
||||||
// a solid fill — return solid instead of looping every family.
|
// then substitute one solid fill. A single dense family must neither hide
|
||||||
if u.world_per_pixel > 0.0 && h.n_families > 0u {
|
// itself nor turn a complex hatch solid.
|
||||||
var min_spacing_world: f32 = 1.0e30;
|
var all_families_subpixel = h.n_families > 0u && u.world_per_pixel > 0.0;
|
||||||
|
if all_families_subpixel {
|
||||||
for (var i = 0u; i < h.n_families; i++) {
|
for (var i = 0u; i < h.n_families; i++) {
|
||||||
let s = abs(load_family(i).perp_step) * h.scale;
|
let spacing_world = abs(load_family(i).perp_step) * h.scale;
|
||||||
if s > 0.0 && s < min_spacing_world {
|
if spacing_world <= 0.0 {
|
||||||
min_spacing_world = s;
|
all_families_subpixel = false;
|
||||||
|
} else if spacing_world / u.world_per_pixel >= 2.0 {
|
||||||
|
all_families_subpixel = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if min_spacing_world / u.world_per_pixel < 2.0 {
|
}
|
||||||
return h.color;
|
if all_families_subpixel {
|
||||||
}
|
return h.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cos_off = cos(h.angle_offset);
|
let cos_off = cos(h.angle_offset);
|
||||||
let sin_off = sin(h.angle_offset);
|
let sin_off = sin(h.angle_offset);
|
||||||
for (var i = 0u; i < h.n_families; i++) {
|
for (var i = 0u; i < h.n_families; i++) {
|
||||||
if check_family(v.xz, ddx_xz, ddy_xz, load_family(i), cos_off, sin_off, h.scale) {
|
let fam = load_family(i);
|
||||||
|
if check_family(v.xz, ddx_xz, ddy_xz, fam, cos_off, sin_off, h.scale) {
|
||||||
return h.color;
|
return h.color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
tests/hatch_shader_lod.rs
Normal file
40
tests/hatch_shader_lod.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
const STORAGE_SHADER: &str = include_str!("../src/shaders/hatch.wgsl");
|
||||||
|
const TEXTURE_SHADER: &str = include_str!("../src/shaders/hatch_texture.wgsl");
|
||||||
|
|
||||||
|
fn assert_all_family_lod(source: &str, name: &str) {
|
||||||
|
let module = naga::front::wgsl::parse_str(source)
|
||||||
|
.unwrap_or_else(|error| panic!("{name} WGSL must parse: {error}"));
|
||||||
|
naga::valid::Validator::new(
|
||||||
|
naga::valid::ValidationFlags::all(),
|
||||||
|
naga::valid::Capabilities::empty(),
|
||||||
|
)
|
||||||
|
.validate(&module)
|
||||||
|
.unwrap_or_else(|error| panic!("{name} WGSL must validate: {error}"));
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
source.contains("var all_families_subpixel"),
|
||||||
|
"{name} must track the all-family LOD transition"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
source.contains("if all_families_subpixel"),
|
||||||
|
"{name} must solid-fill only at the all-family transition"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!source.contains("if is_subpixel") && !source.contains("visible_families"),
|
||||||
|
"{name} must not skip individual families before LOD activates"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!source.contains("min_spacing_world"),
|
||||||
|
"{name} must not solid-fill a complex pattern because of one dense family"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn storage_hatch_lod_waits_for_every_family() {
|
||||||
|
assert_all_family_lod(STORAGE_SHADER, "storage hatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn texture_hatch_lod_waits_for_every_family() {
|
||||||
|
assert_all_family_lod(TEXTURE_SHADER, "texture hatch");
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue