Segments detection

This commit is contained in:
Mathias Krafft 2025-03-28 09:14:17 +01:00
parent 9cf17321f1
commit 89a733120f
No known key found for this signature in database
GPG key ID: D99E394FA2319429
3 changed files with 321 additions and 133 deletions

View file

@ -1,4 +1,4 @@
import { degreesToRadians } from "./angle";
import { degreesToRadians, radiansToDegrees } from "./angle";
import type {
LocalPoint,
GlobalPoint,
@ -7,7 +7,7 @@ import type {
Vector,
} from "./types";
import { PRECISION } from "./utils";
import { vectorFromPoint, vectorScale } from "./vector";
import { vectorDot, vectorFromPoint, vectorScale } from "./vector";
/**
* Create a properly typed Point instance from the X and Y coordinates.
@ -229,3 +229,61 @@ export const isPointWithinBounds = <P extends GlobalPoint | LocalPoint>(
q[1] >= Math.min(p[1], r[1])
);
};
/**
* Calculates the perpendicular distance from a point to a line segment defined by two endpoints.
*
* If the segment is of zero length, the function returns the distance from the point to the start.
*
* @typeParam P - The point type, restricted to LocalPoint or GlobalPoint.
* @param p - The point from which the perpendicular distance is measured.
* @param start - The starting point of the line segment.
* @param end - The ending point of the line segment.
* @returns The perpendicular distance from point p to the line segment defined by start and end.
*/
export const perpendicularDistance = <P extends GlobalPoint | LocalPoint> (
p: P,
start: P,
end: P):
number => {
const dx = end[0] - start[0];
const dy = end[1] - start[1];
if (dx === 0 && dy === 0) {
return Math.hypot(p[0] - start[0], p[1] - start[1]);
}
// Equation of line distance
const numerator = Math.abs(dy * p[0] - dx * p[1] + end[0] * start[1] - end[1] * start[0]);
const denom = Math.hypot(dx, dy);
return numerator / denom;
}
/** * Calculates the angle between three points in degrees.
* The angle is calculated at the first point (p0) using the second (p1) and third (p2) points.
* The angle is measured in degrees and is always positive.
* The function uses the dot product and the arccosine function to calculate the angle. * The result is clamped to the range [-1, 1] to avoid precision errors.
* @param p0 The first point used to form the angle.
* @param p1 The vertex point where the angle is calculated.
* @param p2 The second point used to form the angle.
* @returns The angle in degrees between the three points.
**/
export const angleBetween = <P extends GlobalPoint | LocalPoint>(
p0: P,
p1: P,
p2: P,
): Degrees => {
const v1 = vectorFromPoint(p0, p1);
const v2 = vectorFromPoint(p1, p2);
// dot and cross product
const magnitude1 = Math.hypot(v1[0], v1[1]), magnitude2 = Math.hypot(v2[0], v2[1]);
if (magnitude1 === 0 || magnitude2 === 0) return 0 as Degrees;
const dot = vectorDot(v1, v2);
let cos = dot / (magnitude1 * magnitude2);
// Clamp cos to [-1,1] to avoid precision errors
cos = Math.max(-1, Math.min(1, cos));
const rad = Math.acos(cos) as Radians;
return radiansToDegrees(rad);
}