diff --git a/Cargo.lock b/Cargo.lock index dfde3da..f651655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1165,7 +1165,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.95", ] [[package]] @@ -6595,7 +6595,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.94", + "syn 2.0.95", ] [[package]] diff --git a/src/data_processing.rs b/src/data_processing.rs index 2b95a39..4c1b8da 100644 --- a/src/data_processing.rs +++ b/src/data_processing.rs @@ -84,18 +84,18 @@ pub fn generate_world( } else if way.tags.contains_key("barrier") { barriers::generate_barriers(&mut editor, element, &ground); } else if way.tags.contains_key("waterway") { - //waterways::generate_waterways(&mut editor, way, &ground); //TODO + waterways::generate_waterways(&mut editor, way, &ground); } else if way.tags.contains_key("bridge") { bridges::generate_bridges(&mut editor, way, &ground); } else if way.tags.contains_key("railway") { - //railways::generate_railways(&mut editor, way, &ground); //TODO + railways::generate_railways(&mut editor, way, &ground); } else if way.tags.get("service") == Some(&"siding".to_string()) { - //highways::generate_siding(&mut editor, way, &ground); //TODO + highways::generate_siding(&mut editor, way, &ground); } } ProcessedElement::Node(node) => { if node.tags.contains_key("door") || node.tags.contains_key("entrance") { - //doors::generate_doors(&mut editor, node, &ground); //TODO + doors::generate_doors(&mut editor, node, &ground); } else if node.tags.contains_key("natural") && node.tags.get("natural") == Some(&"tree".to_string()) { @@ -107,12 +107,19 @@ pub fn generate_world( } else if node.tags.contains_key("highway") { highways::generate_highways(&mut editor, element, &ground, args); } else if node.tags.contains_key("tourism") { - //tourisms::generate_tourisms(&mut editor, node, &ground); //TODO + tourisms::generate_tourisms(&mut editor, node, &ground); } } ProcessedElement::Relation(rel) => { - if rel.tags.contains_key("water") { - //water_areas::generate_water_areas(&mut editor, rel, &ground); //TODO + if rel.tags.contains_key("building") || rel.tags.contains_key("building:part") { + buildings::generate_building_from_relation( + &mut editor, + rel, + &ground, + args, + ); + } else if rel.tags.contains_key("water") { + water_areas::generate_water_areas(&mut editor, rel, &ground); } } } diff --git a/src/element_processing/amenities.rs b/src/element_processing/amenities.rs index 9076779..a9cdfd2 100644 --- a/src/element_processing/amenities.rs +++ b/src/element_processing/amenities.rs @@ -98,8 +98,8 @@ pub fn generate_amenities( 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); + editor.set_block(OAK_LOG, pt.x + 1, y, pt.z, None, None); + editor.set_block(OAK_LOG, pt.x - 1, y, pt.z, None, None); } } "vending" => { diff --git a/src/element_processing/bridges.rs b/src/element_processing/bridges.rs index c182c68..4454001 100644 --- a/src/element_processing/bridges.rs +++ b/src/element_processing/bridges.rs @@ -40,7 +40,7 @@ pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground let y2: i32 = ground.level(cur.xz()); let z2: i32 = cur.z; - let ground_level = 60; // FIXME TODO + let ground_level = 60; // ELEVATION TODO // Generate the line of coordinates between the two nodes let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, 0, z1, x2, 0, z2); diff --git a/src/element_processing/buildings.rs b/src/element_processing/buildings.rs index 1f4bd74..5d3b254 100644 --- a/src/element_processing/buildings.rs +++ b/src/element_processing/buildings.rs @@ -1,6 +1,7 @@ use crate::args::Args; use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; use crate::colors::{color_text_to_rgb_tuple, rgb_distance, RGBTuple}; use crate::floodfill::flood_fill_area; use crate::ground::Ground; @@ -409,27 +410,7 @@ pub fn generate_building_from_relation( // Process the outer way to create the building walls for member in &relation.members { if member.role == ProcessedMemberRole::Outer { - generate_buildings(editor, &member.way, &ground, args); - } - } - - // Handle inner ways (holes, courtyards, etc.) - for member in &relation.members { - if member.role == ProcessedMemberRole::Inner { - let polygon_coords: Vec<(i32, i32)> = - member.way.nodes.iter().map(|n| (n.x, n.z)).collect(); - let hole_area: Vec<(i32, i32)> = - flood_fill_area(&polygon_coords, args.timeout.as_ref()); - - let Some(ground_level) = ground.min_level(member.way.nodes.iter().map(|n| n.xz())) - else { - return; - }; - - for (x, z) in hole_area { - // Remove blocks in the inner area to create a hole - editor.set_block(AIR, x, ground_level, z, None, Some(&[SPONGE])); - } + generate_buildings(editor, &member.way, ground, args); } } } @@ -452,7 +433,7 @@ fn generate_bridge( floodfill_timeout: Option<&Duration>, ) { // Calculate the bridge level - let mut bridge_level: i32 = 60; // TODO + let mut bridge_level: i32 = 60; // ELEVATION 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/doors.rs b/src/element_processing/doors.rs index 1a98f13..8830164 100644 --- a/src/element_processing/doors.rs +++ b/src/element_processing/doors.rs @@ -1,8 +1,10 @@ use crate::block_definitions::*; +use crate::cartesian::XZPoint; use crate::osm_parser::ProcessedNode; +use crate::ground::Ground; use crate::world_editor::WorldEditor; -pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedNode, ground_level: i32) { +pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedNode, ground: &Ground) { // Check if the element is a door or entrance if element.tags.contains_key("door") || element.tags.contains_key("entrance") { // Check for the "level" tag and skip doors that are not at ground level @@ -17,6 +19,9 @@ pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedNode, ground_ let x: i32 = element.x; let z: i32 = element.z; + // Calculate the dynamic ground level + let ground_level = ground.level(XZPoint::new(x, z)); + // Set the ground block and the door blocks editor.set_block(GRAY_CONCRETE, x, ground_level, z, None, None); editor.set_block(DARK_OAK_DOOR_LOWER, x, ground_level + 1, z, None, None); diff --git a/src/element_processing/highways.rs b/src/element_processing/highways.rs index 664dbea..ca3e08f 100644 --- a/src/element_processing/highways.rs +++ b/src/element_processing/highways.rs @@ -284,31 +284,26 @@ pub fn generate_highways( } /// Generates a siding using stone brick slabs -pub fn generate_siding(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) { - let mut previous_node: Option<(i32, i32)> = None; +pub fn generate_siding(editor: &mut WorldEditor, element: &ProcessedWay, ground: &Ground) { + let mut previous_node: Option = None; let siding_block: Block = STONE_BRICK_SLAB; for node in &element.nodes { - let x: i32 = node.x; - let z: i32 = node.z; + let current_node = node.xz(); // Draw the siding using Bresenham's line algorithm between nodes - if let Some(prev) = previous_node { - let bresenham_points: Vec<(i32, i32, i32)> = - bresenham_line(prev.0, ground_level + 1, prev.1, x, ground_level + 1, z); - for (bx, by, bz) in bresenham_points { - if !editor.check_for_block( - bx, - by - 1, - bz, - None, - Some(&[BLACK_CONCRETE, WHITE_CONCRETE]), - ) { - editor.set_block(siding_block, bx, by, bz, None, None); + if let Some(prev_node) = previous_node { + let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev_node.x, 0, prev_node.z, current_node.x, 0, current_node.z); + + for (bx, _, bz) in bresenham_points { + let ground_level = ground.level(XZPoint::new(bx, bz)) + 1; + + if !editor.check_for_block(bx, ground_level - 1, bz, None, Some(&[BLACK_CONCRETE, WHITE_CONCRETE])) { + editor.set_block(siding_block, bx, ground_level, bz, None, None); } } } - previous_node = Some((x, z)); + previous_node = Some(current_node); } } diff --git a/src/element_processing/leisure.rs b/src/element_processing/leisure.rs index c9b61cf..96de68c 100644 --- a/src/element_processing/leisure.rs +++ b/src/element_processing/leisure.rs @@ -115,8 +115,8 @@ pub fn generate_leisure( 0 => { // Benches editor.set_block(OAK_LOG, 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); + editor.set_block(OAK_LOG, x + 1, ground_level, z, None, None); + editor.set_block(OAK_LOG, x - 1, ground_level, z, None, None); } 1..=30 => { // Flowers diff --git a/src/element_processing/railways.rs b/src/element_processing/railways.rs index a38615b..005ef35 100644 --- a/src/element_processing/railways.rs +++ b/src/element_processing/railways.rs @@ -1,9 +1,11 @@ use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; -pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) { +pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay, ground: &Ground) { if let Some(railway_type) = element.tags.get("railway") { if ["proposed", "abandoned", "subway", "construction"].contains(&railway_type.as_str()) { return; @@ -22,19 +24,15 @@ pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay, groun } for i in 1..element.nodes.len() { - let prev: &crate::osm_parser::ProcessedNode = &element.nodes[i - 1]; - let x1: i32 = prev.x; - let z1: i32 = prev.z; - - let cur: &crate::osm_parser::ProcessedNode = &element.nodes[i]; - let x2: i32 = cur.x; - let z2: i32 = cur.z; + let prev_node = element.nodes[i - 1].xz(); + let cur_node = element.nodes[i].xz(); // 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(prev_node.x, 0, prev_node.z, cur_node.x, 0, cur_node.z); for (bx, _, bz) in bresenham_points { + let ground_level = ground.level(XZPoint::new(bx, bz)); + // TODO: Set direction of rail editor.set_block(IRON_BLOCK, bx, ground_level, bz, None, None); editor.set_block(RAIL, bx, ground_level + 1, bz, None, None); diff --git a/src/element_processing/tourisms.rs b/src/element_processing/tourisms.rs index 2bc71fe..c468133 100644 --- a/src/element_processing/tourisms.rs +++ b/src/element_processing/tourisms.rs @@ -1,8 +1,10 @@ use crate::block_definitions::*; +use crate::cartesian::XZPoint; use crate::osm_parser::ProcessedNode; +use crate::ground::Ground; use crate::world_editor::WorldEditor; -pub fn generate_tourisms(editor: &mut WorldEditor, element: &ProcessedNode, ground_level: i32) { +pub fn generate_tourisms(editor: &mut WorldEditor, element: &ProcessedNode, ground: &Ground) { // Skip if 'layer' or 'level' is negative in the tags if let Some(layer) = element.tags.get("layer") { if layer.parse::().unwrap_or(0) < 0 { @@ -20,9 +22,12 @@ pub fn generate_tourisms(editor: &mut WorldEditor, element: &ProcessedNode, grou let x: i32 = element.x; let z: i32 = element.z; + // Calculate the dynamic ground level + let ground_level = ground.level(XZPoint::new(x, z)); + if tourism_type == "information" { if let Some("board") = element.tags.get("information").map(|x: &String| x.as_str()) { - // TODO draw a sign + // Draw an information board editor.set_block(OAK_PLANKS, x, ground_level + 1, z, None, None); } } diff --git a/src/element_processing/water_areas.rs b/src/element_processing/water_areas.rs index fc026a6..d8d677d 100644 --- a/src/element_processing/water_areas.rs +++ b/src/element_processing/water_areas.rs @@ -1,7 +1,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,13 +10,13 @@ use crate::{ pub fn generate_water_areas( editor: &mut WorldEditor, element: &ProcessedRelation, - ground_level: i32, + ground: &Ground, ) { if !element.tags.contains_key("water") { return; } - // don't handle water below layer 0 + // Don't handle water below layer 0 if let Some(layer) = element.tags.get("layer") { if layer.parse::().map(|x| x < 0).unwrap_or(false) { return; @@ -43,24 +44,16 @@ pub fn generate_water_areas( } let (max_x, max_z) = editor.get_max_coords(); - let outers: Vec> = outers + let outers: Vec> = outers .iter() - .map(|x: &Vec| { - x.iter() - .map(|y: &ProcessedNode| (y.x as f64, y.z as f64)) - .collect() - }) + .map(|x| x.iter().map(|y| y.xz()).collect::>()) // Added type annotation .collect(); - let inners: Vec> = inners + let inners: Vec> = inners .iter() - .map(|x: &Vec| { - x.iter() - .map(|y: &ProcessedNode| (y.x as f64, y.z as f64)) - .collect() - }) + .map(|x| x.iter().map(|y| y.xz()).collect::>()) // Added type annotation .collect(); - inverse_floodfill(max_x, max_z, outers, inners, editor, ground_level); + inverse_floodfill(max_x, max_z, outers, inners, editor, ground); } // Merges ways that share nodes into full loops @@ -153,41 +146,41 @@ fn verify_loopy_loops(loops: &[Vec]) -> bool { fn inverse_floodfill( max_x: i32, max_z: i32, - outers: Vec>, - inners: Vec>, + outers: Vec>, + inners: Vec>, editor: &mut WorldEditor, - ground_level: i32, + ground: &Ground, ) { let min_x: i32 = 0; let min_z: i32 = 0; let inners: Vec<_> = inners .into_iter() - .map(|x: Vec<(f64, f64)>| Polygon::new(LineString::from(x), vec![])) + .map(|x| Polygon::new(LineString::from(x.iter().map(|pt| (pt.x as f64, pt.z as f64)).collect::>()), vec![])) .collect(); let outers: Vec<_> = outers .into_iter() - .map(|x: Vec<(f64, f64)>| Polygon::new(LineString::from(x), vec![])) + .map(|x| Polygon::new(LineString::from(x.iter().map(|pt| (pt.x as f64, pt.z as f64)).collect::>()), vec![])) .collect(); inverse_floodfill_recursive( (min_x, min_z), (max_x, max_z), - ground_level, &outers, &inners, editor, + ground, ); } fn inverse_floodfill_recursive( min: (i32, i32), max: (i32, i32), - ground_level: i32, outers: &[Polygon], inners: &[Polygon], editor: &mut WorldEditor, + ground: &Ground, ) { const ITERATIVE_THRES: i32 = 10_000; @@ -196,8 +189,8 @@ fn inverse_floodfill_recursive( } if (max.0 - min.0) * (max.1 - min.1) < ITERATIVE_THRES { + let ground_level = ground.level(XZPoint::new(min.0, min.1)); inverse_floodfill_iterative(min, max, ground_level, outers, inners, editor); - return; } @@ -219,40 +212,22 @@ fn inverse_floodfill_recursive( if outers.iter().any(|outer: &Polygon| outer.contains(&rect)) && !inners.iter().any(|inner: &Polygon| inner.intersects(&rect)) { - // every block in rect is water - // so we can safely just set the whole thing to water - + let ground_level = ground.level(XZPoint::new(min_x, min_z)); rect_fill(min_x, max_x, min_z, max_z, ground_level, editor); - continue; } - // When we recurse, we only really need the polygons we potentially intersect with - // This saves on processing time - let outers_intersects: Vec<_> = outers - .iter() - .filter(|poly: &&Polygon| poly.intersects(&rect)) - .cloned() - .collect(); - - // Moving this inside the below `if` statement makes it slower for some reason. - // I assume it changes how the compiler is able to optimize it - let inners_intersects: Vec<_> = inners - .iter() - .filter(|poly: &&Polygon| poly.intersects(&rect)) - .cloned() - .collect(); + let outers_intersects: Vec<_> = outers.iter().filter(|poly| poly.intersects(&rect)).cloned().collect(); + let inners_intersects: Vec<_> = inners.iter().filter(|poly| poly.intersects(&rect)).cloned().collect(); if !outers_intersects.is_empty() { - // recurse - inverse_floodfill_recursive( (min_x, min_z), (max_x, max_z), - ground_level, &outers_intersects, &inners_intersects, editor, + ground, ); } } @@ -294,3 +269,4 @@ fn rect_fill( } } } + diff --git a/src/element_processing/waterways.rs b/src/element_processing/waterways.rs index 8279187..12af9d5 100644 --- a/src/element_processing/waterways.rs +++ b/src/element_processing/waterways.rs @@ -1,11 +1,13 @@ use crate::block_definitions::*; use crate::bresenham::bresenham_line; +use crate::cartesian::XZPoint; +use crate::ground::Ground; use crate::osm_parser::ProcessedWay; use crate::world_editor::WorldEditor; -pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) { +pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedWay, ground: &Ground) { if let Some(_waterway_type) = element.tags.get("waterway") { - let mut previous_node: Option<(i32, i32)> = None; + let mut previous_node: Option = None; let mut waterway_width: i32 = 4; // Default waterway width // Check for custom width in tags @@ -20,6 +22,8 @@ pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedWay, grou // Process nodes to create waterways for node in &element.nodes { + let current_node = node.xz(); + if let Some(prev) = previous_node { // Skip layers below the ground level if !matches!( @@ -28,11 +32,16 @@ pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedWay, grou ) { // 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.x, 0, prev.z, current_node.x, 0, current_node.z); + for (bx, _, bz) in bresenham_points { + let ground_level = ground.level(XZPoint::new(bx, bz)); + for x in (bx - waterway_width / 2)..=(bx + waterway_width / 2) { for z in (bz - waterway_width / 2)..=(bz + waterway_width / 2) { - editor.set_block(WATER, x, ground_level, z, None, None); // Set water block + // Set water block at the ground level + editor.set_block(WATER, x, ground_level, z, None, None); + // Clear vegetation above the water editor.set_block( AIR, x, @@ -46,7 +55,7 @@ pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedWay, grou } } } - previous_node = Some((node.x, node.z)); + previous_node = Some(current_node); } } }