Answer the review: size the rings to the band the frame covers, and clear the text
The rings are centred on the Sun and the frame need not be. Sizing their step from how far the frame reaches — 210 pc for a star at 190 with the camera 20 pc back — gives 20 pc rings at 180 and 200, both outside a frame 19 pc deep, so a view away from the Sun still had no ring on it and no ladder of labels either. `distanceRings` now takes the span the frame covers rather than its far edge, and the step is a fifth of that: 5 pc rings from 165 to 210 for the same view. Measured in the app, centred on a star 187 pc out in the galactic plane: 4 ring labels drawn 20 pc above the plane and 7 from 2 pc, against 1 and none before. The clearance was a radius around the anchor, and a label is a line of text hanging 135 px to one side of its anchor: at 0.065 NDC apart, past the radius, "50 pc" printed inside "Alpha Centauri". It is now tested against the span the name occupies, on the side it hangs, with the radius kept for the pair whose text runs the other way. Also from the review: the ladder in the clearance test was built at exactly the constant it tests, so 1057 of 2000 camera poses would have decided it by float round-trip error — the rungs now sit 0.02 either side of the rule. And two comments that were wrong: a frame one step short of the survey edge does get its callout, and CSS2DRenderer hides a label behind the camera rather than drawing it at the page edge. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
@@ -72,8 +72,9 @@ const LABEL_MIN_SEPARATION_NDC = 0.12;
|
||||
const LABEL_EDGE_NDC = 0.7;
|
||||
/**
|
||||
* How far a ring label has to sit from a star's name, in NDC — half what two star names keep
|
||||
* between them. A ring label is one short line, and the rungs of its ladder are a twentieth of the
|
||||
* screen apart, so the full separation would have one name clear three rungs.
|
||||
* between them, as a clearance around the anchor and as the height of the row its text occupies.
|
||||
* A ring label is one short line, and the rungs of its ladder are a twentieth of the screen apart,
|
||||
* so the full separation would have one name clear three rungs.
|
||||
*/
|
||||
const RING_LABEL_CLEARANCE_NDC = LABEL_MIN_SEPARATION_NDC / 2;
|
||||
/** How far right of its point a label's text reaches, in aspect-scaled NDC (~135px at 1440). */
|
||||
@@ -621,7 +622,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
centre: new THREE.Vector3(centre.x, centre.y, centre.z),
|
||||
emphasisRadii: [SUN_GALACTOCENTRIC_RADIUS_PC]
|
||||
});
|
||||
this.setLocalGridRadii(distanceRings(GALAXY_OVERVIEW_POSITION.length(), LOCAL_GRID_RING_COUNT, SURVEY_EDGE_PC));
|
||||
this.setLocalGridRadii(distanceRings(0, GALAXY_OVERVIEW_POSITION.length(), LOCAL_GRID_RING_COUNT, SURVEY_EDGE_PC));
|
||||
// A fixed set rather than whatever is currently labelled: a tether that appears and vanishes
|
||||
// as the camera drifts reads as a glitch.
|
||||
this.tethers = new TetherField(TETHERED_STAR_COUNT);
|
||||
@@ -925,7 +926,13 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
* Not held apart from each other, as the star names are: they are a ladder up one ray, a few
|
||||
* hundredths of the screen apart, and reading them in order is the point. What they must not do
|
||||
* is sit on a star's name, which is worth more than a distance — or be handed to the overlay
|
||||
* from behind or beside the camera, which draws them at the edge of the page rather than not at all.
|
||||
* from beside the camera, which CSS2DRenderer places past the edge of the container rather than
|
||||
* hiding, since all it tests is depth.
|
||||
*
|
||||
* A name is a line of text hanging to one side of its point, about 135 px of it, not the point:
|
||||
* two anchors a tenth of the screen apart still print one inside the other. So the test is
|
||||
* against the span the name occupies, with the anchors' own clearance kept for the pair whose
|
||||
* text runs the other way.
|
||||
*/
|
||||
private ringLabelsInTheClear(candidates: readonly LabeledPoint[], camera: SceneCamera, stars: readonly LabeledPoint[]): LabeledPoint[] {
|
||||
const projected = new THREE.Vector3();
|
||||
@@ -934,10 +941,21 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
const outside = projected.z < -1 || projected.z > 1 || Math.abs(projected.x) > 1 || Math.abs(projected.y) > 1;
|
||||
return outside ? null : new THREE.Vector2(projected.x * this.viewportAspect(), projected.y);
|
||||
};
|
||||
const taken = stars.map(onScreen).filter((point): point is THREE.Vector2 => point !== null);
|
||||
const taken = stars
|
||||
.map((star) => ({ at: onScreen(star), side: star.side }))
|
||||
.filter((name): name is { at: THREE.Vector2; side: LabelSide | undefined } => name.at !== null)
|
||||
.map(({ at, side }) => ({ at, from: side === 'left' ? at.x - LABEL_REACH_NDC : at.x, to: side === 'left' ? at.x : at.x + LABEL_REACH_NDC }));
|
||||
return candidates.filter((label) => {
|
||||
const point = onScreen(label);
|
||||
return point !== null && !taken.some((other) => other.distanceTo(point) < RING_LABEL_CLEARANCE_NDC);
|
||||
// Ring labels hang right, as `applyPresentation` leaves anything with no side of its own.
|
||||
return (
|
||||
point !== null &&
|
||||
!taken.some(
|
||||
(name) =>
|
||||
name.at.distanceTo(point) < RING_LABEL_CLEARANCE_NDC ||
|
||||
(Math.abs(name.at.y - point.y) < RING_LABEL_CLEARANCE_NDC && name.from < point.x + LABEL_REACH_NDC && point.x < name.to)
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -962,7 +980,12 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
||||
// vertex of the new ones, and the set changes on any zoom that crosses a round step.
|
||||
if (!isGalactic && this.display().grid) {
|
||||
const orbitPc = this.engine.currentProjection === 'perspective' ? camera.position.distanceTo(target) : this.effectiveDistance(camera);
|
||||
const radii = distanceRings(target.length() + orbitPc, LOCAL_GRID_RING_COUNT, SURVEY_EDGE_PC);
|
||||
// Half the frame's diagonal, at the depth it is centred on: how near the Sun the frame
|
||||
// reaches, as well as how far. A step sized to the far edge alone is no use to a frame that
|
||||
// does not contain the Sun — 20 pc rings for a view of a 19 pc band at 190 pc drew none of
|
||||
// them on screen, and the ladder of labels went with them.
|
||||
const frameRadiusPc = this.engine.visibleHalfHeight(orbitPc) * Math.hypot(1, this.viewportAspect());
|
||||
const radii = distanceRings(Math.max(0, target.length() - frameRadiusPc), target.length() + orbitPc, LOCAL_GRID_RING_COUNT, SURVEY_EDGE_PC);
|
||||
if (radii.join() !== this.localGridRadii.join()) {
|
||||
this.setLocalGridRadii(radii);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user