71 lines
2.5 KiB
JavaScript
71 lines
2.5 KiB
JavaScript
/**
|
|
* Pure math for the per-system visual effects — NO Phaser import, so the
|
|
* dev harness (dev/system-effects.test.mjs) can run it in bare Node.
|
|
*
|
|
* The system effect is a full-screen composite over the WORLD camera
|
|
* (see SystemEffects.js): a fragment shader displaces the camera's
|
|
* screen-space UVs around the system's star. The star is never rendered
|
|
* (it is invisible flavor — data/systems.json), but it sits at the
|
|
* system origin (world 0,0), so the ripple center is "where world (0,0)
|
|
* projects to, in the camera's UV space".
|
|
*
|
|
* A camera's `matrixCombined` is the world->screen affine transform
|
|
* (a·x + c·y + tx, b·x + d·y + ty). Dividing the screen point by the
|
|
* camera's viewport size gives the UV: (0,0) = the camera's top-left,
|
|
* (1,1) = its bottom-right — exactly the space the shader's
|
|
* `outTexCoord` lives in.
|
|
*/
|
|
|
|
/**
|
|
* @typedef {{ a: number, b: number, c: number, d: number, tx: number, ty: number }} Affine2D
|
|
*
|
|
* Affine2D — the shape of a Phaser camera's matrixCombined (the
|
|
* world->screen transform). Duck-typed on purpose: the tests pass plain
|
|
* objects, the game passes the real matrix.
|
|
*/
|
|
|
|
/**
|
|
* Screen-space point of a world point under an affine transform.
|
|
*
|
|
* @param {Affine2D} m world->screen transform
|
|
* @param {number} [wx=0] world x
|
|
* @param {number} [wy=0] world y
|
|
* @returns {{ x: number, y: number }} screen point (camera viewport px)
|
|
*/
|
|
export function worldToScreen(m, wx = 0, wy = 0) {
|
|
return {
|
|
x: m.a * wx + m.c * wy + m.tx,
|
|
y: m.b * wx + m.d * wy + m.ty,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* UV coordinate of a world point for a camera of the given viewport size
|
|
* (0,0 top-left -> 1,1 bottom-right).
|
|
*
|
|
* @param {Affine2D} m world->screen transform
|
|
* @param {number} width camera viewport width (px)
|
|
* @param {number} height camera viewport height (px)
|
|
* @param {number} [wx=0] world x
|
|
* @param {number} [wy=0] world y
|
|
* @returns {{ x: number, y: number }} UV
|
|
*/
|
|
export function worldToUV(m, width, height, wx = 0, wy = 0) {
|
|
const p = worldToScreen(m, wx, wy);
|
|
return { x: p.x / width, y: p.y / height };
|
|
}
|
|
|
|
/**
|
|
* The wave's phase (radians) at a moment — the shader's `time` uniform.
|
|
* Monotonic in the game clock, scaled by the configured speed, so the
|
|
* ripple keeps its pace across system entries without any per-frame
|
|
* accumulation to drift.
|
|
*
|
|
* @param {number} nowMs game-loop time (ms, monotonic)
|
|
* @param {number} [speed=1] angular speed multiplier (radians/second)
|
|
* @returns {number} phase, radians
|
|
*/
|
|
export function ripplePhase(nowMs, speed = 1) {
|
|
return (nowMs / 1000) * (Number(speed) || 0);
|
|
}
|