Answer the review: the plan view was flat against the wrong plane
Two findings, both right, and the second was the feature not doing what its own commit message said. The depth range stopped being updated under a plan view. It is worked out in perspective terms — near from the distance, far from eight times it — and the orthographic camera derives its own range from that one, so skipping the calculation left the far plane wherever it had been when the projection changed. Flying out to the whole Galaxy from a plan view clipped away most of it. The range is written to the perspective camera whichever one is live now, and the plan view goes on deriving from it every frame. And the galaxy-scale plan looked down the celestial pole. "The plane the current scale is read against" is this system's orbital plane inside a system, and the galactic plane outside one — but the fallback was the scene's own z, which is the Earth's rotation axis. The normal is the north galactic pole now, and up is the direction of the galactic centre, so a plan of the Galaxy is laid out the way the model that draws it is described. The arms are face-on. Verified: build clean, 596/596 unit, 16/16 end-to-end, and the Milky Way photographed flat from 28.3 kpc with nothing clipped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
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();
|
||||||
|
|||||||
Reference in New Issue
Block a user