feat(earth): satellite dot rendering with hover/lock rings, dim cables when satellite locked
- Change satellite points from squares to circular dots - Add hover ring (white) and lock ring (yellow) for satellites - Fix satellite hover/lock ring state management - Dim all cables when satellite is locked - Increase MAX_SATELLITES to 2000 - Fix satIntersects scoping bug
This commit is contained in:
@@ -6,7 +6,7 @@ export const CONFIG = {
|
|||||||
minZoom: 0.5,
|
minZoom: 0.5,
|
||||||
maxZoom: 5.0,
|
maxZoom: 5.0,
|
||||||
earthRadius: 100,
|
earthRadius: 100,
|
||||||
rotationSpeed: 0.002,
|
rotationSpeed: 0.0005,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Earth coordinate constants
|
// Earth coordinate constants
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from './ui.js';
|
} from './ui.js';
|
||||||
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
|
import { createEarth, createClouds, createTerrain, createStars, createGridLines, toggleTerrain, getEarth } from './earth.js';
|
||||||
import { loadGeoJSONFromPath, loadLandingPoints, handleCableClick, clearCableSelection, getCableLines, getCablesById, lockedCable as cableLocked, getCableState, setCableState, clearAllCableStates } 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 { createSatellites, loadSatellites, updateSatellitePositions, toggleSatellites, toggleTrails, getShowSatellites, selectSatellite, getSatelliteData, getSatellitePoints, setSatelliteRingState, updateLockedRingPosition, updateHoverRingPosition, getSatellitePositions } from './satellites.js';
|
||||||
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
|
import { setupControls, getAutoRotate, getShowTerrain, zoomLevel, setAutoRotate, toggleAutoRotate, resetView } from './controls.js';
|
||||||
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
|
import { initInfoCard, showInfoCard, hideInfoCard, getCurrentType, setInfoCardNoBorder } from './info-card.js';
|
||||||
|
|
||||||
@@ -24,8 +24,10 @@ let isDragging = false;
|
|||||||
let previousMousePosition = { x: 0, y: 0 };
|
let previousMousePosition = { x: 0, y: 0 };
|
||||||
let hoveredCable = null;
|
let hoveredCable = null;
|
||||||
let hoveredSatellite = null;
|
let hoveredSatellite = null;
|
||||||
|
let hoveredSatelliteIndex = null;
|
||||||
let cableLockedData = null;
|
let cableLockedData = null;
|
||||||
let lockedSatellite = null;
|
let lockedSatellite = null;
|
||||||
|
let lockedSatelliteIndex = null;
|
||||||
let lockedObject = null;
|
let lockedObject = null;
|
||||||
let lockedObjectType = null;
|
let lockedObjectType = null;
|
||||||
let dragStartTime = 0;
|
let dragStartTime = 0;
|
||||||
@@ -33,10 +35,14 @@ let isLongDrag = false;
|
|||||||
|
|
||||||
function clearLockedObject() {
|
function clearLockedObject() {
|
||||||
hoveredCable = null;
|
hoveredCable = null;
|
||||||
|
hoveredSatellite = null;
|
||||||
|
hoveredSatelliteIndex = null;
|
||||||
clearAllCableStates();
|
clearAllCableStates();
|
||||||
|
setSatelliteRingState(null, 'none', null);
|
||||||
lockedObject = null;
|
lockedObject = null;
|
||||||
lockedObjectType = null;
|
lockedObjectType = null;
|
||||||
lockedSatellite = null;
|
lockedSatellite = null;
|
||||||
|
lockedSatelliteIndex = null;
|
||||||
cableLockedData = null;
|
cableLockedData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +101,7 @@ function applyCableVisualState() {
|
|||||||
break;
|
break;
|
||||||
case CABLE_STATE.NORMAL:
|
case CABLE_STATE.NORMAL:
|
||||||
default:
|
default:
|
||||||
if (lockedObjectType === 'cable' && lockedObject) {
|
if ((lockedObjectType === 'cable' && lockedObject) || (lockedObjectType === 'satellite' && lockedSatellite)) {
|
||||||
c.material.opacity = CABLE_CONFIG.otherOpacity;
|
c.material.opacity = CABLE_CONFIG.otherOpacity;
|
||||||
const origColor = c.userData.originalColor;
|
const origColor = c.userData.originalColor;
|
||||||
const brightness = CABLE_CONFIG.otherBrightness;
|
const brightness = CABLE_CONFIG.otherBrightness;
|
||||||
@@ -271,13 +277,14 @@ function onMouseMove(event, camera) {
|
|||||||
|
|
||||||
const hasHoveredCable = intersects.length > 0;
|
const hasHoveredCable = intersects.length > 0;
|
||||||
let hoveredSat = null;
|
let hoveredSat = null;
|
||||||
|
let hoveredSatIndexFromIntersect = null;
|
||||||
if (getShowSatellites()) {
|
if (getShowSatellites()) {
|
||||||
const satPoints = getSatellitePoints();
|
const satPoints = getSatellitePoints();
|
||||||
if (satPoints) {
|
if (satPoints) {
|
||||||
const satIntersects = raycaster.intersectObject(satPoints);
|
const satIntersects = raycaster.intersectObject(satPoints);
|
||||||
if (satIntersects.length > 0) {
|
if (satIntersects.length > 0) {
|
||||||
const index = satIntersects[0].index;
|
hoveredSatIndexFromIntersect = satIntersects[0].index;
|
||||||
hoveredSat = selectSatellite(index);
|
hoveredSat = selectSatellite(hoveredSatIndexFromIntersect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,6 +299,13 @@ function onMouseMove(event, camera) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hoveredSatelliteIndex !== null && hoveredSatelliteIndex !== hoveredSatIndexFromIntersect) {
|
||||||
|
if (hoveredSatelliteIndex !== lockedSatelliteIndex) {
|
||||||
|
setSatelliteRingState(hoveredSatelliteIndex, 'none', null);
|
||||||
|
}
|
||||||
|
hoveredSatelliteIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasHoveredCable) {
|
if (hasHoveredCable) {
|
||||||
const cable = intersects[0].object;
|
const cable = intersects[0].object;
|
||||||
if (!isSameCable(cable, lockedObject)) {
|
if (!isSameCable(cable, lockedObject)) {
|
||||||
@@ -306,11 +320,24 @@ function onMouseMove(event, camera) {
|
|||||||
hideTooltip();
|
hideTooltip();
|
||||||
} else if (hasHoveredSatellite) {
|
} else if (hasHoveredSatellite) {
|
||||||
hoveredSatellite = hoveredSat;
|
hoveredSatellite = hoveredSat;
|
||||||
|
hoveredSatelliteIndex = hoveredSatIndexFromIntersect;
|
||||||
|
if (hoveredSatelliteIndex !== lockedSatelliteIndex) {
|
||||||
|
const satPositions = getSatellitePositions();
|
||||||
|
if (satPositions && satPositions[hoveredSatelliteIndex]) {
|
||||||
|
setSatelliteRingState(hoveredSatelliteIndex, 'hover', satPositions[hoveredSatelliteIndex].current);
|
||||||
|
}
|
||||||
|
}
|
||||||
showSatelliteInfo(hoveredSat.properties);
|
showSatelliteInfo(hoveredSat.properties);
|
||||||
setInfoCardNoBorder(true);
|
setInfoCardNoBorder(true);
|
||||||
} else if (lockedObjectType === 'cable' && lockedObject) {
|
} else if (lockedObjectType === 'cable' && lockedObject) {
|
||||||
showCableInfo(lockedObject);
|
showCableInfo(lockedObject);
|
||||||
} else if (lockedObjectType === 'satellite' && lockedSatellite) {
|
} else if (lockedObjectType === 'satellite' && lockedSatellite) {
|
||||||
|
if (lockedSatelliteIndex !== null && lockedSatelliteIndex !== undefined) {
|
||||||
|
const satPositions = getSatellitePositions();
|
||||||
|
if (satPositions && satPositions[lockedSatelliteIndex]) {
|
||||||
|
setSatelliteRingState(lockedSatelliteIndex, 'locked', satPositions[lockedSatelliteIndex].current);
|
||||||
|
}
|
||||||
|
}
|
||||||
showSatelliteInfo(lockedSatellite.properties);
|
showSatelliteInfo(lockedSatellite.properties);
|
||||||
} else {
|
} else {
|
||||||
hideInfoCard();
|
hideInfoCard();
|
||||||
@@ -399,8 +426,14 @@ function onClick(event, camera, renderer) {
|
|||||||
lockedObject = sat;
|
lockedObject = sat;
|
||||||
lockedObjectType = 'satellite';
|
lockedObjectType = 'satellite';
|
||||||
lockedSatellite = sat;
|
lockedSatellite = sat;
|
||||||
|
lockedSatelliteIndex = index;
|
||||||
setAutoRotate(false);
|
setAutoRotate(false);
|
||||||
|
|
||||||
|
const satPositions = getSatellitePositions();
|
||||||
|
if (satPositions && satPositions[index]) {
|
||||||
|
setSatelliteRingState(index, 'locked', satPositions[index].current);
|
||||||
|
}
|
||||||
|
|
||||||
const props = sat.properties;
|
const props = sat.properties;
|
||||||
|
|
||||||
const meanMotion = props.mean_motion || 0;
|
const meanMotion = props.mean_motion || 0;
|
||||||
@@ -444,6 +477,16 @@ function animate() {
|
|||||||
|
|
||||||
updateSatellitePositions(16);
|
updateSatellitePositions(16);
|
||||||
|
|
||||||
|
const satPositions = getSatellitePositions();
|
||||||
|
|
||||||
|
if (lockedObjectType === 'satellite' && lockedSatelliteIndex !== null) {
|
||||||
|
if (satPositions && satPositions[lockedSatelliteIndex]) {
|
||||||
|
updateLockedRingPosition(satPositions[lockedSatelliteIndex].current);
|
||||||
|
}
|
||||||
|
} else if (hoveredSatelliteIndex !== null && satPositions && satPositions[hoveredSatelliteIndex]) {
|
||||||
|
updateHoverRingPosition(satPositions[hoveredSatelliteIndex].current);
|
||||||
|
}
|
||||||
|
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,25 +12,76 @@ let showTrails = true;
|
|||||||
let animationTime = 0;
|
let animationTime = 0;
|
||||||
let selectedSatellite = null;
|
let selectedSatellite = null;
|
||||||
let satellitePositions = [];
|
let satellitePositions = [];
|
||||||
|
let hoverRingSprite = null;
|
||||||
|
let lockedRingSprite = null;
|
||||||
|
|
||||||
const SATELLITE_API = '/api/v1/visualization/geo/satellites?limit=2000';
|
const SATELLITE_API = '/api/v1/visualization/geo/satellites?limit=2000';
|
||||||
const MAX_SATELLITES = 500;
|
const MAX_SATELLITES = 2000;
|
||||||
const TRAIL_LENGTH = 30;
|
const TRAIL_LENGTH = 30;
|
||||||
|
const DOT_TEXTURE_SIZE = 32;
|
||||||
|
|
||||||
|
function createCircularDotTexture() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = DOT_TEXTURE_SIZE;
|
||||||
|
canvas.height = DOT_TEXTURE_SIZE;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const center = DOT_TEXTURE_SIZE / 2;
|
||||||
|
const radius = center - 2;
|
||||||
|
|
||||||
|
const gradient = ctx.createRadialGradient(center, center, 0, center, center, radius);
|
||||||
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
|
||||||
|
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.8)');
|
||||||
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
||||||
|
|
||||||
|
ctx.fillStyle = gradient;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(center, center, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRingTexture(innerRadius, outerRadius, color = '#ffffff') {
|
||||||
|
const size = DOT_TEXTURE_SIZE * 2;
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const center = size / 2;
|
||||||
|
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(center, center, (innerRadius + outerRadius) / 2, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
const texture = new THREE.CanvasTexture(canvas);
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
export function createSatellites(scene, earthObj) {
|
export function createSatellites(scene, earthObj) {
|
||||||
|
initSatelliteScene(scene, earthObj);
|
||||||
|
|
||||||
const positions = new Float32Array(MAX_SATELLITES * 3);
|
const positions = new Float32Array(MAX_SATELLITES * 3);
|
||||||
const colors = new Float32Array(MAX_SATELLITES * 3);
|
const colors = new Float32Array(MAX_SATELLITES * 3);
|
||||||
|
|
||||||
|
const dotTexture = createCircularDotTexture();
|
||||||
|
|
||||||
const pointsGeometry = new THREE.BufferGeometry();
|
const pointsGeometry = new THREE.BufferGeometry();
|
||||||
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||||
pointsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
pointsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
||||||
|
|
||||||
const pointsMaterial = new THREE.PointsMaterial({
|
const pointsMaterial = new THREE.PointsMaterial({
|
||||||
size: 3,
|
size: 1.5,
|
||||||
|
map: dotTexture,
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
sizeAttenuation: true
|
sizeAttenuation: true,
|
||||||
|
alphaTest: 0.1
|
||||||
});
|
});
|
||||||
|
|
||||||
satellitePoints = new THREE.Points(pointsGeometry, pointsMaterial);
|
satellitePoints = new THREE.Points(pointsGeometry, pointsMaterial);
|
||||||
@@ -176,7 +227,7 @@ export function updateSatellitePositions(deltaTime = 0) {
|
|||||||
const trailColors = satelliteTrails.geometry.attributes.color.array;
|
const trailColors = satelliteTrails.geometry.attributes.color.array;
|
||||||
|
|
||||||
const baseTime = new Date();
|
const baseTime = new Date();
|
||||||
const count = Math.min(satelliteData.length, 500);
|
const count = Math.min(satelliteData.length, MAX_SATELLITES);
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const satellite = satelliteData[i];
|
const satellite = satelliteData[i];
|
||||||
@@ -315,3 +366,91 @@ export function selectSatellite(index) {
|
|||||||
export function getSatellitePoints() {
|
export function getSatellitePoints() {
|
||||||
return satellitePoints;
|
return satellitePoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSatellitePositions() {
|
||||||
|
return satellitePositions;
|
||||||
|
}
|
||||||
|
|
||||||
|
let earthObjRef = null;
|
||||||
|
let sceneRef = null;
|
||||||
|
|
||||||
|
export function showHoverRing(position, isLocked = false) {
|
||||||
|
if (!sceneRef || !earthObjRef) return;
|
||||||
|
|
||||||
|
const ringTexture = createRingTexture(8, 12, isLocked ? '#ffcc00' : '#ffffff');
|
||||||
|
const spriteMaterial = new THREE.SpriteMaterial({
|
||||||
|
map: ringTexture,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8,
|
||||||
|
depthTest: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const sprite = new THREE.Sprite(spriteMaterial);
|
||||||
|
sprite.position.copy(position);
|
||||||
|
sprite.scale.set(3, 3, 1);
|
||||||
|
|
||||||
|
earthObjRef.add(sprite);
|
||||||
|
|
||||||
|
if (isLocked) {
|
||||||
|
if (lockedRingSprite) {
|
||||||
|
earthObjRef.remove(lockedRingSprite);
|
||||||
|
}
|
||||||
|
lockedRingSprite = sprite;
|
||||||
|
} else {
|
||||||
|
if (hoverRingSprite) {
|
||||||
|
earthObjRef.remove(hoverRingSprite);
|
||||||
|
}
|
||||||
|
hoverRingSprite = sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideHoverRings() {
|
||||||
|
if (!earthObjRef) return;
|
||||||
|
|
||||||
|
if (hoverRingSprite) {
|
||||||
|
earthObjRef.remove(hoverRingSprite);
|
||||||
|
hoverRingSprite = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideLockedRing() {
|
||||||
|
if (!earthObjRef || !lockedRingSprite) return;
|
||||||
|
earthObjRef.remove(lockedRingSprite);
|
||||||
|
lockedRingSprite = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateLockedRingPosition(position) {
|
||||||
|
if (lockedRingSprite && position) {
|
||||||
|
lockedRingSprite.position.copy(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateHoverRingPosition(position) {
|
||||||
|
if (hoverRingSprite && position) {
|
||||||
|
hoverRingSprite.position.copy(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSatelliteRingState(index, state, position) {
|
||||||
|
switch (state) {
|
||||||
|
case 'hover':
|
||||||
|
hideHoverRings();
|
||||||
|
showHoverRing(position, false);
|
||||||
|
break;
|
||||||
|
case 'locked':
|
||||||
|
hideHoverRings();
|
||||||
|
showHoverRing(position, true);
|
||||||
|
break;
|
||||||
|
case 'none':
|
||||||
|
hideHoverRings();
|
||||||
|
hideLockedRing();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initSatelliteScene(scene, earth) {
|
||||||
|
sceneRef = scene;
|
||||||
|
earthObjRef = earth;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user