diff --git a/pomme-client/src/particle.rs b/pomme-client/src/particle.rs index 1700653c..b94339a5 100644 --- a/pomme-client/src/particle.rs +++ b/pomme-client/src/particle.rs @@ -340,7 +340,11 @@ impl ParticleStore { && faces.tint != Tint::None && block_id != "grass_block" { - let tint = self.blend_tint(faces.tint, pos, chunks, biome_climate); + let tint = if faces.tint == Tint::Redstone { + crate::world::block::redstone_wire_rgb(state) + } else { + self.blend_tint(faces.tint, pos, chunks, biome_climate) + }; for (c, t) in color.iter_mut().zip(tint) { *c *= t; } @@ -510,7 +514,8 @@ impl ParticleStore { Tint::Grass => grass_color(&climate, &self.grass_colormap, x, z), Tint::Foliage => foliage_color(&climate, &self.foliage_colormap), Tint::DryFoliage => dry_foliage_color(&climate, &self.dry_foliage_colormap), - Tint::None => [1.0; 3], + // Redstone is state-derived, resolved by the caller. + Tint::None | Tint::Redstone => [1.0; 3], } }) } diff --git a/pomme-client/src/renderer/chunk/mesher.rs b/pomme-client/src/renderer/chunk/mesher.rs index 63ff6c28..066c32be 100644 --- a/pomme-client/src/renderer/chunk/mesher.rs +++ b/pomme-client/src/renderer/chunk/mesher.rs @@ -259,12 +259,23 @@ impl Default for BiomeClimate { } } -fn tint_color(tint: Tint, grass: [f32; 3], foliage: [f32; 3], dry_foliage: [f32; 3]) -> u32 { +/// For paths `Tint::Redstone` can't reach (redstone wire always has multipart +/// quads): greedy meshing and plain cubes. +const NO_REDSTONE: fn() -> [f32; 3] = || [1.0; 3]; + +fn tint_color( + tint: Tint, + grass: [f32; 3], + foliage: [f32; 3], + dry_foliage: [f32; 3], + redstone: impl FnOnce() -> [f32; 3], +) -> u32 { match tint { Tint::None => PACKED_WHITE_SHIFTED, Tint::Grass => pack_tint_shifted(grass), Tint::Foliage => pack_tint_shifted(foliage), Tint::DryFoliage => pack_tint_shifted(dry_foliage), + Tint::Redstone => pack_tint_shifted(redstone()), } } @@ -1345,6 +1356,7 @@ fn greedy_mesh_section( snapshot.grass_tint(block_x, section_y, block_z), snapshot.foliage_tint(block_x, section_y, block_z), snapshot.dry_foliage_tint(block_x, section_y, block_z), + NO_REDSTONE, ); let ao = quad.ao_levels(); @@ -1526,11 +1538,11 @@ fn mesh_chunk_snapshot( ); } else if let Some(baked) = registry.get_baked_model(state) { emit_baked_model( - sink, block_pos, baked, snapshot, registry, uv_map, bx, by, bz, + sink, block_pos, state, baked, snapshot, registry, uv_map, bx, by, bz, ); } else if let Some(quads) = registry.get_multipart_quads(state) { emit_multipart( - sink, block_pos, &quads, snapshot, registry, uv_map, bx, by, bz, + sink, block_pos, state, &quads, snapshot, registry, uv_map, bx, by, bz, ); } else if let Some(textures) = registry.get_textures(state) { emit_cube_faces( @@ -1596,6 +1608,7 @@ fn mesh_chunk_snapshot( fn emit_baked_model( sink: &mut MeshSink, block_pos: [f32; 3], + state: azalea_block::BlockState, model: &BakedModel, snapshot: &ChunkStoreSnapshot, registry: &BlockRegistry, @@ -1619,6 +1632,7 @@ fn emit_baked_model( snapshot.grass_tint(bx, by, bz), snapshot.foliage_tint(bx, by, bz), snapshot.dry_foliage_tint(bx, by, bz), + || crate::world::block::redstone_wire_rgb(state), ); let lights = if let Some(dir) = quad.cullface { compute_face_ao(snapshot, registry, bx, by, bz, dir) @@ -1654,6 +1668,7 @@ fn emit_cube_faces( snapshot.grass_tint(bx, by, bz), snapshot.foliage_tint(bx, by, bz), snapshot.dry_foliage_tint(bx, by, bz), + NO_REDSTONE, ); for (i, dir) in CUBE_FACE_DIRS.iter().enumerate() { @@ -1763,6 +1778,7 @@ fn block_face_tex_tint( snapshot.grass_tint(bx, by, bz), snapshot.foliage_tint(bx, by, bz), snapshot.dry_foliage_tint(bx, by, bz), + NO_REDSTONE, ); let tex_name = match dir { Direction::Up => &textures.top, @@ -1859,6 +1875,7 @@ fn emit_fluid( fn emit_multipart( sink: &mut MeshSink, block_pos: [f32; 3], + state: azalea_block::BlockState, quads: &[&crate::world::block::model::BakedQuad], snapshot: &ChunkStoreSnapshot, registry: &BlockRegistry, @@ -1882,6 +1899,7 @@ fn emit_multipart( snapshot.grass_tint(bx, by, bz), snapshot.foliage_tint(bx, by, bz), snapshot.dry_foliage_tint(bx, by, bz), + || crate::world::block::redstone_wire_rgb(state), ); emit_face( sink, diff --git a/pomme-client/src/world/block/mod.rs b/pomme-client/src/world/block/mod.rs index 9317c72f..cc6208fb 100644 --- a/pomme-client/src/world/block/mod.rs +++ b/pomme-client/src/world/block/mod.rs @@ -515,6 +515,25 @@ pub fn block_behavior(state: BlockState) -> &'static BlockBehavior { &block_data(state).behavior } +/// Vanilla `RedStoneWireBlock.getColorForPower`: dust tint for the state's +/// `power` value. +pub fn redstone_wire_rgb(state: BlockState) -> [f32; 3] { + static COLORS: std::sync::LazyLock<[[f32; 3]; 16]> = std::sync::LazyLock::new(|| { + std::array::from_fn(|power| { + let f = power as f32 / 15.0; + let red = f * 0.6 + if power > 0 { 0.4 } else { 0.3 }; + let green = (f * f * 0.7 - 0.5).clamp(0.0, 1.0); + let blue = (f * f * 0.6 - 0.7).clamp(0.0, 1.0); + [red, green, blue] + }) + }); + let power: usize = block_properties(state) + .get("power") + .and_then(|p| p.parse().ok()) + .unwrap_or(0); + COLORS[power.min(15)] +} + /// Vanilla `isAir`: includes cave and void air. pub fn is_air(state: BlockState) -> bool { block_data(state).is_air @@ -613,6 +632,17 @@ mod tests { assert!(try_state(count - 1).is_some()); } + #[test] + fn redstone_wire_colors() { + setup(); + let unpowered = find_state("redstone_wire", &[("power", "0")]); + assert_eq!(redstone_wire_rgb(unpowered), [0.3, 0.0, 0.0]); + let full = redstone_wire_rgb(find_state("redstone_wire", &[("power", "15")])); + for (got, want) in full.iter().zip([1.0, 0.2, 0.0]) { + assert!((got - want).abs() < 1e-6, "{full:?}"); + } + } + #[test] fn fluid_states() { setup(); diff --git a/pomme-client/src/world/block/model.rs b/pomme-client/src/world/block/model.rs index 18031653..15cb32fa 100644 --- a/pomme-client/src/world/block/model.rs +++ b/pomme-client/src/world/block/model.rs @@ -1401,7 +1401,9 @@ fn is_non_occluding(block_name: &str) -> bool { } fn determine_tint(block_name: &str) -> Tint { - if GRASS_TINTED.contains(&block_name) { + if block_name == "redstone_wire" { + Tint::Redstone + } else if GRASS_TINTED.contains(&block_name) { Tint::Grass } else if DRY_FOLIAGE_TINTED.contains(&block_name) { Tint::DryFoliage diff --git a/pomme-client/src/world/block/registry.rs b/pomme-client/src/world/block/registry.rs index 9b9f24d5..4bf125aa 100644 --- a/pomme-client/src/world/block/registry.rs +++ b/pomme-client/src/world/block/registry.rs @@ -4,7 +4,7 @@ use std::path::Path; use azalea_block::BlockState; use serde::{Deserialize, Serialize}; -pub const BLOCK_CACHE_FILE: &str = "block_cache_v2.json"; +pub const BLOCK_CACHE_FILE: &str = "block_cache_v3.json"; use super::model; use super::model::BakedModel; @@ -16,6 +16,8 @@ pub enum Tint { Grass, Foliage, DryFoliage, + /// Power-level color, resolved at mesh time from the state's `power`. + Redstone, } #[derive(Clone, Serialize, Deserialize)]