Add more natural elements

This commit is contained in:
louis-e
2025-07-20 23:24:12 +02:00
parent 3ea85009e7
commit 52fe87b90d
2 changed files with 196 additions and 7 deletions

View File

@@ -108,6 +108,8 @@ pub fn generate_world(
buildings::generate_building_from_relation(&mut editor, rel, args);
} else if rel.tags.contains_key("water") {
water_areas::generate_water_areas(&mut editor, rel);
} else if rel.tags.contains_key("natural") {
natural::generate_natural_from_relation(&mut editor, rel, args);
} else if rel.tags.get("leisure") == Some(&"park".to_string()) {
leisure::generate_leisure_from_relation(&mut editor, rel, args);
}

View File

@@ -1,9 +1,10 @@
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::coordinate_system::cartesian::XZPoint;
use crate::element_processing::tree::Tree;
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedElement;
use crate::osm_parser::{ProcessedElement, ProcessedMemberRole, ProcessedRelation, ProcessedWay};
use crate::world_editor::WorldEditor;
use rand::Rng;
@@ -38,10 +39,19 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ar
"blockfield" => COBBLESTONE,
"glacier" => PACKED_ICE,
"mud" | "wetland" => MUD,
"mountain_range" => COBBLESTONE,
"saddle" | "ridge" => STONE,
"shrubbery" | "tundra" | "hill" => GRASS_BLOCK,
"cliff" => STONE,
_ => GRASS_BLOCK,
};
let ProcessedElement::Way(way) = element else {
println!(
"Warning: Element with natural type '{}' is not a way, skipping.",
natural_type
);
print!("Element: {element:?}");
return;
};
@@ -82,16 +92,14 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ar
// Generate custom layer instead of dirt, must be stone on the lowest level
match natural_type.as_str() {
"beach" | "sand" | "dune" | "shoal" => {
editor.set_block(SAND, x, -1, z, None, None);
editor.set_block(STONE, x, -2, z, None, None);
editor.set_block(SAND, x, 0, z, None, None);
}
"glacier" => {
editor.set_block(PACKED_ICE, x, -1, z, None, None);
editor.set_block(STONE, x, -2, z, None, None);
editor.set_block(PACKED_ICE, x, 0, z, None, None);
editor.set_block(STONE, x, -1, z, None, None);
}
"bare_rock" => {
editor.set_block(STONE, x, -1, z, None, None);
editor.set_block(STONE, x, -2, z, None, None);
editor.set_block(STONE, x, 0, z, None, None);
}
_ => {}
}
@@ -254,6 +262,149 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ar
editor.set_block(GRASS, x, 1, z, None, None);
}
}
"mountain_range" => {
// Create block clusters instead of random placement
let cluster_chance = rng.gen_range(0..1000);
if cluster_chance < 50 { // 5% chance to start a new cluster
let cluster_block = match rng.gen_range(0..7) {
0 => DIRT,
1 => STONE,
2 => GRAVEL,
3 => GRANITE,
4 => DIORITE,
5 => ANDESITE,
_ => GRASS_BLOCK,
};
// Generate cluster size (5-10 blocks radius)
let cluster_size = rng.gen_range(5..=10);
// Create cluster around current position
for dx in -(cluster_size as i32)..=(cluster_size as i32) {
for dz in -(cluster_size as i32)..=(cluster_size as i32) {
let cluster_x = x + dx;
let cluster_z = z + dz;
// Use distance to create more natural cluster shape
let distance = ((dx * dx + dz * dz) as f32).sqrt();
if distance <= cluster_size as f32 {
// Probability decreases with distance from center
let place_prob = 1.0 - (distance / cluster_size as f32);
if rng.gen::<f32>() < place_prob {
editor.set_block(cluster_block, cluster_x, 0, cluster_z, None, None);
// Add vegetation on grass blocks
if cluster_block == GRASS_BLOCK {
let vegetation_chance = rng.gen_range(0..100);
if vegetation_chance == 0 { // 1% chance for rare trees
Tree::create(editor, (cluster_x, 1, cluster_z));
} else if vegetation_chance < 15 { // 15% chance for grass
editor.set_block(GRASS, cluster_x, 1, cluster_z, None, None);
} else if vegetation_chance < 25 { // 10% chance for oak leaves
editor.set_block(OAK_LEAVES, cluster_x, 1, cluster_z, None, None);
}
}
}
}
}
}
}
}
"saddle" => {
// Saddle areas - lowest point between peaks, mix of stone and grass
let terrain_chance = rng.gen_range(0..100);
if terrain_chance < 30 { // 30% chance for exposed stone
editor.set_block(STONE, x, 0, z, None, None);
} else if terrain_chance < 50 { // 20% chance for gravel/rocky terrain
editor.set_block(GRAVEL, x, 0, z, None, None);
} else { // 50% chance for grass
editor.set_block(GRASS_BLOCK, x, 0, z, None, None);
if rng.gen_bool(0.4) { // 40% chance for grass on top
editor.set_block(GRASS, x, 1, z, None, None);
}
}
}
"ridge" => {
// Ridge areas - elevated crest, mostly rocky with some vegetation
let ridge_chance = rng.gen_range(0..100);
if ridge_chance < 60 { // 60% chance for stone/rocky terrain
let rock_type = match rng.gen_range(0..4) {
0 => STONE,
1 => COBBLESTONE,
2 => GRANITE,
_ => ANDESITE,
};
editor.set_block(rock_type, x, 0, z, None, None);
} else { // 40% chance for grass with sparse vegetation
editor.set_block(GRASS_BLOCK, x, 0, z, None, None);
let vegetation_chance = rng.gen_range(0..100);
if vegetation_chance < 20 { // 20% chance for grass
editor.set_block(GRASS, x, 1, z, None, None);
} else if vegetation_chance < 25 { // 5% chance for small shrubs
editor.set_block(OAK_LEAVES, x, 1, z, None, None);
}
}
}
"shrubbery" => {
// Manicured shrubs and decorative vegetation
editor.set_block(OAK_LEAVES, x, 1, z, None, None);
editor.set_block(OAK_LEAVES, x, 2, z, None, None);
}
"tundra" => {
// Treeless habitat with low vegetation, mosses, lichens
if !editor.check_for_block(x, 0, z, Some(&[GRASS_BLOCK])) {
continue;
}
let tundra_chance = rng.gen_range(0..100);
if tundra_chance < 40 { // 40% chance for grass (sedges, grasses)
editor.set_block(GRASS, x, 1, z, None, None);
} else if tundra_chance < 60 { // 20% chance for moss
editor.set_block(MOSS_BLOCK, x, 0, z, Some(&[GRASS_BLOCK]), None);
} else if tundra_chance < 70 { // 10% chance for dead bush (lichens)
editor.set_block(DEAD_BUSH, x, 1, z, None, None);
}
// 30% chance for bare ground (no surface block)
}
"cliff" => {
// Cliff areas - predominantly stone with minimal vegetation
let cliff_chance = rng.gen_range(0..100);
if cliff_chance < 90 { // 90% chance for stone variants
let stone_type = match rng.gen_range(0..4) {
0 => STONE,
1 => COBBLESTONE,
2 => ANDESITE,
_ => DIORITE,
};
editor.set_block(stone_type, x, 0, z, None, None);
} else { // 10% chance for gravel/loose rock
editor.set_block(GRAVEL, x, 0, z, None, None);
}
}
"hill" => {
// Hill areas - elevated terrain with sparse trees and mostly grass
if !editor.check_for_block(x, 0, z, Some(&[GRASS_BLOCK])) {
continue;
}
let hill_chance = rng.gen_range(0..1000);
if hill_chance == 0 { // 0.1% chance for rare trees
Tree::create(editor, (x, 1, z));
} else if hill_chance < 50 { // 5% chance for flowers
let flower_block = match rng.gen_range(1..=4) {
1 => RED_FLOWER,
2 => BLUE_FLOWER,
3 => YELLOW_FLOWER,
_ => WHITE_FLOWER,
};
editor.set_block(flower_block, x, 1, z, None, None);
} else if hill_chance < 600 { // 55% chance for grass
editor.set_block(GRASS, x, 1, z, None, None);
} else if hill_chance < 650 { // 5% chance for tall grass
editor.set_block(TALL_GRASS_BOTTOM, x, 1, z, None, None);
editor.set_block(TALL_GRASS_TOP, x, 2, z, None, None);
}
// 35% chance for bare grass block
}
_ => {}
}
}
@@ -261,3 +412,39 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ar
}
}
}
pub fn generate_natural_from_relation(
editor: &mut WorldEditor,
rel: &ProcessedRelation,
args: &Args,
) {
if rel.tags.contains_key("natural") {
// Generate individual ways with their original tags
for member in &rel.members {
if member.role == ProcessedMemberRole::Outer {
generate_natural(editor, &ProcessedElement::Way(member.way.clone()), args);
}
}
// Combine all outer ways into one with relation tags
let mut combined_nodes = Vec::new();
for member in &rel.members {
if member.role == ProcessedMemberRole::Outer {
combined_nodes.extend(member.way.nodes.clone());
}
}
// Only process if we have nodes
if !combined_nodes.is_empty() {
// Create combined way with relation tags
let combined_way = ProcessedWay {
id: rel.id,
nodes: combined_nodes,
tags: rel.tags.clone(),
};
// Generate natural area from combined way
generate_natural(editor, &ProcessedElement::Way(combined_way), args);
}
}
}