Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/universe-labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ A path's opacity follows from that:
| Any path in an overview, or one the reader points at | Softens to 30% up close; never dimmed by family. |

The outside dimming eases away between half and twice the host's distance from
its star as the camera pulls back; a moons view holds it. Viewport clipping,
its star as the camera pulls back, in a moons view too. Viewport clipping,
body occlusion, the system's distance fade, the on-screen size fade and explicit
hidden-orbit settings still apply.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,13 @@ test('the close-up fades keep a floor for context and none for the focused body\
assert.deepEqual(closeOrbitFades(fade, .5), { context: .3, own: 0 });
});

test('the dimming outside a family relaxes as the camera reaches the parent system, and a moons view holds it', () => {
test('the dimming outside a family relaxes as the camera reaches the parent system', () => {
const host = { positionM: [10, 0, 0], orbit: { centerPositionM: [0, 0, 0] } };
const at = (distance: number, held = false) => outsideFamilyOrbitOpacity(host, () => distance, held);
const at = (distance: number) => outsideFamilyOrbitOpacity(host, () => distance);
assert.deepEqual([at(1), at(5), at(20), at(100)], [.25, .25, 1, 1]);
assert.ok(at(10) > .25 && at(10) < 1);
assert.equal(at(100, true), .25);
assert.equal(outsideFamilyOrbitOpacity(undefined, () => 1, false), 1);
assert.equal(outsideFamilyOrbitOpacity({ positionM: [0, 0, 0] }, () => 1, false), 1);
assert.equal(outsideFamilyOrbitOpacity(undefined, () => 1), 1);
assert.equal(outsideFamilyOrbitOpacity({ positionM: [0, 0, 0] }, () => 1), 1);
});

test('a moon is named inside the kept families, and beside a planet or a moon its star\'s minor bodies are not', () => {
Expand Down
9 changes: 5 additions & 4 deletions packages/renderer/src/universe/context-presentation-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export interface ContextSubject {
readonly families: ReadonlySet<string>;
/** A selected body's own page, with no flight: a host's page fades its moons' paths with the host. */
readonly page: boolean;
/** A moons view is selected: the dimming outside the family holds at every range, the host's own path included. */
/** A moons view is selected: it holds its host as the subject, and the dimming outside the family includes the host's
* own path. */
readonly held: boolean;
/** Beside the page of a body that is not a system's star, that star's minor bodies are not named. */
readonly quietMinors: boolean;
Expand Down Expand Up @@ -69,13 +70,13 @@ export function closeOrbitFades(fade: OrbitLineFade, discHeightShare: number) {
/** An orbit belongs to the family it circles, not the body travelling on it, so a path outside the subject's family is
* dim context: the other planets', and the host's own path about its star when the subject is not the host itself.
* `host` is the family's host, absent without an emphasised body or when it is a star. The dimming relaxes from half to
* twice the host's distance from its star, where the subject is the parent system again; a moons view holds it.
* twice the host's distance from its star, where the view is the parent system again; a moons view relaxes it too.
* 1/64 steps avoid rewriting opacity for tiny camera changes. */
export function outsideFamilyOrbitOpacity<Host extends { readonly positionM: readonly number[]; readonly orbit?: { readonly centerPositionM: readonly number[] } | null }>(
host: Host | undefined, cameraDistanceM: (host: Host) => number, held: boolean): number {
host: Host | undefined, cameraDistanceM: (host: Host) => number): number {
if (!host?.orbit) return 1;
const radiusM = Math.hypot(...host.positionM.map((value, axis) => value - host.orbit!.centerPositionM[axis]!));
const t = held || !(radiusM > 0) ? 0 : Math.max(0, Math.min(1, Math.log2(cameraDistanceM(host) / (.5 * radiusM)) / 2));
const t = !(radiusM > 0) ? 0 : Math.max(0, Math.min(1, Math.log2(cameraDistanceM(host) / (.5 * radiusM)) / 2));
return 1 - (1 - OUTSIDE_FAMILY_OPACITY) * Math.round((1 - t * t * (3 - 2 * t)) * 64) / 64;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export function createWorldContextPlanner(plan: PreparedWorldContext | PreparedW
Math.abs(selectedY) < height / 2 + focusDiameter / 2;
const focusShare = focusInView ? focusDiameter / height : 0;
const fades = closeOrbitFades(plan.camera.presentation.orbitLineFade, focusShare);
const outsideFamily = outsideFamilyOrbitOpacity(subject.id === null || subject.hostIsStar ? undefined : byId.get(subject.hostId), host => Math.hypot(...frame.eye(host)), subject.held);
const outsideFamily = outsideFamilyOrbitOpacity(subject.id === null || subject.hostIsStar ? undefined : byId.get(subject.hostId), host => Math.hypot(...frame.eye(host)));
const highlighting = bodies.some(entry => entry.highlighted === true), otherSystems = otherSystemsOpacity(systemFade.of(0), starFieldFade(focusDistanceM, plan.system));
const near = opacity > 0
? Math.max(1, Math.min(...bodies.map(entry => Math.hypot(...frame.eye(entry.body)))) * 0.01) : 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ test('a selected satellite overview retains its selected host locator', () => {
const close = orbits(view), closePlain = orbits({ ...view, overviewSelection: false });
const dimmed = orbits(wide), plain = orbits({ ...wide, overviewSelection: false });
const hovered = orbits({ ...wide, bodies: wide.bodies.map((body, at) => at === index('saturn') ? { ...body, hovered: true } : body) });
for (const id of ['saturn', 'jupiter']) assert.deepEqual([dimmed.get(id), plain.get(id), hovered.get(id)], [.25, 1, id === 'saturn' ? 1 : .25], id);
assert.equal(close.get('jupiter'), .25);
// On the host's own page its path is the subject's and stays whole; the other planets' paths are still context.
const page = orbits({ ...wide, overview: false, overviewSelection: false });
const page = orbits({ ...wide, overview: false, overviewSelection: false }), relaxed = page.get('saturn')!;
assert.equal(page.get('jupiter'), 1);
assert.ok(page.get('saturn')! >= .25 && page.get('saturn')! < 1);
assert.ok(relaxed > .25 && relaxed < 1);
// Pulled back towards the parent system, the moons view relaxes the dimming as the page does, the host's own path with it.
for (const id of ['saturn', 'jupiter']) assert.deepEqual([dimmed.get(id), plain.get(id), hovered.get(id)], [relaxed, 1, id === 'saturn' ? 1 : relaxed], id);
assert.ok(moons.some(id => close.get(id) === 1));
for (const id of moons) assert.equal(close.get(id), closePlain.get(id), id);
// One emphasis per body: a highlighted set dims the rest, another star's bodies read as not belonging inside the
Expand Down
Loading