/** * Kleine Vektor- und Zahlenhelfer ohne Phaser-Abhängigkeit. * Vektoren sind einfache Objekte `{x, y}`; die Funktionen verändern ihre Eingaben nie. * @module logic/math */ /** @typedef {{x: number, y: number}} Vec2 */ /** * Begrenzt `value` auf das Intervall [min, max]. * @param {number} value * @param {number} min * @param {number} max * @returns {number} */ export function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } /** * Lineare Interpolation zwischen `a` und `b`. * @param {number} a * @param {number} b * @param {number} t 0..1 * @returns {number} */ export function lerp(a, b, t) { return a + (b - a) * t; } /** @param {number} deg @returns {number} */ export function degToRad(deg) { return (deg * Math.PI) / 180; } /** @param {number} x @param {number} y @returns {Vec2} */ export function vec(x = 0, y = 0) { return { x, y }; } /** @param {Vec2} a @param {Vec2} b @returns {Vec2} */ export function add(a, b) { return { x: a.x + b.x, y: a.y + b.y }; } /** @param {Vec2} a @param {Vec2} b @returns {Vec2} */ export function sub(a, b) { return { x: a.x - b.x, y: a.y - b.y }; } /** @param {Vec2} v @param {number} s @returns {Vec2} */ export function scale(v, s) { return { x: v.x * s, y: v.y * s }; } /** @param {Vec2} v @returns {number} */ export function length(v) { return Math.hypot(v.x, v.y); } /** @param {Vec2} a @param {Vec2} b @returns {number} */ export function distance(a, b) { return Math.hypot(b.x - a.x, b.y - a.y); } /** * Einheitsvektor in Richtung `v`. Der Nullvektor bleibt der Nullvektor – * so muss kein Aufrufer Division durch null abfangen. * @param {Vec2} v * @returns {Vec2} */ export function normalize(v) { const len = length(v); return len > 0 ? { x: v.x / len, y: v.y / len } : { x: 0, y: 0 }; } /** * Kürzt `v` auf höchstens `max` Länge, die Richtung bleibt erhalten. * @param {Vec2} v * @param {number} max * @returns {Vec2} */ export function clampLength(v, max) { const len = length(v); return len > max && len > 0 ? scale(v, max / len) : { x: v.x, y: v.y }; } /** * Dreht `v` um `angle` (Bogenmaß, im Uhrzeigersinn bei y nach unten wie im Canvas). * @param {Vec2} v * @param {number} angle * @returns {Vec2} */ export function rotate(v, angle) { const cos = Math.cos(angle); const sin = Math.sin(angle); return { x: v.x * cos - v.y * sin, y: v.x * sin + v.y * cos }; } /** * Winkel des Vektors von `from` nach `to` (Bogenmaß, 0 = nach rechts). * @param {Vec2} from * @param {Vec2} to * @returns {number} */ export function angleTo(from, to) { return Math.atan2(to.y - from.y, to.x - from.x); } /** @param {number} angle @returns {Vec2} Einheitsvektor für den Winkel. */ export function fromAngle(angle) { return { x: Math.cos(angle), y: Math.sin(angle) }; } /** * Prüft, ob ein Punkt in einem Kreis liegt (inkl. Radius eines Objekts). * @param {Vec2} point * @param {Vec2} center * @param {number} radius * @param {number} [objectRadius=0] Ausdehnung des Objekts, damit große Gegner am Rand zählen. * @returns {boolean} */ export function inCircle(point, center, radius, objectRadius = 0) { return distance(point, center) <= radius + objectRadius; } /** * Richtungsvektor aus gedrückten Tasten; diagonal wird normalisiert, damit man * schräg nicht schneller läuft (klassischer 8-Wege-Fehler). * @param {{up: boolean, down: boolean, left: boolean, right: boolean}} keys * @returns {Vec2} */ export function directionFromKeys(keys) { const x = (keys.right ? 1 : 0) - (keys.left ? 1 : 0); const y = (keys.down ? 1 : 0) - (keys.up ? 1 : 0); return normalize({ x, y }); }