123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816 |
- /* *
- * (c) 2016 Highsoft AS
- * Author: Øystein Moseng
- *
- * License: www.highcharts.com/license
- */
- 'use strict';
- import H from '../parts/Globals.js';
- import '../parts/Utilities.js';
- var min = Math.min,
- max = Math.max,
- abs = Math.abs,
- pick = H.pick;
- /**
- * Get index of last obstacle before xMin. Employs a type of binary search, and
- * thus requires that obstacles are sorted by xMin value.
- *
- * @private
- * @function findLastObstacleBefore
- *
- * @param {Array<object>} obstacles
- * Array of obstacles to search in.
- *
- * @param {number} xMin
- * The xMin threshold.
- *
- * @param {number} startIx
- * Starting index to search from. Must be within array range.
- *
- * @return {number}
- * The index of the last obstacle element before xMin.
- */
- function findLastObstacleBefore(obstacles, xMin, startIx) {
- var left = startIx || 0, // left limit
- right = obstacles.length - 1, // right limit
- min = xMin - 0.0000001, // Make sure we include all obstacles at xMin
- cursor,
- cmp;
- while (left <= right) {
- cursor = (right + left) >> 1;
- cmp = min - obstacles[cursor].xMin;
- if (cmp > 0) {
- left = cursor + 1;
- } else if (cmp < 0) {
- right = cursor - 1;
- } else {
- return cursor;
- }
- }
- return left > 0 ? left - 1 : 0;
- }
- /**
- * Test if a point lays within an obstacle.
- *
- * @private
- * @function pointWithinObstacle
- *
- * @param {object} obstacle
- * Obstacle to test.
- *
- * @param {Highcharts.Point} point
- * Point with x/y props.
- *
- * @return {boolean}
- * Whether point is within the obstacle or not.
- */
- function pointWithinObstacle(obstacle, point) {
- return (
- point.x <= obstacle.xMax &&
- point.x >= obstacle.xMin &&
- point.y <= obstacle.yMax &&
- point.y >= obstacle.yMin
- );
- }
- /**
- * Find the index of an obstacle that wraps around a point.
- * Returns -1 if not found.
- *
- * @private
- * @function findObstacleFromPoint
- *
- * @param {Array<object>} obstacles
- * Obstacles to test.
- *
- * @param {Highcharts.Point} point
- * Point with x/y props.
- *
- * @return {number}
- * Ix of the obstacle in the array, or -1 if not found.
- */
- function findObstacleFromPoint(obstacles, point) {
- var i = findLastObstacleBefore(obstacles, point.x + 1) + 1;
- while (i--) {
- if (obstacles[i].xMax >= point.x &&
- // optimization using lazy evaluation
- pointWithinObstacle(obstacles[i], point)) {
- return i;
- }
- }
- return -1;
- }
- /**
- * Get SVG path array from array of line segments.
- *
- * @private
- * @function pathFromSegments
- *
- * @param {Array<object>} segments
- * The segments to build the path from.
- *
- * @return {Highcharts.SVGPathArray}
- * SVG path array as accepted by the SVG Renderer.
- */
- function pathFromSegments(segments) {
- var path = [];
- if (segments.length) {
- path.push('M', segments[0].start.x, segments[0].start.y);
- for (var i = 0; i < segments.length; ++i) {
- path.push('L', segments[i].end.x, segments[i].end.y);
- }
- }
- return path;
- }
- /**
- * Limits obstacle max/mins in all directions to bounds. Modifies input
- * obstacle.
- *
- * @private
- * @function limitObstacleToBounds
- *
- * @param {object} obstacle
- * Obstacle to limit.
- *
- * @param {object} bounds
- * Bounds to use as limit.
- */
- function limitObstacleToBounds(obstacle, bounds) {
- obstacle.yMin = max(obstacle.yMin, bounds.yMin);
- obstacle.yMax = min(obstacle.yMax, bounds.yMax);
- obstacle.xMin = max(obstacle.xMin, bounds.xMin);
- obstacle.xMax = min(obstacle.xMax, bounds.xMax);
- }
- // Define the available pathfinding algorithms.
- // Algorithms take up to 3 arguments: starting point, ending point, and an
- // options object.
- var algorithms = {
- /**
- * Get an SVG path from a starting coordinate to an ending coordinate.
- * Draws a straight line.
- *
- * @function Highcharts.Pathfinder.algorithms.straight
- *
- * @param {object} start
- * Starting coordinate, object with x/y props.
- *
- * @param {object} end
- * Ending coordinate, object with x/y props.
- *
- * @return {object}
- * An object with the SVG path in Array form as accepted by the SVG
- * renderer, as well as an array of new obstacles making up this
- * path.
- */
- straight: function (start, end) {
- return {
- path: ['M', start.x, start.y, 'L', end.x, end.y],
- obstacles: [{ start: start, end: end }]
- };
- },
- /**
- * Find a path from a starting coordinate to an ending coordinate, using
- * right angles only, and taking only starting/ending obstacle into
- * consideration.
- *
- * @function Highcharts.Pathfinder.algorithms.simpleConnect
- *
- * @param {object} start
- * Starting coordinate, object with x/y props.
- *
- * @param {object} end
- * Ending coordinate, object with x/y props.
- *
- * @param {object} options
- * Options for the algorithm:
- * - chartObstacles: Array of chart obstacles to avoid
- * - startDirectionX: Optional. True if starting in the X direction.
- * If not provided, the algorithm starts in the direction that is
- * the furthest between start/end.
- *
- * @return {object}
- * An object with the SVG path in Array form as accepted by the SVG
- * renderer, as well as an array of new obstacles making up this
- * path.
- */
- simpleConnect: H.extend(function (start, end, options) {
- var segments = [],
- endSegment,
- dir = pick(
- options.startDirectionX,
- abs(end.x - start.x) > abs(end.y - start.y)
- ) ? 'x' : 'y',
- chartObstacles = options.chartObstacles,
- startObstacleIx = findObstacleFromPoint(chartObstacles, start),
- endObstacleIx = findObstacleFromPoint(chartObstacles, end),
- startObstacle,
- endObstacle,
- prevWaypoint,
- waypoint,
- waypoint2,
- useMax,
- endPoint;
- // Return a clone of a point with a property set from a target object,
- // optionally with an offset
- function copyFromPoint(from, fromKey, to, toKey, offset) {
- var point = {
- x: from.x,
- y: from.y
- };
- point[fromKey] = to[toKey || fromKey] + (offset || 0);
- return point;
- }
- // Return waypoint outside obstacle
- function getMeOut(obstacle, point, direction) {
- var useMax = abs(point[direction] - obstacle[direction + 'Min']) >
- abs(point[direction] - obstacle[direction + 'Max']);
- return copyFromPoint(
- point,
- direction,
- obstacle,
- direction + (useMax ? 'Max' : 'Min'),
- useMax ? 1 : -1
- );
- }
- // Pull out end point
- if (endObstacleIx > -1) {
- endObstacle = chartObstacles[endObstacleIx];
- waypoint = getMeOut(endObstacle, end, dir);
- endSegment = {
- start: waypoint,
- end: end
- };
- endPoint = waypoint;
- } else {
- endPoint = end;
- }
- // If an obstacle envelops the start point, add a segment to get out,
- // and around it.
- if (startObstacleIx > -1) {
- startObstacle = chartObstacles[startObstacleIx];
- waypoint = getMeOut(startObstacle, start, dir);
- segments.push({
- start: start,
- end: waypoint
- });
- // If we are going back again, switch direction to get around start
- // obstacle.
- if (
- waypoint[dir] > start[dir] === // Going towards max from start
- waypoint[dir] > endPoint[dir] // Going towards min to end
- ) {
- dir = dir === 'y' ? 'x' : 'y';
- useMax = start[dir] < end[dir];
- segments.push({
- start: waypoint,
- end: copyFromPoint(
- waypoint,
- dir,
- startObstacle,
- dir + (useMax ? 'Max' : 'Min'),
- useMax ? 1 : -1
- )
- });
- // Switch direction again
- dir = dir === 'y' ? 'x' : 'y';
- }
- }
- // We are around the start obstacle. Go towards the end in one
- // direction.
- prevWaypoint = segments.length ?
- segments[segments.length - 1].end :
- start;
- waypoint = copyFromPoint(prevWaypoint, dir, endPoint);
- segments.push({
- start: prevWaypoint,
- end: waypoint
- });
- // Final run to end point in the other direction
- dir = dir === 'y' ? 'x' : 'y';
- waypoint2 = copyFromPoint(waypoint, dir, endPoint);
- segments.push({
- start: waypoint,
- end: waypoint2
- });
- // Finally add the endSegment
- segments.push(endSegment);
- return {
- path: pathFromSegments(segments),
- obstacles: segments
- };
- }, {
- requiresObstacles: true
- }),
- /**
- * Find a path from a starting coordinate to an ending coordinate, taking
- * obstacles into consideration. Might not always find the optimal path,
- * but is fast, and usually good enough.
- *
- * @function Highcharts.Pathfinder.algorithms.fastAvoid
- *
- * @param {object} start
- * Starting coordinate, object with x/y props.
- *
- * @param {object} end
- * Ending coordinate, object with x/y props.
- *
- * @param {object} options
- * Options for the algorithm.
- * - chartObstacles: Array of chart obstacles to avoid
- * - lineObstacles: Array of line obstacles to jump over
- * - obstacleMetrics: Object with metrics of chartObstacles cached
- * - hardBounds: Hard boundaries to not cross
- * - obstacleOptions: Options for the obstacles, including margin
- * - startDirectionX: Optional. True if starting in the X direction.
- * If not provided, the algorithm starts in the
- * direction that is the furthest between
- * start/end.
- *
- * @return {object}
- * An object with the SVG path in Array form as accepted by the SVG
- * renderer, as well as an array of new obstacles making up this
- * path.
- */
- fastAvoid: H.extend(function (start, end, options) {
- /*
- Algorithm rules/description
- - Find initial direction
- - Determine soft/hard max for each direction.
- - Move along initial direction until obstacle.
- - Change direction.
- - If hitting obstacle, first try to change length of previous line
- before changing direction again.
- Soft min/max x = start/destination x +/- widest obstacle + margin
- Soft min/max y = start/destination y +/- tallest obstacle + margin
- @todo:
- - Make retrospective, try changing prev segment to reduce
- corners
- - Fix logic for breaking out of end-points - not always picking
- the best direction currently
- - When going around the end obstacle we should not always go the
- shortest route, rather pick the one closer to the end point
- */
- var dirIsX = pick(
- options.startDirectionX,
- abs(end.x - start.x) > abs(end.y - start.y)
- ),
- dir = dirIsX ? 'x' : 'y',
- segments,
- useMax,
- extractedEndPoint,
- endSegments = [],
- forceObstacleBreak = false, // Used in clearPathTo to keep track of
- // when to force break through an obstacle.
- // Boundaries to stay within. If beyond soft boundary, prefer to
- // change direction ASAP. If at hard max, always change immediately.
- metrics = options.obstacleMetrics,
- softMinX = min(start.x, end.x) - metrics.maxWidth - 10,
- softMaxX = max(start.x, end.x) + metrics.maxWidth + 10,
- softMinY = min(start.y, end.y) - metrics.maxHeight - 10,
- softMaxY = max(start.y, end.y) + metrics.maxHeight + 10,
- // Obstacles
- chartObstacles = options.chartObstacles,
- startObstacleIx = findLastObstacleBefore(chartObstacles, softMinX),
- endObstacleIx = findLastObstacleBefore(chartObstacles, softMaxX);
- // How far can you go between two points before hitting an obstacle?
- // Does not work for diagonal lines (because it doesn't have to).
- function pivotPoint(fromPoint, toPoint, directionIsX) {
- var firstPoint,
- lastPoint,
- highestPoint,
- lowestPoint,
- i,
- searchDirection = fromPoint.x < toPoint.x ? 1 : -1;
- if (fromPoint.x < toPoint.x) {
- firstPoint = fromPoint;
- lastPoint = toPoint;
- } else {
- firstPoint = toPoint;
- lastPoint = fromPoint;
- }
- if (fromPoint.y < toPoint.y) {
- lowestPoint = fromPoint;
- highestPoint = toPoint;
- } else {
- lowestPoint = toPoint;
- highestPoint = fromPoint;
- }
- // Go through obstacle range in reverse if toPoint is before
- // fromPoint in the X-dimension.
- i = searchDirection < 0 ?
- // Searching backwards, start at last obstacle before last point
- min(findLastObstacleBefore(chartObstacles, lastPoint.x),
- chartObstacles.length - 1) :
- // Forwards. Since we're not sorted by xMax, we have to look
- // at all obstacles.
- 0;
- // Go through obstacles in this X range
- while (chartObstacles[i] && (
- searchDirection > 0 && chartObstacles[i].xMin <= lastPoint.x ||
- searchDirection < 0 && chartObstacles[i].xMax >= firstPoint.x
- )) {
- // If this obstacle is between from and to points in a straight
- // line, pivot at the intersection.
- if (
- chartObstacles[i].xMin <= lastPoint.x &&
- chartObstacles[i].xMax >= firstPoint.x &&
- chartObstacles[i].yMin <= highestPoint.y &&
- chartObstacles[i].yMax >= lowestPoint.y
- ) {
- if (directionIsX) {
- return {
- y: fromPoint.y,
- x: fromPoint.x < toPoint.x ?
- chartObstacles[i].xMin - 1 :
- chartObstacles[i].xMax + 1,
- obstacle: chartObstacles[i]
- };
- }
- // else ...
- return {
- x: fromPoint.x,
- y: fromPoint.y < toPoint.y ?
- chartObstacles[i].yMin - 1 :
- chartObstacles[i].yMax + 1,
- obstacle: chartObstacles[i]
- };
- }
- i += searchDirection;
- }
- return toPoint;
- }
- /**
- * Decide in which direction to dodge or get out of an obstacle.
- * Considers desired direction, which way is shortest, soft and hard
- * bounds.
- *
- * (? Returns a string, either xMin, xMax, yMin or yMax.)
- *
- * @private
- * @function
- *
- * @param {object} obstacle
- * Obstacle to dodge/escape.
- *
- * @param {object} fromPoint
- * Point with x/y props that's dodging/escaping.
- *
- * @param {object} toPoint
- * Goal point.
- *
- * @param {boolean} dirIsX
- * Dodge in X dimension.
- *
- * @param {object} bounds
- * Hard and soft boundaries.
- *
- * @return {boolean}
- * Use max or not.
- */
- function getDodgeDirection(
- obstacle,
- fromPoint,
- toPoint,
- dirIsX,
- bounds
- ) {
- var softBounds = bounds.soft,
- hardBounds = bounds.hard,
- dir = dirIsX ? 'x' : 'y',
- toPointMax = { x: fromPoint.x, y: fromPoint.y },
- toPointMin = { x: fromPoint.x, y: fromPoint.y },
- minPivot,
- maxPivot,
- maxOutOfSoftBounds = obstacle[dir + 'Max'] >=
- softBounds[dir + 'Max'],
- minOutOfSoftBounds = obstacle[dir + 'Min'] <=
- softBounds[dir + 'Min'],
- maxOutOfHardBounds = obstacle[dir + 'Max'] >=
- hardBounds[dir + 'Max'],
- minOutOfHardBounds = obstacle[dir + 'Min'] <=
- hardBounds[dir + 'Min'],
- // Find out if we should prefer one direction over the other if
- // we can choose freely
- minDistance = abs(obstacle[dir + 'Min'] - fromPoint[dir]),
- maxDistance = abs(obstacle[dir + 'Max'] - fromPoint[dir]),
- // If it's a small difference, pick the one leading towards dest
- // point. Otherwise pick the shortest distance
- useMax = abs(minDistance - maxDistance) < 10 ?
- fromPoint[dir] < toPoint[dir] :
- maxDistance < minDistance;
- // Check if we hit any obstacles trying to go around in either
- // direction.
- toPointMin[dir] = obstacle[dir + 'Min'];
- toPointMax[dir] = obstacle[dir + 'Max'];
- minPivot = pivotPoint(fromPoint, toPointMin, dirIsX)[dir] !==
- toPointMin[dir];
- maxPivot = pivotPoint(fromPoint, toPointMax, dirIsX)[dir] !==
- toPointMax[dir];
- useMax = minPivot ?
- (maxPivot ? useMax : true) :
- (maxPivot ? false : useMax);
- // useMax now contains our preferred choice, bounds not taken into
- // account. If both or neither direction is out of bounds we want to
- // use this.
- // Deal with soft bounds
- useMax = minOutOfSoftBounds ?
- (maxOutOfSoftBounds ? useMax : true) : // Out on min
- (maxOutOfSoftBounds ? false : useMax); // Not out on min
- // Deal with hard bounds
- useMax = minOutOfHardBounds ?
- (maxOutOfHardBounds ? useMax : true) : // Out on min
- (maxOutOfHardBounds ? false : useMax); // Not out on min
- return useMax;
- }
- // Find a clear path between point
- function clearPathTo(fromPoint, toPoint, dirIsX) {
- // Don't waste time if we've hit goal
- if (fromPoint.x === toPoint.x && fromPoint.y === toPoint.y) {
- return [];
- }
- var dir = dirIsX ? 'x' : 'y',
- pivot,
- segments,
- waypoint,
- waypointUseMax,
- envelopingObstacle,
- secondEnvelopingObstacle,
- envelopWaypoint,
- obstacleMargin = options.obstacleOptions.margin,
- bounds = {
- soft: {
- xMin: softMinX,
- xMax: softMaxX,
- yMin: softMinY,
- yMax: softMaxY
- },
- hard: options.hardBounds
- };
- // If fromPoint is inside an obstacle we have a problem. Break out
- // by just going to the outside of this obstacle. We prefer to go to
- // the nearest edge in the chosen direction.
- envelopingObstacle =
- findObstacleFromPoint(chartObstacles, fromPoint);
- if (envelopingObstacle > -1) {
- envelopingObstacle = chartObstacles[envelopingObstacle];
- waypointUseMax = getDodgeDirection(
- envelopingObstacle, fromPoint, toPoint, dirIsX, bounds
- );
- // Cut obstacle to hard bounds to make sure we stay within
- limitObstacleToBounds(envelopingObstacle, options.hardBounds);
- envelopWaypoint = dirIsX ? {
- y: fromPoint.y,
- x: envelopingObstacle[waypointUseMax ? 'xMax' : 'xMin'] +
- (waypointUseMax ? 1 : -1)
- } : {
- x: fromPoint.x,
- y: envelopingObstacle[waypointUseMax ? 'yMax' : 'yMin'] +
- (waypointUseMax ? 1 : -1)
- };
- // If we crashed into another obstacle doing this, we put the
- // waypoint between them instead
- secondEnvelopingObstacle = findObstacleFromPoint(
- chartObstacles, envelopWaypoint
- );
- if (secondEnvelopingObstacle > -1) {
- secondEnvelopingObstacle = chartObstacles[
- secondEnvelopingObstacle
- ];
- // Cut obstacle to hard bounds
- limitObstacleToBounds(
- secondEnvelopingObstacle,
- options.hardBounds
- );
- // Modify waypoint to lay between obstacles
- envelopWaypoint[dir] = waypointUseMax ? max(
- envelopingObstacle[dir + 'Max'] - obstacleMargin + 1,
- (
- secondEnvelopingObstacle[dir + 'Min'] +
- envelopingObstacle[dir + 'Max']
- ) / 2
- ) :
- min((
- envelopingObstacle[dir + 'Min'] + obstacleMargin - 1
- ), (
- (
- secondEnvelopingObstacle[dir + 'Max'] +
- envelopingObstacle[dir + 'Min']
- ) / 2
- ));
- // We are not going anywhere. If this happens for the first
- // time, do nothing. Otherwise, try to go to the extreme of
- // the obstacle pair in the current direction.
- if (fromPoint.x === envelopWaypoint.x &&
- fromPoint.y === envelopWaypoint.y) {
- if (forceObstacleBreak) {
- envelopWaypoint[dir] = waypointUseMax ?
- max(
- envelopingObstacle[dir + 'Max'],
- secondEnvelopingObstacle[dir + 'Max']
- ) + 1 :
- min(
- envelopingObstacle[dir + 'Min'],
- secondEnvelopingObstacle[dir + 'Min']
- ) - 1;
- }
- // Toggle on if off, and the opposite
- forceObstacleBreak = !forceObstacleBreak;
- } else {
- // This point is not identical to previous.
- // Clear break trigger.
- forceObstacleBreak = false;
- }
- }
- segments = [{
- start: fromPoint,
- end: envelopWaypoint
- }];
- } else { // If not enveloping, use standard pivot calculation
- pivot = pivotPoint(fromPoint, {
- x: dirIsX ? toPoint.x : fromPoint.x,
- y: dirIsX ? fromPoint.y : toPoint.y
- }, dirIsX);
- segments = [{
- start: fromPoint,
- end: {
- x: pivot.x,
- y: pivot.y
- }
- }];
- // Pivot before goal, use a waypoint to dodge obstacle
- if (pivot[dirIsX ? 'x' : 'y'] !== toPoint[dirIsX ? 'x' : 'y']) {
- // Find direction of waypoint
- waypointUseMax = getDodgeDirection(
- pivot.obstacle, pivot, toPoint, !dirIsX, bounds
- );
- // Cut waypoint to hard bounds
- limitObstacleToBounds(pivot.obstacle, options.hardBounds);
- waypoint = {
- x: dirIsX ?
- pivot.x :
- pivot.obstacle[waypointUseMax ? 'xMax' : 'xMin'] +
- (waypointUseMax ? 1 : -1),
- y: dirIsX ?
- pivot.obstacle[waypointUseMax ? 'yMax' : 'yMin'] +
- (waypointUseMax ? 1 : -1) :
- pivot.y
- };
- // We're changing direction here, store that to make sure we
- // also change direction when adding the last segment array
- // after handling waypoint.
- dirIsX = !dirIsX;
- segments = segments.concat(clearPathTo({
- x: pivot.x,
- y: pivot.y
- }, waypoint, dirIsX));
- }
- }
- // Get segments for the other direction too
- // Recursion is our friend
- segments = segments.concat(clearPathTo(
- segments[segments.length - 1].end, toPoint, !dirIsX
- ));
- return segments;
- }
- // Extract point to outside of obstacle in whichever direction is
- // closest. Returns new point outside obstacle.
- function extractFromObstacle(obstacle, point, goalPoint) {
- var dirIsX = min(obstacle.xMax - point.x, point.x - obstacle.xMin) <
- min(obstacle.yMax - point.y, point.y - obstacle.yMin),
- bounds = {
- soft: options.hardBounds,
- hard: options.hardBounds
- },
- useMax = getDodgeDirection(
- obstacle, point, goalPoint, dirIsX, bounds
- );
- return dirIsX ? {
- y: point.y,
- x: obstacle[useMax ? 'xMax' : 'xMin'] + (useMax ? 1 : -1)
- } : {
- x: point.x,
- y: obstacle[useMax ? 'yMax' : 'yMin'] + (useMax ? 1 : -1)
- };
- }
- // Cut the obstacle array to soft bounds for optimization in large
- // datasets.
- chartObstacles =
- chartObstacles.slice(startObstacleIx, endObstacleIx + 1);
- // If an obstacle envelops the end point, move it out of there and add
- // a little segment to where it was.
- if ((endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1) {
- extractedEndPoint = extractFromObstacle(
- chartObstacles[endObstacleIx],
- end,
- start
- );
- endSegments.push({
- end: end,
- start: extractedEndPoint
- });
- end = extractedEndPoint;
- }
- // If it's still inside one or more obstacles, get out of there by
- // force-moving towards the start point.
- while (
- (endObstacleIx = findObstacleFromPoint(chartObstacles, end)) > -1
- ) {
- useMax = end[dir] - start[dir] < 0;
- extractedEndPoint = {
- x: end.x,
- y: end.y
- };
- extractedEndPoint[dir] = chartObstacles[endObstacleIx][
- useMax ? dir + 'Max' : dir + 'Min'
- ] + (useMax ? 1 : -1);
- endSegments.push({
- end: end,
- start: extractedEndPoint
- });
- end = extractedEndPoint;
- }
- // Find the path
- segments = clearPathTo(start, end, dirIsX);
- // Add the end-point segments
- segments = segments.concat(endSegments.reverse());
- return {
- path: pathFromSegments(segments),
- obstacles: segments
- };
- }, {
- requiresObstacles: true
- })
- };
- export default algorithms;
|