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:
commit
91835402b3
94 changed files with 12372 additions and 0 deletions
28
core/src/model/brush.rs
Executable file
28
core/src/model/brush.rs
Executable 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
68
core/src/model/dimension.rs
Executable 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
13
core/src/model/layers.rs
Executable 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
17
core/src/model/mod.rs
Executable 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
20
core/src/model/platform.rs
Executable 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
26
core/src/model/terrain.rs
Executable 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
62
core/src/model/tile.rs
Executable 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
68
core/src/model/world.rs
Executable 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")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue