mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 17:51:24 +01:00
fix: skip coastline out points wip
This commit is contained in:
parent
3215b6f0d2
commit
19d7f239c1
9 changed files with 262 additions and 64 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {simplify} from "scripts/simplify";
|
||||
import {clipPoly} from "utils/lineUtils";
|
||||
import {filterOutOfCanvasPoints} from "utils/lineUtils";
|
||||
import {round} from "utils/stringUtils";
|
||||
|
||||
export function drawCoastline(vertices: IGraphVertices, features: TPackFeatures) {
|
||||
|
|
@ -11,14 +11,38 @@ export function drawCoastline(vertices: IGraphVertices, features: TPackFeatures)
|
|||
const lineGen = d3.line().curve(d3.curveBasisClosed);
|
||||
const SIMPLIFICATION_TOLERANCE = 0.5; // px
|
||||
|
||||
// map edge rectangle
|
||||
debug
|
||||
.append("rect")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0)
|
||||
.attr("width", graphWidth)
|
||||
.attr("height", graphHeight)
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", "black")
|
||||
.attr("stroke-width", 0.1);
|
||||
|
||||
for (const feature of features) {
|
||||
if (!feature) continue;
|
||||
if (feature.type === "ocean") continue;
|
||||
|
||||
const points = clipPoly(feature.vertices.map(vertex => vertices.p[vertex]));
|
||||
const simplifiedPoints = simplify(points, SIMPLIFICATION_TOLERANCE);
|
||||
const points = feature.vertices.map(vertex => vertices.p[vertex]);
|
||||
const filteredPoints = filterOutOfCanvasPoints(points);
|
||||
const simplifiedPoints = simplify(filteredPoints, SIMPLIFICATION_TOLERANCE);
|
||||
const path = round(lineGen(simplifiedPoints)!);
|
||||
|
||||
points.forEach(([x, y]) => {
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0.3).attr("fill", "red");
|
||||
});
|
||||
|
||||
filteredPoints.forEach(([x, y]) => {
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0.3).attr("fill", "blue");
|
||||
});
|
||||
|
||||
simplifiedPoints.forEach(([x, y]) => {
|
||||
debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 0.3).attr("fill", "green");
|
||||
});
|
||||
|
||||
if (feature.type === "lake") {
|
||||
landMask
|
||||
.append("path")
|
||||
|
|
|
|||
107
src/scripts/curves/unused-basisFramedClosed.js
Normal file
107
src/scripts/curves/unused-basisFramedClosed.js
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// Custom fork of d3.curveBasisClosed
|
||||
// The idea is to not interpolate (curve) line along the frame
|
||||
// points = [[0, 833],[0, 0],[1007, 0],[1007, 833]]
|
||||
// d3.line().curve(d3.curveBasisClosed)(points) // => M 167.8,138.8 C 335.7,0,671.3,0,839.2,138.8 C1007,277.7,1007,555.3,839.2,694.2C671.3,833,335.7,833,167.8,694.2 C0,555.3,0,277.7,167.8,138.8
|
||||
// d3.line().curve(d3.curveBasisFramedClosed)(points) // => M 0,833 L 0,0 L 1007,0 L 1007,833
|
||||
|
||||
const PRECISION = 2;
|
||||
const round = number => Number(number.toFixed(PRECISION));
|
||||
|
||||
function noop() {}
|
||||
|
||||
function point(that, x, y) {
|
||||
that._context.bezierCurveTo(
|
||||
round((2 * that._x0 + that._x1) / 3),
|
||||
round((2 * that._y0 + that._y1) / 3),
|
||||
round((that._x0 + 2 * that._x1) / 3),
|
||||
round((that._y0 + 2 * that._y1) / 3),
|
||||
round((that._x0 + 4 * that._x1 + x) / 6),
|
||||
round((that._y0 + 4 * that._y1 + y) / 6)
|
||||
);
|
||||
}
|
||||
|
||||
function isEdge(x, y) {
|
||||
return x <= 0 || y <= 0 || x >= graphWidth || y >= graphHeight;
|
||||
}
|
||||
|
||||
function BasisFramedClosed(context) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
BasisFramedClosed.prototype = {
|
||||
areaStart: noop,
|
||||
areaEnd: noop,
|
||||
lineStart: function () {
|
||||
this._x0 = this._x1 = this._x2 = this._x3 = this._x4 = this._y0 = this._y1 = this._y2 = this._y3 = this._y4 = NaN;
|
||||
this._point = 0;
|
||||
},
|
||||
lineEnd: function () {
|
||||
switch (this._point) {
|
||||
case 1: {
|
||||
this._context.moveTo(this._x2, this._y2);
|
||||
this._context.closePath();
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
this._context.moveTo(round((this._x2 + 2 * this._x3) / 3), round((this._y2 + 2 * this._y3) / 3));
|
||||
this._context.lineTo(round((this._x3 + 2 * this._x2) / 3), round((this._y3 + 2 * this._y2) / 3));
|
||||
this._context.closePath();
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
this.point(this._x2, this._y2);
|
||||
this.point(this._x3, this._y3);
|
||||
this.point(this._x4, this._y4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
point: function (x, y) {
|
||||
const edge = isEdge(x, y);
|
||||
if (x <= 0) x -= 20;
|
||||
if (y <= 0) y -= 20;
|
||||
if (x >= graphWidth) x += 20;
|
||||
if (y >= graphHeight) y += 20;
|
||||
|
||||
switch (this._point) {
|
||||
case 0:
|
||||
this._point = 1;
|
||||
this._x2 = x;
|
||||
this._y2 = y;
|
||||
break;
|
||||
case 1:
|
||||
this._point = 2;
|
||||
this._x3 = x;
|
||||
this._y3 = y;
|
||||
break;
|
||||
case 2:
|
||||
this._point = 3;
|
||||
this._x4 = x;
|
||||
this._y4 = y;
|
||||
|
||||
if (edge) {
|
||||
this._context.moveTo(round((this._x1 + x) / 2), round((this._y1 + y) / 2));
|
||||
break;
|
||||
}
|
||||
|
||||
this._context.moveTo(round((this._x0 + 4 * this._x1 + x) / 6), round((this._y0 + 4 * this._y1 + y) / 6));
|
||||
break;
|
||||
default:
|
||||
if (edge) {
|
||||
this._context.lineTo(x, y);
|
||||
break;
|
||||
}
|
||||
|
||||
point(this, x, y);
|
||||
break;
|
||||
}
|
||||
this._x0 = this._x1;
|
||||
this._x1 = x;
|
||||
this._y0 = this._y1;
|
||||
this._y1 = y;
|
||||
}
|
||||
};
|
||||
|
||||
export function curveBasisFramedClosed(context) {
|
||||
return new BasisFramedClosed(context);
|
||||
}
|
||||
|
|
@ -10,11 +10,8 @@ import {clearLegend, dragLegendBox} from "modules/legend";
|
|||
export function setDefaultEventHandlers() {
|
||||
window.Zoom.setZoomBehavior();
|
||||
|
||||
viewbox
|
||||
.style("cursor", "default")
|
||||
.on(".drag", null)
|
||||
.on("click", handleMapClick)
|
||||
.on("touchmove mousemove", onMouseMove);
|
||||
viewbox.style("cursor", "default").on(".drag", null).on("click", handleMapClick);
|
||||
//.on("touchmove mousemove", onMouseMove);
|
||||
|
||||
scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", () => openDialog("unitsEditor"));
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {debounce} from "utils/functionUtils";
|
|||
import {rn} from "utils/numberUtils";
|
||||
import {generateSeed} from "utils/probabilityUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {showStatistics} from "../statistics";
|
||||
import {createGrid} from "./grid";
|
||||
import {createPack} from "./pack/pack";
|
||||
import {getInputValue, setInputValue} from "utils/nodeUtils";
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface ILakeClimateData extends IPackFeatureLake {
|
|||
|
||||
export const getClimateData = function (
|
||||
lakes: IPackFeatureLake[],
|
||||
heights: number[],
|
||||
heights: Float32Array,
|
||||
drainableLakes: Dict<boolean>,
|
||||
gridReference: IPack["cells"]["g"],
|
||||
precipitation: IGrid["cells"]["prec"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as d3 from "d3";
|
||||
|
||||
import {TIME, WARN} from "config/logging";
|
||||
import {INFO, TIME, WARN} from "config/logging";
|
||||
import {rn} from "utils/numberUtils";
|
||||
import {aleaPRNG} from "scripts/aleaPRNG";
|
||||
import {DISTANCE_FIELD, MAX_HEIGHT, MIN_LAND_HEIGHT} from "config/generation";
|
||||
|
|
@ -8,6 +8,7 @@ import {getInputNumber} from "utils/nodeUtils";
|
|||
import {pick} from "utils/functionUtils";
|
||||
import {byId} from "utils/shorthands";
|
||||
import {mergeLakeData, getClimateData, ILakeClimateData} from "./lakes";
|
||||
import {drawArrow} from "utils/debugUtils";
|
||||
|
||||
const {Rivers} = window;
|
||||
const {LAND_COAST} = DISTANCE_FIELD;
|
||||
|
|
@ -96,7 +97,7 @@ export function generateRivers(
|
|||
}
|
||||
|
||||
lake.outlet = riverIds[lakeCell];
|
||||
flowDown(cellId, flux[lakeCell], lake.outlet);
|
||||
flowDown(lakeCell, cellId, flux[lakeCell], lake.outlet);
|
||||
}
|
||||
|
||||
if (lakesDrainingToCell.length && lakesDrainingToCell[0].outlet) {
|
||||
|
|
@ -127,15 +128,6 @@ export function generateRivers(
|
|||
// cells is depressed
|
||||
if (currentCellHeights[cellId] <= currentCellHeights[min]) return;
|
||||
|
||||
debug
|
||||
.append("line")
|
||||
.attr("x1", cells.p[cellId][0])
|
||||
.attr("y1", cells.p[cellId][1])
|
||||
.attr("x2", cells.p[min][0])
|
||||
.attr("y2", cells.p[min][1])
|
||||
.attr("stroke", "#333")
|
||||
.attr("stroke-width", 0.1);
|
||||
|
||||
if (flux[cellId] < MIN_FLUX_TO_FORM_RIVER) {
|
||||
// flux is too small to operate as a river
|
||||
if (currentCellHeights[min] >= MIN_LAND_HEIGHT) flux[min] += flux[cellId];
|
||||
|
|
@ -149,12 +141,14 @@ export function generateRivers(
|
|||
nextRiverId++;
|
||||
}
|
||||
|
||||
flowDown(min, flux[cellId], riverIds[cellId]);
|
||||
flowDown(cellId, min, flux[cellId], riverIds[cellId]);
|
||||
});
|
||||
|
||||
return {flux, lakeData};
|
||||
|
||||
function flowDown(toCell: number, fromFlux: number, riverId: number) {
|
||||
function flowDown(fromCell: number, toCell: number, fromFlux: number, riverId: number) {
|
||||
// drawArrow(cells.p[fromCell], cells.p[toCell]);
|
||||
|
||||
const toFlux = flux[toCell] - confluence[toCell];
|
||||
const toRiver = riverIds[toCell];
|
||||
|
||||
|
|
@ -262,7 +256,7 @@ export function generateRivers(
|
|||
return {r, conf, rivers};
|
||||
}
|
||||
|
||||
function downcutRivers(heights: number[]) {
|
||||
function downcutRivers(heights: Float32Array) {
|
||||
const MAX_DOWNCUT = 5;
|
||||
const MIN_HEIGHT_TO_DOWNCUT = 35;
|
||||
|
||||
|
|
@ -284,10 +278,10 @@ export function generateRivers(
|
|||
|
||||
// add distance to water value to land cells to make map less depressed
|
||||
const applyDistanceField = ({h, c, t}: Pick<IPack["cells"], "h" | "c" | "t">) => {
|
||||
return Array.from(h).map((height, index) => {
|
||||
if (height < MIN_LAND_HEIGHT || t[index] < LAND_COAST) return height;
|
||||
return new Float32Array(h.length).map((_, index) => {
|
||||
if (h[index] < MIN_LAND_HEIGHT || t[index] < LAND_COAST) return h[index];
|
||||
const mean = d3.mean(c[index].map(c => t[c])) || 0;
|
||||
return height + t[index] / 100 + mean / 10000;
|
||||
return h[index] + t[index] / 100 + mean / 10000;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -295,52 +289,55 @@ const applyDistanceField = ({h, c, t}: Pick<IPack["cells"], "h" | "c" | "t">) =>
|
|||
const resolveDepressions = function (
|
||||
cells: Pick<IPack["cells"], "i" | "c" | "b" | "f">,
|
||||
features: TPackFeatures,
|
||||
heights: number[]
|
||||
): [number[], Dict<boolean>] {
|
||||
initialCellHeights: Float32Array
|
||||
): [Float32Array, Dict<boolean>] {
|
||||
TIME && console.time("resolveDepressions");
|
||||
|
||||
const MAX_INTERATIONS = getInputNumber("resolveDepressionsStepsOutput");
|
||||
const checkLakeMaxIteration = MAX_INTERATIONS * 0.85;
|
||||
const elevateLakeMaxIteration = MAX_INTERATIONS * 0.75;
|
||||
|
||||
const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput");
|
||||
|
||||
const LAND_ELEVATION_INCREMENT = 0.1;
|
||||
const LAKE_ELEVATION_INCREMENT = 0.2;
|
||||
|
||||
const lakes = features.filter(feature => feature && feature.type === "lake") as IPackFeatureLake[];
|
||||
lakes.sort((a, b) => a.height - b.height); // lowest lakes go first
|
||||
|
||||
const currentCellHeights = Array.from(heights);
|
||||
const currentLakeHeights = Object.fromEntries(lakes.map(({i, height}) => [i, height]));
|
||||
|
||||
const getHeight = (i: number) => currentLakeHeights[cells.f[i]] || currentCellHeights[i];
|
||||
const getMinHeight = (cellsIds: number[]) => Math.min(...cellsIds.map(getHeight));
|
||||
const getMinLandHeight = (cellsIds: number[]) => Math.min(...cellsIds.map(i => currentCellHeights[i]));
|
||||
|
||||
const drainableLakes = checkLakesDrainability();
|
||||
|
||||
const landCells = cells.i.filter(i => heights[i] >= MIN_LAND_HEIGHT && !cells.b[i]);
|
||||
landCells.sort((a, b) => heights[a] - heights[b]); // lowest cells go first
|
||||
const landCells = cells.i.filter(i => initialCellHeights[i] >= MIN_LAND_HEIGHT && !cells.b[i]);
|
||||
landCells.sort((a, b) => initialCellHeights[a] - initialCellHeights[b]); // lowest cells go first
|
||||
|
||||
const currentCellHeights = Float32Array.from(initialCellHeights);
|
||||
const currentLakeHeights = Object.fromEntries(lakes.map(({i, height}) => [i, height]));
|
||||
const currentDrainableLakes = checkLakesDrainability();
|
||||
const depressions: number[] = [];
|
||||
|
||||
for (let iteration = 0; iteration && depressions.at(-1) && iteration < MAX_INTERATIONS; iteration++) {
|
||||
let bestDepressions = Infinity;
|
||||
let bestCellHeights: typeof currentCellHeights | null = null;
|
||||
let bestDrainableLakes: typeof currentDrainableLakes | null = null;
|
||||
|
||||
for (let iteration = 0; depressions.at(-1) !== 0 && iteration < MAX_INTERATIONS; iteration++) {
|
||||
let depressionsLeft = 0;
|
||||
|
||||
// elevate potentially drainable lakes
|
||||
if (iteration < checkLakeMaxIteration) {
|
||||
for (const lake of lakes) {
|
||||
if (drainableLakes[lake.i] !== true) continue;
|
||||
if (currentDrainableLakes[lake.i] !== true) continue;
|
||||
|
||||
const minShoreHeight = getMinHeight(lake.shoreline);
|
||||
if (minShoreHeight >= MAX_HEIGHT || lake.height > minShoreHeight) continue;
|
||||
const minShoreHeight = getMinLandHeight(lake.shoreline);
|
||||
if (minShoreHeight >= MAX_HEIGHT || currentLakeHeights[lake.i] > minShoreHeight) continue;
|
||||
|
||||
if (iteration > elevateLakeMaxIteration) {
|
||||
// reset heights
|
||||
for (const shoreCellId of lake.shoreline) {
|
||||
// reset heights
|
||||
currentCellHeights[shoreCellId] = heights[shoreCellId];
|
||||
currentLakeHeights[lake.i] = lake.height;
|
||||
currentCellHeights[shoreCellId] = initialCellHeights[shoreCellId];
|
||||
}
|
||||
currentLakeHeights[lake.i] = lake.height;
|
||||
|
||||
drainableLakes[lake.i] = false;
|
||||
currentDrainableLakes[lake.i] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -358,23 +355,36 @@ const resolveDepressions = function (
|
|||
}
|
||||
|
||||
depressions.push(depressionsLeft);
|
||||
|
||||
// check depression resolving progress
|
||||
if (depressions.length > 5) {
|
||||
const depressionsInitial = depressions.at(0) || 0;
|
||||
const depressiosRecently = depressions.at(-6) || 0;
|
||||
|
||||
const isProgressingOverall = depressionsInitial < depressionsLeft;
|
||||
if (!isProgressingOverall) return [heights, drainableLakes];
|
||||
|
||||
const isProgressingRecently = depressiosRecently < depressionsLeft;
|
||||
if (!isProgressingRecently) return [currentCellHeights, drainableLakes];
|
||||
if (depressionsLeft < bestDepressions) {
|
||||
bestDepressions = depressionsLeft;
|
||||
bestCellHeights = Float32Array.from(currentCellHeights);
|
||||
bestDrainableLakes = structuredClone(currentDrainableLakes);
|
||||
}
|
||||
}
|
||||
|
||||
TIME && console.timeEnd("resolveDepressions");
|
||||
|
||||
const depressionsLeft = depressions.at(-1);
|
||||
if (depressionsLeft) {
|
||||
if (bestCellHeights && bestDrainableLakes) {
|
||||
WARN &&
|
||||
console.warn(`Cannot resolve all depressions. Depressions: ${depressions[0]}. Best result: ${bestDepressions}`);
|
||||
return [bestCellHeights, bestDrainableLakes];
|
||||
}
|
||||
|
||||
WARN && console.warn(`Cannot resolve depressions. Depressions: ${depressionsLeft}`);
|
||||
return [initialCellHeights, {}];
|
||||
}
|
||||
|
||||
INFO &&
|
||||
console.info(`ⓘ Resolved all depressions. Depressions: ${depressions[0]}. Interations: ${depressions.length}`);
|
||||
return [currentCellHeights, currentDrainableLakes];
|
||||
|
||||
// define lakes that potentially can be open (drained into another water body)
|
||||
function checkLakesDrainability() {
|
||||
const canBeDrained: Dict<boolean> = {}; // all false by default
|
||||
|
||||
const ELEVATION_LIMIT = getInputNumber("lakeElevationLimitOutput");
|
||||
const drainAllLakes = ELEVATION_LIMIT === MAX_HEIGHT - MIN_LAND_HEIGHT;
|
||||
|
||||
for (const lake of lakes) {
|
||||
|
|
@ -385,7 +395,8 @@ const resolveDepressions = function (
|
|||
|
||||
canBeDrained[lake.i] = false;
|
||||
const minShoreHeight = getMinHeight(lake.shoreline);
|
||||
const minHeightShoreCell = lake.shoreline.find(cellId => heights[cellId] === minShoreHeight) || lake.shoreline[0];
|
||||
const minHeightShoreCell =
|
||||
lake.shoreline.find(cellId => initialCellHeights[cellId] === minShoreHeight) || lake.shoreline[0];
|
||||
|
||||
const queue = [minHeightShoreCell];
|
||||
const checked = [];
|
||||
|
|
@ -397,9 +408,9 @@ const resolveDepressions = function (
|
|||
|
||||
for (const neibCellId of cells.c[cellId]) {
|
||||
if (checked[neibCellId]) continue;
|
||||
if (heights[neibCellId] >= breakableHeight) continue;
|
||||
if (initialCellHeights[neibCellId] >= breakableHeight) continue;
|
||||
|
||||
if (heights[neibCellId] < MIN_LAND_HEIGHT) {
|
||||
if (initialCellHeights[neibCellId] < MIN_LAND_HEIGHT) {
|
||||
const waterFeatureMet = features[cells.f[neibCellId]];
|
||||
const isOceanMet = waterFeatureMet && waterFeatureMet.type === "ocean";
|
||||
const isLakeMet = waterFeatureMet && waterFeatureMet.type === "lake";
|
||||
|
|
@ -418,8 +429,4 @@ const resolveDepressions = function (
|
|||
|
||||
return canBeDrained;
|
||||
}
|
||||
|
||||
depressions && WARN && console.warn(`Unresolved depressions: ${depressions}. Edit heightmap to fix`);
|
||||
|
||||
return [currentCellHeights, drainableLakes];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,27 @@ export function unique<T>(array: T[]) {
|
|||
return [...new Set(array)];
|
||||
}
|
||||
|
||||
export function sliceFragment<T>(array: T[], index: number, zone: number) {
|
||||
if (zone + 1 + zone >= array.length) {
|
||||
return array.slice();
|
||||
}
|
||||
|
||||
const start = index - zone;
|
||||
const end = index + zone;
|
||||
|
||||
if (start < 0) {
|
||||
return array.slice(0, end).concat(array.slice(start));
|
||||
}
|
||||
|
||||
if (end >= array.length) {
|
||||
return array.slice(start).concat(array.slice(0, end % array.length));
|
||||
}
|
||||
|
||||
return array.slice(start, end);
|
||||
}
|
||||
|
||||
window.sliceFragment = sliceFragment;
|
||||
|
||||
interface ICreateTypesArrayLength {
|
||||
maxValue: number;
|
||||
length: number;
|
||||
|
|
|
|||
17
src/utils/debugUtils.ts
Normal file
17
src/utils/debugUtils.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// utils used for debugging (not in PROD) only
|
||||
export function drawArrow([x1, y1]: TPoint, [x2, y2]: TPoint, width = 1, color = "#444"): void {
|
||||
const angle = Math.atan2(y2 - y1, x2 - x1);
|
||||
const normal = angle + Math.PI / 2;
|
||||
|
||||
const [xMid, yMid] = [(x1 + x2) / 2, (y1 + y2) / 2];
|
||||
|
||||
const [xLeft, yLeft] = [xMid + width * Math.cos(normal), yMid + width * Math.sin(normal)];
|
||||
const [xRight, yRight] = [xMid - width * Math.cos(normal), yMid - width * Math.sin(normal)];
|
||||
|
||||
debug
|
||||
.append("path")
|
||||
.attr("d", `M${x1},${y1} L${xMid},${yMid} ${xLeft},${yLeft} ${x2},${y2} ${xRight},${yRight} ${xMid},${yMid} Z`)
|
||||
.attr("fill", color)
|
||||
.attr("stroke", color)
|
||||
.attr("stroke-width", width / 2);
|
||||
}
|
||||
|
|
@ -52,3 +52,29 @@ export function getMiddlePoint(cell1: number, cell2: number) {
|
|||
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
function getOffCanvasSide([x, y]: TPoint) {
|
||||
if (y <= 0) return "top";
|
||||
if (y >= graphHeight) return "bottom";
|
||||
if (x <= 0) return "left";
|
||||
if (x >= graphWidth) return "right";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove intermediate out-of-canvas points from polyline
|
||||
export function filterOutOfCanvasPoints(points: TPoints) {
|
||||
const pointsOutSide = points.map(getOffCanvasSide);
|
||||
const SAFE_ZONE = 3;
|
||||
|
||||
const filterOutCanvasPoint = (i: number) => {
|
||||
const pointSide = pointsOutSide[i];
|
||||
if (pointSide === false) return true;
|
||||
if (pointsOutSide.slice(i - SAFE_ZONE, i + SAFE_ZONE).some(side => !side || side !== pointSide)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const result = points.filter((_, i) => filterOutCanvasPoint(i));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue