60 lines
2.7 KiB
JavaScript
60 lines
2.7 KiB
JavaScript
// 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;
|
||
}
|