Keep a giant's disc clear of its neighbours' names on a phone, not only on a desktop
d1aa22e framed a giant so its disc took half the view's tighter half-extent, inside the ring its
neighbours are named on at 0.74 of it. It measured that at 1600x1000 only. The names hang a fixed
distance in from the ring, 74-78 px, so on a 390x844 phone the 0.24 of the half-side between disc
and ring is 47 px and the names land on the disc: Betelgeuse was drawn 98 px wide with HD 39374's
name 70 px from its centre, Antares with HD 148199's at 68 px.
The framing now takes the canvas's shorter side and keeps the disc inside the ring less 90 px,
at most half the half-extent as before, at least a tenth. Measured in the running app at 390x844
on arrival: Betelgeuse, Antares and Rigel drawn 54 px wide, the nearest names at 70, 68 and 127
px. At 1600x1000 nothing changes: Betelgeuse 250 px, its nearest name at 296. The ring's fraction
moves to system-framing.ts, beside the rule that depends on it, and the scene reads it there.
The review's other half, that the Readout sheet covers 44 % of the disc once opened on a phone, is
not changed: the dock opens with no panel below 640 px and folds it on any tap on the scene, so
the star arrives with nothing over it.
Control: giving the names no reach fails "keeps a giant's disc clear of its neighbours' names on
a phone" alone. The scene's passing of the canvas size has no unit test (the test canvas has no
size); the app measurement above covers it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ import { galacticNormal, PolarGridPlane, TetherField } from './grid-plane';
|
|||||||
import { MilkyWayRenderer } from './milky-way-renderer';
|
import { MilkyWayRenderer } from './milky-way-renderer';
|
||||||
import {
|
import {
|
||||||
closestApproachAu,
|
closestApproachAu,
|
||||||
|
NEIGHBOUR_RING_FRACTION,
|
||||||
SUN_RADIUS_AU,
|
SUN_RADIUS_AU,
|
||||||
systemFrameRadiusAu,
|
systemFrameRadiusAu,
|
||||||
systemFramingDistanceAu,
|
systemFramingDistanceAu,
|
||||||
@@ -218,11 +219,6 @@ const ROUTE_RANGE_CEILING_PC = MAX_JUMP_RANGE_PC;
|
|||||||
|
|
||||||
/** How many neighbouring stars are named from inside a system. */
|
/** How many neighbouring stars are named from inside a system. */
|
||||||
const NEIGHBOUR_COUNT = 4;
|
const NEIGHBOUR_COUNT = 4;
|
||||||
/**
|
|
||||||
* How far out from the centre of the view a neighbour's name sits, as a fraction of the frame's
|
|
||||||
* half-height. Clear of the scale rail at the top and the dock at the bottom.
|
|
||||||
*/
|
|
||||||
const NEIGHBOUR_RING_NDC = 0.74;
|
|
||||||
/**
|
/**
|
||||||
* How far in front of the camera a neighbour's name is planted, in AU. Any depth projects to
|
* How far in front of the camera a neighbour's name is planted, in AU. Any depth projects to
|
||||||
* the same place on the ring, but not to the same stability: unprojecting at the middle of the
|
* the same place on the ring, but not to the same stability: unprojecting at the middle of the
|
||||||
@@ -1523,7 +1519,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
const canvas = this.canvasRef().nativeElement;
|
const canvas = this.canvasRef().nativeElement;
|
||||||
const placed = ringPlacement(
|
const placed = ringPlacement(
|
||||||
angle,
|
angle,
|
||||||
NEIGHBOUR_RING_NDC,
|
NEIGHBOUR_RING_FRACTION,
|
||||||
{ width: canvas.clientWidth, height: canvas.clientHeight },
|
{ width: canvas.clientWidth, height: canvas.clientHeight },
|
||||||
this.reserved,
|
this.reserved,
|
||||||
);
|
);
|
||||||
@@ -2256,7 +2252,8 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
// Framed against the perspective camera whichever is active: the framing distance is what
|
// Framed against the perspective camera whichever is active: the framing distance is what
|
||||||
// the orthographic frustum is then sized from, so both projections show the same extent.
|
// the orthographic frustum is then sized from, so both projections show the same extent.
|
||||||
const framingCamera = this.engine.getPerspectiveCamera();
|
const framingCamera = this.engine.getPerspectiveCamera();
|
||||||
const viewport = { fovDegrees: framingCamera.fov, aspect: framingCamera.aspect };
|
const canvas = this.canvasRef().nativeElement;
|
||||||
|
const viewport = { fovDegrees: framingCamera.fov, aspect: framingCamera.aspect, shorterSidePx: Math.min(canvas.clientWidth, canvas.clientHeight) };
|
||||||
const framingDistance = systemFramingDistanceAu(
|
const framingDistance = systemFramingDistanceAu(
|
||||||
this.systemRenderer.gridOuterRadiusAu,
|
this.systemRenderer.gridOuterRadiusAu,
|
||||||
viewport,
|
viewport,
|
||||||
|
|||||||
@@ -63,6 +63,21 @@ describe('systemFramingDistanceAu', () => {
|
|||||||
expect(systemFramingDistanceAu(0, undefined, 0.00465)).toBe(systemFramingDistanceAu(0));
|
expect(systemFramingDistanceAu(0, undefined, 0.00465)).toBe(systemFramingDistanceAu(0));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps a giant's disc clear of its neighbours' names on a phone, which hang a fixed 78 px in from their ring", () => {
|
||||||
|
// Betelgeuse at 390x844 and at 1600x1000. Kept to half the half-side, its disc was 98 px on the
|
||||||
|
// phone, where the names come within 66 px of the centre.
|
||||||
|
const betelgeuseAu = 2.72;
|
||||||
|
for (const [width, height] of [[390, 844], [1600, 1000]]) {
|
||||||
|
const viewport = { fovDegrees: 50, aspect: width / height, shorterSidePx: Math.min(width, height) };
|
||||||
|
const distance = systemFramingDistanceAu(0, viewport, betelgeuseAu);
|
||||||
|
const tight = Math.tan((25 * Math.PI) / 180) * Math.min(1, viewport.aspect);
|
||||||
|
const halfSidePx = viewport.shorterSidePx / 2;
|
||||||
|
const discPx = (Math.tan(Math.asin(betelgeuseAu / distance)) / tight) * halfSidePx;
|
||||||
|
expect(discPx).toBeLessThan(0.74 * halfSidePx - 78);
|
||||||
|
expect(discPx).toBeGreaterThan(0.2 * halfSidePx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("holds a giant's disc inside the ring its neighbours are named on, and the camera clear of its closest approach", () => {
|
it("holds a giant's disc inside the ring its neighbours are named on, and the camera clear of its closest approach", () => {
|
||||||
// The ring is at 0.74 of the tighter half-extent; the disc is kept to half of it, in either window.
|
// The ring is at 0.74 of the tighter half-extent; the disc is kept to half of it, in either window.
|
||||||
const betelgeuseAu = 2.72;
|
const betelgeuseAu = 2.72;
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const FRAME_MARGIN = 0.12;
|
|||||||
export interface SystemViewport {
|
export interface SystemViewport {
|
||||||
fovDegrees: number;
|
fovDegrees: number;
|
||||||
aspect: number;
|
aspect: number;
|
||||||
|
/** The canvas's shorter side in CSS pixels, which a giant's disc is kept clear of its neighbours' names on. */
|
||||||
|
shorterSidePx?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SYSTEM_VIEWPORT: SystemViewport = { fovDegrees: 50, aspect: 1 };
|
export const DEFAULT_SYSTEM_VIEWPORT: SystemViewport = { fovDegrees: 50, aspect: 1 };
|
||||||
@@ -61,12 +63,35 @@ const MAX_FRAMING_DISTANCE_AU = 200;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* How much of the view's tighter half-extent a giant's disc may take on arrival: inside the ring
|
* How much of the view's tighter half-extent a giant's disc may take on arrival: inside the ring
|
||||||
* the system view names the star's neighbours on, at 0.74 of it, and clear of the names hung
|
* the system view names the star's neighbours on, and clear of the names hung inward from it,
|
||||||
* inward from it, whose nearest corners come within 292 px of the centre on a 1 000 px view.
|
* whose nearest corners come within 292 px of the centre on a 1 000 px view. Framed to fill the
|
||||||
* Framed to fill the frame instead, Betelgeuse settled at the three-radius closest approach with
|
* frame instead, Betelgeuse settled at the three-radius closest approach with a disc of 379 px,
|
||||||
* a disc of 379 px, past the 370 px ring, and its neighbours' names on it.
|
* past the 370 px ring, and its neighbours' names on it.
|
||||||
*/
|
*/
|
||||||
const STAR_FRAME_FRACTION = 0.5;
|
const STAR_FRAME_FRACTION = 0.5;
|
||||||
|
/**
|
||||||
|
* The ring the system view names a star's neighbours on, as a fraction of the frame's shorter
|
||||||
|
* half-side (see `ringPlacement`). Clear of the scale rail at the top and the dock at the bottom.
|
||||||
|
*/
|
||||||
|
export const NEIGHBOUR_RING_FRACTION = 0.74;
|
||||||
|
/**
|
||||||
|
* How far a neighbour's name reaches in from that ring, in pixels whatever the window: its nearest
|
||||||
|
* corner measured 74 px in on a 390 px phone and 78 px on a 1 000 px view, and a margin on that.
|
||||||
|
* The fraction above left the names 0.24 of the half-side, 47 px on a phone, and at 390x844
|
||||||
|
* Betelgeuse's 98 px disc had HD 39374's name 70 px from its centre.
|
||||||
|
*/
|
||||||
|
const NAME_REACH_PX = 90;
|
||||||
|
/** However small the window, the disc still takes this much of it. */
|
||||||
|
const MIN_STAR_FRAME_FRACTION = 0.1;
|
||||||
|
|
||||||
|
/** The fraction of the tighter half-extent a giant's disc may take in this viewport. */
|
||||||
|
function starFrameFraction(viewport: SystemViewport): number {
|
||||||
|
if (!viewport.shorterSidePx) {
|
||||||
|
return STAR_FRAME_FRACTION;
|
||||||
|
}
|
||||||
|
const clear = NEIGHBOUR_RING_FRACTION - NAME_REACH_PX / (viewport.shorterSidePx / 2);
|
||||||
|
return Math.min(STAR_FRAME_FRACTION, Math.max(MIN_STAR_FRAME_FRACTION, clear));
|
||||||
|
}
|
||||||
|
|
||||||
/** Framing for a star with no known planets, where there is nothing to fit. */
|
/** Framing for a star with no known planets, where there is nothing to fit. */
|
||||||
const EMPTY_SYSTEM_FRAMING_DISTANCE_AU = 3;
|
const EMPTY_SYSTEM_FRAMING_DISTANCE_AU = 3;
|
||||||
@@ -127,7 +152,7 @@ export function systemFramingDistanceAu(framedRadiusAu: number, viewport: System
|
|||||||
// A giant drawn at its own radius can be wider than the system around it — Betelgeuse's 584
|
// A giant drawn at its own radius can be wider than the system around it — Betelgeuse's 584
|
||||||
// solar radii are 2.7 AU — or than the empty framing. Its disc is a sphere's, whose silhouette
|
// solar radii are 2.7 AU — or than the empty framing. Its disc is a sphere's, whose silhouette
|
||||||
// from d subtends asin(R / d): the distance that makes it the fraction above of the view.
|
// from d subtends asin(R / d): the distance that makes it the fraction above of the view.
|
||||||
const star = starRadiusAu * Math.sqrt(1 + 1 / (STAR_FRAME_FRACTION * tightHalfExtent(viewport)) ** 2);
|
const star = starRadiusAu * Math.sqrt(1 + 1 / (starFrameFraction(viewport) * tightHalfExtent(viewport)) ** 2);
|
||||||
if (!Number.isFinite(framedRadiusAu) || framedRadiusAu <= 0) {
|
if (!Number.isFinite(framedRadiusAu) || framedRadiusAu <= 0) {
|
||||||
return Math.max(EMPTY_SYSTEM_FRAMING_DISTANCE_AU, star);
|
return Math.max(EMPTY_SYSTEM_FRAMING_DISTANCE_AU, star);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user