diff --git a/native/Cargo.lock b/native/Cargo.lock index 63cb46d..e5dc74e 100644 --- a/native/Cargo.lock +++ b/native/Cargo.lock @@ -145,9 +145,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "equivalent" @@ -161,6 +161,11 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "gdextension-api" +version = "0.1.0" +source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=4.2#2831ebde06033c7942e832155c48bd821540177b" + [[package]] name = "gensym" version = "0.1.1" @@ -199,7 +204,7 @@ checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" [[package]] name = "godot" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ "godot-core", "godot-macros", @@ -208,23 +213,22 @@ dependencies = [ [[package]] name = "godot-bindings" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ - "godot4-prebuilt", + "gdextension-api", ] [[package]] name = "godot-cell" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" [[package]] name = "godot-codegen" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ "godot-bindings", - "godot-fmt", "heck", "nanoserde", "proc-macro2", @@ -235,7 +239,7 @@ dependencies = [ [[package]] name = "godot-core" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ "glam", "godot-bindings", @@ -247,7 +251,7 @@ dependencies = [ [[package]] name = "godot-ffi" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ "gensym", "godot-bindings", @@ -256,18 +260,10 @@ dependencies = [ "paste", ] -[[package]] -name = "godot-fmt" -version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" -dependencies = [ - "proc-macro2", -] - [[package]] name = "godot-macros" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?rev=22fd33d4d5213a3fe5db9a58547888cebe35c647#22fd33d4d5213a3fe5db9a58547888cebe35c647" +source = "git+https://github.com/godot-rust/gdext?rev=4c8ea83fc3452485f7b33f2411c136a248457334#4c8ea83fc3452485f7b33f2411c136a248457334" dependencies = [ "godot-bindings", "proc-macro2", @@ -278,7 +274,7 @@ dependencies = [ [[package]] name = "godot-rust-script" version = "0.1.0" -source = "git+https://github.com/titannano/godot-rust-script?rev=821b21a3a330d2b4de20069a1013e4903f95a601#821b21a3a330d2b4de20069a1013e4903f95a601" +source = "git+https://github.com/titannano/godot-rust-script?rev=da275fb0cb61e24571295c8d5a3cce4123fdce24#da275fb0cb61e24571295c8d5a3cce4123fdce24" dependencies = [ "const-str", "godot", @@ -292,7 +288,7 @@ dependencies = [ [[package]] name = "godot-rust-script-derive" version = "0.1.0" -source = "git+https://github.com/titannano/godot-rust-script?rev=821b21a3a330d2b4de20069a1013e4903f95a601#821b21a3a330d2b4de20069a1013e4903f95a601" +source = "git+https://github.com/titannano/godot-rust-script?rev=da275fb0cb61e24571295c8d5a3cce4123fdce24#da275fb0cb61e24571295c8d5a3cce4123fdce24" dependencies = [ "darling", "proc-macro2", @@ -300,11 +296,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "godot4-prebuilt" -version = "0.0.0" -source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=4.2.2#5e6130a47f3dd31bfd02548cf8d90f47cb741916" - [[package]] name = "hashbrown" version = "0.14.2" @@ -416,6 +407,7 @@ dependencies = [ "lerp", "num", "num_enum", + "rand", "rayon", "thiserror", ] diff --git a/native/Cargo.toml b/native/Cargo.toml index 00da01a..c4fc01f 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -godot = { git = "https://github.com/godot-rust/gdext", rev = "22fd33d4d5213a3fe5db9a58547888cebe35c647", features = ["experimental-threads"]} +godot = { git = "https://github.com/godot-rust/gdext", rev = "4c8ea83fc3452485f7b33f2411c136a248457334", features = ["experimental-threads"] } lerp = "0.4.0" backtrace = "0.3.64" num = "0.4.0" @@ -17,5 +17,6 @@ num_enum = "0.7.1" derive-debug = "0.1.2" thiserror = "1.0.56" anyhow = { version = "1.0.79" } +rand = "0.8.5" -godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "821b21a3a330d2b4de20069a1013e4903f95a601" } +godot-rust-script = { git = "https://github.com/titannano/godot-rust-script", rev = "da275fb0cb61e24571295c8d5a3cce4123fdce24" } diff --git a/native/src/lib.rs b/native/src/lib.rs index 93d0535..c8c8eb1 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -1,4 +1,6 @@ mod objects; +mod resources; +mod road_navigation; mod scripts; mod terrain_builder; mod util; diff --git a/native/src/resources.rs b/native/src/resources.rs new file mode 100644 index 0000000..4f314f8 --- /dev/null +++ b/native/src/resources.rs @@ -0,0 +1,4 @@ +mod road_navigation; +mod world_constants; + +pub use world_constants::*; diff --git a/native/src/resources/road_navigation.rs b/native/src/resources/road_navigation.rs new file mode 100644 index 0000000..3c63413 --- /dev/null +++ b/native/src/resources/road_navigation.rs @@ -0,0 +1,199 @@ +use godot::{ + builtin::{Dictionary, Transform3D, Vector3}, + engine::{IResource, Node3D, Resource}, + obj::{Base, Gd}, + register::{godot_api, GodotClass}, +}; + +use crate::{ + road_navigation::RoadNavigation, + util::logger, + world::city_data::{Building, ToDictionary, TryFromDictionary}, +}; + +use super::WorldConstants; + +#[derive(GodotClass)] +#[class(base = Resource)] +pub(crate) struct RoadNavigationRes { + inner: Option, + + #[var(get = world_constants, set = set_world_constants)] + #[export] + world_constants: Option>, +} + +#[godot_api] +impl RoadNavigationRes { + #[func] + fn world_constants(&self) -> Option> { + self.world_constants.clone() + } + + #[func] + fn set_world_constants(&mut self, value: Gd) { + self.inner = Some(RoadNavigation::new(value.clone())); + self.world_constants = Some(value); + } + + #[func] + fn insert_node(&mut self, building: Dictionary, object: Gd) { + let Some(inner) = self.inner.as_mut() else { + logger::error!("Road Navigation resource is not ready!"); + return; + }; + + let building = match Building::try_from_dict(&building) { + Ok(building) => building, + Err(err) => { + logger::error!("Failed to read building dict: {}", err); + return; + } + }; + + inner.insert_node(building, object); + } + + #[func] + fn get_nearest_node(&self, global_translation: Vector3) -> Dictionary { + let Some(inner) = self.inner.as_ref() else { + logger::error!("Road Navigation resource is not ready!"); + return Dictionary::new(); + }; + + let node = inner.get_nearest_node(global_translation); + + let Some(building) = node.as_ref().map(|node| node.building()) else { + logger::error!("Failed to get nearest navigation node!"); + return Dictionary::new(); + }; + + building.to_dict() + } + + #[func] + fn get_next_node( + &self, + current: Dictionary, + target: Dictionary, + actor_orientation: Vector3, + ) -> Dictionary { + let current = match Building::try_from_dict(¤t) { + Ok(current) => current, + Err(err) => { + logger::error!("Invalid current building dictionary: {}", err); + return Dictionary::new(); + } + }; + + let target = match Building::try_from_dict(&target) { + Ok(target) => target, + Err(err) => { + logger::error!("Invalid target building dictionary: {}", err); + return Dictionary::new(); + } + }; + + let Some(inner) = self.inner.as_ref() else { + logger::error!("Road Navigation resource is not ready!"); + return Dictionary::new(); + }; + + let Some(current_node) = inner.get_node(current.tile_coords) else { + logger::error!("Current node does not exist: {:?}", current); + return Dictionary::new(); + }; + + let Some(target_node) = inner.get_node(target.tile_coords) else { + logger::error!("Target node does not exist: {:?}", target); + return Dictionary::new(); + }; + + let dict = inner + .get_next_node(¤t_node, &target_node, actor_orientation) + .building() + .to_dict(); + + dict + } + + #[func] + fn has_arrived(&self, location: Vector3, direction: Vector3, building: Dictionary) -> bool { + let building = match Building::try_from_dict(&building) { + Ok(building) => building, + Err(err) => { + logger::error!( + "Node is not a valid building dictionary: {}\n{:?}", + err, + building + ); + return false; + } + }; + + let Some(inner) = self.inner.as_ref() else { + logger::error!("Road Navigation resource is not ready!"); + return false; + }; + + let Some(node) = inner.get_node(building.tile_coords) else { + logger::error!("Navigation node does not exist!"); + return false; + }; + + match node.has_arrived(location, direction) { + Ok(result) => result, + Err(err) => { + logger::error!("Failed perform arrival check: {:?}", err); + false + } + } + } + + #[func] + fn get_random_node(&self) -> Dictionary { + let Some(inner) = self.inner.as_ref() else { + logger::error!("Road Navigation resource is not ready!"); + return Dictionary::new(); + }; + + inner.get_random_node().building().to_dict() + } + + #[func] + fn get_global_transform(&self, node: Dictionary, direction: Vector3) -> Transform3D { + let node = match Building::try_from_dict(&node) { + Ok(node) => node, + Err(err) => { + logger::error!( + "Node is not a valid building dictionary: {}\n{:?}", + err, + node + ); + return Transform3D::default(); + } + }; + + let Some(inner) = self.inner.as_ref() else { + logger::error!("Road Navigation resource is not ready!"); + return Transform3D::default(); + }; + + let Some(nav_node) = inner.get_node(node.tile_coords) else { + logger::error!("Nav node does not exist: {:?}", node.tile_coords); + return Transform3D::default(); + }; + + nav_node.get_global_transform(direction) + } +} + +#[godot_api] +impl IResource for RoadNavigationRes { + fn init(_base: Base) -> Self { + Self { + inner: None, + world_constants: None, + } + } +} diff --git a/native/src/resources/world_constants.rs b/native/src/resources/world_constants.rs new file mode 100644 index 0000000..2947477 --- /dev/null +++ b/native/src/resources/world_constants.rs @@ -0,0 +1,36 @@ +use godot::register::{godot_api, GodotClass}; + +#[derive(GodotClass)] +#[class(init, base = Resource)] +pub struct WorldConstants { + #[export] + #[var(get = tile_size, set = set_tile_size)] + tile_size: u8, + + #[export] + #[var(get = tile_height, set = set_tile_height)] + tile_height: u8, +} + +#[godot_api] +impl WorldConstants { + #[func] + pub fn tile_size(&self) -> u8 { + self.tile_size + } + + #[func] + fn set_tile_size(&mut self, value: u8) { + self.tile_size = value; + } + + #[func] + pub fn tile_height(&self) -> u8 { + self.tile_height + } + + #[func] + fn set_tile_height(&mut self, value: u8) { + self.tile_height = value; + } +} diff --git a/native/src/road_navigation.rs b/native/src/road_navigation.rs new file mode 100644 index 0000000..c374ec8 --- /dev/null +++ b/native/src/road_navigation.rs @@ -0,0 +1,406 @@ +use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::sync::OnceLock; + +use anyhow::{anyhow, Result}; +use godot::builtin::{Transform3D, Vector3}; +use godot::engine::utilities::snappedf; +use godot::engine::Node3D; +use godot::obj::Gd; +use rand::distributions::Uniform; +use rand::Rng; + +use crate::{ + resources::WorldConstants, + world::city_data::{Building, TileCoords}, +}; + +enum Corners { + BottomRight, + BottomLeft, + TopLeft, + TopRight, +} + +impl Corners { + fn direction(&self) -> Vector3 { + match self { + Self::TopLeft => Vector3::FORWARD + Vector3::LEFT, + Self::TopRight => Vector3::FORWARD + Vector3::RIGHT, + Self::BottomLeft => Vector3::BACK + Vector3::LEFT, + Self::BottomRight => Vector3::BACK + Vector3::RIGHT, + } + } + + fn from_u8(value: u8) -> Option { + match value { + 0x24 => Some(Self::BottomRight), + 0x25 => Some(Self::BottomLeft), + 0x26 => Some(Self::TopLeft), + 0x23 => Some(Self::TopRight), + _ => None, + } + } +} + +enum Direction { + Forward, + Back, + Left, + Right, +} + +impl Direction { + // Direction degrees in radiants. + const FORWARD: f64 = 0.0; + const BACK: f64 = 180.0 * (std::f64::consts::PI / 180.0); + const BACK_NEGATIVE: f64 = -180.0 * (std::f64::consts::PI / 180.0); + const LEFT: f64 = 90.0 * (std::f64::consts::PI / 180.0); + const RIGHT: f64 = -90.0 * (std::f64::consts::PI / 180.0); +} + +impl TryFrom for Direction { + type Error = anyhow::Error; + + fn try_from(value: f64) -> Result { + match value { + Self::FORWARD => Ok(Self::Forward), + Self::BACK | Self::BACK_NEGATIVE => Ok(Self::Back), + Self::LEFT => Ok(Self::Left), + Self::RIGHT => Ok(Self::Right), + _ => Err(anyhow!("Invalid direction angle: {}", value)), + } + } +} + +struct NavNode { + building: Building, + object: Gd, + neighbors: OnceLock>, +} + +#[derive(Clone)] +pub(crate) struct NavNodeRef<'n> { + node: &'n NavNode, + world_constants: &'n Gd, +} + +impl<'n> NavNodeRef<'n> { + fn new(node: &'n NavNode, world_constants: &'n Gd) -> Self { + Self { + node, + world_constants, + } + } + + pub fn get_global_transform(&self, direction: Vector3) -> Transform3D { + const RAD_90_DEG: f64 = 90.0 * (std::f64::consts::PI / 180.0); + + let transform = self.node.object.get_global_transform(); + let building_id = self.node.building.building_id; + + let width = self.world_constants.bind().tile_size(); + let raw_angle = Vector3::FORWARD.signed_angle_to(direction, Vector3::UP); + let angle = snappedf(raw_angle as f64, RAD_90_DEG); + + let offset = match (Corners::from_u8(building_id), direction) { + (None, _) => (width as f32 / 4.0) * Vector3::RIGHT.rotated(Vector3::UP, angle as f32), + + (Some(corner), Vector3::ZERO) => { + let dir = corner.direction(); + let offset = (Vector3::RIGHT + Vector3::BACK) * (width as f32 / 4.0); + + dir * offset + } + + (Some(corner), _) => { + let dir = corner.direction(); + + let offset = (Vector3::BACK + Vector3::RIGHT) * (width as f32 / 8.0); + + let multiplier = match ( + Direction::try_from(angle).expect("angle should be properly snapped!"), + corner, + ) { + (Direction::Forward, Corners::BottomLeft) + | (Direction::Left, Corners::BottomLeft) + | (Direction::Back, Corners::BottomRight) + | (Direction::Left, Corners::BottomRight) + | (Direction::Forward, Corners::TopLeft) + | (Direction::Right, Corners::TopLeft) + | (Direction::Back, Corners::TopRight) + | (Direction::Right, Corners::TopRight) => 1.0, + + (Direction::Back, Corners::BottomLeft) + | (Direction::Right, Corners::BottomLeft) + | (Direction::Forward, Corners::BottomRight) + | (Direction::Right, Corners::BottomRight) + | (Direction::Back, Corners::TopLeft) + | (Direction::Left, Corners::TopLeft) + | (Direction::Forward, Corners::TopRight) + | (Direction::Left, Corners::TopRight) => 3.0, + }; + + dir * offset * multiplier + } + }; + + transform.translated(offset) + } + + fn tile_coords(&self) -> TileCoords { + self.node.building.tile_coords + } + + pub fn has_arrived(&self, location: Vector3, direction: Vector3) -> Result { + let target = self.get_global_transform(direction).origin; + + // remove Y from all comparisons + let target = Vector3::new(target.x, 0.0, target.z); + let location = Vector3::new(location.x, 0.0, location.z); + + let distance = location.distance_squared_to(target); + + Ok(distance <= f32::powi(4.0, 2)) + } + + pub fn building(&self) -> &Building { + &self.node.building + } +} + +pub(crate) struct RoadNavigation { + network: BTreeMap, + world_contstants: Gd, + rand_distribution: Uniform, +} + +impl RoadNavigation { + pub fn new(world_contstants: Gd) -> Self { + Self { + network: BTreeMap::default(), + world_contstants, + rand_distribution: Uniform::new(0, 1), + } + } + + pub fn insert_node(&mut self, node: Building, object: Gd) { + let tile_coords = node.tile_coords; + let node = NavNode { + building: node, + object, + neighbors: OnceLock::new(), + }; + + self.network.insert(tile_coords, node); + self.rand_distribution = Uniform::new(0, self.network.len()); + } + + pub fn get_node(&self, coords: TileCoords) -> Option> { + let node = self.network.get(&coords)?; + + Some(NavNodeRef::new(node, &self.world_contstants)) + } + + pub fn get_neighbors(&self, tile_coords: TileCoords) -> Option<&[TileCoords]> { + let cache = &self.network.get(&tile_coords)?.neighbors; + + let neighbors = cache.get_or_init(|| { + let (x, y) = tile_coords; + + let neighbors = [ + y.checked_sub(1).map(|y| (x, y)), + x.checked_sub(1).map(|x| (x, y)), + Some((x + 1, y)), + Some((x, y + 1)), + ] + .into_iter() + .flatten() + .filter_map(|tile_coords| self.network.get(&tile_coords)) + .map(|node| node.building.tile_coords) + .collect(); + + neighbors + }); + + Some(neighbors) + } + + pub fn get_nearest_node(&self, global_translation: Vector3) -> Option> { + let coord_ratio = self.world_contstants.bind().tile_size(); + + let (mut low, low_node) = self.network.first_key_value()?; + let (mut high, high_node) = self.network.last_key_value()?; + + let mut low_node = low_node; + let mut high_node = high_node; + + loop { + let distance_low = { + let v = global_translation - low_node.object.get_global_position(); + + Vector3::new(v.x, 0.0, v.z) + }; + + let distance_high = { + let v = high_node.object.get_global_position() - global_translation; + + Vector3::new(v.x, 0.0, v.z) + }; + + if distance_low.is_zero_approx() { + break self.get_node(*low); + } + + if distance_high.is_zero_approx() { + break self.get_node(*high); + } + + let (new_low, new_low_node) = { + let vector = distance_low; // / 2.0; + let coords = ( + (vector.x / coord_ratio as f32).round() as u32, + (vector.z / coord_ratio as f32).round() as u32, + ); + + let ordering = low.cmp(&coords); + + let range = match ordering { + Ordering::Less | Ordering::Equal => (*low)..=coords, + Ordering::Greater => coords..=*low, + }; + + let mut node_range = self.network.range(range); + + let maybe_node = match ordering { + Ordering::Less => node_range.next_back(), + Ordering::Equal | Ordering::Greater => node_range.next(), + }; + + let Some((new, node)) = maybe_node else { + break self.get_node(*high); + }; + + (new, node) + }; + + let (new_high, new_high_node) = { + let vector = distance_high; // / 2.0; + let coords = ( + (high.0 as i32 - (vector.x / coord_ratio as f32).round() as i32) as u32, + (high.1 as i32 - (vector.z / coord_ratio as f32).round() as i32) as u32, + ); + + let ordering = coords.cmp(high); + + let range = match ordering { + Ordering::Less | Ordering::Equal => coords..=*high, + Ordering::Greater => *high..=coords, + }; + + let mut node_range = self.network.range(range); + + let maybe_node = match ordering { + Ordering::Less => node_range.next(), + Ordering::Equal | Ordering::Greater => node_range.next_back(), + }; + + let Some((new, node)) = maybe_node else { + break self.get_node(*high); + }; + + (new, node) + }; + + if new_low > new_high { + high = new_low; + high_node = new_low_node; + low = new_high; + low_node = new_low_node; + continue; + } + + if new_low == new_high { + break self.get_node(*new_low); + } + + if low == new_low && high == new_high { + match distance_low + .length_squared() + .total_cmp(&distance_high.length_squared()) + { + Ordering::Less | Ordering::Equal => { + break self.get_node(*low); + } + + Ordering::Greater => { + break self.get_node(*high); + } + } + } + + low = new_low; + low_node = new_low_node; + high = new_high; + high_node = new_high_node; + } + } + + pub fn get_next_node<'n>( + &'n self, + current: &NavNodeRef<'n>, + target: &NavNodeRef<'n>, + actor_orientation: Vector3, + ) -> NavNodeRef<'n> { + let current_location = current.get_global_transform(Vector3::ZERO).origin; + let dir_target = + current_location.direction_to(target.get_global_transform(Vector3::ZERO).origin); + + let neighbors = self + .get_neighbors(current.tile_coords()) + .expect("curent node must exist"); + + let (_, next) = + neighbors + .iter() + .fold((10.0, current.to_owned()), |(closest, next), coords| { + let Some(neighbor) = self.get_node(*coords) else { + return (closest, next); + }; + + let neighbor_location = neighbor.get_global_transform(Vector3::ZERO).origin; + let dir = current_location.direction_to(neighbor_location); + let angle_actor_orientation = dir.angle_to(actor_orientation); + let angle = dir.angle_to(dir_target); + + // multiplying the angle between the target and the neighbor with the + // angle between the current actor orientation and the required actor + // orientation, adds so bias towards a neighbor that is in the direction + // of the actors current orientation. + let weight = angle * (angle_actor_orientation / 2.0); + + if closest < weight { + return (closest, next); + } + + (weight, neighbor) + }); + + next + } + + pub fn get_random_node(&self) -> NavNodeRef<'_> { + let index = rand::thread_rng().sample(self.rand_distribution); + + let node = self + .network + .values() + .nth(index) + .expect("index must be in range"); + + NavNodeRef { + node, + world_constants: &self.world_contstants, + } + } +} diff --git a/native/src/util/logger.rs b/native/src/util/logger.rs index 2646003..fcc1e16 100644 --- a/native/src/util/logger.rs +++ b/native/src/util/logger.rs @@ -1,4 +1,5 @@ -pub use crate::{error, info, warn}; +#[allow(unused)] +pub use crate::{debug, error, info, warn}; #[macro_export(local_inner_macros)] macro_rules! log { diff --git a/native/src/world/city_data.rs b/native/src/world/city_data.rs index c4dce9d..88f60eb 100644 --- a/native/src/world/city_data.rs +++ b/native/src/world/city_data.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; use godot::builtin::{ - meta::{ConvertError, FromGodot}, + meta::{ConvertError, FromGodot, ToGodot}, Dictionary, VariantArray, }; @@ -9,6 +9,10 @@ pub(crate) trait TryFromDictionary: Sized { fn try_from_dict(value: &Dictionary) -> Result; } +pub(crate) trait ToDictionary { + fn to_dict(&self) -> Dictionary; +} + #[derive(Debug, thiserror::Error)] pub(crate) enum TryFromDictError { #[error("dictionary key \"{0}\" is missing")] @@ -33,12 +37,12 @@ impl From for ErasedConvertError { } } -pub type TileList = BTreeMap<(u32, u32), Tile>; +pub type TileList = BTreeMap; #[derive(Debug)] pub(crate) struct City { pub simulator_settings: SimulatorSettings, - pub buildings: BTreeMap<(u32, u32), Building>, + pub buildings: BTreeMap, pub tilelist: TileList, } @@ -56,12 +60,14 @@ impl TryFromDictionary for City { } } +pub type TileCoords = (u32, u32); + #[derive(Debug)] pub(crate) struct Building { pub size: u8, pub name: String, pub building_id: u8, - pub tile_coords: (u32, u32), + pub tile_coords: TileCoords, } impl TryFromDictionary for Building { @@ -75,6 +81,19 @@ impl TryFromDictionary for Building { } } +impl ToDictionary for Building { + fn to_dict(&self) -> Dictionary { + let mut dict = Dictionary::new(); + + dict.set("size", self.size); + dict.set("name", self.name.to_godot()); + dict.set("building_id", self.building_id); + dict.set("tile_coords", tuple_to_array(self.tile_coords)); + + dict + } +} + #[derive(Debug)] pub(crate) struct Tile { pub altitude: u32, @@ -105,7 +124,7 @@ impl TryFromDictionary for SimulatorSettings { } } -impl TryFromDictionary for BTreeMap<(u32, u32), T> { +impl TryFromDictionary for BTreeMap { fn try_from_dict(value: &Dictionary) -> Result { value .iter_shared() @@ -153,17 +172,26 @@ fn get_dict_key_optional( .map(Some) } -fn array_to_tuple(value: VariantArray) -> Result<(u32, u32), TryFromDictError> { +fn array_to_tuple(value: VariantArray) -> Result { Ok(( value - .try_get(0) + .get(0) .ok_or(TryFromDictError::MissingKey("(x, _)"))? .try_to() .map_err(|err| TryFromDictError::InvalidType("(x, _)".into(), err.into()))?, value - .try_get(1) + .get(1) .ok_or(TryFromDictError::MissingKey("(_, y)"))? .try_to() .map_err(|err| TryFromDictError::InvalidType("(x, _)".into(), err.into()))?, )) } + +fn tuple_to_array(value: TileCoords) -> VariantArray { + let mut array = VariantArray::new(); + + array.push(value.0.to_variant()); + array.push(value.1.to_variant()); + + array +} diff --git a/resources/Data/road_navigation.tres b/resources/Data/road_navigation.tres new file mode 100644 index 0000000..36b9e5b --- /dev/null +++ b/resources/Data/road_navigation.tres @@ -0,0 +1,6 @@ +[gd_resource type="RoadNavigationRes" load_steps=2 format=3 uid="uid://dkfv08m34f2fr"] + +[ext_resource type="WorldConstants" uid="uid://dbxp5cngs1a5g" path="res://resources/Data/world_constants.tres" id="1_xvhvm"] + +[resource] +world_constants = ExtResource("1_xvhvm") diff --git a/resources/Data/world_constants.tres b/resources/Data/world_constants.tres index 63ca299..404f043 100644 --- a/resources/Data/world_constants.tres +++ b/resources/Data/world_constants.tres @@ -1,8 +1,5 @@ -[gd_resource type="Resource" load_steps=2 format=2] - -[ext_resource path="res://src/Objects/Data/WorldConstants.gd" type="Script" id=1] +[gd_resource type="WorldConstants" format=3 uid="uid://dbxp5cngs1a5g"] [resource] -script = ExtResource( 1 ) tile_size = 16 tile_height = 8 diff --git a/resources/Objects/Vehicles/car_station_wagon.tscn b/resources/Objects/Vehicles/car_station_wagon.tscn index 9e22141..2af448c 100644 --- a/resources/Objects/Vehicles/car_station_wagon.tscn +++ b/resources/Objects/Vehicles/car_station_wagon.tscn @@ -51,6 +51,7 @@ mesh = SubResource("BoxMesh_483kg") [node name="DebugTarget" type="MeshInstance3D" parent="." index="4"] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 151, 10) visible = false +cast_shadow = 0 mesh = SubResource("BoxMesh_483kg") [node name="GroundDetector" type="RayCast3D" parent="." index="5"] diff --git a/resources/TestScenes/test_track.tscn b/resources/TestScenes/test_track.tscn index 704a8cc..5ef7135 100644 --- a/resources/TestScenes/test_track.tscn +++ b/resources/TestScenes/test_track.tscn @@ -1,12 +1,11 @@ [gd_scene load_steps=13 format=3 uid="uid://uvhjdh84glq8"] [ext_resource type="PackedScene" uid="uid://blyermwgncstx" path="res://resources/Objects/Spawner/CarSpawner.tscn" id="1"] +[ext_resource type="RoadNavigationRes" uid="uid://dkfv08m34f2fr" path="res://resources/Data/road_navigation.tres" id="1_msi07"] [ext_resource type="PackedScene" uid="uid://c6cmp45buruhr" path="res://resources/Objects/Networks/Road/left_right.tscn" id="2"] [ext_resource type="PackedScene" uid="uid://k8hd8bpyy0c3" path="res://resources/Objects/Networks/Road/bottom_right.tscn" id="3"] [ext_resource type="PackedScene" uid="uid://ctiydbpunmky5" path="res://resources/Objects/Networks/Road/top_bottom.tscn" id="4"] [ext_resource type="PackedScene" uid="uid://ciibxiig2g8k" path="res://resources/Objects/Networks/Road/top_high_bottom.tscn" id="5"] -[ext_resource type="Script" path="res://src/Objects/Networks/RoadNavigation.gd" id="6"] -[ext_resource type="Resource" path="res://resources/Data/world_constants.tres" id="7"] [ext_resource type="PackedScene" uid="uid://4hpb44n7ocar" path="res://resources/Objects/Networks/Road/bottom_left.tscn" id="8"] [ext_resource type="PackedScene" uid="uid://cvvvoyifx4bcy" path="res://resources/Objects/Networks/Road/high_top_bottom.tscn" id="9"] [ext_resource type="PackedScene" uid="uid://c6iv8edftjmas" path="res://resources/Objects/Networks/Road/top_right.tscn" id="10"] @@ -17,30 +16,41 @@ resource_name = "Scene" script/source = "extends Node3D const Building := preload(\"res://src/Objects/Map/Building.gd\") -const RoadNavigation := preload(\"res://src/Objects/Networks/RoadNavigation.gd\") + +@export var road_navigation: RoadNavigationRes + +func scale_window(): + var scale := DisplayServer.screen_get_scale(DisplayServer.SCREEN_OF_MAIN_WINDOW) + var window := self.get_window() + + window.size *= scale + window.position -= Vector2i(self.get_window().size / scale / 2) + func _ready() -> void: + scale_window() + var building_ids = { - 0: 0x26, + 0: 0x24, 1: 0x1D, 2: 0x20, 3: 0x1D, 4: 0x1F, - 5: 0x23, + 5: 0x25, 6: 0x1E, 7: 0x1E, - 8: 0x25, + 8: 0x23, 9: 0x1E, 10: 0x1E, 11: 0x1E, 12: 0x1E, - 13: 0x24, + 13: 0x26, } var idx = 0 - var road_naviagtion := $Road as RoadNavigation + var road_network := $Road - for item in road_naviagtion.get_children(): + for item in road_network.get_children(): var child := item as Node3D var node: Building @@ -74,23 +84,26 @@ func _ready() -> void: \"name\": \"Road\", }) - road_naviagtion.insert_node(node) - road_naviagtion.associate_object(node, child) + road_navigation.insert_node(node.data, child) idx += 1 - road_naviagtion.update_debug() +# road_navigation.update_debug() ($CarSpawner as CarSpawner).spawn_car() " +[sub_resource type="Environment" id="Environment_dejjk"] +background_energy_multiplier = 5.0 +ambient_light_color = Color(1, 1, 1, 1) +ambient_light_energy = 10.0 + [node name="Node3D" type="Node3D"] script = SubResource("2") +road_navigation = ExtResource("1_msi07") [node name="Road" type="Node3D" parent="." groups=["road-network"]] unique_name_in_owner = true -script = ExtResource("6") -world_constants = ExtResource("7") -[node name="BottomLeft" parent="Road" instance=ExtResource("3")] +[node name="BottomRight" parent="Road" instance=ExtResource("3")] [node name="TopBottom" parent="Road" instance=ExtResource("4")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16, 0, 0) @@ -104,7 +117,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 48, 8, 0) [node name="HighTopBottom" parent="Road" instance=ExtResource("9")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 64, 0, 0) -[node name="TopLeft" parent="Road" instance=ExtResource("8")] +[node name="BottomLeft" parent="Road" instance=ExtResource("8")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 80, 0, 0) [node name="LeftRight" parent="Road" instance=ExtResource("2")] @@ -113,7 +126,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 16) [node name="LeftRight2" parent="Road" instance=ExtResource("2")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 80, 0, 16) -[node name="BottomRight" parent="Road" instance=ExtResource("10")] +[node name="TopRight" parent="Road" instance=ExtResource("10")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 32) [node name="TopBottom3" parent="Road" instance=ExtResource("4")] @@ -128,7 +141,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 48, 0, 32) [node name="TopBottom6" parent="Road" instance=ExtResource("4")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 64, 0, 32) -[node name="TopRight" parent="Road" instance=ExtResource("11")] +[node name="TopLeft" parent="Road" instance=ExtResource("11")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 80, 0, 32) [node name="CSGBox3D" type="CSGBox3D" parent="."] @@ -153,3 +166,6 @@ transform = Transform3D(0.993568, -0.0886679, 0.0704306, 0, 0.621979, 0.783034, [node name="CarSpawner" parent="." instance=ExtResource("1")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16, 2, 2) road_network_path = NodePath("../Road") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_dejjk") diff --git a/resources/main.tscn b/resources/main.tscn index 4c455aa..6ff117c 100644 --- a/resources/main.tscn +++ b/resources/main.tscn @@ -13,10 +13,10 @@ [ext_resource type="Script" path="res://native/src/scripts/world/solar_setup.rs" id="12_88vys"] [ext_resource type="Material" uid="uid://bmp5rvu5slnnt" path="res://resources/Materials/ocean_material.tres" id="13"] [ext_resource type="Script" path="res://src/Objects/World/Networks.gd" id="14"] -[ext_resource type="Resource" path="res://resources/Data/world_constants.tres" id="15"] +[ext_resource type="RoadNavigationRes" uid="uid://dkfv08m34f2fr" path="res://resources/Data/road_navigation.tres" id="14_x2ouh"] +[ext_resource type="WorldConstants" uid="uid://dbxp5cngs1a5g" path="res://resources/Data/world_constants.tres" id="15"] [ext_resource type="Script" path="res://native/src/scripts/world/buildings.rs" id="15_23gpq"] [ext_resource type="PackedScene" uid="uid://cmv7rt4gqew38" path="res://resources/Objects/Helis/schweizer_300.tscn" id="16_e6k8r"] -[ext_resource type="Script" path="res://src/Objects/Networks/RoadNavigation.gd" id="17"] [sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_lhcij"] dof_blur_far_enabled = true @@ -143,12 +143,11 @@ directional_shadow_max_distance = 400.0 [node name="Networks" type="Node" parent="SubViewportContainer/SubViewport/World"] script = ExtResource("14") world_constants = ExtResource("15") +road_navigation = ExtResource("14_x2ouh") [node name="Powerlines" type="Node" parent="SubViewportContainer/SubViewport/World/Networks"] [node name="Road" type="Node3D" parent="SubViewportContainer/SubViewport/World/Networks" groups=["road-network"]] -script = ExtResource("17") -world_constants = ExtResource("15") [node name="Buildings" type="Node" parent="SubViewportContainer/SubViewport/World"] script = ExtResource("15_23gpq") diff --git a/src/Objects/Data/WorldConstants.gd b/src/Objects/Data/WorldConstants.gd index a6275f8..b7beeef 100644 --- a/src/Objects/Data/WorldConstants.gd +++ b/src/Objects/Data/WorldConstants.gd @@ -1,5 +1,4 @@ extends Resource -class_name WorldConstants @export var tile_size: int @export var tile_height: int diff --git a/src/Objects/Networks/RoadNavigation.gd b/src/Objects/Networks/RoadNavigation.gd deleted file mode 100644 index 73dd2ec..0000000 --- a/src/Objects/Networks/RoadNavigation.gd +++ /dev/null @@ -1,248 +0,0 @@ -extends Node - -const Building := preload("res://src/Objects/Map/Building.gd") -const Logger := preload("res://src/util/Logger.gd") -const CustomProjectSettings := preload("res://src/CustomProjectSettings.gd") - -@export var world_constants: WorldConstants - -var network := {} -var object_map := {} -var node_map := {} -var neighbor_cache := {} -var rng: RandomNumberGenerator - -enum Corners { - BOTTOM_RIGHT = 0x24, - BOTTOM_LEFT = 0x25, - TOP_LEFT = 0x26, - TOP_RIGHT = 0x23, -} - -const DIRECTION := { - "FORWARD": 0, - "BACK": 180, - "LEFT": 90, - "RIGHT": -90, -} - -const ALL_CORNERS := [ - Corners.BOTTOM_LEFT, - Corners.BOTTOM_RIGHT, - Corners.TOP_LEFT, - Corners.TOP_RIGHT -] - -const CORNER_DIR = { - Corners.TOP_LEFT: Vector3.FORWARD + Vector3.LEFT, - Corners.TOP_RIGHT: Vector3.FORWARD + Vector3.RIGHT, - Corners.BOTTOM_LEFT: Vector3.DOWN + Vector3.LEFT, - Corners.BOTTOM_RIGHT: Vector3.DOWN + Vector3.RIGHT -} - -func _ready() -> void: - self.rng = RandomNumberGenerator.new() - self.rng.randomize() - - -func insert_node(node: Building) -> void: - self.network[node.tile_coords()] = node - self.neighbor_cache.clear() - - -func get_neighbors(node: Building) -> Array[Building]: - var tile_coords := node.tile_coords() - assert(tile_coords.size() == 2) - - if self.neighbor_cache.has(tile_coords): - return self.neighbor_cache[tile_coords] - - var neighbors: Array[Building] = [] - var x := tile_coords[0] - var y := tile_coords[1] - - for nc in [PackedInt32Array([x, y-1]), PackedInt32Array([x-1, y]), PackedInt32Array([x+1, y]), PackedInt32Array([x, y+1])]: - if not self.network.has(nc): - continue - - neighbors.push_back(self.network[nc]) - - self.neighbor_cache[tile_coords] = neighbors - - return neighbors - - -func associate_object(node: Building, object: Node3D) -> void: - self.object_map[object] = node - self.node_map[node] = object - - -func lookup_node(object: Node3D) -> Building: - return self.object_map[object] - - -func lookup_object(node: Building) -> Node3D: - return self.node_map[node] - - -static func diagonal_offset(width: float) -> float: - return sqrt(pow(width, 2) + pow(width, 2)) / 8 - - -func get_global_transform(node: Building, direction := Vector3.ZERO) -> Transform3D: - var obj := self.lookup_object(node) - var trans := obj.global_transform - var node_building_id := node.building_id() - - var width: int = self.world_constants.tile_size - - @warning_ignore("unused_variable", "shadowed_variable") - var diagonal_offset := self.diagonal_offset(width) - var raw_angle := Vector3.FORWARD.signed_angle_to(direction, Vector3.UP) - var angle := snappedi(rad_to_deg(raw_angle), 90) - var offset := (width / 4.0) * Vector3.RIGHT.rotated(Vector3.UP, angle) - - # set diagonal offset for road courbes - if node_building_id in ALL_CORNERS: - offset = CORNER_DIR[node_building_id] - offset.x *= width / 4.0 - offset.z *= width / 4.0 - - # if we have a direction we refine the diagonal offset - if direction != Vector3.ZERO and node_building_id in ALL_CORNERS: - offset = CORNER_DIR[node_building_id] - var unit: float = width / 8.0 - - match [angle, node_building_id]: - [DIRECTION.FORWARD, Corners.BOTTOM_LEFT], \ - [DIRECTION.LEFT, Corners.BOTTOM_LEFT], \ - [DIRECTION.BACK, Corners.BOTTOM_RIGHT], \ - [DIRECTION.LEFT, Corners.BOTTOM_RIGHT], \ - [DIRECTION.FORWARD, Corners.TOP_LEFT], \ - [DIRECTION.RIGHT, Corners.TOP_LEFT], \ - [DIRECTION.BACK, Corners.TOP_RIGHT], \ - [DIRECTION.RIGHT, Corners.TOP_RIGHT]: - pass - - [DIRECTION.BACK, Corners.BOTTOM_LEFT], \ - [DIRECTION.RIGHT, Corners.BOTTOM_LEFT], \ - [DIRECTION.FORWARD, Corners.BOTTOM_RIGHT], \ - [DIRECTION.RIGHT, Corners.BOTTOM_RIGHT], \ - [DIRECTION.BACK, Corners.TOP_LEFT], \ - [DIRECTION.LEFT, Corners.TOP_LEFT], \ - [DIRECTION.FORWARD, Corners.TOP_RIGHT], \ - [DIRECTION.LEFT, Corners.TOP_RIGHT]: - unit *= 3 - - offset.x *= unit - offset.z *= unit - - return trans.translated(offset) - - -func get_nearest_node(global_translation: Vector3) -> Building: - var distance := -1.0 - var nearest: Node3D = null - - for object in self.node_map.values() as Array[Node3D]: - var new := global_translation.distance_squared_to(object.global_transform.origin) - - Logger.debug(["node distance to actor:", new]) - - if distance >= 0 && new > distance: - continue - - distance = new - nearest = object - - return self.lookup_node(nearest) - - -func has_arrived(location: Vector3, direction: Vector3, node: Building) -> bool: - var target := self.get_global_transform(node, direction).origin - - target = target - Vector3(0, target.y, 0) - location = location - Vector3(0, location.y, 0) - - var distance := location.distance_squared_to(target) - - Logger.debug(["distance from target:", distance, pow(4, 2)]) - - return distance <= pow(4, 2) - - -func get_next_node(current: Building, target: Building, actor_orientation: Vector3) -> Building: - var next := current - var closest := 10.0 - var current_location := self.get_global_transform(current).origin - var dir_target := current_location.direction_to(self.get_global_transform(target).origin) - - for neighbor in self.get_neighbors(current): - var neighbor_location := self.get_global_transform(neighbor).origin - var dir := current_location.direction_to(neighbor_location) - var angle_actor_orientation := dir.angle_to(actor_orientation) - var angle := dir.angle_to(dir_target) - - # multiplying the angle betten the target and the neighbor with the - # angle between the current actor orientation and the required actor - # orientation, adds so bias towards a neighbor that is in the direction - # of the actors current orientation. - var weight := angle * (angle_actor_orientation / 2) - - if closest < weight: - continue - - closest = weight - next = neighbor - - assert(next != current) - - return next - - -func get_random_node() -> Building: - var keys := self.network.keys() - var r := self.rng.randi() - var index := r % keys.size() - - return self.network.get(keys[index]) - - -func update_debug(): - if not ProjectSettings.get(CustomProjectSettings.DEBUG_SHAPES_ROAD_NAVIGATION_DISPLAY_NETWORK): - return - - for child in self.get_children(): - if not child.is_in_group("debug"): - continue - - self.remove_child(child) - - for tile_coords in self.network: - var node: Building = self.network[tile_coords] - var node_debug := CSGSphere3D.new() - - node_debug.add_to_group("debug") - node_debug.global_transform = self.get_global_transform(node) - node_debug.translate(Vector3(0, 4, 0)) - - self.add_child(node_debug) - - for con in self.get_neighbors(node): - var con_debug := CSGCylinder3D.new() - var node_pos := self.get_global_transform(node).origin + Vector3(0, 4, 0) - var con_pos := self.get_global_transform(con).origin + Vector3(0, 4, 0) - var dis := node_pos.distance_to(con_pos) - var dir := node_pos.direction_to(con_pos) - var angle := Vector3.UP.angle_to(dir) * -1 - var rotation_axis := dir.cross(Vector3.UP).normalized() - - con_debug.radius = 0.3 - con_debug.height = dis / 2.0 - - self.add_child(con_debug) - - con_debug.global_transform.origin = node_pos - con_debug.translate(dir * (dis / 4.0)) - con_debug.rotate(rotation_axis, angle) - diff --git a/src/Objects/Vehicles/Car.gd b/src/Objects/Vehicles/Car.gd index f0012a7..f0128c8 100644 --- a/src/Objects/Vehicles/Car.gd +++ b/src/Objects/Vehicles/Car.gd @@ -1,7 +1,6 @@ extends RigidBody3D const V3Util := preload("res://src/util/V3Util.gd") -const RoadNavigation := preload("res://src/Objects/Networks/RoadNavigation.gd") const Building := preload("res://src/Objects/Map/Building.gd") const Logger := preload("res://src/util/Logger.gd") const CustomProjectSettings := preload("res://src/CustomProjectSettings.gd") @@ -24,7 +23,8 @@ var current_nav_node: Building @onready var debug_target: MeshInstance3D = $DebugTarget @onready var ground_detector: RayCast3D = $GroundDetector -@onready var road_network: RoadNavigation = get_node(road_network_path) + +@export var road_network: RoadNavigationRes = preload("res://resources/Data/road_navigation.tres") func _ready() -> void: self.rng = RandomNumberGenerator.new() @@ -52,6 +52,7 @@ func _physics_process(_delta: float) -> void: if self.stuck >= 5: Logger.info("despawning stuck car") self.queue_free() + debug_target.queue_free() self.last_transform = self.global_transform @@ -148,13 +149,13 @@ func _on_choose_target() -> void: if not self.current_nav_node: var node := self.road_network.get_nearest_node(self.global_transform.origin) - self.current_nav_node = node + self.current_nav_node = Building.new(node) Logger.debug(["car next target:", target_translation]) func get_random_street_location() -> Vector3: - var node := self.road_network.get_random_node() + var node := Building.new(self.road_network.get_random_node()) if self.target_nav_node != null: var current := self.current_nav_node.tile_coords() @@ -169,14 +170,14 @@ func get_random_street_location() -> Vector3: self.target_nav_node = node - return self.road_network.get_global_transform(node).origin + return self.road_network.get_global_transform(node.data, Vector3.ZERO).origin func is_target_reached() -> bool: @warning_ignore("shadowed_variable_base_class") var rotation := self.global_rotation var orientation := Vector3.FORWARD.rotated(Vector3.UP, rotation.y) - return self.road_network.has_arrived(self.global_transform.origin, orientation, self.target_nav_node) + return self.road_network.has_arrived(self.global_transform.origin, orientation, self.target_nav_node.data) func get_next_location() -> Vector3: @@ -185,10 +186,10 @@ func get_next_location() -> Vector3: Logger.debug(["agent rotation:", self.global_rotation.y]) - if not self.road_network.has_arrived(agent_pos, agent_rot, self.current_nav_node): - return self.road_network.get_global_transform(self.current_nav_node, agent_rot).origin + if not self.road_network.has_arrived(agent_pos, agent_rot, self.current_nav_node.data): + return self.road_network.get_global_transform(self.current_nav_node.data, agent_rot).origin - self.current_nav_node = self.road_network.get_next_node(self.current_nav_node, self.target_nav_node, agent_rot) + self.current_nav_node = Building.new(self.road_network.get_next_node(self.current_nav_node.data, self.target_nav_node.data, agent_rot)) return self.get_next_location() diff --git a/src/Objects/World/Networks.gd b/src/Objects/World/Networks.gd index 6d95de0..6a1f538 100644 --- a/src/Objects/World/Networks.gd +++ b/src/Objects/World/Networks.gd @@ -4,17 +4,17 @@ const TimeBudget := preload("res://src/util/TimeBudget.gd") const CityCoordsFeature := preload("res://src/features/CityCoordsFeature.gd") const SceneObjectRegistry := preload("res://src/SceneObjectRegistry.gd") const Building := preload("res://src/Objects/Map/Building.gd") -const RoadNavigation := preload("res://src/Objects/Networks/RoadNavigation.gd") signal loading_progress(value) @export var is_built := false @export var world_constants: WorldConstants +@export var road_navigation: RoadNavigationRes var city_coords_feature: CityCoordsFeature @onready var powerline_network := $Powerlines -@onready var road_network: RoadNavigation = $Road +@onready var road_network := $Road func _ready() -> void: assert(world_constants is WorldConstants, "Networks.world_constants is not of type WorldConstants") @@ -66,8 +66,7 @@ func build_async(city: Dictionary): powerline_network.add_child(instance, true) elif network_section.building_id() in (range(0x1D, 0x2C) + range(0x51, 0x5E) + range(0x43, 0x45)): road_network.add_child(instance, true) - road_network.insert_node(network_section) - road_network.associate_object(network_section, instance) + road_navigation.insert_node(network_section.data, instance) else: print("network secction doesn't belong to any network, ", network_section) @@ -80,8 +79,6 @@ func build_async(city: Dictionary): budget.restart() await self.get_tree().process_frame - road_network.update_debug() - for _i in range(3): var car_spawner: CarSpawner = (load("res://resources/Objects/Spawner/CarSpawner.tscn") as PackedScene).instantiate() var random_child: Node3D = road_network.get_child(randi() % road_network.get_child_count())