From ffb0632891f33b6822b7b973e710599f852f040f Mon Sep 17 00:00:00 2001 From: lazy-importer Date: Mon, 5 Oct 2026 16:39:47 +0200 Subject: [PATCH 1/2] fix(client): stop copper grates and barriers from culling neighbor faces Copper grates (all oxidation and waxed states) and barriers bake as full cubes but are cutout/noOcclusion in vanilla, so the mesher culled the adjacent face of every solid block touching them and left a see-through hole, most visible next to a placed copper grate. Adds the grate family and barrier to is_non_occluding, and a regression test covering the eight grate states, barrier, the existing glass/ice/leaves sets, and the solid lookalikes that must keep culling. Fixes #660. --- pomme-client/src/world/block/model.rs | 48 ++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/pomme-client/src/world/block/model.rs b/pomme-client/src/world/block/model.rs index 656d95ec..32d2aff3 100644 --- a/pomme-client/src/world/block/model.rs +++ b/pomme-client/src/world/block/model.rs @@ -1817,10 +1817,16 @@ fn face_textures_base( /// Full-cube blocks that mustn't cull adjacent faces (vanilla `noOcclusion`). /// Solid `packed_ice`/`blue_ice` still occlude, so `ice` is matched exactly. +/// Every oxidation/waxed state of the grate family is `noOcclusion`, hence the +/// suffix; `barrier` bakes as a full cube but is cutout in vanilla. fn is_non_occluding(block_name: &str) -> bool { block_name.ends_with("_leaves") || block_name.ends_with("_stained_glass") - || matches!(block_name, "glass" | "tinted_glass" | "ice" | "frosted_ice") + || block_name.ends_with("_copper_grate") + || matches!( + block_name, + "glass" | "tinted_glass" | "ice" | "frosted_ice" | "barrier" | "copper_grate" + ) } fn determine_tint(block_name: &str) -> Tint { @@ -2410,4 +2416,44 @@ mod tests { assert_eq!(parts[0].path, "block/oak_stairs"); assert!(parts[0].transform.is_none()); } + + /// The grate family and `barrier` are the only full-cube blocks vanilla + /// marks `noOcclusion` besides the glass/ice/leaves sets: a baked full cube + /// of them must keep its neighbors' faces alive. + #[test] + fn non_occluding_covers_copper_grates_and_barrier() { + for name in [ + "copper_grate", + "exposed_copper_grate", + "weathered_copper_grate", + "oxidized_copper_grate", + "waxed_copper_grate", + "waxed_exposed_copper_grate", + "waxed_weathered_copper_grate", + "waxed_oxidized_copper_grate", + "barrier", + "glass", + "blue_stained_glass", + "tinted_glass", + "ice", + "frosted_ice", + "oak_leaves", + ] { + assert!(is_non_occluding(name), "{name} should not occlude"); + } + + // Solid lookalikes that must keep culling. + for name in [ + "stone", + "oak_planks", + "copper_block", + "exposed_copper", + "packed_ice", + "blue_ice", + "tinted_glass_pane", + "glass_pane", + ] { + assert!(!is_non_occluding(name), "{name} should occlude"); + } + } } From 0538141d8ebf1e299ca8d7fcc46fad77318e05c6 Mon Sep 17 00:00:00 2001 From: Purdze <29306641+Purdze@users.noreply.github.com> Date: Thu, 8 Oct 2026 20:24:50 +0100 Subject: [PATCH 2/2] gate face culling on generated can_occlude --- pomme-client/src/world/block/mod.rs | 23 ++++++++ pomme-client/src/world/block/model.rs | 68 +----------------------- pomme-client/src/world/block/registry.rs | 16 ++---- 3 files changed, 29 insertions(+), 78 deletions(-) diff --git a/pomme-client/src/world/block/mod.rs b/pomme-client/src/world/block/mod.rs index 37153791..3daff75f 100644 --- a/pomme-client/src/world/block/mod.rs +++ b/pomme-client/src/world/block/mod.rs @@ -1610,6 +1610,29 @@ mod tests { assert_eq!(light_props(BlockState::AIR).dampening, 0); } + #[test] + fn can_occlude_follows_no_occlusion() { + setup(); + for (name, occludes) in [ + ("copper_grate", false), + ("waxed_oxidized_copper_grate", false), + ("test_instance_block", false), + ("glass", false), + ("oak_leaves", false), + ("ice", false), + ("stone", true), + ("packed_ice", true), + ("blue_ice", true), + ("copper_block", true), + ] { + assert_eq!( + light_props(find_state(name, &[])).can_occlude, + occludes, + "{name}" + ); + } + } + #[test] fn slab_face_occlusion() { setup(); diff --git a/pomme-client/src/world/block/model.rs b/pomme-client/src/world/block/model.rs index 32d2aff3..5973de9c 100644 --- a/pomme-client/src/world/block/model.rs +++ b/pomme-client/src/world/block/model.rs @@ -359,9 +359,6 @@ pub struct BakedQuad { pub struct BakedModel { pub quads: Vec, pub is_full_cube: bool, - /// Vanilla `canOcclude`: full cubes occlude, but cutout blocks like leaves - /// don't, so neighbor faces against them still render. - pub occludes: bool, } #[derive(Clone)] @@ -456,12 +453,9 @@ pub fn bake_all_models( &mut model_cache, packs, ); - if let Some(mut baked) = + if let Some(baked) = bake_resolved_model(&resolved, model_ref.x, model_ref.y, block_tint) { - if is_non_occluding(block_name) { - baked.occludes = false; - } variants_map.insert(variant_key.clone(), baked); } } @@ -637,7 +631,6 @@ pub fn bake_item_models( Some(mut model) => { model.quads.extend(baked.quads); model.is_full_cube = false; - model.occludes = false; model } }); @@ -774,7 +767,6 @@ fn bake_chest_item_model(gui: DisplayTransform, gui_light: GuiLight) -> BakedMod BakedModel { quads, is_full_cube: false, - occludes: false, } } @@ -1518,11 +1510,9 @@ fn bake_resolved_model( return None; } - let is_full_cube = check_full_cube(&quads); Some(BakedModel { + is_full_cube: check_full_cube(&quads), quads, - is_full_cube, - occludes: is_full_cube, }) } @@ -1815,20 +1805,6 @@ fn face_textures_base( None } -/// Full-cube blocks that mustn't cull adjacent faces (vanilla `noOcclusion`). -/// Solid `packed_ice`/`blue_ice` still occlude, so `ice` is matched exactly. -/// Every oxidation/waxed state of the grate family is `noOcclusion`, hence the -/// suffix; `barrier` bakes as a full cube but is cutout in vanilla. -fn is_non_occluding(block_name: &str) -> bool { - block_name.ends_with("_leaves") - || block_name.ends_with("_stained_glass") - || block_name.ends_with("_copper_grate") - || matches!( - block_name, - "glass" | "tinted_glass" | "ice" | "frosted_ice" | "barrier" | "copper_grate" - ) -} - fn determine_tint(block_name: &str) -> Tint { if block_name == "redstone_wire" { Tint::Redstone @@ -2416,44 +2392,4 @@ mod tests { assert_eq!(parts[0].path, "block/oak_stairs"); assert!(parts[0].transform.is_none()); } - - /// The grate family and `barrier` are the only full-cube blocks vanilla - /// marks `noOcclusion` besides the glass/ice/leaves sets: a baked full cube - /// of them must keep its neighbors' faces alive. - #[test] - fn non_occluding_covers_copper_grates_and_barrier() { - for name in [ - "copper_grate", - "exposed_copper_grate", - "weathered_copper_grate", - "oxidized_copper_grate", - "waxed_copper_grate", - "waxed_exposed_copper_grate", - "waxed_weathered_copper_grate", - "waxed_oxidized_copper_grate", - "barrier", - "glass", - "blue_stained_glass", - "tinted_glass", - "ice", - "frosted_ice", - "oak_leaves", - ] { - assert!(is_non_occluding(name), "{name} should not occlude"); - } - - // Solid lookalikes that must keep culling. - for name in [ - "stone", - "oak_planks", - "copper_block", - "exposed_copper", - "packed_ice", - "blue_ice", - "tinted_glass_pane", - "glass_pane", - ] { - assert!(!is_non_occluding(name), "{name} should occlude"); - } - } } diff --git a/pomme-client/src/world/block/registry.rs b/pomme-client/src/world/block/registry.rs index d91ca82e..d7640be2 100644 --- a/pomme-client/src/world/block/registry.rs +++ b/pomme-client/src/world/block/registry.rs @@ -217,22 +217,14 @@ impl BlockRegistry { if quads.is_empty() { None } else { Some(quads) } } - fn baked_model_flag(&self, state: BlockState, f: impl Fn(&BakedModel) -> bool) -> bool { - if super::is_air(state) { - return false; - } - self.get_baked_model(state).map(f).unwrap_or(false) - } - pub fn is_opaque_full_cube(&self, state: BlockState) -> bool { - self.baked_model_flag(state, |m| m.is_full_cube) + !super::is_air(state) && self.get_baked_model(state).is_some_and(|m| m.is_full_cube) } - /// Whether `state` culls a neighbor's adjacent face. Unlike - /// [`Self::is_opaque_full_cube`], non-occluding blocks like leaves return - /// false even though they bake as full cubes. + /// Whether `state` culls a neighbor's adjacent face: a full cube that can + /// occlude, so `noOcclusion` cubes like glass and leaves don't. pub fn occludes_neighbor(&self, state: BlockState) -> bool { - self.baked_model_flag(state, |m| m.occludes) + self.is_opaque_full_cube(state) && super::light_props(state).can_occlude } pub fn texture_names(&self) -> impl Iterator + '_ {