diff --git a/rust/src/geometry.rs b/rust/src/geometry.rs index 60d24ae..f5b724b 100644 --- a/rust/src/geometry.rs +++ b/rust/src/geometry.rs @@ -25,6 +25,11 @@ impl Point { Self { x, y } } + pub fn from_line_x(line: &Line, x: f64) -> Self { + let y = line.calc_y(x); + Self { x, y } + } + pub fn distance_to(&self, to: Point) -> f64 { (self.y - to.y).hypot(self.x - to.x) } @@ -55,3 +60,128 @@ impl From<&JsPoint> for Point { Self::new(point.x(), point.y()) } } + +#[derive(Debug, Copy, Clone)] +pub struct Line { + pub m: f64, + pub b: f64, + pub p1: Point, +} + +impl Line { + pub fn new(m: f64, b: f64, p1: Point) -> Self { + Self { m, b, p1 } + } + + pub fn from_points(p1: Point, p2: Point) -> Self { + let m = (p1.y - p2.y) / (p1.x - p2.x); + let b = p1.y - m * p1.x; + Self { m, b, p1 } + } + + pub fn from_point_and_angle(p1: Point, angle: f64) -> Self { + let p2 = Point { + x: p1.x - angle.cos(), + y: p1.y - angle.sin(), + }; + Line::from_points(p1, p2) + } + + pub fn is_vertical(&self) -> bool { + self.m.is_infinite() + } + + pub fn is_horizontal(&self) -> bool { + self.m == 0.0 + } + + pub fn calc_x(&self, y: f64) -> f64 { + (y - self.b) / self.m + } + + pub fn calc_y(&self, x: f64) -> f64 { + self.m * x + self.b + } + + pub fn intersection(&self, other: &Line) -> Option { + // Are both lines vertical? + if self.is_vertical() && other.is_vertical() { + return None; + } + + // Are the lines paralell? + if (self.m - other.m).abs() < 0.00000005 { + return None; + } + + // Is one of the lines vertical? + if self.is_vertical() || other.is_vertical() { + let vertical; + let regular; + if self.is_vertical() { + vertical = self; + regular = other; + } else { + vertical = other; + regular = self; + } + return Some(Point::from_line_x(®ular, vertical.p1.x)); + } + + // Calculate x coordinate of intersection point between both lines + // Find intersection point: x * m1 + b1 = x * m2 + b2 + // Solve for x: x = (b1 - b2) / (m2 - m1) + let x = (self.b - other.b) / (other.m - self.m); + if self.m.abs() < other.m.abs() { + Some(Point::from_line_x(&self, x)) + } else { + Some(Point::from_line_x(&other, x)) + } + } + + pub fn get_perpendicular_through_point(&self, p: Point) -> Self { + let m = -1.0 / self.m; + let b = p.y - m * p.x; + Self { m, b, p1: p } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct LineSegment { + pub p1: Point, + pub p2: Point, + pub line: Line, +} + +impl LineSegment { + pub fn new(p1: Point, p2: Point) -> Self { + Self { + p1, + p2, + line: Line::from_points(p1, p2), + } + } + + pub fn intersection(&self, other: &LineSegment) -> Option { + let intersection = self.line.intersection(&other.line); + intersection.filter(|intersection| { + self.is_intersection_on_segment(*intersection) + && other.is_intersection_on_segment(*intersection) + }) + } + + fn is_intersection_on_segment(&self, intersection: Point) -> bool { + if intersection.is_same_as(&self.p1) || intersection.is_same_as(&self.p2) { + return true; + } + if self.line.is_vertical() || self.line.m.abs() > 1.0 { + return between(intersection.y, self.p1.y, self.p2.y); + } + between(intersection.x, self.p1.x, self.p2.x) + } +} + +pub fn between(num: T, a: T, b: T) -> bool { + let (min, max) = if a < b { (a, b) } else { (b, a) }; + num >= min && num <= max +} diff --git a/rust/src/js_api.rs b/rust/src/js_api.rs index 9ed2773..02daabb 100644 --- a/rust/src/js_api.rs +++ b/rust/src/js_api.rs @@ -19,14 +19,6 @@ extern "C" { pub fn log(s: &str); } -#[wasm_bindgen( - inline_js = "export function collidesWithWall(p1, p2) { return canvas.walls.checkCollision(new Ray(p1, p2));}" -)] -extern "C" { - #[wasm_bindgen(js_name=collidesWithWall)] - pub fn collides_with_wall(p1: Point, p2: Point) -> bool; -} - #[wasm_bindgen] extern "C" { pub type JsWall; diff --git a/rust/src/pathfinder.rs b/rust/src/pathfinder.rs index f447a07..54ad360 100644 --- a/rust/src/pathfinder.rs +++ b/rust/src/pathfinder.rs @@ -5,8 +5,8 @@ use wasm_bindgen::prelude::*; use rustc_hash::FxHashMap; use crate::{ - geometry::Point, - js_api::{collides_with_wall, Wall}, + geometry::{LineSegment, Point}, + js_api::Wall, ptr_indexed_hash_set::PtrIndexedHashSet, }; @@ -79,13 +79,16 @@ impl NodeStorage { self.regular_nodes.push(node); } - fn initialize_edges(&mut self, node: &NodePtr) { + fn initialize_edges(&mut self, node: &NodePtr, walls: &Vec) { if node.borrow().final_edge.is_none() { let final_edge = self .final_node .as_ref() .filter(|neighbor| { - !collides_with_wall(node.borrow().point, neighbor.borrow().point) + !self.collides_with_wall( + &LineSegment::new(node.borrow().point, neighbor.borrow().point), + walls, + ) }) .map(|neighbor| Edge { target: neighbor.clone(), @@ -104,7 +107,7 @@ impl NodeStorage { continue; } let neighbor_point = neighbor.borrow().point; - if !collides_with_wall(point, neighbor_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(), @@ -115,6 +118,10 @@ impl NodeStorage { node.borrow_mut().edges = Some(edges); } + fn collides_with_wall(&self, line: &LineSegment, walls: &Vec) -> bool { + walls.iter().any(|wall| line.intersection(wall).is_some()) + } + pub fn cleanup_final_edges(&mut self) { for node in &self.regular_nodes { node.borrow_mut().final_edge = None; @@ -135,6 +142,8 @@ impl NodeStorage { pub struct Pathfinder { #[wasm_bindgen(skip)] pub nodes: NodeStorage, + #[wasm_bindgen(skip)] + pub walls: Vec, } impl Pathfinder { @@ -143,6 +152,7 @@ impl Pathfinder { I: IntoIterator, { let mut endpoints = FxHashMap::>::default(); + let mut line_segments = Vec::new(); for wall in walls { let x_diff = wall.p2.x - wall.p1.x; let y_diff = wall.p2.y - wall.p1.y; @@ -152,6 +162,7 @@ impl Pathfinder { let angles = endpoints.entry(point).or_insert_with(Vec::new); angles.push(angle); } + line_segments.push(LineSegment::new(wall.p1, wall.p2)); } endpoints .values_mut() @@ -175,7 +186,10 @@ impl Pathfinder { nodes.push(calc_pathfinding_node(point, angle_between)); } // TODO Eliminating nodes close to each other may improve performance - Self { nodes } + Self { + nodes, + walls: line_segments, + } } pub fn find_path(&mut self, from: Point, to: Point) -> Option { @@ -183,7 +197,7 @@ impl Pathfinder { let mut nodes = self.nodes.clone(); nodes.final_node = Some(NodePtr::from(Node::new(from))); let to_node = NodePtr::from(Node::new(to)); - nodes.initialize_edges(&to_node); + nodes.initialize_edges(&to_node, &self.walls); let to = DiscoveredNode { node: to_node, cost: 0.0, @@ -216,7 +230,7 @@ impl Pathfinder { if previous_nodes.contains(neighbor) { continue; } - nodes.initialize_edges(neighbor); + nodes.initialize_edges(neighbor, &self.walls); let cost = current_node.borrow().cost + edge.cost; let discovered_neighbor = DiscoveredNode { node: neighbor.clone(),