use crate::types::InputBlock;
#[derive(Debug, Clone)]
pub struct TintColors {
pub grass: [f32; 4],
pub foliage: [f32; 4],
pub water: [f32; 4],
pub redstone: [[f32; 4]; 16],
pub stem: [[f32; 4]; 8],
pub lily_pad: [f32; 4],
pub cauldron_water: [f32; 4],
}
impl Default for TintColors {
fn default() -> Self {
Self {
grass: [0.56, 0.74, 0.35, 1.0],
foliage: [0.47, 0.66, 0.23, 1.0],
water: [0.247, 0.463, 0.894, 1.0],
redstone: Self::default_redstone_colors(),
stem: Self::default_stem_colors(),
lily_pad: [0.13, 0.55, 0.13, 1.0],
cauldron_water: [0.247, 0.463, 0.894, 1.0],
}
}
}
impl TintColors {
pub fn for_biome(biome: &str) -> Self {
let mut colors = Self::default();
match biome {
"swamp" | "minecraft:swamp" | "mangrove_swamp" | "minecraft:mangrove_swamp" => {
colors.grass = [0.41, 0.55, 0.27, 1.0];
colors.foliage = [0.41, 0.55, 0.27, 1.0];
colors.water = [0.38, 0.48, 0.27, 1.0];
}
"badlands" | "minecraft:badlands" | "wooded_badlands" | "minecraft:wooded_badlands"
| "eroded_badlands" | "minecraft:eroded_badlands" => {
colors.grass = [0.56, 0.50, 0.30, 1.0];
colors.foliage = [0.62, 0.56, 0.35, 1.0];
}
"jungle" | "minecraft:jungle" | "bamboo_jungle" | "minecraft:bamboo_jungle"
| "sparse_jungle" | "minecraft:sparse_jungle" => {
colors.grass = [0.35, 0.75, 0.15, 1.0];
colors.foliage = [0.30, 0.72, 0.20, 1.0];
}
"dark_forest" | "minecraft:dark_forest" => {
colors.grass = [0.31, 0.55, 0.20, 1.0];
colors.foliage = [0.31, 0.55, 0.20, 1.0];
}
"snowy_plains" | "minecraft:snowy_plains" | "snowy_taiga" | "minecraft:snowy_taiga"
| "snowy_beach" | "minecraft:snowy_beach" | "snowy_slopes" | "minecraft:snowy_slopes" => {
colors.grass = [0.50, 0.70, 0.50, 1.0];
colors.foliage = [0.39, 0.61, 0.39, 1.0];
}
"desert" | "minecraft:desert" => {
colors.grass = [0.75, 0.72, 0.45, 1.0];
colors.foliage = [0.68, 0.68, 0.40, 1.0];
}
"ocean" | "minecraft:ocean" | "deep_ocean" | "minecraft:deep_ocean"
| "cold_ocean" | "minecraft:cold_ocean" | "deep_cold_ocean" | "minecraft:deep_cold_ocean" => {
colors.water = [0.24, 0.36, 0.75, 1.0];
}
"warm_ocean" | "minecraft:warm_ocean" | "lukewarm_ocean" | "minecraft:lukewarm_ocean"
| "deep_lukewarm_ocean" | "minecraft:deep_lukewarm_ocean" => {
colors.water = [0.26, 0.53, 0.80, 1.0];
}
"frozen_ocean" | "minecraft:frozen_ocean" | "deep_frozen_ocean" | "minecraft:deep_frozen_ocean" => {
colors.water = [0.24, 0.30, 0.60, 1.0];
}
_ => {
}
}
colors
}
fn default_redstone_colors() -> [[f32; 4]; 16] {
let mut colors = [[0.0; 4]; 16];
for power in 0..16 {
let brightness = (power as f32) / 15.0;
let r = 0.3 + brightness * 0.7;
let g = brightness * 0.1;
let b = brightness * 0.1;
colors[power] = [r, g, b, 1.0];
}
colors
}
fn default_stem_colors() -> [[f32; 4]; 8] {
let mut colors = [[0.0; 4]; 8];
for stage in 0..8 {
let t = stage as f32 / 7.0;
let r = 0.2 + t * 0.6;
let g = 0.7 - t * 0.2;
let b = 0.1;
colors[stage] = [r, g, b, 1.0];
}
colors
}
}
#[derive(Debug, Clone)]
pub struct TintProvider {
colors: TintColors,
}
impl TintProvider {
pub fn new() -> Self {
Self {
colors: TintColors::default(),
}
}
pub fn with_colors(colors: TintColors) -> Self {
Self { colors }
}
pub fn for_biome(biome: &str) -> Self {
Self {
colors: TintColors::for_biome(biome),
}
}
pub fn get_tint(&self, block: &InputBlock, tint_index: i32) -> [f32; 4] {
if tint_index < 0 {
return [1.0, 1.0, 1.0, 1.0];
}
let block_id = block.block_id();
match self.categorize_block(block_id) {
TintCategory::Grass => self.colors.grass,
TintCategory::Foliage => self.colors.foliage,
TintCategory::Water => self.colors.water,
TintCategory::Redstone => self.get_redstone_tint(block),
TintCategory::Stem => self.get_stem_tint(block),
TintCategory::LilyPad => self.colors.lily_pad,
TintCategory::None => [1.0, 1.0, 1.0, 1.0],
}
}
pub fn colors(&self) -> &TintColors {
&self.colors
}
fn categorize_block(&self, block_id: &str) -> TintCategory {
if matches!(block_id,
"grass_block" | "grass" | "tall_grass" | "fern" | "large_fern" |
"potted_fern" | "short_grass"
) {
return TintCategory::Grass;
}
if block_id.ends_with("_leaves") && !block_id.starts_with("azalea") {
return TintCategory::Foliage;
}
if matches!(block_id, "vine" | "oak_leaves" | "jungle_leaves" | "acacia_leaves" |
"dark_oak_leaves" | "mangrove_leaves") {
return TintCategory::Foliage;
}
if matches!(block_id, "water" | "bubble_column") {
return TintCategory::Water;
}
if block_id == "water_cauldron" {
return TintCategory::Water;
}
if block_id == "redstone_wire" || block_id == "redstone_dust" {
return TintCategory::Redstone;
}
if matches!(block_id, "melon_stem" | "pumpkin_stem" | "attached_melon_stem" | "attached_pumpkin_stem") {
return TintCategory::Stem;
}
if block_id == "lily_pad" {
return TintCategory::LilyPad;
}
if block_id == "sugar_cane" {
return TintCategory::Grass;
}
TintCategory::None
}
fn get_redstone_tint(&self, block: &InputBlock) -> [f32; 4] {
let power = block.properties
.get("power")
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0)
.min(15);
self.colors.redstone[power]
}
fn get_stem_tint(&self, block: &InputBlock) -> [f32; 4] {
let block_id = block.block_id();
if block_id.starts_with("attached_") {
return self.colors.stem[7];
}
let age = block.properties
.get("age")
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0)
.min(7);
self.colors.stem[age]
}
}
impl Default for TintProvider {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TintCategory {
Grass,
Foliage,
Water,
Redstone,
Stem,
LilyPad,
None,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_tint_provider() {
let provider = TintProvider::new();
let stone = InputBlock::new("minecraft:stone");
assert_eq!(provider.get_tint(&stone, -1), [1.0, 1.0, 1.0, 1.0]);
let grass = InputBlock::new("minecraft:grass_block");
let tint = provider.get_tint(&grass, 0);
assert!(tint[0] < 1.0); assert!(tint[1] > tint[0]); }
#[test]
fn test_redstone_tint() {
let provider = TintProvider::new();
let redstone_0 = InputBlock::new("minecraft:redstone_wire")
.with_property("power", "0");
let redstone_15 = InputBlock::new("minecraft:redstone_wire")
.with_property("power", "15");
let tint_0 = provider.get_tint(&redstone_0, 0);
let tint_15 = provider.get_tint(&redstone_15, 0);
assert!(tint_15[0] > tint_0[0]);
}
#[test]
fn test_stem_tint() {
let provider = TintProvider::new();
let stem_0 = InputBlock::new("minecraft:melon_stem")
.with_property("age", "0");
let stem_7 = InputBlock::new("minecraft:melon_stem")
.with_property("age", "7");
let tint_0 = provider.get_tint(&stem_0, 0);
let tint_7 = provider.get_tint(&stem_7, 0);
assert!(tint_7[0] > tint_0[0]);
}
#[test]
fn test_biome_tints() {
let plains = TintProvider::new();
let swamp = TintProvider::for_biome("swamp");
let jungle = TintProvider::for_biome("jungle");
assert_ne!(plains.colors().grass, swamp.colors().grass);
assert!(jungle.colors().foliage[1] > plains.colors().foliage[1]);
}
}