fertig-classic-games/src/games/mastervega/VegaZoom.js

60 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Master of Vega — star-map zoom maths.
//
// Phaser-free on purpose, following the CivilizationIcons/CivilizationTooltips
// precedent: the deciding half of a UI concern is checkable headlessly even
// though the drawing half is not. tools/verifyMasterOfVega.js asserts the
// invariant that matters — you can never pull back past the edge of the map.
import { GAME_HEIGHT, GAME_WIDTH } from '../../config.js';
// The ladder is built per galaxy rather than fixed, because the galaxies differ
// in size by more than 2x: one ladder that frames a huge galaxy leaves a small
// one floating in dead space. The bottom rung is always exactly the zoom at
// which the map covers the viewport, so the map edge is the furthest you can
// pull back and empty space is never visible.
export const ZOOM_STEPS = 5;
export const MAX_ZOOM = 2.2;
export const DEFAULT_ZOOM_INDEX = 2;
/** Smallest zoom at which the galaxy still covers the whole viewport. */
export function minZoomFor(worldW, worldH) {
return Math.max(GAME_WIDTH / worldW, GAME_HEIGHT / worldH);
}
/**
* Geometric ladder from "whole map fills the screen" up to close inspection.
* `steps`/`maxZoom` default to the galaxy-map constants above — pass
* overrides for a differently-scaled world (e.g. VegaCombatCamera.js's much
* smaller, fixed-size battle world, which wants a higher max zoom than a
* galaxy for close-up ship inspection).
*/
export function buildZoomLadder(worldW, worldH, { steps = ZOOM_STEPS, maxZoom = MAX_ZOOM } = {}) {
const min = minZoomFor(worldW, worldH);
// A world small enough that covering it already exceeds the close-inspection
// zoom gets a single fixed step rather than an inverted ladder.
if (min >= maxZoom) return [min];
const ratio = (maxZoom / min) ** (1 / (steps - 1));
return Array.from({ length: steps }, (_, i) => min * ratio ** i);
}
/**
* Which ladder rung best frames a `boundsW`×`boundsH` area (world units,
* plus `padding` on every side) without cropping it — the largest rung that
* still fits, so a small cluster starts zoomed in close and a big spread
* starts zoomed further out, rather than every battle opening at the same
* fixed rung regardless of how many ships are actually in it. Falls back to
* the ladder's own bottom rung (index 0) if even that is more zoomed-in
* than the area needs — VegaCombatCamera.js's "frame the whole fleet"
* feature is built on this.
*/
export function pickFitZoomIndex(zooms, boundsW, boundsH, padding = 0) {
const w = Math.max(1, boundsW + padding * 2);
const h = Math.max(1, boundsH + padding * 2);
const ideal = Math.min(GAME_WIDTH / w, GAME_HEIGHT / h);
let idx = 0;
for (let i = 0; i < zooms.length; i += 1) {
if (zooms[i] <= ideal) idx = i;
}
return idx;
}