mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(tours): lake legs keep off the shore instead of hugging it one cell out
The lake cost only added half again within 12 m of the bank, so the cheapest line round a bend ran one grid cell off the water's edge (7 to 19 m on the big lakes), and pulling it straight could draw it over land. A clearance ramp now makes the water nearer than 80 m (40 m along the shore) steadily dearer, which also centres the route in anything narrower than twice that, the shore style rides a band 100 to 350 m out instead of the bank itself, and a shortcut may not come nearer the shore than the path it replaces. Measured on nine lakes: no sample on land (was up to two), direct keeps 51 to 78 m at its closest (was one cell), along the shore the median is 60 to 100 m (was 30 to 40) and under 1 % of it within 25 m (was up to 22 %); lengths change by under 1 %.
This commit is contained in:
@@ -6,10 +6,15 @@ import type { PaddleLakeStyle } from '@trek/shared';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing across one lake on its grid of water cells. Every cell knows how
|
* Routing across one lake on its grid of water cells. Every cell knows how
|
||||||
* far it lies from the shore; a crossing costs its length weighed by that:
|
* far it lies from the shore; a crossing costs its length weighed by that.
|
||||||
* straight over is cheapest with the default style, staying near the shore
|
* Closer to the shore than a style's clearance gets steadily dearer, so a
|
||||||
* is with `shore`, and water farther out than half a boat's open-water limit
|
* route keeps off the bank and runs down the middle of anything narrower than
|
||||||
* costs so much that the route takes it only when there is no other way.
|
* twice that: without it the cheapest line hugs every inner bend one grid
|
||||||
|
* cell off the water's edge, which on the map reads as paddling over land.
|
||||||
|
* With `shore` the open water beyond a band along the bank costs more too, so
|
||||||
|
* the route follows the shore at a distance rather than on it. Water farther
|
||||||
|
* out than half a boat's open-water limit costs so much that the route takes
|
||||||
|
* it only when there is no other way.
|
||||||
*
|
*
|
||||||
* Two levels keep a large lake quick: the costs between the lake's portals
|
* Two levels keep a large lake quick: the costs between the lake's portals
|
||||||
* come from a coarse copy of the grid (a few ten thousand cells, however big
|
* come from a coarse copy of the grid (a few ten thousand cells, however big
|
||||||
@@ -17,11 +22,13 @@ import type { PaddleLakeStyle } from '@trek/shared';
|
|||||||
* by A* towards its end.
|
* by A* towards its end.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Keep this far off the shore where the lake is wide enough, in metres. */
|
/** How far off the shore each style keeps where the water is wide enough, in metres. */
|
||||||
const SHORE_MARGIN_M = 12;
|
const CLEARANCE_M: Readonly<Record<PaddleLakeStyle, number>> = { direct: 80, shore: 40 };
|
||||||
|
/** What a metre right at the shore costs on top, falling off squared to nothing at the clearance. */
|
||||||
|
const CLEARANCE_WEIGHT = 4;
|
||||||
/** With `shore`, open water costs more from here out, up to SHORE_FAR_M. */
|
/** With `shore`, open water costs more from here out, up to SHORE_FAR_M. */
|
||||||
const SHORE_NEAR_M = 40;
|
const SHORE_NEAR_M = 100;
|
||||||
const SHORE_FAR_M = 200;
|
const SHORE_FAR_M = 350;
|
||||||
const SHORE_WEIGHT = 1.5;
|
const SHORE_WEIGHT = 1.5;
|
||||||
/** Water beyond a boat's open-water reach costs this many times its length. */
|
/** Water beyond a boat's open-water reach costs this many times its length. */
|
||||||
const OVER_LIMIT_WEIGHT = 20;
|
const OVER_LIMIT_WEIGHT = 20;
|
||||||
@@ -58,7 +65,8 @@ interface Grid {
|
|||||||
|
|
||||||
function weightOf(shore: number, profile: LakeProfile): number {
|
function weightOf(shore: number, profile: LakeProfile): number {
|
||||||
let weight = 1;
|
let weight = 1;
|
||||||
if (shore < SHORE_MARGIN_M) weight += 0.5;
|
const clearance = CLEARANCE_M[profile.style];
|
||||||
|
if (shore < clearance) weight += CLEARANCE_WEIGHT * (1 - shore / clearance) ** 2;
|
||||||
if (profile.style === 'shore') {
|
if (profile.style === 'shore') {
|
||||||
weight += SHORE_WEIGHT * Math.min(1, Math.max(0, (shore - SHORE_NEAR_M) / (SHORE_FAR_M - SHORE_NEAR_M)));
|
weight += SHORE_WEIGHT * Math.min(1, Math.max(0, (shore - SHORE_NEAR_M) / (SHORE_FAR_M - SHORE_NEAR_M)));
|
||||||
}
|
}
|
||||||
@@ -314,7 +322,8 @@ export class LakeField {
|
|||||||
/**
|
/**
|
||||||
* The line from one cell to another as a paddler would take it: the
|
* The line from one cell to another as a paddler would take it: the
|
||||||
* cheapest cell path, then pulled straight wherever a straight line stays on
|
* cheapest cell path, then pulled straight wherever a straight line stays on
|
||||||
* water no more costly than the cells it joins.
|
* water no more costly than the cells it joins and no nearer the shore than
|
||||||
|
* the path it replaces.
|
||||||
*/
|
*/
|
||||||
path(from: number, to: number, profile: LakeProfile): Array<[number, number]> | null {
|
path(from: number, to: number, profile: LakeProfile): Array<[number, number]> | null {
|
||||||
const cells = astar(this.fine(profile), from, to);
|
const cells = astar(this.fine(profile), from, to);
|
||||||
@@ -322,21 +331,38 @@ export class LakeField {
|
|||||||
return this.pull(cells, profile).map((cell) => this.centre(cell));
|
return this.pull(cells, profile).map((cell) => this.centre(cell));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Straightens a cell path. A shortcut may not run nearer the shore than the
|
||||||
|
* stretch of path it replaces (up to the style's clearance): pulling a line
|
||||||
|
* taut round a bend otherwise draws it onto the bank the path kept off.
|
||||||
|
*/
|
||||||
private pull(cells: number[], profile: LakeProfile): number[] {
|
private pull(cells: number[], profile: LakeProfile): number[] {
|
||||||
|
const shore = this.shoreDistance();
|
||||||
|
const clearance = CLEARANCE_M[profile.style];
|
||||||
|
const depth = (cell: number) => Math.min(clearance, shore[cell] as number);
|
||||||
const kept = [cells[0] as number];
|
const kept = [cells[0] as number];
|
||||||
let anchor = 0;
|
let anchor = 0;
|
||||||
|
let floor = depth(cells[0] as number);
|
||||||
for (let i = 2; i < cells.length; i++) {
|
for (let i = 2; i < cells.length; i++) {
|
||||||
if (!this.clear(cells[anchor] as number, cells[i] as number, profile)) {
|
floor = Math.min(floor, depth(cells[i - 1] as number), depth(cells[i] as number));
|
||||||
|
if (!this.clear(cells[anchor] as number, cells[i] as number, profile, floor)) {
|
||||||
kept.push(cells[i - 1] as number);
|
kept.push(cells[i - 1] as number);
|
||||||
anchor = i - 1;
|
anchor = i - 1;
|
||||||
|
floor = Math.min(depth(cells[i - 1] as number), depth(cells[i] as number));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cells.length > 1) kept.push(cells[cells.length - 1] as number);
|
if (cells.length > 1) kept.push(cells[cells.length - 1] as number);
|
||||||
return kept;
|
return kept;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether a straight line between two cells runs on water no more costly than its ends. */
|
/**
|
||||||
private clear(a: number, b: number, profile: LakeProfile): boolean {
|
* Whether a straight line between two cells runs on water no more costly
|
||||||
|
* than its ends and nowhere nearer the shore than `floor` (less half a cell,
|
||||||
|
* for the line's rounding onto cells).
|
||||||
|
*/
|
||||||
|
private clear(a: number, b: number, profile: LakeProfile, floor: number): boolean {
|
||||||
|
const shore = this.shoreDistance();
|
||||||
|
const least = floor - this.lake.cell / 2;
|
||||||
const { width } = this.lake;
|
const { width } = this.lake;
|
||||||
const ac = a % width;
|
const ac = a % width;
|
||||||
const ar = (a - ac) / width;
|
const ar = (a - ac) / width;
|
||||||
@@ -348,7 +374,7 @@ export class LakeField {
|
|||||||
const c = Math.round(ac + ((bc - ac) * s) / steps);
|
const c = Math.round(ac + ((bc - ac) * s) / steps);
|
||||||
const r = Math.round(ar + ((br - ar) * s) / steps);
|
const r = Math.round(ar + ((br - ar) * s) / steps);
|
||||||
const cell = r * width + c;
|
const cell = r * width + c;
|
||||||
if (!this.isWater(cell) || this.weight(cell, profile) > ceiling) return false;
|
if (!this.isWater(cell) || this.weight(cell, profile) > ceiling || (shore[cell] as number) < least) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user