release: bump version to 0.41.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,56 @@ export let lockedCable = null;
|
||||
let cableIdMap = new Map();
|
||||
let cableStates = new Map();
|
||||
let cablesVisible = true;
|
||||
let landingPointGeometry = null;
|
||||
let landingPointTexture = null;
|
||||
const _lpWorldPos = new THREE.Vector3();
|
||||
|
||||
function createLandingPointTexture() {
|
||||
const size = CABLE_CONFIG.landingPoint.textureSize;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext("2d");
|
||||
const iconPath = new Path2D(
|
||||
[
|
||||
"M400 704",
|
||||
"C386 704 375 697 367 684",
|
||||
"L173 378",
|
||||
"C117 290 144 173 229 111",
|
||||
"C278 75 337 57 400 57",
|
||||
"C463 57 522 75 571 111",
|
||||
"C656 173 683 290 627 378",
|
||||
"L433 684",
|
||||
"C425 697 414 704 400 704",
|
||||
"Z",
|
||||
].join(" "),
|
||||
);
|
||||
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.save();
|
||||
ctx.translate(size * 0.12, size * 0.02);
|
||||
ctx.scale(size / 1000, size / 1000);
|
||||
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fill(iconPath);
|
||||
|
||||
ctx.globalCompositeOperation = "destination-out";
|
||||
ctx.beginPath();
|
||||
ctx.arc(400, 320, 86, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
texture.colorSpace = THREE.SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
}
|
||||
|
||||
function getLandingPointTexture() {
|
||||
if (!landingPointTexture) {
|
||||
landingPointTexture = createLandingPointTexture();
|
||||
}
|
||||
return landingPointTexture;
|
||||
}
|
||||
|
||||
function clamp(value, min, max) {
|
||||
return Math.min(max, Math.max(min, value));
|
||||
@@ -49,7 +98,7 @@ function disposeMaterial(material) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (material.map) {
|
||||
if (material.map && !material.userData?.sharedMap) {
|
||||
material.map.dispose();
|
||||
}
|
||||
material.dispose();
|
||||
@@ -69,6 +118,22 @@ function disposeObject(object, parent) {
|
||||
}
|
||||
}
|
||||
|
||||
function setLandingPointMaterialState(point, { color, opacity, emissive, emissiveIntensity }) {
|
||||
point.material.color.set(color);
|
||||
point.material.opacity = opacity;
|
||||
if (point.material.emissive && emissive !== undefined) {
|
||||
point.material.emissive.setHex(emissive);
|
||||
}
|
||||
if ("emissiveIntensity" in point.material && emissiveIntensity !== undefined) {
|
||||
point.material.emissiveIntensity = emissiveIntensity;
|
||||
}
|
||||
}
|
||||
|
||||
function setLandingPointScale(point, heightScale) {
|
||||
const aspect = CABLE_CONFIG.landingPoint.iconAspectRatio;
|
||||
point.scale.set(heightScale * aspect, heightScale, 1);
|
||||
}
|
||||
|
||||
function getCableColor(properties) {
|
||||
if (properties.color) {
|
||||
if (
|
||||
@@ -357,13 +422,6 @@ export async function loadLandingPoints(scene, earthObj, options = {}) {
|
||||
|
||||
clearLandingPoints(earthObj);
|
||||
|
||||
if (!landingPointGeometry) {
|
||||
landingPointGeometry = new THREE.SphereGeometry(
|
||||
CABLE_CONFIG.landingPoint.radius,
|
||||
CABLE_CONFIG.landingPoint.widthSegments,
|
||||
CABLE_CONFIG.landingPoint.heightSegments,
|
||||
);
|
||||
}
|
||||
let validCount = 0;
|
||||
|
||||
for (const feature of data.features) {
|
||||
@@ -396,29 +454,35 @@ export async function loadLandingPoints(scene, earthObj, options = {}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sphere = new THREE.Mesh(
|
||||
landingPointGeometry,
|
||||
new THREE.MeshStandardMaterial({
|
||||
const marker = new THREE.Sprite(
|
||||
new THREE.SpriteMaterial({
|
||||
map: getLandingPointTexture(),
|
||||
color: CABLE_CONFIG.landingPoint.color,
|
||||
emissive: CABLE_CONFIG.landingPoint.emissive,
|
||||
emissiveIntensity: CABLE_CONFIG.landingPoint.emissiveIntensity,
|
||||
transparent: true,
|
||||
opacity: CABLE_CONFIG.landingPoint.opacity,
|
||||
depthTest: true,
|
||||
depthWrite: false,
|
||||
}),
|
||||
);
|
||||
sphere.position.copy(position);
|
||||
sphere.userData = {
|
||||
marker.material.userData.sharedMap = true;
|
||||
marker.renderOrder = CABLE_CONFIG.landingPoint.renderOrder;
|
||||
marker.center.set(
|
||||
CABLE_CONFIG.landingPoint.anchorX,
|
||||
CABLE_CONFIG.landingPoint.anchorY,
|
||||
);
|
||||
marker.position.copy(position);
|
||||
marker.userData = {
|
||||
type: "landingPoint",
|
||||
name: properties.name || "未知登陆站",
|
||||
cableNames: properties.cable_names || [],
|
||||
country: properties.country || "未知国家",
|
||||
status: properties.status || "Unknown",
|
||||
baseScale: CABLE_CONFIG.landingPoint.baseScale,
|
||||
sharedGeometry: true,
|
||||
};
|
||||
setLandingPointScale(marker, CABLE_CONFIG.landingPoint.baseScale);
|
||||
|
||||
earthObj.add(sphere);
|
||||
landingPoints.push(sphere);
|
||||
earthObj.add(marker);
|
||||
landingPoints.push(marker);
|
||||
validCount++;
|
||||
}
|
||||
|
||||
@@ -533,6 +597,18 @@ 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();
|
||||
}
|
||||
|
||||
export function applyLandingPointVisualState(lockedCableName, dimAll = false, camera = null) {
|
||||
const pulse =
|
||||
(Math.sin(Date.now() * CABLE_CONFIG.landingPointVisual.pulseSpeed) + 1) * 0.5;
|
||||
@@ -544,58 +620,65 @@ export function applyLandingPointVisualState(lockedCableName, dimAll = false, ca
|
||||
: [];
|
||||
|
||||
landingPoints.forEach((lp) => {
|
||||
lp.visible = cablesVisible && (camera ? isFacingCamera(lp, camera) : true);
|
||||
const isRelated =
|
||||
!dimAll &&
|
||||
Array.isArray(lp.userData.cableNames) &&
|
||||
lp.userData.cableNames.some((name) => relatedNames.includes(name));
|
||||
|
||||
if (isRelated) {
|
||||
lp.material.color.setHex(0xffd27a);
|
||||
lp.material.emissive.setHex(0x7a4a00);
|
||||
lp.material.emissiveIntensity =
|
||||
CABLE_CONFIG.landingPointVisual.related.emissiveIntensityBase +
|
||||
0.2 +
|
||||
pulse * (CABLE_CONFIG.landingPointVisual.related.emissiveIntensityPulse + 0.2);
|
||||
lp.material.opacity =
|
||||
Math.max(
|
||||
setLandingPointMaterialState(lp, {
|
||||
color: 0xffd27a,
|
||||
emissive: 0x7a4a00,
|
||||
emissiveIntensity:
|
||||
CABLE_CONFIG.landingPointVisual.related.emissiveIntensityBase +
|
||||
0.2 +
|
||||
pulse * (CABLE_CONFIG.landingPointVisual.related.emissiveIntensityPulse + 0.2),
|
||||
opacity: Math.max(
|
||||
0.92,
|
||||
CABLE_CONFIG.landingPointVisual.related.opacityBase +
|
||||
pulse * CABLE_CONFIG.landingPointVisual.related.opacityPulse,
|
||||
);
|
||||
),
|
||||
});
|
||||
const distanceScale = getLandingPointDistanceScale(lp, camera);
|
||||
const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale;
|
||||
lp.scale.setScalar(
|
||||
setLandingPointScale(
|
||||
lp,
|
||||
(CABLE_CONFIG.landingPointVisual.related.scaleBase +
|
||||
pulse * CABLE_CONFIG.landingPointVisual.related.scalePulse) *
|
||||
baseScale *
|
||||
distanceScale,
|
||||
distanceScale,
|
||||
);
|
||||
} else {
|
||||
const dimColor = CABLE_CONFIG.landingPointVisual.dimmed.colorRGB;
|
||||
const r = dimColor.r * brightness;
|
||||
const g = dimColor.g * brightness;
|
||||
const b = dimColor.b * brightness;
|
||||
lp.material.color.setRGB(r / 255, g / 255, b / 255);
|
||||
lp.material.emissive.setHex(CABLE_CONFIG.landingPointVisual.dimmed.emissive);
|
||||
lp.material.emissiveIntensity =
|
||||
CABLE_CONFIG.landingPointVisual.dimmed.emissiveIntensity;
|
||||
lp.material.opacity = CABLE_CONFIG.landingPointVisual.dimmed.opacity;
|
||||
setLandingPointMaterialState(lp, {
|
||||
color: new THREE.Color(r / 255, g / 255, b / 255),
|
||||
emissive: CABLE_CONFIG.landingPointVisual.dimmed.emissive,
|
||||
emissiveIntensity: CABLE_CONFIG.landingPointVisual.dimmed.emissiveIntensity,
|
||||
opacity: CABLE_CONFIG.landingPointVisual.dimmed.opacity,
|
||||
});
|
||||
const distanceScale = getLandingPointDistanceScale(lp, camera);
|
||||
const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale;
|
||||
lp.scale.setScalar(baseScale * distanceScale);
|
||||
setLandingPointScale(lp, baseScale * distanceScale);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function resetLandingPointVisualState(camera = null) {
|
||||
landingPoints.forEach((lp) => {
|
||||
lp.material.color.setHex(CABLE_CONFIG.landingPoint.color);
|
||||
lp.material.emissive.setHex(CABLE_CONFIG.landingPoint.emissive);
|
||||
lp.material.emissiveIntensity = CABLE_CONFIG.landingPoint.emissiveIntensity;
|
||||
lp.material.opacity = CABLE_CONFIG.landingPoint.opacity;
|
||||
lp.visible = cablesVisible && (camera ? isFacingCamera(lp, camera) : true);
|
||||
setLandingPointMaterialState(lp, {
|
||||
color: CABLE_CONFIG.landingPoint.color,
|
||||
emissive: CABLE_CONFIG.landingPoint.emissive,
|
||||
emissiveIntensity: CABLE_CONFIG.landingPoint.emissiveIntensity,
|
||||
opacity: CABLE_CONFIG.landingPoint.opacity,
|
||||
});
|
||||
const distanceScale = getLandingPointDistanceScale(lp, camera);
|
||||
const baseScale = lp.userData?.baseScale || CABLE_CONFIG.landingPoint.baseScale;
|
||||
lp.scale.setScalar(baseScale * distanceScale);
|
||||
setLandingPointScale(lp, baseScale * distanceScale);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user