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

View file

@ -0,0 +1,19 @@
"""Download Minecraft colormap PNG files for embedding."""
import urllib.request
import os
base_url = "https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.21.11/assets/minecraft/textures/colormap"
output_dir = os.path.join(os.path.dirname(__file__), "..", "crates", "biome-db", "data")
files = ["grass.png", "foliage.png", "dry_foliage.png"]
for name in files:
url = f"{base_url}/{name}"
outpath = os.path.join(output_dir, name)
print(f"Downloading {name}...")
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
resp = urllib.request.urlopen(req, timeout=15)
data = resp.read()
with open(outpath, "wb") as f:
f.write(data)
print(f" {len(data)} bytes -> {outpath}")

90
scripts/extract_colormaps.py Executable file
View file

@ -0,0 +1,90 @@
"""Download and extract Minecraft colour map PNGs as raw RGB arrays.
The grass.png and foliage.png are 256x256 colour lookup tables.
x-axis = temperature (0-255), y-axis = downfall (0-255).
"""
import urllib.request
import struct
import os
import json
import zlib
def download_file(url):
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
resp = urllib.request.urlopen(req, timeout=15)
return resp.read()
def parse_png_rgb(data):
"""Parse a PNG file and extract RGB pixel data as a flat array."""
# PNG signature check
assert data[:8] == b'\x89PNG\r\n\x1a\n', "Not a valid PNG"
# Find IHDR and IDAT chunks
pos = 8
chunks = []
idat_data = b''
while pos < len(data):
length = struct.unpack('>I', data[pos:pos+4])[0]
chunk_type = data[pos+4:pos+8]
chunk_data = data[pos+8:pos+8+length]
if chunk_type == b'IHDR':
width = struct.unpack('>I', chunk_data[0:4])[0]
height = struct.unpack('>I', chunk_data[4:8])[0]
bit_depth = chunk_data[8]
color_type = chunk_data[9]
elif chunk_type == b'IDAT':
idat_data += chunk_data
elif chunk_type == b'IEND':
break
pos += 12 + length
# Decompress
raw_data = zlib.decompress(idat_data)
# Extract RGB pixels (filter byte per row)
pixels = []
row_size = 1 + width * 3 # filter byte + RGB per pixel
for y in range(height):
row_start = y * row_size + 1 # skip filter byte
for x in range(width):
offset = row_start + x * 3
r = raw_data[offset]
g = raw_data[offset + 1]
b = raw_data[offset + 2]
pixels.append([r, g, b])
return width, height, pixels
def main():
base_url = "https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.21.11/assets/minecraft/textures/colormap"
output_dir = os.path.join(os.path.dirname(__file__), "..", "crates", "biome-db", "data")
for name in ["grass", "foliage"]:
url = f"{base_url}/{name}.png"
print(f"Downloading {name}.png...")
png_data = download_file(url)
width, height, pixels = parse_png_rgb(png_data)
print(f" Size: {width}x{height}, pixels: {len(pixels)}")
# Save as JSON array: [[r,g,b], ...] flattened
flat = [v for pixel in pixels for v in pixel]
output = {
"width": width,
"height": height,
"data": flat,
}
out_path = os.path.join(output_dir, f"{name}_colormap.json")
with open(out_path, "w") as f:
json.dump(output, f)
print(f" Written to {out_path}")
if __name__ == "__main__":
main()

11
scripts/release.ps1 Executable file
View file

@ -0,0 +1,11 @@
# Build release binaries
cargo build --release
# Create dist directory
New-Item -ItemType Directory -Force -Path dist
# Copy binaries
Copy-Item target/release/terrafier-cli.exe dist/
Copy-Item target/release/terrafier-gui.exe dist/
Write-Host "Release binaries in dist/"

6
scripts/release.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/bash
cargo build --release
mkdir -p dist
cp target/release/terrafier-cli dist/
cp target/release/terrafier-gui dist/
echo "Release binaries in dist/"

122
scripts/scrape_biomes.py Executable file
View file

