Add distance-based Bezier curve

This commit is contained in:
Tom
2025-05-12 00:08:41 +02:00
parent 59e71c83f2
commit bb7934e962
3 changed files with 106 additions and 36 deletions
+1 -1
View File
@@ -382,7 +382,7 @@ export class UnitLayer implements Layer {
let newTrailSize = 1;
const trail = this.unitToTrail.get(unit);
// The nuke can move faster than 1 pixel, draw a line for the trail or else it will be dotted
// It can move faster than 1 pixel, draw a line for the trail or else it will be dotted
if (trail.length >= 1) {
const currentX = this.game.x(unit.lastTile());
const currentY = this.game.y(unit.lastTile());
+14 -13
View File
@@ -2,35 +2,37 @@ import { consolex } from "../Consolex";
import { Game } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PseudoRandom } from "../PseudoRandom";
import { BezierCurve } from "../utilities/Line";
import { DistanceBasedBezierCurve } from "../utilities/Line";
import { AStar, PathFindResultType, TileResult } from "./AStar";
import { MiniAStar } from "./MiniAStar";
const parabolaMinHeight = 50;
export class ParabolaPathFinder {
constructor(private mg: GameMap) {}
private curve: BezierCurve;
private distance: number;
private curve: DistanceBasedBezierCurve;
computeControlPoints(
orig: TileRef,
dst: TileRef,
distanceBasedVertex = true,
distanceBasedHeight = true,
) {
const origX = this.mg.x(orig);
const origY = this.mg.y(orig);
const dstX = this.mg.x(dst);
const dstY = this.mg.y(dst);
this.curve = new BezierCurve(origX, origY, dstX, dstY);
this.curve = new DistanceBasedBezierCurve(origX, origY, dstX, dstY);
const dx = dstX - origX;
const dy = dstY - origY;
this.distance = Math.sqrt(dx * dx + dy * dy);
const distance = Math.sqrt(dx * dx + dy * dy);
const maxHeight = distanceBasedHeight
? Math.max(distance / 3, parabolaMinHeight)
: 0;
// Use a bezier curve always pointing up
const x0 = origX + (dstX - origX) / 4;
const maxVertex = distanceBasedVertex ? Math.max(this.distance / 3, 50) : 0;
const y0 = Math.max(origY + (dstY - origY) / 4 - maxVertex, 0);
const y0 = Math.max(origY + (dstY - origY) / 4 - maxHeight, 0);
const x1 = origX + ((dstX - origX) * 3) / 4;
const y1 = Math.max(origY + ((dstY - origY) * 3) / 4 - maxVertex, 0);
const y1 = Math.max(origY + ((dstY - origY) * 3) / 4 - maxHeight, 0);
this.curve.setControlPoint0(x0, y0);
this.curve.setControlPoint1(x1, y1);
@@ -40,8 +42,7 @@ export class ParabolaPathFinder {
if (!this.curve) {
return;
}
const incr = speed / (this.distance * 2);
const nextPoint = this.curve.increment(incr);
const nextPoint = this.curve.increment(speed);
if (!nextPoint) {
return true;
}
+91 -22
View File
@@ -54,12 +54,8 @@ export class BezierCurve {
this.controlPoint0Y = y0;
this.controlPoint1X = x1;
this.controlPoint1Y = y1;
const dx = this.x1 - this.x0;
const dy = this.y1 - this.y0;
const dist = Math.abs(this.x1 - this.x0);
}
private t: number = 0;
private controlPoint0X: number;
private controlPoint0Y: number;
private controlPoint1X: number;
@@ -75,23 +71,96 @@ export class BezierCurve {
this.controlPoint1Y = y;
}
increment(incr: number): { x: number; y: number } {
// Calculate the next point on the Bézier curve
// const incr = speed / (this.distance * 2);
this.t = this.t + incr;
if (this.t >= 1) {
return null; // end reached
}
const nextX =
Math.pow(1 - this.t, 3) * this.x0 +
3 * Math.pow(1 - this.t, 2) * this.t * this.controlPoint0X +
3 * (1 - this.t) * Math.pow(this.t, 2) * this.controlPoint1X +
Math.pow(this.t, 3) * this.x1;
const nextY =
Math.pow(1 - this.t, 3) * this.y0 +
3 * Math.pow(1 - this.t, 2) * this.t * this.controlPoint0Y +
3 * (1 - this.t) * Math.pow(this.t, 2) * this.controlPoint1Y +
Math.pow(this.t, 3) * this.y1;
return { x: nextX, y: nextY };
getPointAt(t: number): { x: number; y: number } {
const x =
Math.pow(1 - t, 3) * this.x0 +
3 * Math.pow(1 - t, 2) * t * this.controlPoint0X +
3 * (1 - t) * Math.pow(t, 2) * this.controlPoint1X +
Math.pow(t, 3) * this.x1;
const y =
Math.pow(1 - t, 3) * this.y0 +
3 * Math.pow(1 - t, 2) * t * this.controlPoint0Y +
3 * (1 - t) * Math.pow(t, 2) * this.controlPoint1Y +
Math.pow(t, 3) * this.y1;
return { x, y };
}
}
/**
* Use a cumulative distance LUT to approximate the traveled distance
*/
export class DistanceBasedBezierCurve extends BezierCurve {
private totalDistance: number = 0;
private cumulativeDistanceLUT: Array<{ t: number; distance: number }> = [];
private lastFoundIndex: number = 0; // To keep track of the last found index
increment(distance: number): { x: number; y: number } {
this.totalDistance += distance;
const targetDistance = Math.min(
this.totalDistance,
this.cumulativeDistanceLUT[this.cumulativeDistanceLUT.length - 1]
?.distance || 0,
);
const t = this.computeTForDistance(targetDistance);
if (t >= 1) {
return null; // end reached
}
return this.getPointAt(t);
}
generateCumulativeDistanceLUT(numSteps: number = 500): void {
this.cumulativeDistanceLUT = [];
let cumulativeDistance = 0;
let prevPoint = this.getPointAt(0);
for (let i = 1; i <= numSteps; i++) {
const t = i / numSteps;
const currentPoint = this.getPointAt(t);
const dx = currentPoint.x - prevPoint.x;
const dy = currentPoint.y - prevPoint.y;
const segmentLength = Math.sqrt(dx * dx + dy * dy);
cumulativeDistance += segmentLength;
this.cumulativeDistanceLUT.push({ t, distance: cumulativeDistance });
prevPoint = currentPoint;
}
}
computeTForDistance(distance: number): number {
if (this.cumulativeDistanceLUT.length === 0) {
this.generateCumulativeDistanceLUT();
}
if (distance <= 0) return 0;
if (
distance >=
this.cumulativeDistanceLUT[this.cumulativeDistanceLUT.length - 1].distance
) {
return 1;
}
let lowerIndex = this.lastFoundIndex;
let upperIndex = this.cumulativeDistanceLUT.length - 1;
// Binary search for the closest range
while (upperIndex - lowerIndex > 1) {
const midIndex = Math.floor((upperIndex + lowerIndex) / 2);
if (this.cumulativeDistanceLUT[midIndex].distance < distance) {
lowerIndex = midIndex;
} else {
upperIndex = midIndex;
}
}
// Interpolate between these two points
const lower = this.cumulativeDistanceLUT[lowerIndex];
const upper = this.cumulativeDistanceLUT[upperIndex];
this.lastFoundIndex = lowerIndex;
// Linear interpolation of t based on the distance
const t =
lower.t +
((distance - lower.distance) * (upper.t - lower.t)) /
(upper.distance - lower.distance);
return t;
}
}