Use an internal colission cecker for better performance
This commit is contained in:
@@ -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<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(®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<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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+22
-8
@@ -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<LineSegment>) {
|
||||
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<LineSegment>) -> 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<LineSegment>,
|
||||
}
|
||||
|
||||
impl Pathfinder {
|
||||
@@ -143,6 +152,7 @@ impl Pathfinder {
|
||||
I: IntoIterator<Item = Wall>,
|
||||
{
|
||||
let mut endpoints = FxHashMap::<Point, Vec<f64>>::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<DiscoveredNodePtr> {
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user