Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c08d65ffad | |||
| 445c03d29a |
@@ -2,7 +2,7 @@ import {measureDistances} from "./compatibility.js";
|
|||||||
import {getMovementHistory} from "./movement_tracking.js";
|
import {getMovementHistory} from "./movement_tracking.js";
|
||||||
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
|
import {GenericSpeedProvider, SpeedProvider} from "./speed_provider.js"
|
||||||
import {settingsKey} from "./settings.js"
|
import {settingsKey} from "./settings.js"
|
||||||
import {getTokenShape} from "./util.js";
|
import {getAreaFromPositionAndShape, getTokenShape} from "./util.js";
|
||||||
|
|
||||||
export const availableSpeedProviders = {}
|
export const availableSpeedProviders = {}
|
||||||
export let currentSpeedProvider = undefined
|
export let currentSpeedProvider = undefined
|
||||||
@@ -140,6 +140,10 @@ export function getMovedDistanceFromToken(token) {
|
|||||||
return distances.reduce((acc, val) => acc + val, 0);
|
return distances.reduce((acc, val) => acc + val, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildCostFunction(token, shape) {
|
||||||
|
return (x, y, costOptions={}) => getCostFromSpeedProvider(token, getAreaFromPositionAndShape({x, y}, shape), costOptions);
|
||||||
|
}
|
||||||
|
|
||||||
export function registerModule(moduleId, speedProvider) {
|
export function registerModule(moduleId, speedProvider) {
|
||||||
// Check if a module with the given id exists and is currently enabled
|
// Check if a module with the given id exists and is currently enabled
|
||||||
const module = game.modules.get(moduleId)
|
const module = game.modules.get(moduleId)
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
import {getCostFromSpeedProvider} from "./api.js";
|
import {buildCostFunction} from "./api.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {getAreaFromPositionAndShape, highlightTokenShape} from "./util.js";
|
import {highlightTokenShape} from "./util.js";
|
||||||
|
|
||||||
export function getHexSizeSupportTokenGridCenter(token) {
|
export function getHexSizeSupportTokenGridCenter(token) {
|
||||||
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
|
const tokenCenterOffset = CONFIG.hexSizeSupport.getCenterOffset(token)
|
||||||
@@ -27,7 +27,7 @@ export function measureDistances(segments, entity, shape, options={}) {
|
|||||||
const newSegments = segments.slice(firstNewSegmentIndex);
|
const newSegments = segments.slice(firstNewSegmentIndex);
|
||||||
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
const distances = previousSegments.map(segment => segment.ray.dragRulerVisitedSpaces[segment.ray.dragRulerVisitedSpaces.length - 1].distance);
|
||||||
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
previousSegments.forEach(segment => segment.ray.terrainRulerVisitedSpaces = duplicate(segment.ray.dragRulerVisitedSpaces));
|
||||||
opts.costFunction = (x, y, costOptions={}) => { return getCostFromSpeedProvider(entity, getAreaFromPositionAndShape({x, y}, shape), costOptions); }
|
opts.costFunction = buildCostFunction(entity, shape);
|
||||||
if (previousSegments.length > 0)
|
if (previousSegments.length > 0)
|
||||||
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
opts.terrainRulerInitialState = previousSegments[previousSegments.length - 1].ray.dragRulerFinalState;
|
||||||
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
return distances.concat(terrainRuler.measureDistances(newSegments, opts));
|
||||||
|
|||||||
+4
-1
@@ -7,7 +7,7 @@ import {disableSnap, registerKeybindings} from "./keybindings.js";
|
|||||||
import {libWrapper} from "./libwrapper_shim.js";
|
import {libWrapper} from "./libwrapper_shim.js";
|
||||||
import {performMigrations} from "./migration.js"
|
import {performMigrations} from "./migration.js"
|
||||||
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
import {removeLastHistoryEntryIfAt, resetMovementHistory} from "./movement_tracking.js";
|
||||||
import {wipePathfindingCache, initializePathfinding, startBackgroundCaching} from "./pathfinding.js";
|
import {wipePathfindingCache, initializePathfinding, startBackgroundCaching, terrainRulerWrapper} from "./pathfinding.js";
|
||||||
import {extendRuler} from "./ruler.js";
|
import {extendRuler} from "./ruler.js";
|
||||||
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
import {registerSettings, RightClickAction, settingsKey} from "./settings.js"
|
||||||
import {recalculate} from "./socket.js";
|
import {recalculate} from "./socket.js";
|
||||||
@@ -62,6 +62,9 @@ Hooks.once("init", () => {
|
|||||||
registerSystem,
|
registerSystem,
|
||||||
recalculate,
|
recalculate,
|
||||||
resetMovementHistory,
|
resetMovementHistory,
|
||||||
|
private: {
|
||||||
|
terrainRulerWrapper
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+72
-13
@@ -1,11 +1,12 @@
|
|||||||
import {getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js";
|
import {getCenterFromGridPositionObj, getGridPositionFromPixelsObj, getPixelsFromGridPositionObj} from "./foundry_fixes.js";
|
||||||
import {moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
|
import {moveWithoutAnimation, togglePathfinding} from "./keybindings.js";
|
||||||
import {debugGraphics} from "./main.js";
|
import {debugGraphics} from "./main.js";
|
||||||
import {settingsKey} from "./settings.js";
|
import {settingsKey} from "./settings.js";
|
||||||
import {buildSnapPointTokenData, getSnapPointForTokenDataObj, isModuleActive, iterPairs} from "./util.js";
|
import {buildSnapPointTokenData, getSnapPointForTokenDataObj, getTokenShape, getTokenShapeForTokenData, isModuleActive, iterPairs} from "./util.js";
|
||||||
|
|
||||||
import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js";
|
import * as GridlessPathfinding from "../wasm/gridless_pathfinding.js";
|
||||||
import {PriorityQueueSet, ProcessOnceQueue} from "./data_structures.js";
|
import {PriorityQueueSet, ProcessOnceQueue} from "./data_structures.js";
|
||||||
|
import {buildCostFunction} from "./api.js";
|
||||||
|
|
||||||
class CacheLayer {
|
class CacheLayer {
|
||||||
constructor(tokenData, cacheId) {
|
constructor(tokenData, cacheId) {
|
||||||
@@ -79,7 +80,18 @@ class Cache {
|
|||||||
*/
|
*/
|
||||||
getCacheLayer(token) {
|
getCacheLayer(token) {
|
||||||
const tokenData = buildTokenData(token);
|
const tokenData = buildTokenData(token);
|
||||||
const cacheId = JSON.stringify(tokenData);
|
// TODO Request this from the speed providers so they can set their own options
|
||||||
|
let terrainData = canvas.terrain.listAllTerrain({token});
|
||||||
|
terrainData = terrainData.map(data => {
|
||||||
|
return {
|
||||||
|
x: data.object.x,
|
||||||
|
y: data.object.y,
|
||||||
|
cost: data.cost,
|
||||||
|
shape: data.shape,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const cacheIdData = {tokenData, terrainData};
|
||||||
|
const cacheId = GridlessPathfinding.sha1(JSON.stringify(cacheIdData));
|
||||||
let cacheLayer = this.layers.get(cacheId);
|
let cacheLayer = this.layers.get(cacheId);
|
||||||
// If we don't already have a cache layer for this cache ID, create one now
|
// If we don't already have a cache layer for this cache ID, create one now
|
||||||
if (!cacheLayer) {
|
if (!cacheLayer) {
|
||||||
@@ -241,7 +253,10 @@ export function findPath(from, to, token, previousWaypoints) {
|
|||||||
let pathfinder = gridlessPathfinders.get(tokenSize);
|
let pathfinder = gridlessPathfinders.get(tokenSize);
|
||||||
if (!pathfinder) {
|
if (!pathfinder) {
|
||||||
let radiusMultiplier = game.settings.get(settingsKey, "pathfindingRadius");
|
let radiusMultiplier = game.settings.get(settingsKey, "pathfindingRadius");
|
||||||
pathfinder = GridlessPathfinding.initialize(canvas.walls.placeables, tokenSize * radiusMultiplier, token.data.elevation, Boolean(game.modules.get("wall-height")?.active));
|
// TODO Pass proper options to listAllTerrain
|
||||||
|
// TODO Wipe caches when they become invalid
|
||||||
|
// TODO Multiple caches, analog to gridded pathfinding
|
||||||
|
pathfinder = GridlessPathfinding.initialize(canvas.walls.placeables, canvas.terrain.listAllTerrain(), tokenSize * radiusMultiplier, token.data.elevation, Boolean(game.modules.get("wall-height")?.active));
|
||||||
gridlessPathfinders.set(tokenSize, pathfinder);
|
gridlessPathfinders.set(tokenSize, pathfinder);
|
||||||
}
|
}
|
||||||
paintGridlessPathfindingDebug(pathfinder);
|
paintGridlessPathfindingDebug(pathfinder);
|
||||||
@@ -255,19 +270,49 @@ export function findPath(from, to, token, previousWaypoints) {
|
|||||||
const path = [];
|
const path = [];
|
||||||
let currentNode = firstNode;
|
let currentNode = firstNode;
|
||||||
while (currentNode) {
|
while (currentNode) {
|
||||||
// TODO Check if the distance doesn't change
|
|
||||||
if (path.length >= 2 && !stepCollidesWithWall(path[path.length - 2], currentNode.node, cacheLayer.tokenData)) {
|
if (path.length >= 2 && !stepCollidesWithWall(path[path.length - 2], currentNode.node, cacheLayer.tokenData)) {
|
||||||
// Replace last waypoint if the current waypoint leads to a valid path
|
// Replace last waypoint if the current waypoint leads to a valid path that isn't longer than the old path
|
||||||
path[path.length - 1] = {x: currentNode.node.x, y: currentNode.node.y};
|
if (window.terrainRuler) {
|
||||||
} else {
|
let startNode = getCenterFromGridPositionObj(path[path.length - 2]);
|
||||||
path.push({x: currentNode.node.x, y: currentNode.node.y});
|
let middleNode = getCenterFromGridPositionObj(path[path.length - 1]);
|
||||||
|
let endNode = getCenterFromGridPositionObj(currentNode.node);
|
||||||
|
let oldPath = [{ray: new Ray(startNode, middleNode)}, {ray: new Ray(middleNode, endNode)}];
|
||||||
|
let newPath = [{ray: new Ray(startNode, endNode)}];
|
||||||
|
let costFunction = buildCostFunction(token, getTokenShape(token));
|
||||||
|
// TODO Cache the used measurement for use in the next loop to improve performance
|
||||||
|
let oldDistance = terrainRuler.measureDistances(oldPath, {costFunction}).reduce((a, b) => a + b);
|
||||||
|
let newDistance = terrainRuler.measureDistances(newPath, {costFunction})[0];
|
||||||
|
|
||||||
|
// TODO We might need to check if the diagonal count has increased on 5-10-5
|
||||||
|
if (newDistance < oldDistance) {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
else if (newDistance === oldDistance) {
|
||||||
|
let oldNoDiagonals = oldPath[1].ray.terrainRulerFinalState?.noDiagonals;
|
||||||
|
let newNoDiagonals = newPath[0].ray.terrainRulerFinalState?.noDiagonals;
|
||||||
|
// This uses === && < instead of <= because the variables might be undefined (which shall lead to a true result)
|
||||||
|
if (oldNoDiagonals === newNoDiagonals || newNoDiagonals < oldNoDiagonals) {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
path.push({x: currentNode.node.x, y: currentNode.node.y});
|
||||||
currentNode = currentNode.next;
|
currentNode = currentNode.next;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function terrainRulerWrapper(from, to) {
|
||||||
|
// TODO Send list of terrain to terrain layer
|
||||||
|
const ray = new Ray(from, to);
|
||||||
|
return terrainRuler.measureDistances([{ray}])[0] / canvas.dimensions.distance * canvas.dimensions.size;
|
||||||
|
}
|
||||||
|
|
||||||
function buildTokenData(token) {
|
function buildTokenData(token) {
|
||||||
// Almost all the information we need is for calculating the snap point
|
// Almost all the information we need is for calculating the snap point
|
||||||
const tokenData = buildSnapPointTokenData(token);
|
const tokenData = buildSnapPointTokenData(token);
|
||||||
@@ -295,11 +340,25 @@ function getNode(pos, cacheLayer, initialize = true) {
|
|||||||
// TODO Work with pixels instead of grid locations
|
// TODO Work with pixels instead of grid locations
|
||||||
if (!stepCollidesWithWall(pos, neighborPos, cacheLayer.tokenData)) {
|
if (!stepCollidesWithWall(pos, neighborPos, cacheLayer.tokenData)) {
|
||||||
const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE;
|
const isDiagonal = node.x !== neighborPos.x && node.y !== neighborPos.y && canvas.grid.type === CONST.GRID_TYPES.SQUARE;
|
||||||
|
let edgeCost;
|
||||||
|
if (window.terrainRuler) {
|
||||||
|
let ray = new Ray(getCenterFromGridPositionObj(pos), getCenterFromGridPositionObj(neighborPos));
|
||||||
|
let measuredDistance = terrainRuler.measureDistances([{ray}], {costFunction: buildCostFunction(cacheLayer.tokenData, getTokenShapeForTokenData(cacheLayer.tokenData))})[0];
|
||||||
|
edgeCost = Math.round(measuredDistance / canvas.dimensions.distance);
|
||||||
|
if (ray.terrainRulerFinalState?.noDiagonals === 1) {
|
||||||
|
edgeCost = 1.5;
|
||||||
|
}
|
||||||
|
// Charge 1.0001 instead of 1 for diagonals to discourage unnecessary diagonals
|
||||||
|
if (isDiagonal && edgeCost == 1) {
|
||||||
|
edgeCost = 1.0001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Count 5-10-5 diagonals as 1.5 (so two add up to 3) and 5-5-5 diagonals as 1.0001 (to discourage unnecessary diagonals)
|
||||||
|
// TODO Account for difficult terrain
|
||||||
|
edgeCost = isDiagonal ? (use5105 ? 1.5 : 1.0001) : 1;
|
||||||
|
}
|
||||||
const neighbor = getNode(neighborPos, cacheLayer, false);
|
const neighbor = getNode(neighborPos, cacheLayer, false);
|
||||||
|
|
||||||
// Count 5-10-5 diagonals as 1.5 (so two add up to 3) and 5-5-5 diagonals as 1.0001 (to discourage unnecessary diagonals)
|
|
||||||
// TODO Account for difficult terrain
|
|
||||||
let edgeCost = isDiagonal ? (use5105 ? 1.5 : 1.0001) : 1;
|
|
||||||
node.edges.push({target: neighbor, cost: edgeCost});
|
node.edges.push({target: neighbor, cost: edgeCost});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-9
@@ -167,15 +167,19 @@ export function getAreaFromPositionAndShape(position, shape) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getTokenShape(token) {
|
export function getTokenShape(token) {
|
||||||
if (token.scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) {
|
return getTokenShapeForTokenData(buildSnapPointTokenData(token), token.scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTokenShapeForTokenData(tokenData, scene=canvas.scene) {
|
||||||
|
if (scene.data.gridType === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
return [{x: 0, y: 0}]
|
return [{x: 0, y: 0}]
|
||||||
}
|
}
|
||||||
else if (token.scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
|
else if (scene.data.gridType === CONST.GRID_TYPES.SQUARE) {
|
||||||
const topOffset = -Math.floor(token.data.height / 2)
|
const topOffset = -Math.floor(tokenData.height / 2)
|
||||||
const leftOffset = -Math.floor(token.data.width / 2)
|
const leftOffset = -Math.floor(tokenData.width / 2)
|
||||||
const shape = []
|
const shape = []
|
||||||
for (let y = 0;y < token.data.height;y++) {
|
for (let y = 0;y < tokenData.height;y++) {
|
||||||
for (let x = 0;x < token.data.width;x++) {
|
for (let x = 0;x < tokenData.width;x++) {
|
||||||
shape.push({x: x + leftOffset, y: y + topOffset})
|
shape.push({x: x + leftOffset, y: y + topOffset})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,8 +187,8 @@ export function getTokenShape(token) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Hex grids
|
// Hex grids
|
||||||
if (game.modules.get("hex-size-support")?.active && CONFIG.hexSizeSupport.getAltSnappingFlag(token)) {
|
if (game.modules.get("hex-size-support")?.active && tokenData.hexSizeSupport.altSnappingFlag) {
|
||||||
const borderSize = token.data.flags["hex-size-support"].borderSize;
|
const borderSize = tokenData.hexSizeSupport.borderSize;
|
||||||
let shape = [{x: 0, y: 0}];
|
let shape = [{x: 0, y: 0}];
|
||||||
if (borderSize >= 2)
|
if (borderSize >= 2)
|
||||||
shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]);
|
shape = shape.concat([{x: 0, y: -1}, {x: -1, y: -1}]);
|
||||||
@@ -193,7 +197,7 @@ export function getTokenShape(token) {
|
|||||||
if (borderSize >= 4)
|
if (borderSize >= 4)
|
||||||
shape = shape.concat([{x: -2, y: -1}, {x: 1, y: -1}, {x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}])
|
shape = shape.concat([{x: -2, y: -1}, {x: 1, y: -1}, {x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}])
|
||||||
|
|
||||||
if (Boolean(CONFIG.hexSizeSupport.getAltOrientationFlag(token)) !== canvas.grid.grid.options.columns)
|
if (Boolean(tokenData.hexSizeSupport.altOrientationFlag) !== canvas.grid.grid.options.columns)
|
||||||
shape.forEach(space => space.y *= -1);
|
shape.forEach(space => space.y *= -1);
|
||||||
if (canvas.grid.grid.options.columns)
|
if (canvas.grid.grid.options.columns)
|
||||||
shape = shape.map(space => {return {x: space.y, y: space.x}});
|
shape = shape.map(space => {return {x: space.y, y: space.x}});
|
||||||
|
|||||||
Generated
+78
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.9.1"
|
version = "3.9.1"
|
||||||
@@ -24,6 +33,45 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gridless-pathfinding"
|
name = "gridless-pathfinding"
|
||||||
version = "1.12.8"
|
version = "1.12.8"
|
||||||
@@ -31,6 +79,7 @@ dependencies = [
|
|||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
"sha1",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -49,6 +98,12 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.125"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -82,6 +137,17 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.86"
|
version = "1.0.86"
|
||||||
@@ -93,12 +159,24 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.79"
|
version = "0.2.79"
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ lto = true
|
|||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
js-sys = "0.3.56"
|
js-sys = "0.3.56"
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
|
sha1 = "0.10.1"
|
||||||
wasm-bindgen = "0.2.79"
|
wasm-bindgen = "0.2.79"
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ impl Point {
|
|||||||
let e = 0.000001;
|
let e = 0.000001;
|
||||||
(self.x - other.x).abs() < e && (self.y - other.y).abs() < e
|
(self.x - other.x).abs() < e && (self.y - other.y).abs() < e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_in_rectangle(&self, rect: &Rectangle) -> bool {
|
||||||
|
between(self.x, rect.left.p1.x, rect.right.p1.x)
|
||||||
|
&& between(self.y, rect.top.p1.y, rect.bottom.p1.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Point {}
|
impl Eq for Point {}
|
||||||
@@ -179,9 +184,70 @@ impl LineSegment {
|
|||||||
}
|
}
|
||||||
between(intersection.x, self.p1.x, self.p2.x)
|
between(intersection.x, self.p1.x, self.p2.x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn intersects_rect(&self, rect: &Rectangle) -> bool {
|
||||||
|
if self.p1.is_in_rectangle(rect) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
[rect.left, rect.top, rect.right, rect.bottom]
|
||||||
|
.iter()
|
||||||
|
.any(|edge| self.intersection(edge).is_some())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn between<T: Copy + PartialOrd>(num: T, a: T, b: T) -> bool {
|
pub fn between<T: Copy + PartialOrd>(num: T, a: T, b: T) -> bool {
|
||||||
let (min, max) = if a < b { (a, b) } else { (b, a) };
|
let (min, max) = if a < b { (a, b) } else { (b, a) };
|
||||||
num >= min && num <= max
|
num >= min && num <= max
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
pub type JsRectangle;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn left(this: &JsRectangle) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn top(this: &JsRectangle) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn right(this: &JsRectangle) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn bottom(this: &JsRectangle) -> f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Rectangle {
|
||||||
|
pub left: LineSegment,
|
||||||
|
pub top: LineSegment,
|
||||||
|
pub right: LineSegment,
|
||||||
|
pub bottom: LineSegment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rectangle {
|
||||||
|
pub fn new(left: f64, top: f64, right: f64, bottom: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
left: LineSegment::new(Point::new(left, top), Point::new(left, bottom)),
|
||||||
|
top: LineSegment::new(Point::new(left, top), Point::new(right, top)),
|
||||||
|
right: LineSegment::new(Point::new(right, top), Point::new(right, bottom)),
|
||||||
|
bottom: LineSegment::new(Point::new(left, bottom), Point::new(right, bottom)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsRectangle> for Rectangle {
|
||||||
|
fn from(rect: &JsRectangle) -> Self {
|
||||||
|
let left = rect.left();
|
||||||
|
let top = rect.top();
|
||||||
|
let right = rect.right();
|
||||||
|
let bottom = rect.bottom();
|
||||||
|
Self::new(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Circle {
|
||||||
|
pub center: Point,
|
||||||
|
pub radius: f64,
|
||||||
|
}
|
||||||
|
|||||||
+130
-5
@@ -1,9 +1,11 @@
|
|||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::Point,
|
geometry::{Circle, LineSegment, Point, Rectangle},
|
||||||
pathfinder::{DiscoveredNodePtr, Pathfinder},
|
pathfinder::{DiscoveredNodePtr, Pathfinder},
|
||||||
|
util::Windows,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
@@ -17,6 +19,9 @@ macro_rules! log {
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#[wasm_bindgen(js_namespace = console, js_name=warn)]
|
#[wasm_bindgen(js_namespace = console, js_name=warn)]
|
||||||
pub fn log(s: &str);
|
pub fn log(s: &str);
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_namespace = ["dragRuler", "private"], js_name=terrainRulerWrapper)]
|
||||||
|
pub fn distance_with_terrain(a: Point, b: Point) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -74,6 +79,112 @@ impl From<JsPoint> for Point {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
pub type JsTerrainInfo;
|
||||||
|
pub type JsTerrainObject;
|
||||||
|
pub type JsTerrainShape;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn object(this: &JsTerrainInfo) -> JsTerrainObject;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn shape(this: &JsTerrainInfo) -> JsTerrainShape;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn x(this: &JsTerrainObject) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn y(this: &JsTerrainObject) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn width(this: &JsTerrainObject) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn height(this: &JsTerrainObject) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter, js_name = "type")]
|
||||||
|
fn shape_type(this: &JsTerrainShape) -> u32;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn x(this: &JsTerrainShape) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn y(this: &JsTerrainShape) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn radius(this: &JsTerrainShape) -> f64;
|
||||||
|
|
||||||
|
#[wasm_bindgen(method, getter)]
|
||||||
|
fn points(this: &JsTerrainShape) -> Vec<f64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsTerrainObject {
|
||||||
|
fn to_bounding_rect(&self) -> Rectangle {
|
||||||
|
let left = self.x();
|
||||||
|
let top = self.y();
|
||||||
|
let right = left + self.width();
|
||||||
|
let bottom = top + self.height();
|
||||||
|
Rectangle::new(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsTerrainShape {
|
||||||
|
fn to_segments(&self, x: f64, y: f64) -> Vec<LineSegment> {
|
||||||
|
let points = self.points();
|
||||||
|
assert!(points.len() % 2 == 0);
|
||||||
|
points
|
||||||
|
.chunks(2)
|
||||||
|
.map(|coordinates| Point::new(coordinates[0] + x, coordinates[1] + y))
|
||||||
|
.windows()
|
||||||
|
.map(|(p1, p2)| LineSegment::new(p1, p2))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_circle(&self, x: f64, y: f64) -> Circle {
|
||||||
|
let center = Point::new(self.x() + x, self.y() + y);
|
||||||
|
let radius = self.radius();
|
||||||
|
Circle { center, radius }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsTerrainInfo> for TerrainShape {
|
||||||
|
fn from(terrain: &JsTerrainInfo) -> Self {
|
||||||
|
let shape = terrain.shape();
|
||||||
|
let object = terrain.object();
|
||||||
|
let x = object.x();
|
||||||
|
let y = object.y();
|
||||||
|
match shape.shape_type() {
|
||||||
|
0 => TerrainShape::Polygon(shape.to_segments(x, y)),
|
||||||
|
2 => TerrainShape::Circle(shape.to_circle(x, y)),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TerrainShape {
|
||||||
|
Polygon(Vec<LineSegment>),
|
||||||
|
Circle(Circle),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Terrain {
|
||||||
|
pub shape: TerrainShape,
|
||||||
|
pub bounding_box: Rectangle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsTerrainInfo> for Terrain {
|
||||||
|
fn from(terrain: &JsTerrainInfo) -> Self {
|
||||||
|
let bounding_box = terrain.object().to_bounding_rect();
|
||||||
|
let shape = terrain.into();
|
||||||
|
Self {
|
||||||
|
bounding_box,
|
||||||
|
shape,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum DoorState {
|
pub enum DoorState {
|
||||||
@@ -211,8 +322,7 @@ impl Wall {
|
|||||||
c.iter_mut().for_each(|val| *val = val.round());
|
c.iter_mut().for_each(|val| *val = val.round());
|
||||||
let height = if enable_height {
|
let height = if enable_height {
|
||||||
data.flags().wall_height().into()
|
data.flags().wall_height().into()
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
WallHeight::default()
|
WallHeight::default()
|
||||||
};
|
};
|
||||||
Self::new(
|
Self::new(
|
||||||
@@ -228,13 +338,20 @@ impl Wall {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn initialize(js_walls: Vec<JsValue>, token_size: f64, token_elevation: f64, enable_height: bool) -> Pathfinder {
|
pub fn initialize(
|
||||||
|
js_walls: Vec<JsValue>,
|
||||||
|
js_terrain: Vec<JsTerrainInfo>,
|
||||||
|
token_size: f64,
|
||||||
|
token_elevation: f64,
|
||||||
|
enable_height: bool,
|
||||||
|
) -> Pathfinder {
|
||||||
let mut walls = Vec::with_capacity(js_walls.len());
|
let mut walls = Vec::with_capacity(js_walls.len());
|
||||||
for wall in js_walls {
|
for wall in js_walls {
|
||||||
let wall = JsWall::from(wall);
|
let wall = JsWall::from(wall);
|
||||||
walls.push(Wall::from_js(&wall, enable_height));
|
walls.push(Wall::from_js(&wall, enable_height));
|
||||||
}
|
}
|
||||||
Pathfinder::initialize(walls, token_size, token_elevation)
|
let terrain = js_terrain.iter().map(|terrain| terrain.into()).collect();
|
||||||
|
Pathfinder::initialize(walls, terrain, token_size, token_elevation)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -262,6 +379,14 @@ pub fn debug_get_pathfinding_points(pathfinder: &Pathfinder) -> Array {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn sha1(input: &str) -> String {
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(input);
|
||||||
|
format!("{:x}", hasher.finalize())
|
||||||
|
}
|
||||||
|
|
||||||
trait IteratePath {
|
trait IteratePath {
|
||||||
fn iter_path(&self) -> PathIterator;
|
fn iter_path(&self) -> PathIterator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod geometry;
|
|||||||
mod js_api;
|
mod js_api;
|
||||||
mod pathfinder;
|
mod pathfinder;
|
||||||
mod ptr_indexed_hash_set;
|
mod ptr_indexed_hash_set;
|
||||||
|
mod util;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
|||||||
+86
-35
@@ -5,8 +5,8 @@ use wasm_bindgen::prelude::*;
|
|||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{LineSegment, Point},
|
geometry::{LineSegment, Point, Rectangle},
|
||||||
js_api::{Wall, WallSenseType},
|
js_api::{distance_with_terrain, Terrain, TerrainShape, Wall, WallSenseType, log},
|
||||||
ptr_indexed_hash_set::PtrIndexedHashSet,
|
ptr_indexed_hash_set::PtrIndexedHashSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ impl NodeStorage {
|
|||||||
self.regular_nodes.push(node);
|
self.regular_nodes.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_edges(&mut self, node: &NodePtr, walls: &[LineSegment]) {
|
fn initialize_edges(&mut self, node: &NodePtr, walls: &[LineSegment], terrain: &[Rectangle]) {
|
||||||
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
|
||||||
@@ -97,7 +97,11 @@ impl NodeStorage {
|
|||||||
})
|
})
|
||||||
.map(|neighbor| Edge {
|
.map(|neighbor| Edge {
|
||||||
target: neighbor.clone(),
|
target: neighbor.clone(),
|
||||||
cost: node.borrow().point.distance_to(neighbor.borrow().point),
|
cost: Self::measure_distance(
|
||||||
|
node.borrow().point,
|
||||||
|
neighbor.borrow().point,
|
||||||
|
terrain,
|
||||||
|
),
|
||||||
});
|
});
|
||||||
node.borrow_mut().final_edge = Some(final_edge);
|
node.borrow_mut().final_edge = Some(final_edge);
|
||||||
}
|
}
|
||||||
@@ -114,7 +118,8 @@ impl NodeStorage {
|
|||||||
}
|
}
|
||||||
let neighbor_point = neighbor.borrow().point;
|
let neighbor_point = neighbor.borrow().point;
|
||||||
if !self.collides_with_wall(&LineSegment::new(point, neighbor_point), walls) {
|
if !self.collides_with_wall(&LineSegment::new(point, neighbor_point), walls) {
|
||||||
let cost = point.distance_to(neighbor_point);
|
let cost =
|
||||||
|
Self::measure_distance(node.borrow().point, neighbor.borrow().point, terrain);
|
||||||
edges.push(Edge {
|
edges.push(Edge {
|
||||||
target: neighbor.clone(),
|
target: neighbor.clone(),
|
||||||
cost,
|
cost,
|
||||||
@@ -128,6 +133,15 @@ impl NodeStorage {
|
|||||||
walls.iter().any(|wall| line.intersection(wall).is_some())
|
walls.iter().any(|wall| line.intersection(wall).is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn measure_distance(a: Point, b: Point, terrain: &[Rectangle]) -> f64 {
|
||||||
|
let segment = LineSegment::new(a, b);
|
||||||
|
if terrain.iter().any(|rect| segment.intersects_rect(rect)) {
|
||||||
|
distance_with_terrain(a, b)
|
||||||
|
} else {
|
||||||
|
a.distance_to(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -145,35 +159,51 @@ pub struct Pathfinder {
|
|||||||
pub nodes: NodeStorage,
|
pub nodes: NodeStorage,
|
||||||
#[wasm_bindgen(skip)]
|
#[wasm_bindgen(skip)]
|
||||||
pub walls: Vec<LineSegment>,
|
pub walls: Vec<LineSegment>,
|
||||||
|
#[wasm_bindgen(skip)]
|
||||||
|
pub terrain: Vec<Rectangle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pathfinder {
|
impl Pathfinder {
|
||||||
pub fn initialize<I>(walls: I, token_size: f64, token_elevation: f64) -> Self
|
pub fn initialize(
|
||||||
where
|
walls: Vec<Wall>,
|
||||||
I: IntoIterator<Item = Wall>,
|
terrain: Vec<Terrain>,
|
||||||
{
|
token_size: f64,
|
||||||
|
token_elevation: f64,
|
||||||
|
) -> Self {
|
||||||
|
log!("{:#?}", terrain);
|
||||||
let distance_from_walls = token_size / 2.0;
|
let distance_from_walls = token_size / 2.0;
|
||||||
|
|
||||||
|
// TODO Place pathfinding nodes around terrain edges
|
||||||
|
let mut polygon_terrain = Vec::new();
|
||||||
|
let mut circle_terrain = Vec::new();
|
||||||
|
|
||||||
|
for terrain in &terrain {
|
||||||
|
match &terrain.shape {
|
||||||
|
TerrainShape::Polygon(polygon) => polygon_terrain.append(&mut polygon.clone()),
|
||||||
|
TerrainShape::Circle(circle) => circle_terrain.push(circle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut walls = walls
|
||||||
|
.into_iter()
|
||||||
|
.filter(|wall| wall.move_type != WallSenseType::NONE)
|
||||||
|
.filter(|wall| !(wall.is_door() && wall.is_open()))
|
||||||
|
.filter(|wall| wall.height.contains(token_elevation))
|
||||||
|
.map(|wall| LineSegment::new(wall.p1, wall.p2))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// TODO Generate points around difficult terrain
|
||||||
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 segments in walls.iter().chain(polygon_terrain.iter()) {
|
||||||
if wall.move_type == WallSenseType::NONE {
|
let x_diff = segments.p2.x - segments.p1.x;
|
||||||
continue;
|
let y_diff = segments.p2.y - segments.p1.y;
|
||||||
}
|
|
||||||
if wall.is_door() && wall.is_open() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if !wall.height.contains(token_elevation) {
|
|
||||||
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 p1_angle = y_diff.atan2(x_diff).rem_euclid(2.0 * PI);
|
||||||
let p2_angle = (p1_angle + PI).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)] {
|
for (point, angle) in [(segments.p1, p1_angle), (segments.p2, p2_angle)] {
|
||||||
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()
|
||||||
@@ -196,19 +226,19 @@ impl Pathfinder {
|
|||||||
point,
|
point,
|
||||||
angle_between,
|
angle_between,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
nodes.push(calc_pathfinding_node(
|
nodes.push(calc_pathfinding_node(
|
||||||
point,
|
point,
|
||||||
angle1 + 0.5 * PI,
|
angle1 + 0.5 * PI,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
nodes.push(calc_pathfinding_node(
|
nodes.push(calc_pathfinding_node(
|
||||||
point,
|
point,
|
||||||
angle2 - 0.5 * PI,
|
angle2 - 0.5 * PI,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let angle1 = angles.last().unwrap();
|
let angle1 = angles.last().unwrap();
|
||||||
@@ -222,25 +252,46 @@ impl Pathfinder {
|
|||||||
point,
|
point,
|
||||||
angle_between,
|
angle_between,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
nodes.push(calc_pathfinding_node(
|
nodes.push(calc_pathfinding_node(
|
||||||
point,
|
point,
|
||||||
angle1 + 0.5 * PI,
|
angle1 + 0.5 * PI,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
nodes.push(calc_pathfinding_node(
|
nodes.push(calc_pathfinding_node(
|
||||||
point,
|
point,
|
||||||
angle2 - 0.5 * PI,
|
angle2 - 0.5 * PI,
|
||||||
distance_from_walls,
|
distance_from_walls,
|
||||||
&mut line_segments,
|
&mut walls,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Check bounding box of circle terrain
|
||||||
|
for circle in circle_terrain {
|
||||||
|
let angle_step = f64::asin(token_size / 2.0 / circle.radius);
|
||||||
|
let mut angle = 0.0;
|
||||||
|
while angle < 2.0 * PI {
|
||||||
|
let point = Point {
|
||||||
|
x: circle.center.x + angle.cos() * circle.radius,
|
||||||
|
y: circle.center.y + angle.sin() * circle.radius,
|
||||||
|
};
|
||||||
|
nodes.push(calc_pathfinding_node(
|
||||||
|
point,
|
||||||
|
angle,
|
||||||
|
distance_from_walls,
|
||||||
|
&mut walls,
|
||||||
|
));
|
||||||
|
angle += angle_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Eliminating nodes close to each other may improve performance
|
// TODO Eliminating nodes close to each other may improve performance
|
||||||
Self {
|
Self {
|
||||||
nodes,
|
nodes,
|
||||||
walls: line_segments,
|
walls,
|
||||||
|
terrain: terrain.iter().map(|terrain| terrain.bounding_box).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +300,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, &self.walls);
|
nodes.initialize_edges(&to_node, &self.walls, &self.terrain);
|
||||||
let to = DiscoveredNode {
|
let to = DiscoveredNode {
|
||||||
node: to_node,
|
node: to_node,
|
||||||
cost: 0.0,
|
cost: 0.0,
|
||||||
@@ -282,7 +333,7 @@ impl Pathfinder {
|
|||||||
if previous_nodes.contains(neighbor) {
|
if previous_nodes.contains(neighbor) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
nodes.initialize_edges(neighbor, &self.walls);
|
nodes.initialize_edges(neighbor, &self.walls, &self.terrain);
|
||||||
// Add a flat 0.00001 cost per node to discurage creation of unnecessary waypoints
|
// 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 cost = current_node.borrow().cost + edge.cost + 0.00001;
|
||||||
let discovered_neighbor = DiscoveredNode {
|
let discovered_neighbor = DiscoveredNode {
|
||||||
@@ -312,11 +363,11 @@ fn calc_pathfinding_node(
|
|||||||
p: Point,
|
p: Point,
|
||||||
angle: f64,
|
angle: f64,
|
||||||
distance_from_walls: f64,
|
distance_from_walls: f64,
|
||||||
line_segments: &mut Vec<LineSegment>,
|
walls: &mut Vec<LineSegment>,
|
||||||
) -> NodePtr {
|
) -> NodePtr {
|
||||||
let offset_x = angle.cos() * distance_from_walls;
|
let offset_x = angle.cos() * distance_from_walls;
|
||||||
let offset_y = angle.sin() * distance_from_walls;
|
let offset_y = angle.sin() * distance_from_walls;
|
||||||
line_segments.push(LineSegment::new(
|
walls.push(LineSegment::new(
|
||||||
p,
|
p,
|
||||||
Point {
|
Point {
|
||||||
x: p.x + offset_x * 0.99,
|
x: p.x + offset_x * 0.99,
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
pub struct WindowIterator<I: Iterator> {
|
||||||
|
iterator: I,
|
||||||
|
previous: Option<I::Item>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator> Iterator for WindowIterator<I>
|
||||||
|
where
|
||||||
|
I::Item: Copy,
|
||||||
|
{
|
||||||
|
type Item = (I::Item, I::Item);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.previous.is_none() {
|
||||||
|
self.previous = self.iterator.next();
|
||||||
|
}
|
||||||
|
let current = self.iterator.next();
|
||||||
|
if current.is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let current = current.unwrap();
|
||||||
|
let previous = self.previous.unwrap();
|
||||||
|
let result = (current, previous);
|
||||||
|
self.previous = Some(current);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Windows<T> {
|
||||||
|
fn windows(self) -> WindowIterator<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + Iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator> Windows<I> for I {
|
||||||
|
fn windows(self) -> WindowIterator<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + Iterator,
|
||||||
|
{
|
||||||
|
WindowIterator {
|
||||||
|
iterator: self,
|
||||||
|
previous: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user