Newer
Older
rpg / packages / engine / src / math / shapes.ts
/**
 * Простейшие проверки фигур и выбора целей — чистая математика без Pixi.
 * Используется для боёв (конус удара, радиус взрыва), триггеров и подбора ближайшего.
 */

import type { Vec2 } from './Vec2';

/** Точка внутри круга (включая границу). */
export function inCircle(center: Vec2, radius: number, point: Vec2): boolean {
    const dx = point.x - center.x;
    const dy = point.y - center.y;
    return dx * dx + dy * dy <= radius * radius;
}

/** Угол между векторами a и b (радианы, 0..PI). */
export function angleBetween(a: Vec2, b: Vec2): number {
    const la = Math.hypot(a.x, a.y);
    const lb = Math.hypot(b.x, b.y);
    if (la === 0 || lb === 0) return 0;
    let cos = (a.x * b.x + a.y * b.y) / (la * lb);
    cos = Math.min(1, Math.max(-1, cos));
    return Math.acos(cos);
}

/**
 * Точка внутри конуса: в пределах range от from и угол до dir не больше halfAngle.
 * Нулевое направление — конус «схлопывается» в отрезок по range (угол не проверяется).
 */
export function inCone(
    from: Vec2,
    dir: Vec2,
    range: number,
    halfAngle: number,
    point: Vec2
): boolean {
    const dx = point.x - from.x;
    const dy = point.y - from.y;
    if (dx * dx + dy * dy > range * range) return false;
    if (dir.x === 0 && dir.y === 0) return true;
    return angleBetween(dir, { x: dx, y: dy }) <= halfAngle;
}

/** Ближайшая к from точка из списка (в пределах maxRange включительно), или null. */
export function nearest<T extends Vec2>(list: T[], from: Vec2, maxRange = Infinity): T | null {
    let best: T | null = null;
    let bestDist = maxRange * maxRange;
    for (const p of list) {
        const dx = p.x - from.x;
        const dy = p.y - from.y;
        const d = dx * dx + dy * dy;
        if (d <= bestDist) {
            bestDist = d;
            best = p;
        }
    }
    return best;
}