feat(earth): cable state management, hover/lock visual separation, fix isSameCable undefined bug

This commit is contained in:
rayd1o
2026-03-19 16:46:40 +08:00
parent 869d661a94
commit 0ecc1bc537
3 changed files with 113 additions and 77 deletions

View File

@@ -1,7 +1,7 @@
import * as THREE from 'three';
import { createNoise3D } from 'simplex-noise';
import { CONFIG, CABLE_CONFIG } from './constants.js';
import { CONFIG, CABLE_CONFIG, CABLE_STATE } from './constants.js';
import { latLonToVector3, vector3ToLatLon, screenToEarthCoords } from './utils.js';
import {
showStatusMessage,
@@ -13,7 +13,7 @@ import {
hideTooltip
} from './ui.js';
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked } from './cables.js';
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates } from './cables.js';
import { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints } from './satellites.js';
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
@@ -33,6 +33,7 @@ let isLongDrag = false;
function clearLockedObject() {
hoveredCable = null;
clearAllCableStates();
lockedObject = null;
lockedObjectType = null;
lockedSatellite = null;
@@ -40,36 +41,73 @@ function clearLockedObject() {
}
function isSameCable(cable1, cable2) {
return cable1 && cable2 && cable1.userData.cableId === cable2.userData.cableId;
if (!cable1 || !cable2) return false;
const id1 = cable1.userData?.cableId;
const id2 = cable2.userData?.cableId;
if (id1 === undefined || id2 === undefined) return false;
return id1 === id2;
}
function showCableInfo(cable) {
showInfoCard('cable', {
name: cable.userData.name,
owner: cable.userData.owner,
status: cable.userData.status,
length: cable.userData.length,
coords: cable.userData.coords,
rfs: cable.userData.rfs
});
}
function showSatelliteInfo(props) {
const meanMotion = props?.mean_motion || 0;
const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-';
const ecc = props?.eccentricity || 0;
const perigee = (6371 * (1 - ecc)).toFixed(0);
const apogee = (6371 * (1 + ecc)).toFixed(0);
showInfoCard('satellite', {
name: props?.name || '-',
norad_id: props?.norad_cat_id,
inclination: props?.inclination ? props.inclination.toFixed(2) : '-',
period: period,
perigee: perigee,
apogee: apogee
});
}
function applyCableVisualState() {
const allCables = getCableLines();
const pulse = (Math.sin(Date.now() * CABLE_CONFIG.pulseSpeed) + 1) * 0.5;
const isLocked = lockedObjectType === 'cable' && lockedObject;
allCables.forEach(c => {
const cableIsLocked = isSameCable(c, lockedObject);
const cableIsHovered = isSameCable(c, hoveredCable);
const cableId = c.userData.cableId;
const state = getCableState(cableId);
if (cableIsLocked) {
c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * CABLE_CONFIG.pulseCoefficient;
c.material.color.setRGB(1, 1, 1);
} else if (cableIsHovered) {
c.material.opacity = 1;
c.material.color.setRGB(1, 1, 1);
} else if (isLocked) {
c.material.opacity = CABLE_CONFIG.otherOpacity;
const origColor = c.userData.originalColor;
const brightness = CABLE_CONFIG.otherBrightness;
c.material.color.setRGB(
((origColor >> 16) & 255) / 255 * brightness,
((origColor >> 8) & 255) / 255 * brightness,
(origColor & 255) / 255 * brightness
);
} else {
c.material.opacity = 1;
c.material.color.setHex(c.userData.originalColor);
switch (state) {
case CABLE_STATE.LOCKED:
c.material.opacity = CABLE_CONFIG.lockedOpacityMin + pulse * CABLE_CONFIG.pulseCoefficient;
c.material.color.setRGB(1, 1, 1);
break;
case CABLE_STATE.HOVERED:
c.material.opacity = 1;
c.material.color.setRGB(1, 1, 1);
break;
case CABLE_STATE.NORMAL:
default:
if (lockedObjectType === 'cable' && lockedObject) {
c.material.opacity = CABLE_CONFIG.otherOpacity;
const origColor = c.userData.originalColor;
const brightness = CABLE_CONFIG.otherBrightness;
c.material.color.setRGB(
((origColor >> 16) & 255) / 255 * brightness,
((origColor >> 8) & 255) / 255 * brightness,
(origColor & 255) / 255 * brightness
);
} else {
c.material.opacity = 1;
c.material.color.setHex(c.userData.originalColor);
}
}
});
}
@@ -231,10 +269,6 @@ function onMouseMove(event, camera) {
const frontCables = getFrontFacingCables(allCableLines, camera);
const intersects = raycaster.intersectObjects(frontCables);
if (hoveredCable && (lockedObjectType !== 'cable' || !isSameCable(hoveredCable, lockedObject))) {
hoveredCable = null;
}
const hasHoveredCable = intersects.length > 0;
let hoveredSat = null;
if (getShowSatellites()) {
@@ -249,57 +283,35 @@ function onMouseMove(event, camera) {
}
const hasHoveredSatellite = hoveredSat && hoveredSat.properties;
if (hoveredCable) {
if (!hasHoveredCable || !isSameCable(intersects[0]?.object, hoveredCable)) {
if (!isSameCable(hoveredCable, lockedObject)) {
setCableState(hoveredCable.userData.cableId, CABLE_STATE.NORMAL);
}
hoveredCable = null;
}
}
if (hasHoveredCable) {
const cable = intersects[0].object;
hoveredCable = cable;
if (!isSameCable(cable, lockedObject)) {
hoveredCable = cable;
setCableState(cable.userData.cableId, CABLE_STATE.HOVERED);
} else {
hoveredCable = cable;
}
showInfoCard('cable', {
name: cable.userData.name,
owner: cable.userData.owner,
status: cable.userData.status,
length: cable.userData.length,
coords: cable.userData.coords,
rfs: cable.userData.rfs
});
showCableInfo(cable);
setInfoCardNoBorder(true);
hideTooltip();
} else if (hasHoveredSatellite) {
hoveredSatellite = hoveredSat;
const props = hoveredSat.properties;
const meanMotion = props.mean_motion || 0;
const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-';
const ecc = props.eccentricity || 0;
const perigee = (6371 * (1 - ecc)).toFixed(0);
const apogee = (6371 * (1 + ecc)).toFixed(0);
showInfoCard('satellite', {
name: props.name,
norad_id: props.norad_cat_id,
inclination: props.inclination ? props.inclination.toFixed(2) : '-',
period: period,
perigee: perigee,
apogee: apogee
});
showSatelliteInfo(hoveredSat.properties);
setInfoCardNoBorder(true);
} else if (lockedObjectType === 'cable') {
handleCableClick(lockedObject);
} else if (lockedObjectType === 'satellite') {
const props = lockedSatellite.properties;
const meanMotion = props?.mean_motion || 0;
const period = meanMotion > 0 ? (1440 / meanMotion).toFixed(1) : '-';
const ecc = props?.eccentricity || 0;
const perigee = (6371 * (1 - ecc)).toFixed(0);
const apogee = (6371 * (1 + ecc)).toFixed(0);
showInfoCard('satellite', {
name: props?.name || '-',
norad_id: props?.norad_cat_id,
inclination: props?.inclination ? props.inclination.toFixed(2) : '-',
period: period,
perigee: perigee,
apogee: apogee
});
} else if (lockedObjectType === 'cable' && lockedObject) {
showCableInfo(lockedObject);
} else if (lockedObjectType === 'satellite' && lockedSatellite) {
showSatelliteInfo(lockedSatellite.properties);
} else {
hideInfoCard();
}
@@ -368,12 +380,8 @@ function onClick(event, camera, renderer) {
const clickedCable = intersects[0].object;
const cableId = clickedCable.userData.cableId;
const sameCables = getCablesById(cableId);
sameCables.forEach(c => {
c.material.color.setHex(0xffffff);
c.material.opacity = 1;
});
setCableState(cableId, CABLE_STATE.LOCKED);
lockedObject = clickedCable;
lockedObjectType = 'cable';