Files
GDevelop/Extensions/PathfindingBehavior/PathTools.ts

165 lines
5.9 KiB
TypeScript

namespace gdjs {
/** @category Behaviors > 2D Pathfinding */
export namespace pathfinding {
/**
* Simplify a path according to an allowed gap.
*
* The simplified path vertices are the same instances as the one in
* the source. They must be cloned to make them truly independent from each
* other.
*
* @param sourceVertices The path to simplify.
* @param maxGap The maximum distance the edge of the contour may deviate
* from the source geometry.
* @param simplifiedVertices The simplified path.
* @param workingVertices It avoids allocations.
*/
export const simplifyPath = (
sourceVertices: FloatPoint[],
maxGap: float,
simplifiedVertices: FloatPoint[] = [],
workingVertices: FloatPoint[] = []
): FloatPoint[] => {
if (sourceVertices.length <= 2) {
simplifiedVertices.length = 0;
simplifiedVertices.push.apply(simplifiedVertices, sourceVertices);
return simplifiedVertices;
}
const maxGapSq = maxGap * maxGap;
// We start with only one rope part.
// Stretch a rope between the start and the end of the path.
let previousStepVertices: FloatPoint[] = workingVertices;
previousStepVertices.length = 0;
previousStepVertices.push(sourceVertices[0]);
previousStepVertices.push(sourceVertices[sourceVertices.length - 1]);
do {
simplifiedVertices.length = 0;
simplifiedVertices.push(previousStepVertices[0]);
// For each part of the rope...
let sourceIndex = 0;
for (
let previousStepVerticesIndex = 0;
previousStepVerticesIndex + 1 < previousStepVertices.length;
previousStepVerticesIndex++
) {
const startVertex = previousStepVertices[previousStepVerticesIndex];
const endVertex = previousStepVertices[previousStepVerticesIndex + 1];
const startX = startVertex[0];
const startY = startVertex[1];
const endX = endVertex[0];
const endY = endVertex[1];
// Search the furthest vertex from the rope part.
let maxDeviationSq = maxGapSq;
let maxDeviationVertex: FloatPoint | null = null;
// The first and last vertices of the rope part are not checked.
for (
sourceIndex++;
sourceVertices[sourceIndex] !== endVertex;
sourceIndex++
) {
const sourceVertex = sourceVertices[sourceIndex];
const deviationSq = gdjs.pathfinding.getPointSegmentDistanceSq(
sourceVertex[0],
sourceVertex[1],
startX,
startY,
endX,
endY
);
if (deviationSq > maxDeviationSq) {
maxDeviationSq = deviationSq;
maxDeviationVertex = sourceVertex;
}
}
// Add the furthest vertex to the rope.
// The current rope part is split in 2 for the next step.
if (maxDeviationVertex) {
simplifiedVertices.push(maxDeviationVertex);
}
simplifiedVertices.push(endVertex);
}
const swapVertices = previousStepVertices;
previousStepVertices = simplifiedVertices;
simplifiedVertices = swapVertices;
} while (
// Stop when no new vertex were added.
// It means that the maxGap constraint is fulfilled.
// Otherwise, iterate over the full path once more.
simplifiedVertices.length !== previousStepVertices.length
);
return simplifiedVertices;
};
/**
* Returns the distance squared from the point to the line segment.
*
* Behavior is undefined if the the closest distance is outside the
* line segment.
*
* @param px The X position of point (px, py).
* @param py The Y position of point (px, py)
* @param ax The X position of the line segment's vertex A.
* @param ay The Y position of the line segment's vertex A.
* @param bx The X position of the line segment's vertex B.
* @param by The Y position of the line segment's vertex B.
* @return The distance squared from the point (px, py) to line segment AB.
*/
export const getPointSegmentDistanceSq = (
px: float,
py: float,
ax: float,
ay: float,
bx: float,
by: float
): float => {
// This implementation is strongly inspired from CritterAI class "Geometry".
//
// Reference: http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/
//
// The goal of the algorithm is to find the point on line segment AB
// that is closest to P and then calculate the distance between P
// and that point.
const deltaABx = bx - ax;
const deltaABy = by - ay;
const deltaAPx = px - ax;
const deltaAPy = py - ay;
const segmentABLengthSq = deltaABx * deltaABx + deltaABy * deltaABy;
if (segmentABLengthSq === 0) {
// AB is not a line segment. So just return
// distanceSq from P to A
return deltaAPx * deltaAPx + deltaAPy * deltaAPy;
}
const u = (deltaAPx * deltaABx + deltaAPy * deltaABy) / segmentABLengthSq;
if (u < 0) {
// Closest point on line AB is outside outside segment AB and
// closer to A. So return distanceSq from P to A.
return deltaAPx * deltaAPx + deltaAPy * deltaAPy;
} else if (u > 1) {
// Closest point on line AB is outside segment AB and closer to B.
// So return distanceSq from P to B.
return (px - bx) * (px - bx) + (py - by) * (py - by);
}
// Closest point on lineAB is inside segment AB. So find the exact
// point on AB and calculate the distanceSq from it to P.
// The calculation in parenthesis is the location of the point on
// the line segment.
const deltaX = ax + u * deltaABx - px;
const deltaY = ay + u * deltaABy - py;
return deltaX * deltaX + deltaY * deltaY;
};
}
}