Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f97081236 | |||
| 18253dc42b | |||
| fb702cd850 |
@@ -1 +1,3 @@
|
||||
foundry-*.js
|
||||
artifact/
|
||||
wasm/
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
## 1.12.0
|
||||
### New features
|
||||
- Pathfinding is now supported on gridless scenes
|
||||
|
||||
|
||||
## 1.11.5
|
||||
### Bugfixes
|
||||
- Fixed a bug that was causing Drag Ruler to spam useless warnings into the console (this was a regression introduced in 1.11.4)
|
||||
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
from pathlib import PurePath, Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
wasm_pack = Path("~/.cargo/bin/wasm-pack").expanduser()
|
||||
|
||||
root_files = ["module.json", "README.md", "CHANGELOG.md", "LICENSE"]
|
||||
wasm_files = ["gridless_pathfinding_bg.wasm", "gridless_pathfinding.js"]
|
||||
output_dir = Path("artifact")
|
||||
copy_everything_directories = ["js", "lang", "templates", "wasm/snippets"]
|
||||
wasm_dir = Path("wasm")
|
||||
root_dir = Path(".")
|
||||
rust_dir = Path("rust")
|
||||
build_dir_tmp = tempfile.TemporaryDirectory()
|
||||
build_dir = Path(build_dir_tmp.name)
|
||||
|
||||
with open("module.json", "r") as file:
|
||||
manifest = json.load(file)
|
||||
|
||||
zip_root = PurePath(f'{manifest["name"]}')
|
||||
|
||||
filename = f'{manifest["name"]}-{manifest["version"]}.zip'
|
||||
|
||||
result = subprocess.run([wasm_pack, "build", "--target", "web", "--out-dir", build_dir, root_dir / rust_dir])
|
||||
if result.returncode != 0:
|
||||
raise Exception("Wasm build failed")
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def write_directory(archive, d):
|
||||
for f in (root_dir / d).iterdir():
|
||||
if f.is_dir():
|
||||
write_directory(archive, f)
|
||||
else:
|
||||
assert(f.is_file())
|
||||
archive.write(f, arcname=zip_root / d / f.name)
|
||||
|
||||
with zipfile.ZipFile(output_dir / filename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
|
||||
for f in root_files:
|
||||
archive.write(root_dir / f, arcname=zip_root / f)
|
||||
for d in copy_everything_directories:
|
||||
write_directory(archive, d)
|
||||
for f in wasm_files:
|
||||
archive.write(build_dir / f, arcname=zip_root / wasm_dir / f)
|
||||
|
||||
print(f"Successfully built {output_dir / filename}")
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
root_dir = Path(".")
|
||||
wasm_dir = root_dir / Path("wasm")
|
||||
rust_dir = root_dir / Path("rust")
|
||||
|
||||
debug = " --debug" if len(sys.argv) >= 2 and sys.argv[1] == "--debug" else ""
|
||||
|
||||
result = subprocess.run(["cargo", "watch", "-C" , rust_dir, "-s", f"wasm-pack build --target web --out-dir {wasm_dir.resolve()}{debug}"])
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cargo install cargo-watch
|
||||
cargo install wasm-pack
|
||||
@@ -7,15 +7,26 @@ import {disableSnap, registerKeybindings} from "./keybindings.js";
|
||||
import {libWrapper} from "./libwrapper_shim.js";
|
||||
import {performMigrations} from "./migration.js"
|
||||
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||
import {wipePathfindingCache} from "./pathfinding.js";
|
||||
import {extendRuler} from "./ruler.js";
|
||||
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
||||
import {recalculate} from "./socket.js";
|
||||
import {SpeedProvider} from "./speed_provider.js"
|
||||
import {setSnapParameterOnOptions} from "./util.js";
|
||||
|
||||
import initGridlessPathfinding, * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"
|
||||
|
||||
CONFIG.debug.dragRuler = false;
|
||||
export let debugGraphics = undefined;
|
||||
|
||||
initGridlessPathfinding().then(() => {
|
||||
Hooks.on("canvasInit", wipePathfindingCache);
|
||||
Hooks.on("canvasReady", wipePathfindingCache);
|
||||
Hooks.on("createWall", wipePathfindingCache);
|
||||
Hooks.on("updateWall", wipePathfindingCache);
|
||||
Hooks.on("deleteWall", wipePathfindingCache);
|
||||
});
|
||||
|
||||
Hooks.once("init", () => {
|
||||
registerSettings()
|
||||
registerKeybindings()
|
||||
@@ -4,24 +4,36 @@ import {debugGraphics} from "./main.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getSnapPointForTokenObj, iterPairs} from "./util.js";
|
||||
|
||||
import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js"
|
||||
|
||||
let cachedNodes = undefined;
|
||||
let use5105 = false;
|
||||
let gridlessPathfinders = new Map();
|
||||
|
||||
export function isPathfindingEnabled() {
|
||||
if (this.user !== game.user)
|
||||
return false;
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS)
|
||||
return false;
|
||||
if (!game.user.isGM && !game.settings.get(settingsKey, "allowPathfinding"))
|
||||
return false;
|
||||
return game.settings.get(settingsKey, "autoPathfinding") != togglePathfinding;
|
||||
}
|
||||
|
||||
export function findPath(from, to, token, previousWaypoints) {
|
||||
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||
let tokenSize = Math.max(token.data.width, token.data.height) * canvas.dimensions.size;
|
||||
let pathfinder = gridlessPathfinders.get(tokenSize);
|
||||
if (!pathfinder) {
|
||||
pathfinder = GridlessPathfinding.initialize(canvas.walls.placeables, tokenSize);
|
||||
gridlessPathfinders.set(tokenSize, pathfinder);
|
||||
}
|
||||
paintGridlessPathfindingDebug(pathfinder);
|
||||
return GridlessPathfinding.findPath(pathfinder, from, to);
|
||||
}
|
||||
else {
|
||||
const lastNode = calculatePath(from, to, token, previousWaypoints);
|
||||
if (!lastNode)
|
||||
return null;
|
||||
paintPathfindingDebug(lastNode, token);
|
||||
paintGriddedPathfindingDebug(lastNode, token);
|
||||
const path = [];
|
||||
let currentNode = lastNode;
|
||||
while (currentNode) {
|
||||
@@ -34,10 +46,17 @@ export function findPath(from, to, token, previousWaypoints) {
|
||||
currentNode = currentNode.previous;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
export function wipePathfindingCache() {
|
||||
cachedNodes = undefined;
|
||||
for (const pathfinder of gridlessPathfinders.values()) {
|
||||
GridlessPathfinding.free(pathfinder);
|
||||
}
|
||||
gridlessPathfinders.clear();
|
||||
if (debugGraphics)
|
||||
debugGraphics.removeChildren().forEach(c => c.destroy());
|
||||
}
|
||||
|
||||
function getNode(pos, token, initialize=true) {
|
||||
@@ -137,11 +156,11 @@ function stepCollidesWithWall(from, to, token) {
|
||||
return canvas.walls.checkCollision(new Ray(stepStart, stepEnd));
|
||||
}
|
||||
|
||||
function paintPathfindingDebug(lastNode, token) {
|
||||
function paintGriddedPathfindingDebug(lastNode, token) {
|
||||
if (!CONFIG.debug.dragRuler)
|
||||
return;
|
||||
|
||||
debugGraphics.removeChildren();
|
||||
debugGraphics.removeChildren().forEach(c => c.destroy());
|
||||
let currentNode = lastNode;
|
||||
while (currentNode) {
|
||||
let text = new PIXI.Text(currentNode.cost.toFixed(0));
|
||||
@@ -153,3 +172,16 @@ function paintPathfindingDebug(lastNode, token) {
|
||||
currentNode = currentNode.previous;
|
||||
}
|
||||
}
|
||||
|
||||
function paintGridlessPathfindingDebug(pathfinder) {
|
||||
if (!CONFIG.debug.dragRuler)
|
||||
return;
|
||||
|
||||
debugGraphics.removeChildren().forEach(c => c.destroy());
|
||||
let graphic = new PIXI.Graphics();
|
||||
graphic.lineStyle(2, 0x440000);
|
||||
for (const point of GridlessPathfinding.debugGetPathfindingPoints(pathfinder)) {
|
||||
graphic.drawCircle(point.x, point.y, 5);
|
||||
}
|
||||
debugGraphics.addChild(graphic);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import {currentSpeedProvider, getColorForDistanceAndToken, getRangesFromSpeedPro
|
||||
import {getHexSizeSupportTokenGridCenter} from "./compatibility.js";
|
||||
import {cancelScheduledMeasurement, measure} from "./foundry_imports.js"
|
||||
import {getMovementHistory} from "./movement_tracking.js";
|
||||
import {wipePathfindingCache} from "./pathfinding.js";
|
||||
import {settingsKey} from "./settings.js";
|
||||
import {getSnapPointForEntity} from "./util.js";
|
||||
|
||||
@@ -187,7 +186,6 @@ export function extendRuler() {
|
||||
return;
|
||||
const ruler = canvas.controls.ruler;
|
||||
ruler.clear();
|
||||
wipePathfindingCache();
|
||||
ruler._state = Ruler.STATES.STARTING;
|
||||
let entityCenter;
|
||||
if (isToken && canvas.grid.isHex && game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(entity))
|
||||
@@ -10,6 +10,10 @@ export const RightClickAction = Object.freeze({
|
||||
ABORT_DRAG:2,
|
||||
});
|
||||
|
||||
function delayedReload() {
|
||||
window.setTimeout(() => location.reload(), 500);
|
||||
}
|
||||
|
||||
export function registerSettings() {
|
||||
game.settings.register(settingsKey, "dataVersion", {
|
||||
scope: "world",
|
||||
@@ -90,7 +94,7 @@ export function registerSettings() {
|
||||
config: true,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
onChange: () => location.reload(),
|
||||
onChange: delayedReload,
|
||||
});
|
||||
|
||||
game.settings.register(settingsKey, "autoPathfinding", {
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
"name": "drag-ruler",
|
||||
"title": "Drag Ruler",
|
||||
"description": "When dragging a token displays a ruler showing how far you've moved that token.",
|
||||
"version": "1.11.5",
|
||||
"version": "1.12.0",
|
||||
"minimumCoreVersion" : "9.245",
|
||||
"compatibleCoreVersion" : "9",
|
||||
"authors": [
|
||||
@@ -13,9 +13,9 @@
|
||||
}
|
||||
],
|
||||
"esmodules": [
|
||||
"src/libwrapper_shim.js",
|
||||
"src/main.js",
|
||||
"src/socket.js"
|
||||
"js/libwrapper_shim.js",
|
||||
"js/main.js",
|
||||
"js/socket.js"
|
||||
],
|
||||
"templates": [
|
||||
"speed_provider_settings.html"
|
||||
@@ -65,7 +65,7 @@
|
||||
],
|
||||
"socket": true,
|
||||
"url": "https://github.com/manuelVo/foundryvtt-drag-ruler",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.11.5.zip",
|
||||
"download": "https://github.com/manuelVo/foundryvtt-drag-ruler/archive/v1.12.0.zip",
|
||||
"manifest": "https://raw.githubusercontent.com/manuelVo/foundryvtt-drag-ruler/master/module.json",
|
||||
"readme": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/README.md",
|
||||
"changelog": "https://github.com/manuelVo/foundryvtt-drag-ruler/blob/master/CHANGELOG.md",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
target/
|
||||
Generated
+154
@@ -0,0 +1,154 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "console_error_panic_hook"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gridless-pathfinding"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
"rustc-hash",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.86"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "gridless-pathfinding"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
#debug = true
|
||||
|
||||
[dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
js-sys = "0.3.56"
|
||||
rustc-hash = "1.1.0"
|
||||
wasm-bindgen = "0.2.79"
|
||||
@@ -0,0 +1 @@
|
||||
hard_tabs = true
|
||||
@@ -0,0 +1,187 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type JsPoint;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn x(this: &JsPoint) -> f64;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn y(this: &JsPoint) -> f64;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Point {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn new(x: f64, y: f64) -> Self {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn is_same_as(&self, other: &Self) -> bool {
|
||||
let e = 0.000001;
|
||||
(self.x - other.x).abs() < e && (self.y - other.y).abs() < e
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
impl PartialEq for Point {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Point {
|
||||
fn hash<H: Hasher>(&self, hasher: &mut H) {
|
||||
self.x.to_bits().hash(hasher);
|
||||
self.y.to_bits().hash(hasher);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JsPoint> for Point {
|
||||
fn from(point: &JsPoint) -> Self {
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
use js_sys::Array;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
geometry::Point,
|
||||
pathfinder::{DiscoveredNodePtr, Pathfinder},
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
macro_rules! log {
|
||||
( $( $t:tt )* ) => {
|
||||
log(&format!( $( $t )* ));
|
||||
};
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_namespace = console, js_name=warn)]
|
||||
pub fn log(s: &str);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type JsWall;
|
||||
pub type JsWallData;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn data(this: &JsWall) -> JsWallData;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn c(this: &JsWallData) -> Vec<f64>;
|
||||
|
||||
#[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(method, getter, js_name = "move")]
|
||||
fn move_type(this: &JsWallData) -> WallSenseType;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
pub type JsPoint;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn x(this: &JsPoint) -> f64;
|
||||
|
||||
#[wasm_bindgen(method, getter)]
|
||||
fn y(this: &JsPoint) -> f64;
|
||||
}
|
||||
|
||||
impl From<JsPoint> for Point {
|
||||
fn from(point: JsPoint) -> Self {
|
||||
Point {
|
||||
x: point.x(),
|
||||
y: point.y(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum DoorState {
|
||||
CLOSED = 0,
|
||||
OPEN = 1,
|
||||
LOCKED = 2,
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for DoorState {
|
||||
type Error = ();
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
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<usize> for DoorType {
|
||||
type Error = ();
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
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(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum WallSenseType {
|
||||
NONE = 0,
|
||||
LIMITED = 10,
|
||||
NORMAL = 20,
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for WallSenseType {
|
||||
type Error = ();
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
x if x == Self::NONE as usize => Ok(Self::NONE),
|
||||
x if x == Self::LIMITED as usize => Ok(Self::LIMITED),
|
||||
x if x == Self::NORMAL as usize => Ok(Self::NORMAL),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Wall {
|
||||
pub p1: Point,
|
||||
pub p2: Point,
|
||||
pub door_type: DoorType,
|
||||
pub door_state: DoorState,
|
||||
pub move_type: WallSenseType,
|
||||
}
|
||||
|
||||
impl Wall {
|
||||
pub fn new(
|
||||
p1: Point,
|
||||
p2: Point,
|
||||
door_type: DoorType,
|
||||
door_state: DoorState,
|
||||
move_type: WallSenseType,
|
||||
) -> Self {
|
||||
Self {
|
||||
p1,
|
||||
p2,
|
||||
door_type,
|
||||
door_state,
|
||||
move_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_door(&self) -> bool {
|
||||
self.door_type != DoorType::NONE
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.door_state == DoorState::OPEN
|
||||
}
|
||||
}
|
||||
|
||||
impl Wall {
|
||||
fn from_js(wall: &JsWall) -> Self {
|
||||
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]),
|
||||
data.door_type(),
|
||||
data.door_state(),
|
||||
data.move_type(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[wasm_bindgen]
|
||||
pub fn initialize(js_walls: Vec<JsValue>, token_size: f64) -> Pathfinder {
|
||||
let mut walls = Vec::with_capacity(js_walls.len());
|
||||
for wall in js_walls {
|
||||
let wall = JsWall::from(wall);
|
||||
walls.push(Wall::from_js(&wall));
|
||||
}
|
||||
Pathfinder::initialize(walls, token_size)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[wasm_bindgen]
|
||||
pub fn free(pathfinder: Pathfinder) {
|
||||
drop(pathfinder);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[wasm_bindgen(js_name=findPath)]
|
||||
pub fn find_path(pathfinder: &mut Pathfinder, from: JsPoint, to: JsPoint) -> Option<Array> {
|
||||
pathfinder
|
||||
.find_path(from.into(), to.into())
|
||||
.map(|first_node| first_node.iter_path().map(JsValue::from).collect())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[wasm_bindgen(js_name=debugGetPathfindingPoints)]
|
||||
pub fn debug_get_pathfinding_points(pathfinder: &Pathfinder) -> Array {
|
||||
pathfinder
|
||||
.nodes
|
||||
.iter()
|
||||
.map(|node| node.borrow().point)
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
trait IteratePath {
|
||||
fn iter_path(&self) -> PathIterator;
|
||||
}
|
||||
|
||||
impl IteratePath for DiscoveredNodePtr {
|
||||
fn iter_path(&self) -> PathIterator {
|
||||
PathIterator {
|
||||
current_node: Some(self.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PathIterator {
|
||||
current_node: Option<DiscoveredNodePtr>,
|
||||
}
|
||||
|
||||
impl Iterator for PathIterator {
|
||||
type Item = Point;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(node) = self.current_node.clone() {
|
||||
let point = node.borrow().node.borrow().point;
|
||||
self.current_node = node.borrow().previous.clone();
|
||||
Some(point)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
mod geometry;
|
||||
#[macro_use]
|
||||
mod js_api;
|
||||
mod pathfinder;
|
||||
mod ptr_indexed_hash_set;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() {
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
use std::{cell::RefCell, f64::consts::PI, rc::Rc};
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::{
|
||||
geometry::{LineSegment, Point},
|
||||
js_api::{Wall, WallSenseType},
|
||||
ptr_indexed_hash_set::PtrIndexedHashSet,
|
||||
};
|
||||
|
||||
pub struct Edge {
|
||||
target: NodePtr,
|
||||
cost: f64,
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
pub point: Point,
|
||||
edges: Option<Vec<Edge>>,
|
||||
final_edge: Option<Option<Edge>>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new(point: Point) -> Self {
|
||||
Self {
|
||||
point,
|
||||
edges: None,
|
||||
final_edge: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_edges(
|
||||
&self,
|
||||
) -> std::iter::Chain<std::slice::Iter<'_, Edge>, std::option::Iter<'_, Edge>> {
|
||||
self.edges
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.chain(self.final_edge.as_ref().unwrap().iter())
|
||||
}
|
||||
}
|
||||
|
||||
type NodePtr = Rc<RefCell<Node>>;
|
||||
|
||||
impl From<Node> for NodePtr {
|
||||
fn from(node: Node) -> Self {
|
||||
Rc::new(RefCell::new(node))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiscoveredNode {
|
||||
pub node: NodePtr,
|
||||
cost: f64,
|
||||
estimated: f64,
|
||||
pub previous: Option<DiscoveredNodePtr>,
|
||||
}
|
||||
|
||||
pub type DiscoveredNodePtr = Rc<RefCell<DiscoveredNode>>;
|
||||
|
||||
impl From<DiscoveredNode> for DiscoveredNodePtr {
|
||||
fn from(node: DiscoveredNode) -> Self {
|
||||
Rc::new(RefCell::new(node))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct NodeStorage {
|
||||
regular_nodes: Vec<NodePtr>,
|
||||
final_node: Option<NodePtr>,
|
||||
}
|
||||
|
||||
pub type NodeStorageIterator<'a> = std::iter::Chain<
|
||||
std::slice::Iter<'a, Rc<RefCell<Node>>>,
|
||||
std::option::Iter<'a, Rc<RefCell<Node>>>,
|
||||
>;
|
||||
|
||||
impl NodeStorage {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn push(&mut self, node: NodePtr) {
|
||||
self.regular_nodes.push(node);
|
||||
}
|
||||
|
||||
fn initialize_edges(&mut self, node: &NodePtr, walls: &[LineSegment]) {
|
||||
if node.borrow().final_edge.is_none() {
|
||||
let final_edge = self
|
||||
.final_node
|
||||
.as_ref()
|
||||
.filter(|neighbor| {
|
||||
!self.collides_with_wall(
|
||||
&LineSegment::new(node.borrow().point, neighbor.borrow().point),
|
||||
walls,
|
||||
)
|
||||
})
|
||||
.map(|neighbor| Edge {
|
||||
target: neighbor.clone(),
|
||||
cost: node.borrow().point.distance_to(neighbor.borrow().point),
|
||||
});
|
||||
node.borrow_mut().final_edge = Some(final_edge);
|
||||
}
|
||||
|
||||
if node.borrow().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 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
node.borrow_mut().edges = Some(edges);
|
||||
}
|
||||
|
||||
fn collides_with_wall(&self, line: &LineSegment, walls: &[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;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> NodeStorageIterator {
|
||||
self.regular_nodes.iter().chain(self.final_node.iter())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Pathfinder {
|
||||
#[wasm_bindgen(skip)]
|
||||
pub nodes: NodeStorage,
|
||||
#[wasm_bindgen(skip)]
|
||||
pub walls: Vec<LineSegment>,
|
||||
}
|
||||
|
||||
impl Pathfinder {
|
||||
pub fn initialize<I>(walls: I, token_size: f64) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = Wall>,
|
||||
{
|
||||
let distance_from_walls = token_size / 2.0;
|
||||
let mut endpoints = FxHashMap::<Point, Vec<f64>>::default();
|
||||
let mut line_segments = Vec::new();
|
||||
for wall in walls {
|
||||
if wall.move_type == WallSenseType::NONE {
|
||||
continue;
|
||||
}
|
||||
if wall.is_door() && wall.is_open() {
|
||||
continue;
|
||||
}
|
||||
let x_diff = wall.p2.x - wall.p1.x;
|
||||
let y_diff = wall.p2.y - wall.p1.y;
|
||||
let p1_angle = y_diff.atan2(x_diff).rem_euclid(2.0 * PI);
|
||||
let p2_angle = (p1_angle + PI).rem_euclid(2.0 * PI);
|
||||
for (point, angle) in [(wall.p1, p1_angle), (wall.p2, p2_angle)] {
|
||||
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()
|
||||
.for_each(|angles| angles.sort_by(|a, b| a.partial_cmp(b).unwrap()));
|
||||
let mut nodes = NodeStorage::new();
|
||||
for (point, angles) in endpoints {
|
||||
assert!(!angles.is_empty());
|
||||
for i in 1..angles.len() {
|
||||
let angle1 = angles[i - 1];
|
||||
let angle2 = angles[i];
|
||||
if angle1 == angle2 {
|
||||
continue;
|
||||
}
|
||||
let angle_diff = angle2 - angle1;
|
||||
if angle_diff <= PI {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
let angle_between = angle_diff / 2.0 + angle1;
|
||||
let pathfinding_node = calc_pathfinding_node(
|
||||
point,
|
||||
angle_between,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
);
|
||||
if angle_diff > 1.5 * PI {
|
||||
nodes.push(pathfinding_node);
|
||||
}
|
||||
}
|
||||
nodes.push(calc_pathfinding_node(
|
||||
point,
|
||||
angle1 + 0.5 * PI,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
));
|
||||
nodes.push(calc_pathfinding_node(
|
||||
point,
|
||||
angle2 - 0.5 * PI,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
));
|
||||
}
|
||||
let angle1 = angles.last().unwrap();
|
||||
let angle2 = angles.first().unwrap() + 2.0 * PI;
|
||||
let angle_diff = angle2 - angle1;
|
||||
if angle_diff <= PI {
|
||||
continue;
|
||||
}
|
||||
if angle_diff > 1.5 * PI {
|
||||
let angle_between = angle_diff / 2.0 + angle1;
|
||||
nodes.push(calc_pathfinding_node(
|
||||
point,
|
||||
angle_between,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
));
|
||||
}
|
||||
nodes.push(calc_pathfinding_node(
|
||||
point,
|
||||
angle1 + 0.5 * PI,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
));
|
||||
nodes.push(calc_pathfinding_node(
|
||||
point,
|
||||
angle2 - 0.5 * PI,
|
||||
distance_from_walls,
|
||||
&mut line_segments,
|
||||
));
|
||||
}
|
||||
// TODO Eliminating nodes close to each other may improve performance
|
||||
Self {
|
||||
nodes,
|
||||
walls: line_segments,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_path(&mut self, from: Point, to: Point) -> Option<DiscoveredNodePtr> {
|
||||
self.nodes.cleanup_final_edges();
|
||||
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, &self.walls);
|
||||
let to = DiscoveredNode {
|
||||
node: to_node,
|
||||
cost: 0.0,
|
||||
estimated: to.distance_to(from),
|
||||
previous: None,
|
||||
};
|
||||
// TODO Use a sorted set for next_nodes for better performance
|
||||
let mut next_nodes = vec![DiscoveredNodePtr::from(to)];
|
||||
let mut previous_nodes = PtrIndexedHashSet::new();
|
||||
while !next_nodes.is_empty() {
|
||||
// Sort by estimated cost, high to low
|
||||
// TODO Maybe tere's a faster way to do this than re-sorting every iteration?
|
||||
next_nodes.sort_by(|a, b| {
|
||||
b.borrow()
|
||||
.estimated
|
||||
.partial_cmp(&a.borrow().estimated)
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
// Get node with cheapest estimate
|
||||
let current_node = next_nodes.pop().unwrap();
|
||||
if current_node.borrow().node.borrow().point.x == from.x
|
||||
&& current_node.borrow().node.borrow().point.y == from.y
|
||||
{
|
||||
return Some(current_node);
|
||||
}
|
||||
previous_nodes.insert(current_node.borrow().node.clone());
|
||||
for edge in current_node.borrow().node.borrow().iter_edges() {
|
||||
let neighbor = &edge.target;
|
||||
if previous_nodes.contains(neighbor) {
|
||||
continue;
|
||||
}
|
||||
nodes.initialize_edges(neighbor, &self.walls);
|
||||
// Add a flat 0.00001 cost per node to discurage creation of unnecessary waypoints
|
||||
let cost = current_node.borrow().cost + edge.cost + 0.00001;
|
||||
let discovered_neighbor = DiscoveredNode {
|
||||
node: neighbor.clone(),
|
||||
cost,
|
||||
estimated: cost + neighbor.borrow().point.distance_to(from),
|
||||
previous: Some(current_node.clone()),
|
||||
};
|
||||
let neighbor_entry = next_nodes
|
||||
.iter()
|
||||
.find(|node| Rc::ptr_eq(&node.borrow().node, neighbor));
|
||||
if let Some(entry) = neighbor_entry {
|
||||
// If the neighbor is cheaper to reach via the current route than through previously discovered routes, replace it
|
||||
if entry.borrow().cost > cost {
|
||||
*entry.borrow_mut() = discovered_neighbor;
|
||||
}
|
||||
} else {
|
||||
next_nodes.push(discovered_neighbor.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_pathfinding_node(
|
||||
p: Point,
|
||||
angle: f64,
|
||||
distance_from_walls: f64,
|
||||
line_segments: &mut Vec<LineSegment>,
|
||||
) -> NodePtr {
|
||||
let offset_x = angle.cos() * distance_from_walls;
|
||||
let offset_y = angle.sin() * distance_from_walls;
|
||||
line_segments.push(LineSegment::new(
|
||||
p,
|
||||
Point {
|
||||
x: p.x + offset_x * 0.99,
|
||||
y: p.y + offset_y * 0.99,
|
||||
},
|
||||
));
|
||||
NodePtr::from(Node::new(Point {
|
||||
x: p.x + offset_x,
|
||||
y: p.y + offset_y,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
use std::collections::hash_set;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PtrIndexedHashSet<T>(FxHashSet<PtrIndexedRc<T>>);
|
||||
|
||||
impl<T> PtrIndexedHashSet<T> {
|
||||
pub fn new() -> Self {
|
||||
PtrIndexedHashSet(FxHashSet::default())
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, value: Rc<T>) -> bool {
|
||||
self.0.insert(PtrIndexedRc(value))
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, value: &Rc<T>) -> bool {
|
||||
self.0.remove(&PtrIndexedRc(Rc::clone(value)))
|
||||
}
|
||||
|
||||
pub fn contains(&mut self, value: &Rc<T>) -> bool {
|
||||
self.0.contains(&PtrIndexedRc(Rc::clone(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::fmt::Debug> std::fmt::Debug for PtrIndexedHashSet<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
f.debug_set().entries(self.0.iter().map(|e| &e.0)).finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct PtrIndexedRc<T>(Rc<T>);
|
||||
|
||||
impl<T> Hash for PtrIndexedRc<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Rc::as_ptr(&self.0).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for PtrIndexedRc<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for PtrIndexedRc<T> {}
|
||||
|
||||
pub struct PtrIndexedHashSetIterator<'a, T>(hash_set::Iter<'a, PtrIndexedRc<T>>);
|
||||
|
||||
impl<'a, T> Iterator for PtrIndexedHashSetIterator<'a, T> {
|
||||
type Item = &'a Rc<T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.0.next() {
|
||||
Some(item) => Some(&item.0),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoIterator for &'a PtrIndexedHashSet<T> {
|
||||
type Item = &'a Rc<T>;
|
||||
type IntoIter = PtrIndexedHashSetIterator<'a, T>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
PtrIndexedHashSetIterator::<T>((&self.0).iter())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user