Initial commit: Terrafier v0.1.0

- Core world model (World, Dimension, Tile, Terrain, Platform)
- NBT reader/writer with full tag support
- Anvil region (.mca) format reader with chunk parsing
- Block palette compression and export
- Biome database with Minecraft 1.21 biomes
- CLI commands: new, import, export, info, render
- GUI application with egui (viewport, tools, undo/redo)
- Noise-based heightmap generation and editing operations

License: GPL-3.0-or-later
This commit is contained in:
loki5512344 2026-06-15 19:14:11 +02:00
commit 91835402b3
94 changed files with 12372 additions and 0 deletions

10
crates/biome-db/Cargo.toml Executable file
View file

@ -0,0 +1,10 @@
[package]
name = "terrafier-biome-db"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Biome database for Terrafier — biome IDs and names for all Minecraft versions"
[dependencies]
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true

View file

@ -0,0 +1,65 @@
[
{"id":0,"name":"ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":1,"name":"plains","temperature":0.8,"downfall":0.4,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":2,"name":"desert","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":13095579,"foliage_color":11759899,"fog_color":12638463},
{"id":3,"name":"windswept_hills","temperature":0.2,"downfall":0.3,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10927977,"foliage_color":10854697,"fog_color":12638463},
{"id":4,"name":"forest","temperature":0.7,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":8107825,"foliage_color":7318464,"fog_color":12638463},
{"id":5,"name":"taiga","temperature":0.25,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":12207467,"foliage_color":11599129,"fog_color":12638463},
{"id":6,"name":"swamp","temperature":0.8,"downfall":0.9,"precipitation":"rain","water_color":9478516,"sky_color":7907327,"grass_color":6975545,"foliage_color":6975545,"fog_color":12638463},
{"id":7,"name":"river","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":8,"name":"nether_wastes","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":8792071,"foliage_color":8792071,"fog_color":3348480},
{"id":9,"name":"the_end","temperature":0.5,"downfall":0.5,"precipitation":"none","water_color":4159204,"sky_color":0,"grass_color":10065867,"foliage_color":10065867,"fog_color":10526880},
{"id":10,"name":"frozen_ocean","temperature":0.0,"downfall":0.5,"precipitation":"snow","water_color":3750089,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":11,"name":"frozen_river","temperature":0.0,"downfall":0.5,"precipitation":"snow","water_color":3750089,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":12,"name":"snowy_plains","temperature":0.0,"downfall":0.5,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":13,"name":"old_growth_pine_taiga","temperature":0.3,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10855236,"foliage_color":10214081,"fog_color":12638463},
{"id":14,"name":"old_growth_spruce_taiga","temperature":0.25,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":12207467,"foliage_color":11599129,"fog_color":12638463},
{"id":15,"name":"snowy_taiga","temperature":-0.5,"downfall":0.4,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":16,"name":"savanna","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":13551763,"foliage_color":12434877,"fog_color":12638463},
{"id":17,"name":"savanna_plateau","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":13551763,"foliage_color":12434877,"fog_color":12638463},
{"id":18,"name":"badlands","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":10658679,"foliage_color":10658679,"fog_color":12638463},
{"id":19,"name":"wooded_badlands","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":10658679,"foliage_color":10658679,"fog_color":12638463},
{"id":20,"name":"eroded_badlands","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":10658679,"foliage_color":10658679,"fog_color":12638463},
{"id":21,"name":"warm_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4020182,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":22,"name":"lukewarm_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4566514,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":23,"name":"cold_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":3750089,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":24,"name":"deep_lukewarm_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4566514,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":25,"name":"deep_cold_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":3750089,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":26,"name":"deep_frozen_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":3750089,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":27,"name":"dark_forest","temperature":0.7,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":7109967,"foliage_color":6776627,"fog_color":12638463},
{"id":28,"name":"snowy_slopes","temperature":-0.3,"downfall":0.9,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":29,"name":"jagged_peaks","temperature":-0.7,"downfall":0.9,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":30,"name":"frozen_peaks","temperature":-0.7,"downfall":0.9,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":31,"name":"stony_peaks","temperature":1.0,"downfall":0.3,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10927977,"foliage_color":10854697,"fog_color":12638463},
{"id":32,"name":"bamboo_jungle","temperature":0.95,"downfall":0.9,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":12304591,"foliage_color":11437115,"fog_color":12638463},
{"id":33,"name":"jungle","temperature":0.95,"downfall":0.9,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":12304591,"foliage_color":11437115,"fog_color":12638463},
{"id":34,"name":"sparse_jungle","temperature":0.95,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":12304591,"foliage_color":11437115,"fog_color":12638463},
{"id":35,"name":"deep_ocean","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":36,"name":"mushroom_fields","temperature":0.9,"downfall":1.0,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10215985,"foliage_color":10215985,"fog_color":12638463},
{"id":37,"name":"dripstone_caves","temperature":0.8,"downfall":0.4,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":38,"name":"lush_caves","temperature":0.5,"downfall":0.5,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":8107825,"foliage_color":7318464,"fog_color":12638463},
{"id":39,"name":"deep_dark","temperature":0.8,"downfall":0.4,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":40,"name":"meadow","temperature":0.5,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":41,"name":"cherry_grove","temperature":0.5,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":42,"name":"grove","temperature":-0.2,"downfall":0.8,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":43,"name":"snowy_beach","temperature":0.05,"downfall":0.3,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":44,"name":"beach","temperature":0.8,"downfall":0.4,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":13095579,"foliage_color":11759899,"fog_color":12638463},
{"id":45,"name":"stone_shore","temperature":0.2,"downfall":0.3,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10927977,"foliage_color":10854697,"fog_color":12638463},
{"id":46,"name":"sunflower_plains","temperature":0.8,"downfall":0.4,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":9551193,"foliage_color":10387789,"fog_color":12638463},
{"id":47,"name":"flower_forest","temperature":0.7,"downfall":0.8,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":8107825,"foliage_color":7318464,"fog_color":12638463},
{"id":48,"name":"ice_spikes","temperature":0.0,"downfall":0.5,"precipitation":"snow","water_color":4020182,"sky_color":7907327,"grass_color":12255547,"foliage_color":11599129,"fog_color":12638463},
{"id":49,"name":"old_growth_birch_forest","temperature":0.6,"downfall":0.6,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":8107825,"foliage_color":7318464,"fog_color":12638463},
{"id":51,"name":"birch_forest","temperature":0.6,"downfall":0.6,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":8107825,"foliage_color":7318464,"fog_color":12638463},
{"id":54,"name":"windswept_savanna","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":13551763,"foliage_color":12434877,"fog_color":12638463},
{"id":55,"name":"windswept_forest","temperature":0.2,"downfall":0.3,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10927977,"foliage_color":10854697,"fog_color":12638463},
{"id":56,"name":"windswept_gravelly_hills","temperature":0.2,"downfall":0.3,"precipitation":"rain","water_color":4159204,"sky_color":7907327,"grass_color":10927977,"foliage_color":10854697,"fog_color":12638463},
{"id":66,"name":"mangrove_swamp","temperature":0.8,"downfall":0.9,"precipitation":"rain","water_color":9478516,"sky_color":7907327,"grass_color":6975545,"foliage_color":6975545,"fog_color":12638463},
{"id":72,"name":"soul_sand_valley","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":5636761,"foliage_color":5636761,"fog_color":3348480},
{"id":73,"name":"crimson_forest","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":9115493,"foliage_color":9115493,"fog_color":3348480},
{"id":74,"name":"warped_forest","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":7856483,"foliage_color":7856483,"fog_color":3348480},
{"id":75,"name":"basalt_deltas","temperature":2.0,"downfall":0.0,"precipitation":"none","water_color":4159204,"sky_color":7254527,"grass_color":6578527,"foliage_color":6578527,"fog_color":3348480},
{"id":77,"name":"end_highlands","temperature":0.5,"downfall":0.5,"precipitation":"none","water_color":4159204,"sky_color":0,"grass_color":10065867,"foliage_color":10065867,"fog_color":10526880},
{"id":78,"name":"end_midlands","temperature":0.5,"downfall":0.5,"precipitation":"none","water_color":4159204,"sky_color":0,"grass_color":10065867,"foliage_color":10065867,"fog_color":10526880},
{"id":79,"name":"end_barrens","temperature":0.5,"downfall":0.5,"precipitation":"none","water_color":4159204,"sky_color":0,"grass_color":10065867,"foliage_color":10065867,"fog_color":10526880},
{"id":80,"name":"small_end_islands","temperature":0.5,"downfall":0.5,"precipitation":"none","water_color":4159204,"sky_color":0,"grass_color":10065867,"foliage_color":10065867,"fog_color":10526880}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
crates/biome-db/data/foliage.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

BIN
crates/biome-db/data/grass.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

File diff suppressed because one or more lines are too long

140
crates/biome-db/src/colour.rs Executable file
View file

@ -0,0 +1,140 @@
//! Biome colour utilities.
//!
//! Converts packed u32 colours to RGB arrays and applies
//! temperature/downfall adjustments for grass and foliage.
use crate::db::BiomeEntry;
pub struct BiomeColour;
fn u32_to_rgb(colour: u32) -> [u8; 3] {
let r = ((colour >> 16) & 0xFF) as u8;
let g = ((colour >> 8) & 0xFF) as u8;
let b = (colour & 0xFF) as u8;
[r, g, b]
}
impl BiomeColour {
pub fn water_color(biome: &BiomeEntry) -> [u8; 3] {
u32_to_rgb(biome.water_color)
}
pub fn sky_color(biome: &BiomeEntry) -> [u8; 3] {
u32_to_rgb(biome.sky_color)
}
pub fn fog_color(biome: &BiomeEntry) -> [u8; 3] {
u32_to_rgb(biome.fog_color)
}
/// Returns grass colour adjusted for temperature and downfall.
///
/// When `temperature * downfall` is low (cold/dry), the colour is
/// muted toward cooler tones. When high (warm/humid), it stays vibrant.
pub fn grass_color(biome: &BiomeEntry, temperature: f64, downfall: f64) -> [u8; 3] {
let rgb = u32_to_rgb(biome.grass_color);
adjust_colour(rgb, temperature, downfall)
}
/// Returns foliage colour adjusted for temperature and downfall.
pub fn foliage_color(biome: &BiomeEntry, temperature: f64, downfall: f64) -> [u8; 3] {
let rgb = u32_to_rgb(biome.foliage_color);
adjust_colour(rgb, temperature, downfall)
}
}
/// Applies Minecraft-style temperature/humidity colour adjustment.
///
/// Low `temp * downfall` mutes the colour (colder/grayer),
/// high values preserve the original vibrancy (warmer/greener).
fn adjust_colour(rgb: [u8; 3], temperature: f64, downfall: f64) -> [u8; 3] {
let temp = temperature.clamp(0.0, 1.0) as f32;
let humidity = downfall.clamp(0.0, 1.0) as f32;
let factor = (temp * humidity).clamp(0.0, 1.0);
let [r, g, b] = rgb;
[
(r as f32 * (0.7 + 0.3 * factor)) as u8,
(g as f32 * (0.6 + 0.4 * factor)) as u8,
(b as f32 * (0.5 + 0.5 * factor)) as u8,
]
}
#[cfg(test)]
mod tests {
use super::*;
use crate::db::BiomeDb;
fn sample_biome(name: &str) -> BiomeEntry {
let db = BiomeDb::new();
db.get_by_name(name).expect("biome must exist").clone()
}
#[test]
fn test_colours_non_zero() {
let db = BiomeDb::new();
for biome in db.all_biomes() {
let water = BiomeColour::water_color(biome);
let sky = BiomeColour::sky_color(biome);
let fog = BiomeColour::fog_color(biome);
let grass =
BiomeColour::grass_color(biome, biome.temperature as f64, biome.downfall as f64);
let foliage =
BiomeColour::foliage_color(biome, biome.temperature as f64, biome.downfall as f64);
assert!(
water != [0, 0, 0] || biome.name == "the_end" || biome.name.contains("end_"),
"water colour should not be zero for {}: {:?}",
biome.name,
water
);
let all = [water, sky, fog, grass, foliage];
for (i, c) in all.iter().enumerate() {
assert_eq!(
c.len(),
3,
"colour {} for {} should have 3 components",
i,
biome.name
);
}
}
}
#[test]
fn test_plains_colours() {
let biome = sample_biome("plains");
let water = BiomeColour::water_color(&biome);
assert_eq!(water, [63, 118, 228]);
let grass = BiomeColour::grass_color(&biome, 0.8, 0.4);
assert_eq!(grass.len(), 3);
}
#[test]
fn test_desert_colours() {
let biome = sample_biome("desert");
let sky = BiomeColour::sky_color(&biome);
assert_eq!(sky, [110, 177, 255]);
let water = BiomeColour::water_color(&biome);
assert_eq!(water, [63, 118, 228]);
}
#[test]
fn test_cold_dry_adjustment() {
let biome = sample_biome("plains");
let warm = BiomeColour::grass_color(&biome, 1.0, 1.0);
let cold = BiomeColour::grass_color(&biome, 0.0, 0.0);
assert!(
warm[0] >= cold[0] && warm[1] >= cold[1] && warm[2] >= cold[2],
"warm climate should produce more vibrant colours: warm={:?} cold={:?}",
warm,
cold
);
}
#[test]
fn test_swamp_water() {
let biome = sample_biome("swamp");
let water = BiomeColour::water_color(&biome);
assert_eq!(water, [144, 161, 116]);
}
}

118
crates/biome-db/src/db.rs Executable file
View file

@ -0,0 +1,118 @@
//! Biome database with Minecraft 1.21 biome data.
//! Loaded from embedded JSON via `include_str!`.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BiomeEntry {
pub name: String,
pub id: i32,
pub temperature: f32,
pub downfall: f32,
pub precipitation: String,
pub water_color: u32,
pub sky_color: u32,
pub grass_color: u32,
pub foliage_color: u32,
pub fog_color: u32,
}
pub struct BiomeDb {
by_name: HashMap<String, BiomeEntry>,
by_id: HashMap<i32, BiomeEntry>,
}
impl BiomeDb {
pub fn new() -> Self {
let data = include_str!("../data/biomes.json");
let entries: Vec<BiomeEntry> =
serde_json::from_str(data).expect("biomes.json must be valid JSON");
let mut by_name = HashMap::new();
let mut by_id = HashMap::new();
for entry in entries {
by_name.insert(entry.name.clone(), entry.clone());
by_id.insert(entry.id, entry);
}
Self { by_name, by_id }
}
pub fn get_by_name(&self, name: &str) -> Option<&BiomeEntry> {
self.by_name.get(name)
}
pub fn get_by_id(&self, id: i32) -> Option<&BiomeEntry> {
self.by_id.get(&id)
}
pub fn all_biomes(&self) -> impl Iterator<Item = &BiomeEntry> {
let mut ids: Vec<_> = self.by_id.keys().copied().collect();
ids.sort();
ids.into_iter().map(move |id| &self.by_id[&id])
}
pub fn len(&self) -> usize {
self.by_id.len()
}
pub fn is_empty(&self) -> bool {
self.by_id.is_empty()
}
}
impl Default for BiomeDb {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_all_biomes() {
let db = BiomeDb::new();
assert!(!db.is_empty(), "biome db should not be empty");
assert!(db.len() > 60, "expected 60+ biomes, got {}", db.len());
}
#[test]
fn test_lookup_by_name() {
let db = BiomeDb::new();
let plains = db.get_by_name("plains").expect("plains should exist");
assert_eq!(plains.id, 1);
assert_eq!(plains.temperature, 0.8);
let desert = db.get_by_name("desert").expect("desert should exist");
assert_eq!(desert.id, 2);
assert_eq!(desert.precipitation, "none");
let ocean = db.get_by_name("ocean").expect("ocean should exist");
assert_eq!(ocean.id, 0);
}
#[test]
fn test_lookup_by_id() {
let db = BiomeDb::new();
let plains = db.get_by_id(1).expect("id 1 should be plains");
assert_eq!(plains.name, "plains");
let desert = db.get_by_id(2).expect("id 2 should be desert");
assert_eq!(desert.name, "desert");
}
#[test]
fn test_unknown_biome() {
let db = BiomeDb::new();
assert!(db.get_by_name("nonexistent").is_none());
assert!(db.get_by_id(9999).is_none());
}
#[test]
fn test_all_biomes_iter() {
let db = BiomeDb::new();
let count = db.all_biomes().count();
assert_eq!(count, db.len());
}
}

10
crates/biome-db/src/lib.rs Executable file
View file

@ -0,0 +1,10 @@
//! Biome database for Terrafier.
//!
//! Maps biome IDs to names, colours, and patterns for all supported
//! Minecraft versions (1.0 through 1.21+).
pub mod colour;
pub mod db;
pub use colour::BiomeColour;
pub use db::{BiomeDb, BiomeEntry};