Merge pull request #31 from avalon-vanguard/fix/grid-rings
Size the distance rings by what the frame reaches, and keep their labels off the star names
This commit is contained in:
@@ -28,30 +28,45 @@ describe('distanceRings', () => {
|
||||
// The opening view sits about 307 pc from the Sun: the rings the map always had, with the
|
||||
// survey edge at the fifth, and on out past the camera for the stars now drawn beyond it.
|
||||
it('reaches past the camera from the opening view', () => {
|
||||
expect(distanceRings(307, 5, 250)).toEqual([50, 100, 150, 200, 250, 300, 350]);
|
||||
expect(distanceRings(0, 307, 5, 250)).toEqual([50, 100, 150, 200, 250, 300, 350]);
|
||||
});
|
||||
|
||||
it('closes in with the camera', () => {
|
||||
expect(distanceRings(20, 5, 250)).toEqual([2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
|
||||
expect(distanceRings(1, 5, 250)).toEqual([0.2, 0.4, 0.6, 0.8, 1]);
|
||||
expect(distanceRings(0, 20, 5, 250)).toEqual([2, 4, 6, 8, 10, 12, 14, 16, 18, 20]);
|
||||
expect(distanceRings(0, 1, 5, 250)).toEqual([0.2, 0.4, 0.6, 0.8, 1]);
|
||||
});
|
||||
|
||||
// Near Mirfak the camera is 155 pc out; rounding the step down to 20 pc must not leave the
|
||||
// rings stopping at 100.
|
||||
it('covers the whole distance whatever the rounding', () => {
|
||||
expect(distanceRings(155, 5, 250)).toEqual([20, 40, 60, 80, 100, 120, 140, 160]);
|
||||
expect(distanceRings(0, 155, 5, 250)).toEqual([20, 40, 60, 80, 100, 120, 140, 160]);
|
||||
});
|
||||
|
||||
// A star 190 pc out seen from 20 pc away: the frame is a band about 19 pc either side of it and
|
||||
// the Sun is nowhere in it. Sized to the 210 pc it reaches, the step would be 20 pc and the
|
||||
// nearest rings — 180 and 200 — would both miss the frame.
|
||||
it('spaces the rings for a frame that does not hold the Sun', () => {
|
||||
const radii = distanceRings(171, 210, 5, 250);
|
||||
|
||||
expect(radii).toEqual([170, 175, 180, 185, 190, 195, 200, 205, 210]);
|
||||
expect(radii.some((radius) => Math.abs(radius - 190) < 19)).toBe(true);
|
||||
});
|
||||
|
||||
it('marks the callout among rings the step does not land on', () => {
|
||||
expect(distanceRings(1000, 5, 250)).toEqual([200, 250, 400, 600, 800, 1000]);
|
||||
expect(distanceRings(0, 1000, 5, 250)).toEqual([200, 250, 400, 600, 800, 1000]);
|
||||
});
|
||||
|
||||
it('leaves the callout out when it is past the last ring', () => {
|
||||
expect(distanceRings(100, 5, 250)).toEqual([20, 40, 60, 80, 100]);
|
||||
it('leaves the callout out when it is past the last ring, or behind the first', () => {
|
||||
expect(distanceRings(0, 100, 5, 250)).toEqual([20, 40, 60, 80, 100]);
|
||||
// Short of the survey edge by less than one step is not the rule — the last ring is: 245 pc
|
||||
// overshoots to 260 and gets it, 235 pc stops at 240 and does not, on the same 20 pc step.
|
||||
expect(distanceRings(0, 245, 5, 250)).toContain(250);
|
||||
expect(distanceRings(0, 235, 5, 250)).not.toContain(250);
|
||||
expect(distanceRings(400, 440, 5, 250)).toEqual([400, 405, 410, 415, 420, 425, 430, 435, 440]);
|
||||
});
|
||||
|
||||
it('draws no rings for a camera with no distance', () => {
|
||||
expect(distanceRings(0, 5, 250)).toEqual([]);
|
||||
expect(distanceRings(0, 0, 5, 250)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -16,19 +16,33 @@ export function roundLengthAtMost(value: number): number | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Rings at a round step of about `reach / count`, out to `reach` or just past it, plus `callout`
|
||||
* wherever it falls among them: the grid's own radii are round, and the one radius that means
|
||||
* something in its own right is marked whether the step lands on it or not. Rounding the step
|
||||
* down makes for `count` to `2.5 × count` rings, never fewer than it takes to cover `reach`.
|
||||
* Rings across the span from `nearest` to `reach`, at a round step of about a `count`th of it,
|
||||
* plus `callout` where it falls between the first ring and the last: the grid's own radii are
|
||||
* round, and the one radius that means something in its own right is marked whether the step lands
|
||||
* on it or not. A frame that stops short of it gets it only when the last ring — the first multiple
|
||||
* of `step` at or past `reach` — is past it: reach 245 with a 20 pc step gets it, reach 235 does
|
||||
* not, since its last ring is 240.
|
||||
*
|
||||
* Two numbers rather than one because these rings are centred on a fixed point — the Sun — and a
|
||||
* frame need not be. Looking at something 200 pc out from 20 pc away, what is on screen is a band
|
||||
* 200 pc wide at its narrowest and nowhere near the Sun; a step sized to the whole 220 puts every
|
||||
* ring off the frame. The span is what the frame covers, so the step is what it can resolve.
|
||||
*
|
||||
* Rounding the step down, over a span that need not start at the Sun, makes for `count` to
|
||||
* `ceil(2.5 × count) + 2` rings — `ceil(reach / step) - floor(nearest / step) + 1` — and the
|
||||
* callout can add one: 5 to 16 for a count of 5.
|
||||
*/
|
||||
export function distanceRings(reach: number, count: number, callout: number): number[] {
|
||||
const step = roundLengthAtMost(reach / count);
|
||||
export function distanceRings(nearest: number, reach: number, count: number, callout: number): number[] {
|
||||
const step = roundLengthAtMost((reach - nearest) / count);
|
||||
if (step === null) {
|
||||
return [];
|
||||
}
|
||||
// The ring just inside the near edge of the span, so the band is crossed rather than started at.
|
||||
const first = Math.max(1, Math.floor(nearest / step));
|
||||
const last = Math.ceil(reach / step);
|
||||
// `toPrecision` clears the binary noise of stepping by a tenth: 0.1 × 3 is 0.30000000000000004.
|
||||
const radii = Array.from({ length: Math.ceil(reach / step) }, (_, index) => Number((step * (index + 1)).toPrecision(12)));
|
||||
if (callout > step && callout < radii[radii.length - 1] && !radii.includes(callout)) {
|
||||
const radii = Array.from({ length: last - first + 1 }, (_, index) => Number((step * (first + index)).toPrecision(12)));
|
||||
if (callout > radii[0] && callout < radii[radii.length - 1] && !radii.includes(callout)) {
|
||||
radii.push(callout);
|
||||
radii.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user