From db7dd1c1c9624aeb47f12f650f3f0bfc881737a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20V=C3=B6gele?= Date: Wed, 9 Feb 2022 20:54:05 +0100 Subject: [PATCH] Allow walking through open doors --- rust/src/js_api.rs | 86 +++++++++++++++++++++-- rust/src/pathfinder.rs | 152 +++++++++++++++++++++++++++++++---------- 2 files changed, 199 insertions(+), 39 deletions(-) diff --git a/rust/src/js_api.rs b/rust/src/js_api.rs index a6fad42..43ef77d 100644 --- a/rust/src/js_api.rs +++ b/rust/src/js_api.rs @@ -24,11 +24,20 @@ extern "C" { pub type JsWall; pub type JsWallData; + #[wasm_bindgen(method, getter)] + fn id(this: &JsWall) -> String; + #[wasm_bindgen(method, getter)] fn data(this: &JsWall) -> JsWallData; #[wasm_bindgen(method, getter)] fn c(this: &JsWallData) -> Vec; + + #[wasm_bindgen(method, getter, js_name = "door")] + fn door_type(this: &JsWallData) -> DoorType; + + #[wasm_bindgen(method, getter, js_name = "ds")] + fn door_state(this: &JsWallData) -> DoorState; } #[wasm_bindgen] @@ -51,15 +60,78 @@ impl From for Point { } } -#[derive(Debug, Clone, Copy)] +#[wasm_bindgen] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DoorState { + CLOSED = 0, + OPEN = 1, + LOCKED = 2, +} + +impl TryFrom for DoorState { + type Error = (); + fn try_from(value: usize) -> Result { + match value { + x if x == Self::CLOSED as usize => Ok(Self::CLOSED), + x if x == Self::OPEN as usize => Ok(Self::OPEN), + x if x == Self::LOCKED as usize => Ok(Self::LOCKED), + _ => Err(()), + } + } +} + +#[wasm_bindgen] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum DoorType { + NONE = 0, + DOOR = 1, + SECRET = 2, +} + +impl TryFrom for DoorType { + type Error = (); + fn try_from(value: usize) -> Result { + match value { + x if x == Self::NONE as usize => Ok(Self::NONE), + x if x == Self::DOOR as usize => Ok(Self::DOOR), + x if x == Self::SECRET as usize => Ok(Self::SECRET), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone)] pub struct Wall { + pub id: String, pub p1: Point, pub p2: Point, + pub door_type: DoorType, + pub door_state: DoorState, } impl Wall { - pub fn new(p1: Point, p2: Point) -> Self { - Self { p1, p2 } + pub fn new( + id: String, + p1: Point, + p2: Point, + door_type: DoorType, + door_state: DoorState, + ) -> Self { + Self { + id, + p1, + p2, + door_type, + door_state, + } + } + + pub fn is_door(&self) -> bool { + self.door_type != DoorType::NONE + } + + pub fn is_open(&self) -> bool { + self.door_state == DoorState::OPEN } } @@ -68,7 +140,13 @@ impl Wall { let data = wall.data(); let mut c = data.c(); c.iter_mut().for_each(|val| *val = val.round()); - Self::new(Point::new(c[0], c[1]), Point::new(c[2], c[3])) + Self::new( + wall.id(), + Point::new(c[0], c[1]), + Point::new(c[2], c[3]), + data.door_type(), + data.door_state(), + ) } } diff --git a/rust/src/pathfinder.rs b/rust/src/pathfinder.rs index 7696357..0b8a411 100644 --- a/rust/src/pathfinder.rs +++ b/rust/src/pathfinder.rs @@ -18,6 +18,8 @@ pub struct Edge { pub struct Node { pub point: Point, edges: Option>, + dynamic_neighbors: Option>, + dynamic_edges: Option>, final_edge: Option>, } @@ -26,18 +28,17 @@ impl Node { Self { point, edges: None, + dynamic_neighbors: None, + dynamic_edges: None, final_edge: None, } } - fn iter_edges( - &self, - ) -> std::iter::Chain, std::option::Iter<'_, Edge>> { - self.edges - .as_ref() - .unwrap() - .iter() - .chain(self.final_edge.as_ref().unwrap().iter()) + fn iter_edges(&self) -> impl Iterator { + let edge_iter = self.edges.as_ref().unwrap().iter(); + let dynamic_edges_iter = self.dynamic_edges.as_ref().unwrap().iter(); + let final_edge_iter = self.final_edge.as_ref().unwrap().iter(); + edge_iter.chain(dynamic_edges_iter).chain(final_edge_iter) } } @@ -84,7 +85,7 @@ impl NodeStorage { self.regular_nodes.push(node); } - fn initialize_edges(&mut self, node: &NodePtr, walls: &[LineSegment]) { + fn initialize_edges(&mut self, node: &NodePtr, walls: &WallStorage) { if node.borrow().final_edge.is_none() { let final_edge = self .final_node @@ -92,7 +93,7 @@ impl NodeStorage { .filter(|neighbor| { !self.collides_with_wall( &LineSegment::new(node.borrow().point, neighbor.borrow().point), - walls, + walls.iter_all(), ) }) .map(|neighbor| Edge { @@ -102,29 +103,63 @@ impl NodeStorage { node.borrow_mut().final_edge = Some(final_edge); } - if node.borrow().edges.is_some() { + let persistent_initialized = node.borrow().edges.is_some(); + if persistent_initialized && node.borrow().dynamic_edges.is_some() { return; } + let point = node.borrow().point; - let mut edges = Vec::new(); - for neighbor in &self.regular_nodes { - if Rc::ptr_eq(neighbor, node) { - continue; + let mut dynamic_edges = Vec::new(); + if persistent_initialized { + for neighbor in node.borrow().dynamic_neighbors.as_ref().unwrap() { + let neighbor_point = neighbor.borrow().point; + let line = LineSegment::new(point, neighbor_point); + if !self.collides_with_wall(&line, walls.iter_dynamic_active()) { + let cost = point.distance_to(neighbor_point); + let edge = Edge { + target: neighbor.clone(), + cost, + }; + dynamic_edges.push(edge); + } } - let neighbor_point = neighbor.borrow().point; - if !self.collides_with_wall(&LineSegment::new(point, neighbor_point), walls) { - let cost = point.distance_to(neighbor_point); - edges.push(Edge { - target: neighbor.clone(), - cost, - }); + } else { + let mut edges = Vec::new(); + let mut dynamic_neighbors = Vec::new(); + for neighbor in &self.regular_nodes { + if Rc::ptr_eq(neighbor, node) { + continue; + } + let neighbor_point = neighbor.borrow().point; + let line = LineSegment::new(point, neighbor_point); + if !self.collides_with_wall(&line, &walls.persistent) { + let cost = point.distance_to(neighbor_point); + let edge = Edge { + target: neighbor.clone(), + cost, + }; + if self.collides_with_wall(&line, walls.iter_dynamic_active()) { + dynamic_neighbors.push(neighbor.clone()); + } else if self.collides_with_wall(&line, walls.iter_dynamic_inactive()) { + dynamic_neighbors.push(neighbor.clone()); + dynamic_edges.push(edge); + } else { + edges.push(edge); + } + } } + node.borrow_mut().edges = Some(edges); } - node.borrow_mut().edges = Some(edges); + node.borrow_mut().dynamic_edges = Some(dynamic_edges); } - fn collides_with_wall(&self, line: &LineSegment, walls: &[LineSegment]) -> bool { - walls.iter().any(|wall| line.intersection(wall).is_some()) + fn collides_with_wall<'a, I>(&self, line: &LineSegment, walls: I) -> bool + where + I: IntoIterator, + { + walls + .into_iter() + .any(|wall| line.intersection(wall).is_some()) } pub fn cleanup_final_edges(&mut self) { @@ -138,12 +173,50 @@ impl NodeStorage { } } +#[derive(Debug, Clone, Copy)] +pub struct DynamicWall { + pub line: LineSegment, + pub active: bool, +} + +impl DynamicWall { + fn new(line: LineSegment, active: bool) -> Self { + Self { line, active } + } +} + +#[derive(Debug, Default)] +pub struct WallStorage { + pub persistent: Vec, + pub dynamic: FxHashMap, +} + +impl WallStorage { + pub fn iter_dynamic_inactive(&self) -> impl Iterator { + self.dynamic + .values() + .filter(|wall| !wall.active) + .map(|wall| &wall.line) + } + + pub fn iter_dynamic_active(&self) -> impl Iterator { + self.dynamic + .values() + .filter(|wall| wall.active) + .map(|wall| &wall.line) + } + + pub fn iter_all(&self) -> impl Iterator { + self.persistent.iter().chain(self.iter_dynamic_active()) + } +} + #[wasm_bindgen] pub struct Pathfinder { #[wasm_bindgen(skip)] pub nodes: NodeStorage, #[wasm_bindgen(skip)] - pub walls: Vec, + pub walls: WallStorage, } impl Pathfinder { @@ -153,7 +226,8 @@ impl Pathfinder { { let distance_from_walls = token_size / 2.0; let mut endpoints = FxHashMap::>::default(); - let mut line_segments = Vec::new(); + let mut wall_storage = WallStorage::default(); + for wall in walls { let x_diff = wall.p2.x - wall.p1.x; let y_diff = wall.p2.y - wall.p1.y; @@ -163,7 +237,15 @@ impl Pathfinder { let angles = endpoints.entry(point).or_insert_with(Vec::new); angles.push(angle); } - line_segments.push(LineSegment::new(wall.p1, wall.p2)); + let line = LineSegment::new(wall.p1, wall.p2); + if !wall.is_door() { + wall_storage.persistent.push(line); + } else { + let is_open = wall.is_open(); + wall_storage + .dynamic + .insert(wall.id, DynamicWall::new(line, !is_open)); + } } endpoints .values_mut() @@ -187,20 +269,20 @@ impl Pathfinder { point, angle_between, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); } nodes.push(calc_pathfinding_node( point, angle1 + 0.5 * PI, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); nodes.push(calc_pathfinding_node( point, angle2 - 0.5 * PI, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); } let angle1 = angles.last().unwrap(); @@ -215,26 +297,26 @@ impl Pathfinder { point, angle_between, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); } nodes.push(calc_pathfinding_node( point, angle1 + 0.5 * PI, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); nodes.push(calc_pathfinding_node( point, angle2 - 0.5 * PI, distance_from_walls, - &mut line_segments, + &mut wall_storage.persistent, )); } // TODO Eliminating nodes close to each other may improve performance Self { nodes, - walls: line_segments, + walls: wall_storage, } }