manuelVo / foundryvtt-routinglib

A Foundry VTT library that allows other modules perform pathfinding within the scene

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Option to limit pathfinding to the last few segments

MaterialFoundry opened this issue · comments

RoutingLib will calculate the shortest distance between the movement's start and end, regardless of the path that the user has taken to go from the start to the end. The result of this is that the calculated path suddenly changes if a shorter route becomes available.

For example, see the attached image, where the token's path is correctly displayed. However, if the token is moved one more step to the right, the calculated path will go anti-clockwise around the rectangle.

This causes 2 issues:

  1. The total moved distance can suddenly decrease compared to the actual token movement (for example, if the player decides to follow the wall clockwise, the distance will decrease although the tokens is actually moving further).
  2. The new path might reveal passageways that the player has not discovered yet (the player might not have been aware that anti-clockwise movement was possible).

What I'd like to suggest is to add an option to limit the pathfinding to the last few segments. If, in the attached image, the first segment is locked (so pathfinding starts from the top-left corner), this issue would be solved. Of course, it isn't as easy as just locking all segments except for the last, but maybe some algorithm could be thought of.

Here's some ideas:

  1. Locking down all segments in the first half of the path if a certain total length is reached.
  2. Looking at the difference between a newly generated path and the previous path, and stick to the old path if the difference is large.
  3. Define a radius around the token. Only segments that are within that radius can change.

image

I've implemented the 3rd idea I mentioned in Material Plane, and I'm quite happy with how it works, so it might be worth looking into implementing something similar into routingLib.

See below for my code.

As you can see in the attached video, in the beginning (standard routingLib behavior), the pathfinding always finds the shortest route. I then set the pathfinding distance to 2 (grid spaces) which will find the shortest route for short distances (such as going around a 1x1 square), but for larger distances the start of the path is locked. Backtracking is possible by moving close enough to the last segment.

2024-03-24.23-08-18.mp4
const pathfindingDistance = game.settings.get(moduleName,'tokenRuler').distance * canvas.grid.grid.options.dimensions.distance;

//variable to store all segments in
let pathSegments = [];

//push all previously locked segments
for (let segment of this.pathFinderSegments) pathSegments.push(segment);

//set the starting coordinate of the pathfinder
if (this.pathFinderStart.x == undefined) this.pathFinderStart = this.origin;

if (pathfindingDistance != 0) {
    //calculate the distance between the current position and the last locked segment
    const distanceFromLastLocked = canvas.grid.measureDistance(position,this.pathFinderStart,{gridSpaces:true});

    //if this distance is small enough remove the last segment from storage and set the starting coordinate. This allows backtracking
    if (distanceFromLastLocked <= pathfindingDistance) {
        const lastSegment = this.pathFinderSegments[this.pathFinderSegments.length-1];
        if (lastSegment != undefined) {
            const coordsArr = canvas.grid.grid.getPixelsFromGridPosition(lastSegment.y, lastSegment.x);
            this.pathFinderStart = {x: coordsArr[0], y: coordsArr[1]};
            this.pathFinderSegments.pop();
        }
    }

    //for all segments that were calculated in the previous pathfinding calculation
    for (let i=0; i<this.pathFinderSegmentsPrevious.length; i++) {
        const segment = this.pathFinderSegmentsPrevious[i];
        const coordsArr = canvas.grid.grid.getPixelsFromGridPosition(segment.y, segment.x);
        const coords = {x: coordsArr[0], y: coordsArr[1]};

        //calculate the distance between the segment and the current position
        const distance = canvas.grid.measureDistance(coords,position,{gridSpaces:true});

        //if the distance is large enough, store all segments up until this point
        if (distance > pathfindingDistance) {
            this.pathFinderStart = coords;
            for (let j=0; j<i; j++) {
                this.pathFinderSegments.push(this.pathFinderSegmentsPrevious[j])
                pathSegments.push(this.pathFinderSegmentsPrevious[j]);
            }
        } 
    }
}

//calculate the new path, starting from the last locked segment
const from = canvas.grid.grid.getGridPositionFromPixels(this.pathFinderStart.x, this.pathFinderStart.y);
const to = canvas.grid.grid.getGridPositionFromPixels(position.x, position.y);
const path = await routinglib.calculatePath({x:from[1],y:from[0]}, {x:to[1],y:to[0]});
this.pathFinderSegmentsPrevious = path.path;

this.ruler.clear();

//push new pathfinder segments to locked segments
for (let segment of path.path) pathSegments.push(segment);

//add waypoints to ruler
for (let segment of pathSegments) {
    const coordsArr = canvas.grid.grid.getPixelsFromGridPosition(segment.y, segment.x);
    const pos = {x: coordsArr[0], y: coordsArr[1]};
    this.ruler._addWaypoint(pos);
}

I'm not sure that I quite understand the issue at hand. If you wanted to lock down certain segments of a path, can't you simply start the pathfinding from the last point of the locked-down path and append the result to the existing segments?

I'm not sure that I quite understand the issue at hand. If you wanted to lock down certain segments of a path, can't you simply start the pathfinding from the last point of the locked-down path and append the result to the existing segments?

That is basically what I did in my previous comment. I just thought it'd be nice to have something like that in the library. But since I've already got it working for my application you can close this if it's not something you're interested in :).

I was just reading through the code and was noticing that that's exactly what happens.

I don't think there is an easy way to integrate this change into routinglib, since the API is stateless and cannot tell whether a calculateRoute call belongs to an existing path or a new one. The only feasible API change I could think of to make this happen is to allow the caller to define a pre-existing set of waypoints that routinglib should work off. Such an API seems dubious to me, however, since routinglib wouldn't do any actual work; it would just ignore that parameter, calculate the route normally and then prepend the passed waypoints to the calculated route.

Makes sense. Maybe this is something you could implement into Drag Ruler, but I'll leave that up to you.
I'll close this issue now, thanks!