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

28
core/src/model/brush.rs Executable file
View file

@ -0,0 +1,28 @@
pub trait Brush: Send + Sync {
fn get_strength(&self, dx: f64, dy: f64) -> f64;
fn radius(&self) -> f64;
}
pub struct SymmetricBrush {
pub radius: f64,
}
impl SymmetricBrush {
pub fn new(radius: f64) -> Self {
Self { radius }
}
}
impl Brush for SymmetricBrush {
fn get_strength(&self, dx: f64, dy: f64) -> f64 {
let dist = (dx * dx + dy * dy).sqrt();
if dist >= self.radius {
0.0
} else {
1.0 - (dist / self.radius)
}
}
fn radius(&self) -> f64 {
self.radius
}
}

68
core/src/model/dimension.rs Executable file
View file

@ -0,0 +1,68 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::model::tile::Tile;
#[derive(Debug, Clone)]
pub struct Dimension {
pub name: String,
pub tiles: HashMap<(i32, i32), Tile>,
pub min_height: i16,
pub max_height: i16,
pub seed: u64,
}
impl Serialize for Dimension {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let tiles: HashMap<String, &Tile> = self
.tiles
.iter()
.map(|(&(tx, tz), tile)| (format!("{},{}", tx, tz), tile))
.collect();
#[derive(Serialize)]
struct Dim<'a> {
name: &'a str,
tiles: HashMap<String, &'a Tile>,
min_height: i16,
max_height: i16,
seed: u64,
}
Dim {
name: &self.name,
tiles,
min_height: self.min_height,
max_height: self.max_height,
seed: self.seed,
}
.serialize(s)
}
}
impl<'de> Deserialize<'de> for Dimension {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Dim {
name: String,
tiles: HashMap<String, Tile>,
min_height: i16,
max_height: i16,
seed: u64,
}
let dim = Dim::deserialize(d)?;
let mut tiles = HashMap::with_capacity(dim.tiles.len());
for (key, tile) in dim.tiles {
if let Some((tx_s, tz_s)) = key.split_once(',') {
if let (Ok(tx), Ok(tz)) = (tx_s.parse::<i32>(), tz_s.parse::<i32>()) {
tiles.insert((tx, tz), tile);
}
}
}
Ok(Self {
name: dim.name,
tiles,
min_height: dim.min_height,
max_height: dim.max_height,
seed: dim.seed,
})
}
}

13
core/src/model/layers.rs Executable file
View file

@ -0,0 +1,13 @@
pub trait Layer: Send + Sync {
fn id(&self) -> &'static str;
fn name(&self) -> &'static str;
fn data_size(&self) -> DataSize;
fn priority(&self) -> i32;
}
pub enum DataSize {
Bit,
Nibble,
Byte,
Int,
}

17
core/src/model/mod.rs Executable file
View file

@ -0,0 +1,17 @@
//! World model — the core data structures.
//!
//! Contains World, Dimension, Tile, Terrain types and related utilities.
pub mod brush;
pub mod dimension;
pub mod layers;
pub mod platform;
pub mod terrain;
pub mod tile;
pub mod world;
pub use dimension::Dimension;
pub use platform::Platform;
pub use terrain::Terrain;
pub use tile::Tile;
pub use world::World;

20
core/src/model/platform.rs Executable file
View file

@ -0,0 +1,20 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Platform {
pub id: String,
pub display_name: String,
pub min_height: i16,
pub max_height: i16,
}
impl Platform {
pub fn java_1_18() -> Self {
Self {
id: "java_anvil_1_18".to_string(),
display_name: "Minecraft Java 1.18+".to_string(),
min_height: -64,
max_height: 320,
}
}
}

26
core/src/model/terrain.rs Executable file
View file

@ -0,0 +1,26 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Terrain {
Desert,
Grass,
Forest,
Rock,
Sand,
Swamp,
Water,
}
impl Terrain {
pub fn name(&self) -> &'static str {
match self {
Terrain::Desert => "Desert",
Terrain::Grass => "Grass",
Terrain::Forest => "Forest",
Terrain::Rock => "Rock",
Terrain::Sand => "Sand",
Terrain::Swamp => "Swamp",
Terrain::Water => "Water",
}
}
}

