/**
* Простейшие проверки фигур и выбора целей — чистая математика без Pixi.
* Используется для боёв (конус удара, радиус взрыва), триггеров и подбора ближайшего.
*/
import type { Vec2 } from './Vec2';
import { DEFAULT_ISO, type IsoLayout, worldToScreen } from './iso';
/** Точка внутри круга (включая границу). */
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;
}
// ---------- мировые юниты: метризованные варианты ----------
/** Точка внутри круга; позиции и радиус — в мировых юнитах. */
export function inCircleW(
center: Vec2,
radius: number,
point: Vec2,
iso: IsoLayout = DEFAULT_ISO
): boolean {
const c = worldToScreen(center.x, center.y, iso);
const p = worldToScreen(point.x, point.y, iso);
return inCircle(c, radius * iso.tileW, p);
}
/** Точка внутри конуса; позиции, range и dir — в мировых юнитах (dir проецируется). */
export function inConeW(
from: Vec2,
dir: Vec2,
range: number,
halfAngle: number,
point: Vec2,
iso: IsoLayout = DEFAULT_ISO
): boolean {
const f = worldToScreen(from.x, from.y, iso);
const p = worldToScreen(point.x, point.y, iso);
// Углы меняются под анизотропной проекцией — меряем их в экранном пространстве.
const d = worldToScreen(dir.x, dir.y, iso);
return inCone(f, d, range * iso.tileW, halfAngle, p);
}
/** Ближайшая к 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;
}