Merge branch 'feat/hud-2d' into feat/hud-about
This commit is contained in:
@@ -4,7 +4,13 @@ import * as THREE from 'three/webgpu';
|
|||||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
|
||||||
import { dateToJulianDate } from '../../shared/astro/constants';
|
import { dateToJulianDate } from '../../shared/astro/constants';
|
||||||
import { galacticCentrePositionPc, galacticToEquatorial, MILKY_WAY_ARMS, SUN_GALACTOCENTRIC_RADIUS_PC } from '../../shared/astro/galaxy';
|
import {
|
||||||
|
GALACTIC_BASIS_EQUATORIAL,
|
||||||
|
MILKY_WAY_ARMS,
|
||||||
|
SUN_GALACTOCENTRIC_RADIUS_PC,
|
||||||
|
galacticCentrePositionPc,
|
||||||
|
galacticToEquatorial
|
||||||
|
} from '../../shared/astro/galaxy';
|
||||||
import { DataLoaderService } from '../../core/data/data-loader.service';
|
import { DataLoaderService } from '../../core/data/data-loader.service';
|
||||||
import { EngineService, SceneCamera } from '../../core/engine/engine.service';
|
import { EngineService, SceneCamera } from '../../core/engine/engine.service';
|
||||||
import { BodyRecord } from '../../shared/models/body.model';
|
import { BodyRecord } from '../../shared/models/body.model';
|
||||||
@@ -630,7 +636,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
// from inside it, so it cannot also be the sky behind a view of the Galaxy from outside.
|
// from inside it, so it cannot also be the sky behind a view of the Galaxy from outside.
|
||||||
this.engine.getScene().backgroundIntensity = display.sky ? 1 - this.galacticStrength : 0;
|
this.engine.getScene().backgroundIntensity = display.sky ? 1 - this.galacticStrength : 0;
|
||||||
|
|
||||||
this.applyGalaxyDepthRange(camera, distancePc);
|
this.applyGalaxyDepthRange(distancePc);
|
||||||
const level: ViewLevel = this.galacticStrength >= GALACTIC_LEVEL_THRESHOLD ? 'galactic' : 'galaxy';
|
const level: ViewLevel = this.galacticStrength >= GALACTIC_LEVEL_THRESHOLD ? 'galactic' : 'galaxy';
|
||||||
if (this.navigationStore.viewLevel() !== level && !this.systemGroup.visible) {
|
if (this.navigationStore.viewLevel() !== level && !this.systemGroup.visible) {
|
||||||
this.navigationStore.setViewLevel(level);
|
this.navigationStore.setViewLevel(level);
|
||||||
@@ -657,19 +663,22 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
return halfHeight / Math.tan((this.engine.getPerspectiveCamera().fov * Math.PI) / 360);
|
return halfHeight / Math.tan((this.engine.getPerspectiveCamera().fov * Math.PI) / 360);
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyGalaxyDepthRange(camera: SceneCamera, distancePc: number): void {
|
private applyGalaxyDepthRange(distancePc: number): void {
|
||||||
// The plan view sets its own depth range, symmetric about the camera; see `frameOrthographic`.
|
|
||||||
if (this.engine.currentProjection === 'orthographic') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const near = THREE.MathUtils.clamp(distancePc / 2000, GALAXY_NEAR_PC, GALACTIC_NEAR_PC);
|
const near = THREE.MathUtils.clamp(distancePc / 2000, GALAXY_NEAR_PC, GALACTIC_NEAR_PC);
|
||||||
const far = THREE.MathUtils.clamp(distancePc * 8, GALAXY_FAR_PC, GALACTIC_FAR_PC);
|
const far = THREE.MathUtils.clamp(distancePc * 8, GALAXY_FAR_PC, GALACTIC_FAR_PC);
|
||||||
|
// Written to the perspective camera whichever one is live, because it is the one this range
|
||||||
|
// is reasoned in and the one `frameOrthographic` reads its own from. Skipping it under a plan
|
||||||
|
// view left the far plane wherever it was when the projection changed, so flying out to the
|
||||||
|
// Galaxy from there clipped away most of it.
|
||||||
|
const perspective = this.engine.getPerspectiveCamera();
|
||||||
// Only when it has drifted enough to matter, so a slow zoom isn't rebuilding the projection
|
// Only when it has drifted enough to matter, so a slow zoom isn't rebuilding the projection
|
||||||
// matrix on every frame of it.
|
// matrix on every frame of it.
|
||||||
if (Math.abs(near - camera.near) > camera.near * 0.05 || Math.abs(far - camera.far) > camera.far * 0.05) {
|
if (Math.abs(near - perspective.near) > perspective.near * 0.05 || Math.abs(far - perspective.far) > perspective.far * 0.05) {
|
||||||
camera.near = near;
|
perspective.near = near;
|
||||||
camera.far = far;
|
perspective.far = far;
|
||||||
camera.updateProjectionMatrix();
|
perspective.updateProjectionMatrix();
|
||||||
|
// The plan view's own range is symmetric about the camera and derived from this one; see
|
||||||
|
// `frameOrthographic`. It is re-derived every frame, so there is nothing to do here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,9 +1083,22 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
|
|
||||||
if (plan) {
|
if (plan) {
|
||||||
// Straight down the plane's normal, from where the camera already was.
|
// Straight down the plane's normal, from where the camera already was.
|
||||||
const normal = this.systemGroup.visible && this.systemRenderer ? new THREE.Vector3(0, 0, 1).applyQuaternion(this.systemRenderer.referenceFrame) : new THREE.Vector3(0, 0, 1);
|
// Down the normal of the plane this scale is actually read against. Inside a system that
|
||||||
|
// is the system's own orbital plane; outside it, the galactic plane — whose normal is the
|
||||||
|
// north galactic pole, not the celestial one. Defaulting to the scene's own z would have
|
||||||
|
// looked down the Earth's rotation axis and called it the plane of the Galaxy.
|
||||||
|
const galactic = GALACTIC_BASIS_EQUATORIAL;
|
||||||
|
const inSystem = this.systemGroup.visible && this.systemRenderer;
|
||||||
|
const normal = inSystem
|
||||||
|
? new THREE.Vector3(0, 0, 1).applyQuaternion(this.systemRenderer!.referenceFrame)
|
||||||
|
: new THREE.Vector3(galactic.z.x, galactic.z.y, galactic.z.z);
|
||||||
next.position.copy(target).add(normal.multiplyScalar(distance));
|
next.position.copy(target).add(normal.multiplyScalar(distance));
|
||||||
next.up.set(0, 1, 0).applyQuaternion(this.systemGroup.visible && this.systemRenderer ? this.systemRenderer.referenceFrame : new THREE.Quaternion());
|
if (inSystem) {
|
||||||
|
next.up.set(0, 1, 0).applyQuaternion(this.systemRenderer!.referenceFrame);
|
||||||
|
} else {
|
||||||
|
// Towards the galactic centre, so the plan is oriented the way the model is described.
|
||||||
|
next.up.set(galactic.x.x, galactic.x.y, galactic.x.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next.lookAt(target);
|
next.lookAt(target);
|
||||||
this.controls.update();
|
this.controls.update();
|
||||||
|
|||||||
@@ -210,6 +210,21 @@ describe('HudDockComponent', () => {
|
|||||||
expect(host().querySelector('[aria-label="Forget Sirius"]')?.getAttribute('aria-pressed')).toBe('true');
|
expect(host().querySelector('[aria-label="Forget Sirius"]')?.getAttribute('aria-pressed')).toBe('true');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the one system whose catalogue id is zero, which truthiness would have lost', () => {
|
||||||
|
// The Sun is star 0. A `@if (id; as ...)` reads that as "no star" and hides the control.
|
||||||
|
const bookmarks = TestBed.inject(BookmarksStore);
|
||||||
|
setReadout();
|
||||||
|
fixture.componentRef.setInput('title', 'Sol');
|
||||||
|
fixture.componentRef.setInput('keepableStarId', 0);
|
||||||
|
fixture.componentRef.setInput('defaultTab', 'readout');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
host().querySelector<HTMLButtonElement>('[aria-label="Keep Sol"]')?.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(bookmarks.has('star', 0)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('offers nothing to keep where the readout is a scale rather than a place', () => {
|
it('offers nothing to keep where the readout is a scale rather than a place', () => {
|
||||||
setReadout();
|
setReadout();
|
||||||
fixture.componentRef.setInput('defaultTab', 'readout');
|
fixture.componentRef.setInput('defaultTab', 'readout');
|
||||||
|
|||||||
@@ -94,16 +94,19 @@ function isWideViewport(): boolean {
|
|||||||
<p class="type-label text-muted">{{ eyebrow() }}</p>
|
<p class="type-label text-muted">{{ eyebrow() }}</p>
|
||||||
<div class="mt-1 flex items-start gap-2">
|
<div class="mt-1 flex items-start gap-2">
|
||||||
<p data-testid="hud-title" class="min-w-0 flex-1 text-lg font-bold tracking-[0.04em] text-text uppercase">{{ title() }}</p>
|
<p data-testid="hud-title" class="min-w-0 flex-1 text-lg font-bold tracking-[0.04em] text-text uppercase">{{ title() }}</p>
|
||||||
@if (keepableStarId(); as starId) {
|
<!-- Against null, not against falsiness: the Sun's catalogue id is 0, and a
|
||||||
|
truthiness test is what would quietly make the Solar System the one
|
||||||
|
system nobody could keep. -->
|
||||||
|
@if (keepableStarId() !== null) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
[attr.aria-label]="(bookmarks.has('star', starId) ? 'Forget ' : 'Keep ') + title()"
|
[attr.aria-label]="(bookmarks.has('star', keepableStarId()!) ? 'Forget ' : 'Keep ') + title()"
|
||||||
[attr.aria-pressed]="bookmarks.has('star', starId)"
|
[attr.aria-pressed]="bookmarks.has('star', keepableStarId()!)"
|
||||||
(click)="bookmarks.toggle({ kind: 'star', id: starId, name: title() })"
|
(click)="bookmarks.toggle({ kind: 'star', id: keepableStarId()!, name: title() })"
|
||||||
class="shrink-0 p-1 transition-colors focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-accent"
|
class="shrink-0 p-1 transition-colors focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-accent"
|
||||||
[class]="bookmarks.has('star', starId) ? 'text-accent' : 'text-muted hover:text-accent'"
|
[class]="bookmarks.has('star', keepableStarId()!) ? 'text-accent' : 'text-muted hover:text-accent'"
|
||||||
>
|
>
|
||||||
<app-bookmark-icon class="h-3.5 w-3.5" [kept]="bookmarks.has('star', starId)" />
|
<app-bookmark-icon class="h-3.5 w-3.5" [kept]="bookmarks.has('star', keepableStarId()!)" />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user