release: bump version to 0.41.2

This commit is contained in:
rayd1o
2026-04-27 23:22:04 +08:00
parent 655e2a7d2d
commit eeee788530
14 changed files with 265 additions and 25 deletions

View File

@@ -21,7 +21,11 @@ let cableIdMap = new Map();
let cableStates = new Map();
let cablesVisible = true;
let landingPointTexture = null;
const _lpEarthWorldPos = new THREE.Vector3();
const _lpWorldPos = new THREE.Vector3();
const _lpCameraRel = new THREE.Vector3();
const _lpPointRel = new THREE.Vector3();
const _lpCameraToPoint = new THREE.Vector3();
function createLandingPointTexture() {
const size = CABLE_CONFIG.landingPoint.textureSize;
@@ -460,7 +464,7 @@ export async function loadLandingPoints(scene, earthObj, options = {}) {
color: CABLE_CONFIG.landingPoint.color,
transparent: true,
opacity: CABLE_CONFIG.landingPoint.opacity,
depthTest: true,
depthTest: false,
depthWrite: false,
}),
);
@@ -597,16 +601,40 @@ export function getAllLandingPoints() {
return landingPoints;
}
// Hide pins within ~3° of the limb (normalised dot < 0.05) to prevent the
// "half-clipped-into-globe" look caused by depthTest clipping the billboard
// against closer earth-surface geometry near the horizon.
const _FACING_DOT_MIN_SQ = 0.05 * 0.05;
function isFacingCamera(lp, camera) {
lp.getWorldPosition(_lpWorldPos);
const d = _lpWorldPos.dot(camera.position);
if (d <= 0) return false;
return d * d > _FACING_DOT_MIN_SQ * _lpWorldPos.lengthSq() * camera.position.lengthSq();
if (lp.parent) {
lp.parent.getWorldPosition(_lpEarthWorldPos);
} else {
_lpEarthWorldPos.set(0, 0, 0);
}
_lpCameraRel.copy(camera.position).sub(_lpEarthWorldPos);
_lpPointRel.copy(_lpWorldPos).sub(_lpEarthWorldPos);
_lpCameraToPoint.subVectors(_lpPointRel, _lpCameraRel);
const distanceSq = _lpCameraToPoint.lengthSq();
if (distanceSq <= 0) return true;
const distance = Math.sqrt(distanceSq);
_lpCameraToPoint.multiplyScalar(1 / distance);
// The pin sprite is rendered without depth testing so its full shape does
// not get sliced by the globe. Instead, hide it when the camera-to-anchor
// segment is occluded by a slightly inflated globe, matching the behavior of
// the BGP and compute-center markers near the limb.
const occlusionRadius =
CONFIG.earthRadius + CABLE_CONFIG.landingPoint.altitudeOffset * 0.45;
const cameraProjection = _lpCameraRel.dot(_lpCameraToPoint);
const cameraRadiusSq = _lpCameraRel.lengthSq();
const discriminant =
cameraProjection * cameraProjection -
(cameraRadiusSq - occlusionRadius * occlusionRadius);
if (discriminant < 0) return true;
const nearestIntersection = -cameraProjection - Math.sqrt(discriminant);
return nearestIntersection <= 0 || nearestIntersection >= distance;
}
export function applyLandingPointVisualState(lockedCableName, dimAll = false, camera = null) {

View File

@@ -3399,6 +3399,12 @@ function animate() {
}
}
// Force matrixWorld to be current before any isFacingCamera checks below.
// Without this, getWorldPosition() on LP sprites reads the previous frame's
// transform (earth rotation is updated above but scene.updateMatrixWorld()
// only runs inside renderer.render(), which hasn't been called yet).
earth?.updateMatrixWorld(true);
applyCableVisualState();
const activeCruiseMarker =
isCruiseModeActive() && isCruisePresentationPinned()