From 52fe87b90d26b44dfd710c19ef4db66dc42567ae Mon Sep 17 00:00:00 2001 From: louis-e <44675238+louis-e@users.noreply.github.com> Date: Sun, 20 Jul 2025 23:24:12 +0200 Subject: [PATCH] Add more natural elements --- src/data_processing.rs | 2 + src/element_processing/natural.rs | 201 ++++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 7 deletions(-) diff --git a/src/data_processing.rs b/src/data_processing.rs index afacc6f..8e845cb 100644 --- a/src/data_processing.rs +++ b/src/data_processing.rs @@ -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); } diff --git a/src/element_processing/natural.rs b/src/element_processing/natural.rs index e9ce98f..fc8615b 100644 --- a/src/element_processing/natural.rs +++ b/src/element_processing/natural.rs @@ -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::() < 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); + } + } +}