62
core/src/model/tile.rs Executable file
View file

@ -0,0 +1,62 @@
use serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
pub use crate::coords::{TILE_SIZE, TILE_SIZE_BITS};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tile {
pub x: i32,
pub z: i32,
pub min_height: i16,
pub max_height: i16,
#[serde(with = "BigArray")]
pub heightmap: [i16; 16384],
#[serde(with = "BigArray")]
pub terrain: [u8; 16384],
#[serde(with = "BigArray")]
pub water_level: [u8; 16384],
pub layer_data: std::collections::HashMap<u32, LayerBuffer>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LayerBuffer {
Bit(Vec<u64>),
Nibble(Vec<u8>),
Byte(Vec<u8>),
Int(Vec<i32>),
}
impl Tile {
pub fn new(x: i32, z: i32, min_height: i16, max_height: i16) -> Self {
Self {
x,
z,
min_height,
max_height,
heightmap: [0i16; 16384],
terrain: [0u8; 16384],
water_level: [0u8; 16384],
layer_data: std::collections::HashMap::new(),
}
}
/// Get height at local tile coordinates.
pub fn get_height(&self, lx: usize, lz: usize) -> i16 {
self.heightmap[lz * TILE_SIZE + lx]
}
/// Set height at local tile coordinates.
pub fn set_height(&mut self, lx: usize, lz: usize, height: i16) {
self.heightmap[lz * TILE_SIZE + lx] = height;
}
/// Get terrain type at local tile coordinates.
pub fn get_terrain(&self, lx: usize, lz: usize) -> u8 {
self.terrain[lz * TILE_SIZE + lx]
}
/// Set terrain type at local tile coordinates.
pub fn set_terrain(&mut self, lx: usize, lz: usize, terrain: u8) {
self.terrain[lz * TILE_SIZE + lx] = terrain;
}
}

68
core/src/model/world.rs Executable file
View file

@ -0,0 +1,68 @@
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use crate::model::dimension::Dimension;
use crate::model::platform::Platform;
use crate::ops::heightmap::{HeightMapSource, NoiseHeightMap};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct World {
pub name: String,
pub platform: Platform,
pub dimensions: Vec<Dimension>,
pub seed: u64,
}
impl World {
/// Create a new world with a single overworld dimension and generated terrain.
pub fn new(name: &str, seed: u64) -> Self {
let platform = Platform::java_1_18();
// Generate tiles in parallel using rayon
let coords: Vec<(i32, i32)> = (-1..=1)
.flat_map(|tx| (-1..=1).map(move |tz| (tx, tz)))
.collect();
let tiles: Vec<(i32, i32, crate::model::tile::Tile)> = coords
.par_iter()
.map(|&(tx, tz)| {
let mut tile =
crate::model::tile::Tile::new(tx, tz, platform.min_height, platform.max_height);
let source = NoiseHeightMap::default();
source.generate(
&mut tile,
seed.wrapping_add(
(tx.wrapping_mul(374_761_393) as u64)
.wrapping_add((tz as u64).wrapping_mul(668_265_263)),
),
);
(tx, tz, tile)
})
.collect();
let mut dim_tiles = std::collections::HashMap::with_capacity(tiles.len());
for (tx, tz, tile) in tiles {
dim_tiles.insert((tx, tz), tile);
}
let dimension = Dimension {
name: "overworld".to_string(),
tiles: dim_tiles,
min_height: platform.min_height,
max_height: platform.max_height,
seed,
};
Self {
name: name.to_string(),
platform,
dimensions: vec![dimension],
seed,
}
}
/// Get mutable reference to the overworld dimension.
pub fn overworld_mut(&mut self) -> Option<&mut Dimension> {
self.dimensions.iter_mut().find(|d| d.name == "overworld")
}
}