diff --git a/src/block_definitions.rs b/src/block_definitions.rs index 8c45a21..7c79826 100644 --- a/src/block_definitions.rs +++ b/src/block_definitions.rs @@ -137,6 +137,7 @@ impl Block { 107 => "dark_oak_door", 108 => "potatoes", 109 => "wheat", + 110 => "bedrock", _ => panic!("Invalid id"), } } @@ -291,6 +292,8 @@ pub const DARK_OAK_DOOR_UPPER: Block = Block::new(107); pub const POTATOES: Block = Block::new(108); pub const WHEAT: Block = Block::new(109); +pub const BEDROCK: Block = Block::new(110); + // Variations for building corners pub fn building_corner_variations() -> Vec { vec![ diff --git a/src/cartesian.rs b/src/cartesian.rs new file mode 100644 index 0000000..a099f96 --- /dev/null +++ b/src/cartesian.rs @@ -0,0 +1,27 @@ +#[derive(Copy, Clone)] +pub struct XZPoint { + pub x: i32, + pub z: i32, +} + +impl XZPoint { + pub fn new(x: i32, z: i32) -> Self { + Self { x, z } + } +} + +pub struct XYZPoint { + pub x: i32, + pub y: i32, + pub z: i32, +} + +impl XYZPoint { + pub fn from_xz(xz: XZPoint, y: i32) -> Self { + Self { + x: xz.x, + y, + z: xz.z, + } + } +} diff --git a/src/data_processing.rs b/src/data_processing.rs index 26ee8c2..24ab0e8 100644 --- a/src/data_processing.rs +++ b/src/data_processing.rs @@ -1,6 +1,8 @@ use crate::args::Args; -use crate::block_definitions::{DIRT, GRASS_BLOCK}; +use crate::block_definitions::{BEDROCK, DIRT, GRASS_BLOCK, STONE}; +use crate::cartesian::XZPoint; use crate::element_processing::*; +use crate::ground::Ground; use crate::osm_parser::ProcessedElement; use crate::world_editor::WorldEditor; use colored::Colorize; @@ -10,6 +12,9 @@ use std::fs; use std::io::Write; use std::path::Path; +const MIN_Y: i32 = -64; +const MAX_Y: i32 = 256; + pub fn generate_world( elements: Vec, args: &Args, @@ -20,7 +25,9 @@ pub fn generate_world( let region_template_path: &str = "region.template"; let region_dir: String = format!("{}/region", args.path); - let ground_level: i32 = -62; + + let ground = Ground::new(); + let ground_level = 60; // TODO // Check if the region.template file exists, and download if necessary if !Path::new(region_template_path).exists() { @@ -58,53 +65,33 @@ pub fn generate_world( match element { ProcessedElement::Way(way) => { if way.tags.contains_key("building") || way.tags.contains_key("building:part") { - buildings::generate_buildings( - &mut editor, - way, - ground_level, - args.timeout.as_ref(), - ); + buildings::generate_buildings(&mut editor, way, &ground, args.timeout.as_ref()); } else if way.tags.contains_key("highway") { highways::generate_highways( &mut editor, element, - ground_level, + &ground, args.timeout.as_ref(), ); } else if way.tags.contains_key("landuse") { - landuse::generate_landuse( - &mut editor, - way, - ground_level, - args.timeout.as_ref(), - ); + landuse::generate_landuse(&mut editor, way, &ground, args.timeout.as_ref()); } else if way.tags.contains_key("natural") { - natural::generate_natural( - &mut editor, - element, - ground_level, - args.timeout.as_ref(), - ); + natural::generate_natural(&mut editor, element, &ground, args.timeout.as_ref()); } else if way.tags.contains_key("amenity") { amenities::generate_amenities( &mut editor, element, - ground_level, + &ground, args.timeout.as_ref(), ); } else if way.tags.contains_key("leisure") { - leisure::generate_leisure( - &mut editor, - way, - ground_level, - args.timeout.as_ref(), - ); + leisure::generate_leisure(&mut editor, way, &ground, args.timeout.as_ref()); } else if way.tags.contains_key("barrier") { - barriers::generate_barriers(&mut editor, element, ground_level); + barriers::generate_barriers(&mut editor, element, &ground); } else if way.tags.contains_key("waterway") { waterways::generate_waterways(&mut editor, way, ground_level); } else if way.tags.contains_key("bridge") { - bridges::generate_bridges(&mut editor, way, ground_level); + bridges::generate_bridges(&mut editor, way, &ground); } else if way.tags.contains_key("railway") { railways::generate_railways(&mut editor, way, ground_level); } else if way.tags.get("service") == Some(&"siding".to_string()) { @@ -117,26 +104,21 @@ pub fn generate_world( } else if node.tags.contains_key("natural") && node.tags.get("natural") == Some(&"tree".to_string()) { - natural::generate_natural( - &mut editor, - element, - ground_level, - args.timeout.as_ref(), - ); + natural::generate_natural(&mut editor, element, &ground, args.timeout.as_ref()); } else if node.tags.contains_key("amenity") { amenities::generate_amenities( &mut editor, element, - ground_level, + &ground, args.timeout.as_ref(), ); } else if node.tags.contains_key("barrier") { - barriers::generate_barriers(&mut editor, element, ground_level); + barriers::generate_barriers(&mut editor, element, &ground); } else if node.tags.contains_key("highway") { highways::generate_highways( &mut editor, element, - ground_level, + &ground, args.timeout.as_ref(), ); } else if node.tags.contains_key("tourism") { @@ -145,7 +127,7 @@ pub fn generate_world( } ProcessedElement::Relation(rel) => { if rel.tags.contains_key("water") { - water_areas::generate_water_areas(&mut editor, rel, ground_level); + water_areas::generate_water_areas(&mut editor, rel, &ground); } } } @@ -171,8 +153,28 @@ pub fn generate_world( for x in 0..=(scale_factor_x as i32) { for z in 0..=(scale_factor_z as i32) { - editor.set_block(GRASS_BLOCK, x, ground_level, z, None, None); - editor.set_block(DIRT, x, ground_level - 1, z, None, None); + // Use the smaller of [current block y, ground level y] + let max_y = (MIN_Y..MAX_Y) + .filter(|y| editor.block_at(x, *y, z)) + .next() + .unwrap_or(MAX_Y) + .min(ground.level(XZPoint::new(x, z))); + + // 1 layer of grass + editor.set_block(GRASS_BLOCK, x, max_y, z, None, None); + + // 3 layers of dirt + for y in (max_y - 3)..max_y { + editor.set_block(DIRT, x, y, z, None, None); + } + + // n - 1 layers of stone + for y in (MIN_Y + 1)..(max_y - 3) { + editor.set_block(STONE, x, y, z, None, None); + } + + // 1 layer of bedrock + editor.set_block(BEDROCK, x, MIN_Y, z, None, None); block_counter += 1; if block_counter % batch_size == 0 { diff --git a/src/element_processing/amenities.rs b/src/element_processing/amenities.rs index a1dc292..9480c00 100644 --- a/src/element_processing/amenities.rs +++ b/src/element_processing/amenities.rs @@ -2,14 +2,16 @@ use std::time::Duration; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::ProcessedElement; use crate::world_editor::WorldEditor; pub fn generate_amenities( editor: &mut WorldEditor, element: &ProcessedElement, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { // Skip if 'layer' or 'level' is negative in the tags @@ -26,19 +28,21 @@ pub fn generate_amenities( } if let Some(amenity_type) = element.tags().get("amenity") { - let first_node = element.nodes().map(|n| (n.x, n.z)).next(); + let first_node = element.nodes().map(|n| XZPoint::new(n.x, n.z)).next(); match amenity_type.as_str() { "waste_disposal" | "waste_basket" => { // Place a cauldron for waste disposal or waste basket - if let Some((x, z)) = first_node { - editor.set_block(CAULDRON, x, ground_level + 1, z, None, None); + if let Some(pt) = first_node { + editor.set_block(CAULDRON, pt.x, ground.level(pt) + 1, pt.z, None, None); } return; } "vending_machine" | "atm" => { - if let Some((x, z)) = first_node { - editor.set_block(IRON_BLOCK, x, ground_level + 1, z, None, None); - editor.set_block(IRON_BLOCK, x, ground_level + 2, z, None, None); + if let Some(pt) = first_node { + let y = ground.level(pt); + + editor.set_block(IRON_BLOCK, pt.x, y + 1, pt.z, None, None); + editor.set_block(IRON_BLOCK, pt.x, y + 2, pt.z, None, None); } return; } @@ -50,9 +54,18 @@ pub fn generate_amenities( let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, floodfill_timeout); + let pts: Vec<_> = floor_area.iter().map(|c| XZPoint::new(c.0, c.1)).collect(); + + if pts.is_empty() { + return; + } + + let y_min = ground.min_level(pts.iter().cloned()).unwrap(); + let roof_y = ground.max_level(pts.iter().cloned()).unwrap() + 5; + // Fill the floor area for (x, z) in floor_area.iter() { - editor.set_block(ground_block, *x, ground_level, *z, None, None); + editor.set_block(ground_block, *x, y_min, *z, None, None); } // Place fences and roof slabs at each corner node directly @@ -60,37 +73,44 @@ pub fn generate_amenities( let x = node.x; let z = node.z; - for y in 1..=4 { - editor.set_block(ground_block, x, ground_level, z, None, None); - editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None); + let pt = XZPoint::new(x, z); + + let y = ground.level(pt); + editor.set_block(ground_block, x, y, z, None, None); + + for cur_y in (y_min + 1)..roof_y { + editor.set_block(OAK_FENCE, x, cur_y, z, None, None); } - editor.set_block(roof_block, x, ground_level + 5, z, None, None); + editor.set_block(roof_block, x, roof_y, z, None, None); } // Flood fill the roof area - let roof_height: i32 = ground_level + 5; for (x, z) in floor_area.iter() { - editor.set_block(roof_block, *x, roof_height, *z, None, None); + editor.set_block(roof_block, *x, roof_y, *z, None, None); } } "bench" => { // Place a bench - if let Some((x, z)) = first_node { - editor.set_block(SMOOTH_STONE, x, ground_level + 1, z, None, None); - editor.set_block(OAK_LOG, x + 1, ground_level + 1, z, None, None); - editor.set_block(OAK_LOG, x - 1, ground_level + 1, z, None, None); + if let Some(pt) = first_node { + let y = ground.level(pt) + 1; + + editor.set_block(SMOOTH_STONE, pt.x, y, pt.z, None, None); + editor.set_block(OAK_LOG, pt.x + 1, y + 1, pt.z, None, None); + editor.set_block(OAK_LOG, pt.x - 1, y + 1, pt.z, None, None); } } "vending" => { // Place vending machine blocks - if let Some((x, z)) = first_node { - editor.set_block(IRON_BLOCK, x, ground_level + 1, z, None, None); - editor.set_block(IRON_BLOCK, x, ground_level + 2, z, None, None); + if let Some(pt) = first_node { + let y = ground.level(pt); + + editor.set_block(IRON_BLOCK, pt.x, y + 1, pt.z, None, None); + editor.set_block(IRON_BLOCK, pt.x, y + 2, pt.z, None, None); } } "parking" | "fountain" => { // Process parking or fountain areas - let mut previous_node: Option<(i32, i32)> = None; + let mut previous_node: Option = None; let mut corner_addup: (i32, i32, i32) = (0, 0, 0); let mut current_amenity: Vec<(i32, i32)> = vec![]; @@ -99,23 +119,19 @@ pub fn generate_amenities( "parking" => GRAY_CONCRETE, _ => GRAY_CONCRETE, }; + + let y = ground.min_level(element.nodes().map(|node| XZPoint::new(node.x, node.z))); + for node in element.nodes() { - let x = node.x; - let z = node.z; + let pt = node.xz(); + let y = y.unwrap(); if let Some(prev) = previous_node { // Create borders for fountain or parking area let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z); + bresenham_line(prev.x, y, prev.z, pt.x, y, pt.z); for (bx, _, bz) in bresenham_points { - editor.set_block( - block_type, - bx, - ground_level, - bz, - Some(&[BLACK_CONCRETE]), - None, - ); + editor.set_block(block_type, bx, y, bz, Some(&[BLACK_CONCRETE]), None); // Decorative border around fountains if amenity_type == "fountain" { @@ -125,7 +141,7 @@ pub fn generate_amenities( editor.set_block( LIGHT_GRAY_CONCRETE, bx + dx, - ground_level, + y, bz + dz, None, None, @@ -141,7 +157,7 @@ pub fn generate_amenities( corner_addup.2 += 1; } } - previous_node = Some((x, z)); + previous_node = Some(pt); } // Flood-fill the interior area for parking or fountains @@ -154,7 +170,7 @@ pub fn generate_amenities( editor.set_block( block_type, x, - ground_level, + y.unwrap(), z, Some(&[BLACK_CONCRETE, GRAY_CONCRETE]), None, @@ -165,7 +181,7 @@ pub fn generate_amenities( editor.set_block( LIGHT_GRAY_CONCRETE, x, - ground_level, + y.unwrap(), z, Some(&[BLACK_CONCRETE, GRAY_CONCRETE]), None, diff --git a/src/element_processing/barriers.rs b/src/element_processing/barriers.rs index 4223930..f806714 100644 --- a/src/element_processing/barriers.rs +++ b/src/element_processing/barriers.rs @@ -1,16 +1,18 @@ use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; +use crate::ground::Ground; use crate::osm_parser::ProcessedElement; use crate::world_editor::WorldEditor; -pub fn generate_barriers(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) { +pub fn generate_barriers(editor: &mut WorldEditor, element: &ProcessedElement, ground: &Ground) { if let Some(barrier_type) = element.tags().get("barrier") { if barrier_type == "bollard" { if let ProcessedElement::Node(node) = element { editor.set_block( COBBLESTONE_WALL, node.x, - ground_level + 1, + ground.level(node.xz()) + 1, node.z, None, None, @@ -37,11 +39,11 @@ pub fn generate_barriers(editor: &mut WorldEditor, element: &ProcessedElement, g let z2 = cur.z; // Generate the line of coordinates between the two nodes - let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(x1, ground_level, z1, x2, ground_level, z2); + let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, 0, z1, x2, 0, z2); for (bx, _, bz) in bresenham_points { // Build the barrier wall to the specified height + let ground_level = ground.level(XZPoint::new(bx, bz)); for y in (ground_level + 1)..=(ground_level + wall_height) { editor.set_block(COBBLESTONE_WALL, bx, y, bz, None, None); // Barrier wall diff --git a/src/element_processing/bridges.rs b/src/element_processing/bridges.rs index 2285f8d..6846e0a 100644 --- a/src/element_processing/bridges.rs +++ b/src/element_processing/bridges.rs @@ -1,9 +1,10 @@ use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; -pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) { +pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground: &Ground) { if let Some(_bridge_type) = element.tags.get("bridge") { let bridge_height: i32 = element .tags @@ -21,7 +22,7 @@ pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground let x2 = nodes[1].x; let z2 = nodes[1].z; - bresenham_line(x1, ground_level, z1, x2, ground_level, z2).len() + bresenham_line(x1, 0, z1, x2, 0, z2).len() }) .sum(); @@ -31,15 +32,18 @@ pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground for i in 1..element.nodes.len() { let prev = &element.nodes[i - 1]; let x1 = prev.x; + let y1 = ground.level(prev.xz()); let z1 = prev.z; let cur = &element.nodes[i]; let x2 = cur.x; + let y2 = ground.level(cur.xz()); let z2 = cur.z; + let ground_level = 60; // FIXME TODO + // Generate the line of coordinates between the two nodes - let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(x1, ground_level, z1, x2, ground_level, z2); + let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, 0, z1, x2, 0, z2); for (bx, _, bz) in bresenham_points { // Calculate the current height of the bridge diff --git a/src/element_processing/buildings.rs b/src/element_processing/buildings.rs index 0ee945d..9ecc57b 100644 --- a/src/element_processing/buildings.rs +++ b/src/element_processing/buildings.rs @@ -2,6 +2,7 @@ use crate::block_definitions::*; use crate::bresenham::bresenham_line; use crate::colors::{color_text_to_rgb_tuple, rgb_distance, RGBTuple}; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; use rand::Rng; @@ -11,7 +12,7 @@ use std::time::Duration; pub fn generate_buildings( editor: &mut WorldEditor, element: &ProcessedWay, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { let mut previous_node: Option<(i32, i32)> = None; @@ -94,9 +95,14 @@ pub fn generate_buildings( let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, floodfill_timeout); + let Some(y) = ground.min_level(element.nodes.iter().map(|n| n.xz())) else { + // No blocks + return; + }; + // Fill the floor area for (x, z) in floor_area.iter() { - editor.set_block(ground_block, *x, ground_level, *z, None, None); + editor.set_block(ground_block, *x, y, *z, None, None); } // Place fences and roof slabs at each corner node directly @@ -105,14 +111,14 @@ pub fn generate_buildings( let z = node.z; for y in 1..=4 { - editor.set_block(ground_block, x, ground_level, z, None, None); - editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None); + editor.set_block(ground_block, x, y, z, None, None); + editor.set_block(OAK_FENCE, x, y + y, z, None, None); } - editor.set_block(roof_block, x, ground_level + 5, z, None, None); + editor.set_block(roof_block, x, y + 5, z, None, None); } // Flood fill the roof area - let roof_height: i32 = ground_level + 5; + let roof_height: i32 = y + 5; for (x, z) in floor_area.iter() { editor.set_block(roof_block, *x, roof_height, *z, None, None); } @@ -120,6 +126,9 @@ pub fn generate_buildings( return; } } else if building_type == "roof" { + let Some(ground_level) = ground.min_level(element.nodes.iter().map(|n| n.xz())) else { + return; + }; let roof_height = ground_level + 5; // Iterate through the nodes to create the roof edges using Bresenham's line algorithm @@ -166,11 +175,16 @@ pub fn generate_buildings( building_height = 23 } } else if building_type == "bridge" { - generate_bridge(editor, element, ground_level, floodfill_timeout); + generate_bridge(editor, element, &ground, floodfill_timeout); return; } } + let Some(y) = ground.min_level(element.nodes.iter().map(|n| n.xz())) else { + // No blocks + return; + }; + // Process nodes to create walls and corners for node in &element.nodes { let x = node.x; @@ -178,15 +192,14 @@ pub fn generate_buildings( if let Some(prev) = previous_node { // Calculate walls and corners using Bresenham line - let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z); + let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, 0, prev.1, x, 0, z); for (bx, _, bz) in bresenham_points { - for h in (ground_level + 1)..=(ground_level + building_height) { + for h in (y + 1)..=(y + building_height) { if element.nodes[0].x == bx && element.nodes[0].x == bz { editor.set_block(corner_block, bx, h, bz, None, None); // Corner block } else { // Add windows to the walls at intervals - if h > ground_level + 1 && h % 4 != 0 && (bx + bz) % 6 < 3 { + if h > y + 1 && h % 4 != 0 && (bx + bz) % 6 < 3 { editor.set_block(window_block, bx, h, bz, None, None); // Window block } else { @@ -195,14 +208,7 @@ pub fn generate_buildings( } } } - editor.set_block( - COBBLESTONE, - bx, - ground_level + building_height + 1, - bz, - None, - None, - ); // Ceiling cobblestone + editor.set_block(COBBLESTONE, bx, y + building_height + 1, bz, None, None); // Ceiling cobblestone current_building.push((bx, bz)); corner_addup = (corner_addup.0 + bx, corner_addup.1 + bz, corner_addup.2 + 1); } @@ -218,11 +224,11 @@ pub fn generate_buildings( for (x, z) in floor_area { if processed_points.insert((x, z)) { - editor.set_block(floor_block, x, ground_level, z, None, None); // Set floor + editor.set_block(floor_block, x, y, z, None, None); // Set floor // Set level ceilings if height > 4 if building_height > 4 { - for h in (ground_level + 2 + 4..ground_level + building_height).step_by(4) { + for h in (y + 2 + 4..y + building_height).step_by(4) { if x % 6 == 0 && z % 6 == 0 { editor.set_block(GLOWSTONE, x, h, z, None, None); // Light fixtures } else { @@ -230,19 +236,12 @@ pub fn generate_buildings( } } } else if x % 6 == 0 && z % 6 == 0 { - editor.set_block(GLOWSTONE, x, ground_level + building_height, z, None, None); + editor.set_block(GLOWSTONE, x, y + building_height, z, None, None); // Light fixtures } // Set the house ceiling - editor.set_block( - floor_block, - x, - ground_level + building_height + 1, - z, - None, - None, - ); + editor.set_block(floor_block, x, y + building_height + 1, z, None, None); } } } @@ -262,11 +261,11 @@ fn find_nearest_block_in_color_map( fn generate_bridge( editor: &mut WorldEditor, element: &ProcessedWay, - base_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { // Calculate the bridge level - let mut bridge_level = base_level; + let mut bridge_level = 60; // TODO if let Some(level_str) = element.tags.get("level") { if let Ok(level) = level_str.parse::() { bridge_level += (level * 3) + 1; // Adjust height by levels diff --git a/src/element_processing/highways.rs b/src/element_processing/highways.rs index 66d10a9..01fa85c 100644 --- a/src/element_processing/highways.rs +++ b/src/element_processing/highways.rs @@ -2,14 +2,16 @@ use std::time::Duration; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::{ProcessedElement, ProcessedWay}; use crate::world_editor::WorldEditor; // Assuming you have a flood fill function for area filling pub fn generate_highways( editor: &mut WorldEditor, element: &ProcessedElement, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { if let Some(highway_type) = element.tags().get("highway") { @@ -17,11 +19,12 @@ pub fn generate_highways( // Handle street lamps if let ProcessedElement::Node(first_node) = element { let x = first_node.x; + let y = ground.level(first_node.xz()); let z = first_node.z; - for y in 1..=4 { - editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None); + for dy in 1..=4 { + editor.set_block(OAK_FENCE, x, y + dy, z, None, None); } - editor.set_block(GLOWSTONE, x, ground_level + 5, z, None, None); + editor.set_block(GLOWSTONE, x, y + 5, z, None, None); } } else if highway_type == "crossing" { // Handle traffic signals for crossings @@ -29,14 +32,16 @@ pub fn generate_highways( if crossing_type == "traffic_signals" { if let ProcessedElement::Node(node) = element { let x = node.x; + let y = ground.level(node.xz()); let z = node.z; - for y in 1..=3 { - editor.set_block(COBBLESTONE_WALL, x, ground_level + y, z, None, None); + + for dy in 1..=3 { + editor.set_block(COBBLESTONE_WALL, x, y + dy, z, None, None); } - editor.set_block(GREEN_WOOL, x, ground_level + 4, z, None, None); - editor.set_block(YELLOW_WOOL, x, ground_level + 5, z, None, None); - editor.set_block(RED_WOOL, x, ground_level + 6, z, None, None); + editor.set_block(GREEN_WOOL, x, y + 4, z, None, None); + editor.set_block(YELLOW_WOOL, x, y + 5, z, None, None); + editor.set_block(RED_WOOL, x, y + 6, z, None, None); } } } @@ -44,13 +49,14 @@ pub fn generate_highways( // Handle bus stops if let ProcessedElement::Node(node) = element { let x = node.x; + let y = ground.level(node.xz()); let z = node.z; - for y in 1..=3 { - editor.set_block(COBBLESTONE_WALL, x, ground_level + y, z, None, None); + for dy in 1..=3 { + editor.set_block(COBBLESTONE_WALL, x, y + dy, z, None, None); } - editor.set_block(WHITE_WOOL, x, ground_level + 4, z, None, None); - editor.set_block(WHITE_WOOL, x + 1, ground_level + 4, z, None, None); + editor.set_block(WHITE_WOOL, x, y + 4, z, None, None); + editor.set_block(WHITE_WOOL, x + 1, y + 4, z, None, None); } } else if element.tags().get("area").map_or(false, |v| v == "yes") { let ProcessedElement::Way(way) = element else { @@ -81,7 +87,14 @@ pub fn generate_highways( let filled_area = flood_fill_area(&polygon_coords, floodfill_timeout); for (x, z) in filled_area { - editor.set_block(surface_block, x, ground_level, z, None, None); + editor.set_block( + surface_block, + x, + ground.level(XZPoint::new(x, z)), + z, + None, + None, + ); } } else { let mut previous_node: Option<(i32, i32)> = None; @@ -148,8 +161,10 @@ pub fn generate_highways( let z2 = node.z; // Generate the line of coordinates between the two nodes + // we don't care about the y because it's going to get overwritten + // I'm not sure if we'll keep it this way let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(x1, ground_level, z1, x2, ground_level, z2); + bresenham_line(x1, 0, z1, x2, 0, z2); // Variables to manage dashed line pattern let mut stripe_length = 0; @@ -174,7 +189,7 @@ pub fn generate_highways( editor.set_block( WHITE_CONCRETE, set_x, - ground_level, + ground.level(XZPoint::new(set_x, set_z)), set_z, Some(&[BLACK_CONCRETE]), None, @@ -183,7 +198,7 @@ pub fn generate_highways( editor.set_block( BLACK_CONCRETE, set_x, - ground_level, + ground.level(XZPoint::new(set_x, set_z)), set_z, None, None, @@ -193,7 +208,7 @@ pub fn generate_highways( editor.set_block( WHITE_CONCRETE, set_x, - ground_level, + ground.level(XZPoint::new(set_x, set_z)), set_z, Some(&[BLACK_CONCRETE]), None, @@ -202,7 +217,7 @@ pub fn generate_highways( editor.set_block( BLACK_CONCRETE, set_x, - ground_level, + ground.level(XZPoint::new(set_x, set_z)), set_z, None, None, @@ -212,7 +227,7 @@ pub fn generate_highways( editor.set_block( block_type, set_x, - ground_level, + ground.level(XZPoint::new(set_x, set_z)), set_z, None, Some(&[BLACK_CONCRETE, WHITE_CONCRETE]), @@ -229,7 +244,7 @@ pub fn generate_highways( editor.set_block( WHITE_CONCRETE, stripe_x, - ground_level, + ground.level(XZPoint::new(stripe_x, stripe_z)), stripe_z, Some(&[BLACK_CONCRETE]), None, diff --git a/src/element_processing/landuse.rs b/src/element_processing/landuse.rs index 131a376..9450689 100644 --- a/src/element_processing/landuse.rs +++ b/src/element_processing/landuse.rs @@ -2,8 +2,10 @@ use std::time::Duration; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::element_processing::tree::create_tree; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; use rand::Rng; @@ -11,7 +13,7 @@ use rand::Rng; pub fn generate_landuse( editor: &mut WorldEditor, element: &ProcessedWay, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { let mut previous_node: Option<(i32, i32)> = None; @@ -43,6 +45,7 @@ pub fn generate_landuse( for node in &element.nodes { let x = node.x; let z = node.z; + let ground_level = ground.level(XZPoint::new(x, z)); if let Some(prev) = previous_node { // Generate the line of coordinates between the two nodes @@ -67,6 +70,7 @@ pub fn generate_landuse( let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); for (x, z) in floor_area { + let ground_level = ground.level(XZPoint::new(x, z)); if landuse_tag == "traffic_island" { editor.set_block(block_type, x, ground_level + 1, z, None, None); } else if landuse_tag == "construction" || landuse_tag == "railway" { diff --git a/src/element_processing/leisure.rs b/src/element_processing/leisure.rs index f901689..4c44e0a 100644 --- a/src/element_processing/leisure.rs +++ b/src/element_processing/leisure.rs @@ -2,8 +2,10 @@ use std::time::Duration; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::element_processing::tree::create_tree; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; use rand::Rng; @@ -11,7 +13,7 @@ use rand::Rng; pub fn generate_leisure( editor: &mut WorldEditor, element: &ProcessedWay, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { if let Some(leisure_type) = element.tags.get("leisure") { @@ -44,12 +46,12 @@ pub fn generate_leisure( if let Some(prev) = previous_node { // Draw a line between the current and previous node let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(prev.0, ground_level, prev.1, node.x, ground_level, node.z); + bresenham_line(prev.0, 0, prev.1, node.x, 0, node.z); for (bx, _, bz) in bresenham_points { editor.set_block( block_type, bx, - ground_level, + ground.level(XZPoint::new(bx, bz)), bz, Some(&[ GRASS_BLOCK, @@ -78,6 +80,7 @@ pub fn generate_leisure( let filled_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, floodfill_timeout); for (x, z) in filled_area { + let ground_level = ground.level(XZPoint::new(x, z)); editor.set_block(block_type, x, ground_level, z, Some(&[GRASS_BLOCK]), None); // Add decorative elements for parks and gardens diff --git a/src/element_processing/natural.rs b/src/element_processing/natural.rs index bd72634..03fbc2c 100644 --- a/src/element_processing/natural.rs +++ b/src/element_processing/natural.rs @@ -2,8 +2,10 @@ use std::time::Duration; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::element_processing::tree::create_tree; use crate::floodfill::flood_fill_area; +use crate::ground::Ground; use crate::osm_parser::ProcessedElement; use crate::world_editor::WorldEditor; use rand::Rng; @@ -11,7 +13,7 @@ use rand::Rng; pub fn generate_natural( editor: &mut WorldEditor, element: &ProcessedElement, - ground_level: i32, + ground: &Ground, floodfill_timeout: Option<&Duration>, ) { if let Some(natural_type) = element.tags().get("natural") { @@ -21,7 +23,13 @@ pub fn generate_natural( let z = node.z; let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); - create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3)); + create_tree( + editor, + x, + ground.level(node.xz()) + 1, + z, + rng.gen_range(1..=3), + ); } } else { let mut previous_node: Option<(i32, i32)> = None; @@ -49,9 +57,16 @@ pub fn generate_natural( if let Some(prev) = previous_node { // Generate the line of coordinates between the two nodes let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z); + bresenham_line(prev.0, 0, prev.1, x, 0, z); for (bx, _, bz) in bresenham_points { - editor.set_block(block_type, bx, ground_level, bz, None, None); + editor.set_block( + block_type, + bx, + ground.level(XZPoint::new(bx, bz)), + bz, + None, + None, + ); } current_natural.push((x, z)); @@ -71,17 +86,19 @@ pub fn generate_natural( let mut rng: rand::prelude::ThreadRng = rand::thread_rng(); for (x, z) in filled_area { - editor.set_block(block_type, x, ground_level, z, None, None); + let y = ground.level(XZPoint::new(x, z)); + + editor.set_block(block_type, x, y, z, None, None); // Generate elements for "wood" and "tree_row" if natural_type == "wood" || natural_type == "tree_row" { - if editor.check_for_block(x, ground_level, z, None, Some(&[WATER])) { + if editor.check_for_block(x, y, z, None, Some(&[WATER])) { continue; } let random_choice: i32 = rng.gen_range(0..26); if random_choice == 25 { - create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3)); + create_tree(editor, x, y + 1, z, rng.gen_range(1..=3)); } else if random_choice == 2 { let flower_block = match rng.gen_range(1..=4) { 1 => RED_FLOWER, @@ -89,9 +106,9 @@ pub fn generate_natural( 3 => YELLOW_FLOWER, _ => WHITE_FLOWER, }; - editor.set_block(flower_block, x, ground_level + 1, z, None, None); + editor.set_block(flower_block, x, y + 1, z, None, None); } else if random_choice <= 1 { - editor.set_block(GRASS, x, ground_level + 1, z, None, None); + editor.set_block(GRASS, x, y + 1, z, None, None); } } } diff --git a/src/element_processing/water_areas.rs b/src/element_processing/water_areas.rs index 781799a..b38ea2e 100644 --- a/src/element_processing/water_areas.rs +++ b/src/element_processing/water_areas.rs @@ -2,6 +2,8 @@ use geo::{Contains, Intersects, LineString, Point, Polygon, Rect}; use crate::{ block_definitions::WATER, + cartesian::XZPoint, + ground::Ground, osm_parser::{ProcessedMemberRole, ProcessedNode, ProcessedRelation}, world_editor::WorldEditor, }; @@ -9,7 +11,7 @@ use crate::{ pub fn generate_water_areas( editor: &mut WorldEditor, element: &ProcessedRelation, - ground_level: i32, + ground: &Ground, ) { if !element.tags.contains_key("water") { return; @@ -52,7 +54,14 @@ pub fn generate_water_areas( .map(|x| x.iter().map(|y| (y.x as f64, y.z as f64)).collect()) .collect(); - inverse_floodfill(max_x, max_z, outers, inners, editor, ground_level); + let pts = inverse_floodfill(max_x, max_z, outers, inners); + let Some(min_y) = ground.min_level(pts.iter().cloned()) else { + return; // no points + }; + + for pt in pts { + editor.set_block(WATER, pt.x, min_y, pt.z, None, None); + } } // Merges ways that share nodes into full loops @@ -147,9 +156,7 @@ fn inverse_floodfill( max_z: i32, outers: Vec>, inners: Vec>, - editor: &mut WorldEditor, - ground_level: i32, -) { +) -> Vec { let min_x = 0; let min_z = 0; @@ -163,16 +170,10 @@ fn inverse_floodfill( .map(|x| Polygon::new(LineString::from(x), vec![])) .collect(); - inverse_floodfill_recursive( - min_x, - max_x, - min_z, - max_z, - ground_level, - &outers, - &inners, - editor, - ); + let mut out = vec![]; + inverse_floodfill_recursive(min_x, max_x, min_z, max_z, &outers, &inners, &mut out); + + out } fn inverse_floodfill_recursive( @@ -180,10 +181,9 @@ fn inverse_floodfill_recursive( max_x: i32, min_z: i32, max_z: i32, - ground_level: i32, outers: &[Polygon], inners: &[Polygon], - editor: &mut WorldEditor, + out: &mut Vec, ) { const ITERATIVE_THRES: i32 = 10_000; @@ -192,16 +192,7 @@ fn inverse_floodfill_recursive( } if (max_x - min_x) * (max_z - min_z) < ITERATIVE_THRES { - inverse_floodfill_iterative( - min_x, - max_x, - min_z, - max_z, - ground_level, - outers, - inners, - editor, - ); + inverse_floodfill_iterative(min_x, max_x, min_z, max_z, outers, inners, out); return; } @@ -227,7 +218,7 @@ fn inverse_floodfill_recursive( // every block in rect is water // so we can safely just set the whole thing to water - rect_fill(min_x, max_x, min_z, max_z, ground_level, editor); + rect_fill(min_x, max_x, min_z, max_z, out); continue; } @@ -256,10 +247,9 @@ fn inverse_floodfill_recursive( max_x, min_z, max_z, - ground_level, &outers_intersects, &inners_intersects, - editor, + out, ); } } @@ -271,10 +261,9 @@ fn inverse_floodfill_iterative( max_x: i32, min_z: i32, max_z: i32, - ground_level: i32, outers: &[Polygon], inners: &[Polygon], - editor: &mut WorldEditor, + out: &mut Vec, ) { for x in min_x..max_x { for z in min_z..max_z { @@ -283,23 +272,16 @@ fn inverse_floodfill_iterative( if outers.iter().any(|poly| poly.contains(&p)) && inners.iter().all(|poly| !poly.contains(&p)) { - editor.set_block(WATER, x, ground_level, z, None, None); + out.push(XZPoint::new(x, z)); } } } } -fn rect_fill( - min_x: i32, - max_x: i32, - min_z: i32, - max_z: i32, - ground_level: i32, - editor: &mut WorldEditor, -) { +fn rect_fill(min_x: i32, max_x: i32, min_z: i32, max_z: i32, out: &mut Vec) { for x in min_x..max_x { for z in min_z..max_z { - editor.set_block(WATER, x, ground_level, z, None, None); + out.push(XZPoint::new(x, z)); } } } diff --git a/src/ground.rs b/src/ground.rs new file mode 100644 index 0000000..a1adbac --- /dev/null +++ b/src/ground.rs @@ -0,0 +1,26 @@ +use crate::cartesian::XZPoint; + +pub struct Ground { + // +} + +impl Ground { + pub fn new() -> Self { + Self {} + } + + #[inline(always)] + pub fn level(&self, coord: XZPoint) -> i32 { + (20.0 * (coord.x as f64 / 100.0).sin() + 20.0 * (coord.z as f64 / 100.0).sin()) as i32 + } + + #[inline(always)] + pub fn min_level>(&self, coords: I) -> Option { + coords.map(|c| self.level(c)).min() + } + + #[inline(always)] + pub fn max_level>(&self, coords: I) -> Option { + coords.map(|c| self.level(c)).max() + } +} diff --git a/src/main.rs b/src/main.rs index f793459..69f2f6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod args; mod block_definitions; mod bresenham; +mod cartesian; mod colors; mod data_processing; mod element_processing; mod floodfill; +mod ground; mod osm_parser; mod retrieve_data; mod version_check; diff --git a/src/osm_parser.rs b/src/osm_parser.rs index 7b92ae6..f31bbb2 100644 --- a/src/osm_parser.rs +++ b/src/osm_parser.rs @@ -1,4 +1,4 @@ -use crate::args::Args; +use crate::{args::Args, cartesian::XZPoint}; use colored::Colorize; use serde::Deserialize; use serde_json::Value; @@ -44,6 +44,15 @@ pub struct ProcessedNode { pub z: i32, } +impl ProcessedNode { + pub fn xz(&self) -> XZPoint { + XZPoint { + x: self.x, + z: self.z, + } + } +} + #[derive(Debug, Clone)] pub struct ProcessedWay { pub id: u64, diff --git a/src/world_editor.rs b/src/world_editor.rs index f5a2fde..305d68b 100644 --- a/src/world_editor.rs +++ b/src/world_editor.rs @@ -275,6 +275,10 @@ impl<'a> WorldEditor<'a> { (self.scale_factor_x as i32, self.scale_factor_x as i32) } + pub fn block_at(&self, x: i32, y: i32, z: i32) -> bool { + self.world.get_block(x, y, z).is_some() + } + /// Sets a block of the specified type at the given coordinates. pub fn set_block( &mut self,