From e808f50faa868583fe2622c13bf29c8c21fe2765 Mon Sep 17 00:00:00 2001 From: Senrokai Date: Fri, 21 Aug 2026 15:42:37 +0200 Subject: [PATCH] Answer the review: the plan view clipped a system, and pulled its neighbours inward MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two more findings against this projection, both of the same shape as the last two: something written to the camera that happens to be live, where the plan view derives from the other one. The system's own depth range — a near plane a five-hundredth of an astronomical unit out, a far plane twenty thousand — was set on the active camera. Entering a system with the plan view already on therefore wrote it to a camera that re-derives near and far from the perspective one every frame, so the range never applied and the system clipped. All three unit-space depth writes go to the perspective camera now, which is the one they are reasoned in. And the ring of neighbour names collapsed toward the middle of the frame. Its placement unprojected a point on the ring, treated the offset from the camera as a direction, and stepped a fixed distance along it — which is a perspective construction. A parallel projection has no vanishing point to step towards: every ray through the frame is the view direction, so normalising threw the sideways part away. Measured before and after, from inside Sol: the two names sat 319 and 335 pixels out under perspective, 104 and gone under the plan, and 323 and 335 with the unprojected point used as what it already is. Verified: build clean, 596/596 unit, 16/16 end-to-end on the branch this merges into, and the ring measured on both projections. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi --- .../galaxy-system-scene.component.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/app/features/galaxy-system/galaxy-system-scene.component.ts b/src/app/features/galaxy-system/galaxy-system-scene.component.ts index dfe04be..f3fb17a 100644 --- a/src/app/features/galaxy-system/galaxy-system-scene.component.ts +++ b/src/app/features/galaxy-system/galaxy-system-scene.component.ts @@ -465,9 +465,13 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { const scene = this.engine.getScene(); const camera = this.engine.getCamera(); camera.position.copy(GALAXY_OVERVIEW_POSITION); - camera.near = GALAXY_NEAR_PC; - camera.far = GALAXY_FAR_PC; - camera.updateProjectionMatrix(); + // The perspective camera whichever one is live: it is where the depth range is reasoned, + // and the plan view re-derives its own from it every frame. Writing to the active camera + // put the astronomical-unit range on one that overwrites it, and the system clipped. + const depthCamera = this.engine.getPerspectiveCamera(); + depthCamera.near = GALAXY_NEAR_PC; + depthCamera.far = GALAXY_FAR_PC; + depthCamera.updateProjectionMatrix(); this.controls = new OrbitControls(camera, canvas); this.controls.enableDamping = true; @@ -902,6 +906,13 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { if (!placed) { return null; } + if (this.engine.currentProjection === 'orthographic') { + // A parallel projection has no vanishing point to walk towards: every ray through the + // frame is the view direction, so treating the unprojected offset as one and stepping + // along it throws the sideways part away and pulls the whole ring into the middle. The + // unprojected point is already where the name goes. + return this.ringPoint.set(placed.x, placed.y, 0).unproject(camera); + } const along = this.ringPoint.set(placed.x, placed.y, 0.5).unproject(camera).sub(camera.position).normalize(); return along.multiplyScalar(NEIGHBOUR_DEPTH_AU).add(camera.position); } @@ -1481,9 +1492,13 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { // clumped over the system's star. this.labelOverlay?.update([]); - camera.near = SYSTEM_NEAR_AU; - camera.far = SYSTEM_FAR_AU; - camera.updateProjectionMatrix(); + // The perspective camera whichever one is live: it is where the depth range is reasoned, + // and the plan view re-derives its own from it every frame. Writing to the active camera + // put the astronomical-unit range on one that overwrites it, and the system clipped. + const depthCamera = this.engine.getPerspectiveCamera(); + depthCamera.near = SYSTEM_NEAR_AU; + depthCamera.far = SYSTEM_FAR_AU; + depthCamera.updateProjectionMatrix(); this.controls!.minDistance = SYSTEM_MIN_DISTANCE_AU; this.controls!.maxDistance = SYSTEM_MAX_DISTANCE_AU; @@ -1532,9 +1547,13 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy { // into the next system entered. this.clearObjectCard(); - camera.near = GALAXY_NEAR_PC; - camera.far = GALAXY_FAR_PC; - camera.updateProjectionMatrix(); + // The perspective camera whichever one is live: it is where the depth range is reasoned, + // and the plan view re-derives its own from it every frame. Writing to the active camera + // put the astronomical-unit range on one that overwrites it, and the system clipped. + const depthCamera = this.engine.getPerspectiveCamera(); + depthCamera.near = GALAXY_NEAR_PC; + depthCamera.far = GALAXY_FAR_PC; + depthCamera.updateProjectionMatrix(); this.controls!.minDistance = GALAXY_MIN_DISTANCE_PC; this.controls!.maxDistance = GALAXY_MAX_DISTANCE_PC;