Add player onboarding and tactical viewer updates

This commit is contained in:
2026-04-06 17:12:44 -04:00
parent 706e1cda8f
commit 63a9f808bb
52 changed files with 2699 additions and 577 deletions

View File

@@ -16,8 +16,6 @@ import {
updateSystemStarPresentation,
getAnimatedShipLocalPosition,
iconWorldScale,
MIN_ICON_PIXELS,
MAX_ICON_PIXELS,
} from "./viewerPresentation";
import { rawObject } from "./viewerScenePrimitives";
import type {
@@ -42,6 +40,13 @@ import type {
type SummaryIconKind = "ship" | "station" | "structure";
const SHIP_BILLBOARD_HIDE_DISTANCE = 0.003;
const SHIP_BILLBOARD_FULL_DISTANCE = 0.018;
const SHIP_BILLBOARD_MIN_PIXELS = 34;
const SHIP_BILLBOARD_MAX_PIXELS = 82;
const STATION_ICON_MIN_PIXELS = 28;
const STATION_ICON_MAX_PIXELS = 72;
export interface WorldOrbitalContext {
world?: WorldState;
worldTimeSyncMs: number;
@@ -53,6 +58,7 @@ export interface WorldOrbitalContext {
export interface WorldPresentationContext extends WorldOrbitalContext {
activeSystemId?: string;
cameraMode: CameraMode;
povLevel: PovLevel;
orbitYaw: number;
camera: THREE.PerspectiveCamera;
@@ -95,14 +101,22 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
const shipVisible = isShipVisible(renderMode, context.activeSystemId, ship);
const distToShip = context.camera.position.distanceTo(displayPosition);
const useTacticalIcon = renderMode !== "local" || distToShip > 0.012;
const billboardOpacity = context.cameraMode === "tactical"
? 1
: THREE.MathUtils.clamp(
(distToShip - SHIP_BILLBOARD_HIDE_DISTANCE) / (SHIP_BILLBOARD_FULL_DISTANCE - SHIP_BILLBOARD_HIDE_DISTANCE),
0,
1,
);
const useTacticalIcon = context.cameraMode === "tactical" || billboardOpacity > 0.01;
const iconScale = THREE.MathUtils.clamp(
visual.iconBaseScale,
iconWorldScale(distToShip, context.camera, MIN_ICON_PIXELS),
iconWorldScale(distToShip, context.camera, MAX_ICON_PIXELS + 10),
iconWorldScale(distToShip, context.camera, SHIP_BILLBOARD_MIN_PIXELS),
iconWorldScale(distToShip, context.camera, SHIP_BILLBOARD_MAX_PIXELS),
);
visual.icon.setScaleScalar(iconScale);
visual.mesh.setVisible(shipVisible && !useTacticalIcon);
visual.icon.setOpacity(shipVisible ? billboardOpacity : 0);
visual.mesh.setVisible(shipVisible && context.cameraMode !== "tactical" && billboardOpacity < 0.98);
visual.icon.setVisible(shipVisible && useTacticalIcon);
const desiredHeading = resolveShipHeading(visual, worldPosition, context.orbitYaw);
if (desiredHeading.lengthSq() > 0.01) {
@@ -135,9 +149,19 @@ export function updateWorldPresentation(context: WorldPresentationContext) {
for (const visual of context.stationVisuals.values()) {
const animatedLocalPosition = resolveStructureAnimatedLocalPosition(context, visual, worldTimeSeconds);
visual.mesh.setPosition(context.toDisplayLocalPosition(animatedLocalPosition));
const displayPosition = context.toDisplayLocalPosition(animatedLocalPosition);
visual.mesh.setPosition(displayPosition);
visual.icon.setPosition(rawObject(visual.mesh).position.clone());
visual.mesh.setVisible(visual.systemId === context.activeSystemId);
const stationVisible = visual.systemId === context.activeSystemId;
const distToStation = context.camera.position.distanceTo(displayPosition);
const stationIconScale = THREE.MathUtils.clamp(
130,
iconWorldScale(distToStation, context.camera, STATION_ICON_MIN_PIXELS),
iconWorldScale(distToStation, context.camera, STATION_ICON_MAX_PIXELS),
);
visual.icon.setScaleScalar(stationIconScale);
visual.icon.setVisible(stationVisible);
visual.mesh.setVisible(stationVisible && renderMode === "local" && context.cameraMode !== "tactical");
}
for (const visual of context.claimVisuals.values()) {