Use an internal colission cecker for better performance

This commit is contained in:
Manuel Vögele
2022-02-01 17:58:06 +01:00
parent 188f6c15bf
commit 9253f3decd
3 changed files with 152 additions and 16 deletions
+130
View File
@@ -25,6 +25,11 @@ impl Point {
Self { x, y } 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 { pub fn distance_to(&self, to: Point) -> f64 {
(self.y - to.y).hypot(self.x - to.x) (self.y - to.y).hypot(self.x - to.x)
} }
@@ -55,3 +60,128 @@ impl From<&JsPoint> for Point {
Self::new(point.x(), point.y()) 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<Point> {
// 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(&regular, 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<Point> {
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<T: Copy + PartialOrd>(num: T, a: T, b: T) -> bool {
let (min, max) = if a < b { (a, b) } else { (b, a) };
num >= min && num <= max
}
-8
View File
@@ -19,14 +19,6 @@ extern "C" {
pub fn log(s: &str); 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] #[wasm_bindgen]
extern "C" { extern "C" {
pub type JsWall; pub type JsWall;
+22 -8
View File
@@ -5,8 +5,8 @@ use wasm_bindgen::prelude::*;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::{ use crate::{
geometry::Point, geometry::{LineSegment, Point},
js_api::{collides_with_wall, Wall}, js_api::Wall,
ptr_indexed_hash_set::PtrIndexedHashSet, ptr_indexed_hash_set::PtrIndexedHashSet,
}; };
@@ -79,13 +79,16 @@ impl NodeStorage {
self.regular_nodes.push(node); self.regular_nodes.push(node);
} }
fn initialize_edges(&mut self, node: &NodePtr) { fn initialize_edges(&mut self, node: &NodePtr, walls: &Vec<LineSegment>) {
if node.borrow().final_edge.is_none() { if node.borrow().final_edge.is_none() {
let final_edge = self let final_edge = self
.final_node .final_node
.as_ref() .as_ref()
.filter(|neighbor| { .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 { .map(|neighbor| Edge {
target: neighbor.clone(), target: neighbor.clone(),
@@ -104,7 +107,7 @@ impl NodeStorage {
continue; continue;
} }
let neighbor_point = neighbor.borrow().point; 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); let cost = point.distance_to(neighbor_point);
edges.push(Edge { edges.push(Edge {
target: neighbor.clone(), target: neighbor.clone(),
@@ -115,6 +118,10 @@ impl NodeStorage {
node.borrow_mut().edges = Some(edges); node.borrow_mut().edges = Some(edges);
} }
fn collides_with_wall(&self, line: &LineSegment, walls: &Vec<LineSegment>) -> bool {
walls.iter().any(|wall| line.intersection(wall).is_some())
}
pub fn cleanup_final_edges(&mut self) { pub fn cleanup_final_edges(&mut self) {
for node in &self.regular_nodes { for node in &self.regular_nodes {
node.borrow_mut().final_edge = None; node.borrow_mut().final_edge = None;
@@ -135,6 +142,8 @@ impl NodeStorage {
pub struct Pathfinder { pub struct Pathfinder {
#[wasm_bindgen(skip)] #[wasm_bindgen(skip)]
pub nodes: NodeStorage, pub nodes: NodeStorage,
#[wasm_bindgen(skip)]
pub walls: Vec<LineSegment>,
} }
impl Pathfinder { impl Pathfinder {
@@ -143,6 +152,7 @@ impl Pathfinder {
I: IntoIterator<Item = Wall>, I: IntoIterator<Item = Wall>,
{ {
let mut endpoints = FxHashMap::<Point, Vec<f64>>::default(); let mut endpoints = FxHashMap::<Point, Vec<f64>>::default();
let mut line_segments = Vec::new();
for wall in walls { for wall in walls {
let x_diff = wall.p2.x - wall.p1.x; let x_diff = wall.p2.x - wall.p1.x;
let y_diff = wall.p2.y - wall.p1.y; 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); let angles = endpoints.entry(point).or_insert_with(Vec::new);
angles.push(angle); angles.push(angle);
} }
line_segments.push(LineSegment::new(wall.p1, wall.p2));
} }
endpoints endpoints
.values_mut() .values_mut()
@@ -175,7 +186,10 @@ impl Pathfinder {
nodes.push(calc_pathfinding_node(point, angle_between)); nodes.push(calc_pathfinding_node(point, angle_between));
} }
// TODO Eliminating nodes close to each other may improve performance // 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<DiscoveredNodePtr> { pub fn find_path(&mut self, from: Point, to: Point) -> Option<DiscoveredNodePtr> {
@@ -183,7 +197,7 @@ impl Pathfinder {
let mut nodes = self.nodes.clone(); let mut nodes = self.nodes.clone();
nodes.final_node = Some(NodePtr::from(Node::new(from))); nodes.final_node = Some(NodePtr::from(Node::new(from)));
let to_node = NodePtr::from(Node::new(to)); let to_node = NodePtr::from(Node::new(to));
nodes.initialize_edges(&to_node); nodes.initialize_edges(&to_node, &self.walls);
let to = DiscoveredNode { let to = DiscoveredNode {
node: to_node, node: to_node,
cost: 0.0, cost: 0.0,
@@ -216,7 +230,7 @@ impl Pathfinder {
if previous_nodes.contains(neighbor) { if previous_nodes.contains(neighbor) {
continue; continue;
} }
nodes.initialize_edges(neighbor); nodes.initialize_edges(neighbor, &self.walls);
let cost = current_node.borrow().cost + edge.cost; let cost = current_node.borrow().cost + edge.cost;
let discovered_neighbor = DiscoveredNode { let discovered_neighbor = DiscoveredNode {
node: neighbor.clone(), node: neighbor.clone(),