explodorama / files / src / render / reveal.ts
reveal.ts
ebcd0a8f 2026-11-02 Pavel Safronov | 1 | /** |
| 2 | * How far to move a view so a tile is not within `margin` tiles of its edge (the controller's target). | |
| 3 | * Pure, so it can be tested without the engine. `aim` is the view's centre and `tile` the tile's | |
| 4 | * centre, both in world units; `cols` and `rows` are how many tiles the view shows. Zero on an | |
| 5 | * axis where the tile is already clear of the edge. | |
| 6 | */ | |
| 7 | export function revealShift( | |
| 8 | aim: { x: number; y: number }, | |
| 9 | tile: { x: number; y: number }, | |
| 10 | cols: number, | |
| 11 | rows: number, | |
| 12 | margin = 1, | |
| 13 | board?: { w: number; h: number }, | |
| 14 | ): { x: number; y: number } { | |
| 15 | const axis = (c: number, a: number, size: number): number => { | |
| 16 | const d = c - a | |
| 17 | // The gap between the tile's near edge and the view's edge is half the view minus the | |
| 18 | // tile's distance from the centre minus half a tile; push it back out to `margin`. | |
| 19 | const over = Math.abs(d) + 0.5 + margin - size / 2 | |
| 20 | return over > 0 ? Math.sign(d) * over : 0 | |
| 21 | } | |
| 22 | // With `board` (world units, the view stays inside it; a board smaller than the view is centred | |
| 23 | // and never moves), only the part of the shift the view can really make is returned. | |
| 24 | const fit = (shift: number, a: number, view: number, size: number | undefined): number => { | |
| 25 | if (shift === 0 || size === undefined) return shift | |
| 26 | if (size <= view) return 0 | |
| 27 | return Math.min(size - view / 2, Math.max(view / 2, a + shift)) - a | |
| 28 | } | |
| 29 | return { | |
| 30 | x: fit(axis(tile.x, aim.x, cols), aim.x, cols, board?.w), | |
| 31 | y: fit(axis(tile.y, aim.y, rows), aim.y, rows, board?.h), | |
| 32 | } | |
| 33 | } |