@ -0,0 +1,122 @@
"""Scrape Minecraft 1.21 biome data from mcasset.cloud GitHub repo."""
import json
import urllib.request
import os
BIOME_NAMES = [
"badlands", "bamboo_jungle", "basalt_deltas", "beach", "birch_forest",
"cherry_grove", "cold_ocean", "crimson_forest", "dark_forest",
"deep_cold_ocean", "deep_dark", "deep_frozen_ocean", "deep_lukewarm_ocean",
"deep_ocean", "desert", "dripstone_caves", "end_barrens", "end_highlands",
"end_midlands", "eroded_badlands", "flower_forest", "forest",
"frozen_ocean", "frozen_peaks", "frozen_river", "grove", "ice_spikes",
"jagged_peaks", "jungle", "lukewarm_ocean", "lush_caves",
"mangrove_swamp", "meadow", "mushroom_fields", "nether_wastes", "ocean",
"old_growth_birch_forest", "old_growth_pine_taiga",
"old_growth_spruce_taiga", "pale_garden", "plains", "river", "savanna",
"savanna_plateau", "small_end_islands", "snowy_beach", "snowy_plains",
"snowy_slopes", "snowy_taiga", "soul_sand_valley", "sparse_jungle",
"stony_peaks", "stony_shore", "sunflower_plains", "swamp", "taiga",
"the_end", "the_void", "warm_ocean", "warped_forest",
"windswept_forest", "windswept_gravelly_hills", "windswept_hills",
"windswept_savanna", "wooded_badlands",
]
BASE_URL = "https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.21.11/data/minecraft/worldgen/biome"
def hex_to_int(hex_str):
"""Convert '#3f76e4' to integer (0x3f76e4 = 4159204)."""
return int(hex_str.lstrip("#"), 16)
def determine_precipitation(temperature, has_precip, name):
"""Determine precipitation type: 'rain', 'snow', or 'none'."""
if not has_precip:
return "none"
if temperature <= 0.15:
return "snow"
return "rain"
def parse_biome(name, data):
"""Parse a Minecraft biome JSON into a flat entry."""
effects = data.get("effects", {})
attributes = data.get("attributes", {})
temperature = data.get("temperature", 0.5)
downfall = data.get("downfall", 0.5)
has_precip = data.get("has_precipitation", True)
# Water color from effects
water_color = hex_to_int(effects.get("water_color", "#3f76e4"))
# Sky color from attributes
sky_attr = attributes.get("minecraft:visual/sky_color", "#78a7ff")
sky_color = hex_to_int(sky_attr)
# Fog color from attributes (Nether biomes use this)
fog_attr = attributes.get("minecraft:visual/fog_color")
if fog_attr:
fog_color = hex_to_int(fog_attr)
else:
# Default fog color same as sky if not specified
fog_color = hex_to_int("#c0d8ff")
# Grass and foliage colours (optional, only when biome overrides default)
grass_color = effects.get("grass_color")
if grass_color:
grass_color = hex_to_int(grass_color)
foliage_color = effects.get("foliage_color")
if foliage_color:
foliage_color = hex_to_int(foliage_color)
grass_modifier = effects.get("grass_color_modifier")
return {
"name": name,
"temperature": temperature,
"downfall": downfall,
"precipitation": determine_precipitation(temperature, has_precip, name),
"water_color": water_color,
"sky_color": sky_color,
"fog_color": fog_color,
"grass_color": grass_color,
"foliage_color": foliage_color,
"grass_color_modifier": grass_modifier,
}
def main():
output_dir = os.path.join(os.path.dirname(__file__), "..", "crates", "biome-db", "data")
os.makedirs(output_dir, exist_ok=True)
biomes = []
for name in BIOME_NAMES:
url = f"{BASE_URL}/{name}.json"
try:
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
resp = urllib.request.urlopen(req, timeout=15)
data = json.loads(resp.read().decode("utf-8"))
entry = parse_biome(name, data)
biomes.append(entry)
print(f" OK {name}: temp={entry['temperature']}, down={entry['downfall']}, precip={entry['precipitation']}")
except Exception as e:
print(f" FAIL {name}: {e}")
print(f"\nTotal biomes scraped: {len(biomes)}")
output = os.path.join(output_dir, "biomes.json")
with open(output, "w", encoding="utf-8") as f:
json.dump(biomes, f, indent=2)
print(f"Written to {output}")
# Verify with biome count
print(f"\nBiomes with custom grass_color: {sum(1 for b in biomes if b['grass_color'] is not None)}")
print(f"Biomes with custom foliage_color: {sum(1 for b in biomes if b['foliage_color'] is not None)}")
print(f"Precipitation types: {set(b['precipitation'] for b in biomes)}")
if __name__ == "__main__":
main()