123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- import { FunctionExt } from '@antv/x6-common';
- import { Point, Rectangle } from '@antv/x6-geometry';
- import { SortedSet } from './sorted-set';
- import { ObstacleMap } from './obstacle-map';
- import * as util from './util';
- import { resolveOptions, } from './options';
- /**
- * Finds the route between two points (`from`, `to`).
- */
- function findRoute(edgeView, from, to, map, options) {
- const precision = options.precision;
- let sourceEndpoint;
- let targetEndpoint;
- if (Rectangle.isRectangle(from)) {
- sourceEndpoint = util.round(util.getSourceEndpoint(edgeView, options).clone(), precision);
- }
- else {
- sourceEndpoint = util.round(from.clone(), precision);
- }
- if (Rectangle.isRectangle(to)) {
- targetEndpoint = util.round(util.getTargetEndpoint(edgeView, options).clone(), precision);
- }
- else {
- targetEndpoint = util.round(to.clone(), precision);
- }
- // Get grid for this route.
- const grid = util.getGrid(options.step, sourceEndpoint, targetEndpoint);
- // Get pathfinding points.
- // -----------------------
- const startPoint = sourceEndpoint;
- const endPoint = targetEndpoint;
- let startPoints;
- let endPoints;
- if (Rectangle.isRectangle(from)) {
- startPoints = util.getRectPoints(startPoint, from, options.startDirections, grid, options);
- }
- else {
- startPoints = [startPoint];
- }
- if (Rectangle.isRectangle(to)) {
- endPoints = util.getRectPoints(targetEndpoint, to, options.endDirections, grid, options);
- }
- else {
- endPoints = [endPoint];
- }
- // take into account only accessible rect points (those not under obstacles)
- startPoints = startPoints.filter((p) => map.isAccessible(p));
- endPoints = endPoints.filter((p) => map.isAccessible(p));
- // There is an accessible route point on both sides.
- if (startPoints.length > 0 && endPoints.length > 0) {
- const openSet = new SortedSet();
- // Keeps the actual points for given nodes of the open set.
- const points = {};
- // Keeps the point that is immediate predecessor of given element.
- const parents = {};
- // Cost from start to a point along best known path.
- const costs = {};
- for (let i = 0, n = startPoints.length; i < n; i += 1) {
- // startPoint is assumed to be aligned already
- const startPoint = startPoints[i];
- const key = util.getKey(startPoint);
- openSet.add(key, util.getCost(startPoint, endPoints));
- points[key] = startPoint;
- costs[key] = 0;
- }
- const previousRouteDirectionAngle = options.previousDirectionAngle;
- // undefined for first route
- const isPathBeginning = previousRouteDirectionAngle === undefined;
- // directions
- let direction;
- let directionChange;
- const directions = util.getGridOffsets(grid, options);
- const numDirections = directions.length;
- const endPointsKeys = endPoints.reduce((res, endPoint) => {
- const key = util.getKey(endPoint);
- res.push(key);
- return res;
- }, []);
- // main route finding loop
- const sameStartEndPoints = Point.equalPoints(startPoints, endPoints);
- let loopsRemaining = options.maxLoopCount;
- while (!openSet.isEmpty() && loopsRemaining > 0) {
- // Get the closest item and mark it CLOSED
- const currentKey = openSet.pop();
- const currentPoint = points[currentKey];
- const currentParent = parents[currentKey];
- const currentCost = costs[currentKey];
- const isStartPoint = currentPoint.equals(startPoint);
- const isRouteBeginning = currentParent == null;
- let previousDirectionAngle;
- if (!isRouteBeginning) {
- previousDirectionAngle = util.getDirectionAngle(currentParent, currentPoint, numDirections, grid, options);
- }
- else if (!isPathBeginning) {
- // a vertex on the route
- previousDirectionAngle = previousRouteDirectionAngle;
- }
- else if (!isStartPoint) {
- // beginning of route on the path
- previousDirectionAngle = util.getDirectionAngle(startPoint, currentPoint, numDirections, grid, options);
- }
- else {
- previousDirectionAngle = null;
- }
- // Check if we reached any endpoint
- const skipEndCheck = isRouteBeginning && sameStartEndPoints;
- if (!skipEndCheck && endPointsKeys.indexOf(currentKey) >= 0) {
- options.previousDirectionAngle = previousDirectionAngle;
- return util.reconstructRoute(parents, points, currentPoint, startPoint, endPoint);
- }
- // Go over all possible directions and find neighbors
- for (let i = 0; i < numDirections; i += 1) {
- direction = directions[i];
- const directionAngle = direction.angle;
- directionChange = util.getDirectionChange(previousDirectionAngle, directionAngle);
- // Don't use the point changed rapidly.
- if (!(isPathBeginning && isStartPoint) &&
- directionChange > options.maxDirectionChange) {
- continue;
- }
- const neighborPoint = util.align(currentPoint
- .clone()
- .translate(direction.gridOffsetX || 0, direction.gridOffsetY || 0), grid, precision);
- const neighborKey = util.getKey(neighborPoint);
- // Closed points were already evaluated.
- if (openSet.isClose(neighborKey) || !map.isAccessible(neighborPoint)) {
- continue;
- }
- // Neighbor is an end point.
- if (endPointsKeys.indexOf(neighborKey) >= 0) {
- const isEndPoint = neighborPoint.equals(endPoint);
- if (!isEndPoint) {
- const endDirectionAngle = util.getDirectionAngle(neighborPoint, endPoint, numDirections, grid, options);
- const endDirectionChange = util.getDirectionChange(directionAngle, endDirectionAngle);
- if (endDirectionChange > options.maxDirectionChange) {
- continue;
- }
- }
- }
- // The current direction is ok.
- // ----------------------------
- const neighborCost = direction.cost;
- const neighborPenalty = isStartPoint
- ? 0
- : options.penalties[directionChange];
- const costFromStart = currentCost + neighborCost + neighborPenalty;
- // Neighbor point has not been processed yet or the cost of
- // the path from start is lower than previously calculated.
- if (!openSet.isOpen(neighborKey) ||
- costFromStart < costs[neighborKey]) {
- points[neighborKey] = neighborPoint;
- parents[neighborKey] = currentPoint;
- costs[neighborKey] = costFromStart;
- openSet.add(neighborKey, costFromStart + util.getCost(neighborPoint, endPoints));
- }
- }
- loopsRemaining -= 1;
- }
- }
- if (options.fallbackRoute) {
- return FunctionExt.call(options.fallbackRoute, this, startPoint, endPoint, options);
- }
- return null;
- }
- function snap(vertices, gridSize = 10) {
- if (vertices.length <= 1) {
- return vertices;
- }
- for (let i = 0, len = vertices.length; i < len - 1; i += 1) {
- const first = vertices[i];
- const second = vertices[i + 1];
- if (first.x === second.x) {
- const x = gridSize * Math.round(first.x / gridSize);
- if (first.x !== x) {
- first.x = x;
- second.x = x;
- }
- }
- else if (first.y === second.y) {
- const y = gridSize * Math.round(first.y / gridSize);
- if (first.y !== y) {
- first.y = y;
- second.y = y;
- }
- }
- }
- return vertices;
- }
- export const router = function (vertices, optionsRaw, edgeView) {
- const options = resolveOptions(optionsRaw);
- const sourceBBox = util.getSourceBBox(edgeView, options);
- const targetBBox = util.getTargetBBox(edgeView, options);
- const sourceEndpoint = util.getSourceEndpoint(edgeView, options);
- // pathfinding
- const map = new ObstacleMap(options).build(edgeView.graph.model, edgeView.cell);
- const oldVertices = vertices.map((p) => Point.create(p));
- const newVertices = [];
- // The origin of first route's grid, does not need snapping
- let tailPoint = sourceEndpoint;
- let from;
- let to;
- for (let i = 0, len = oldVertices.length; i <= len; i += 1) {
- let partialRoute = null;
- from = to || sourceBBox;
- to = oldVertices[i];
- // This is the last iteration
- if (to == null) {
- to = targetBBox;
- // If the target is a point, we should use dragging route
- // instead of main routing method if it has been provided.
- const edge = edgeView.cell;
- const isEndingAtPoint = edge.getSourceCellId() == null || edge.getTargetCellId() == null;
- if (isEndingAtPoint && typeof options.draggingRouter === 'function') {
- const dragFrom = from === sourceBBox ? sourceEndpoint : from;
- const dragTo = to.getOrigin();
- partialRoute = FunctionExt.call(options.draggingRouter, edgeView, dragFrom, dragTo, options);
- }
- }
- // Find the partial route
- if (partialRoute == null) {
- partialRoute = findRoute(edgeView, from, to, map, options);
- }
- // Cannot found the partial route.
- if (partialRoute === null) {
- // eslint-next-line
- console.warn(`Unable to execute manhattan algorithm, use orth instead`);
- return FunctionExt.call(options.fallbackRouter, this, vertices, options, edgeView);
- }
- // Remove the first point if the previous partial route has
- // the same point as last.
- const leadPoint = partialRoute[0];
- if (leadPoint && leadPoint.equals(tailPoint)) {
- partialRoute.shift();
- }
- // Save tailPoint for next iteration
- tailPoint = partialRoute[partialRoute.length - 1] || tailPoint;
- newVertices.push(...partialRoute);
- }
- if (options.snapToGrid) {
- return snap(newVertices, edgeView.graph.grid.getGridSize());
- }
- return newVertices;
- };
- //# sourceMappingURL=router.js.map
|