Kometen jetzt im Sonnensystem

This commit is contained in:
Eskimue
2026-04-11 16:20:56 +02:00
parent 8f0bc4b688
commit 624c4e918e
+571 -4
View File
@@ -7,6 +7,7 @@ session_start();
$loggedIn = isset($_SESSION['user_id']);
$trackedMinorplanetsForJs = [];
$trackedCometsForJs = [];
function h(?string $value): string
{
@@ -71,6 +72,39 @@ function formatMinorplanetDisplayName(array $minorplanet): string
return $designation !== '' ? $designation : (string) ($minorplanet['packedDesignation'] ?? $minorplanet['desig_packed'] ?? '--');
}
function formatCometDisplayName(array $comet): string
{
$designation = trim((string) ($comet['designation_and_name'] ?? $comet['designation'] ?? ''));
return $designation !== '' ? $designation : (string) ($comet['designation_packed'] ?? $comet['packedDesignation'] ?? '--');
}
function buildCometPerihelionIso(array $comet): ?string
{
$year = isset($comet['year_of_perihelion']) ? (int) $comet['year_of_perihelion'] : 0;
$month = isset($comet['month_of_perihelion']) ? (int) $comet['month_of_perihelion'] : 0;
$dayValue = isset($comet['day_of_perihelion']) ? (float) $comet['day_of_perihelion'] : 0.0;
if ($year <= 0 || $month < 1 || $month > 12 || $dayValue <= 0.0) {
return null;
}
$day = (int) floor($dayValue);
if ($day < 1 || $day > 31) {
return null;
}
$fractionalDay = $dayValue - $day;
$seconds = (int) round($fractionalDay * 86400.0);
try {
$date = new DateTimeImmutable(sprintf('%04d-%02d-%02d 00:00:00', $year, $month, $day), new DateTimeZone('UTC'));
} catch (Throwable $e) {
return null;
}
return $date->modify(sprintf('+%d seconds', $seconds))->format('c');
}
if ($loggedIn) {
try {
$config = require __DIR__ . '/../config/database.php';
@@ -138,8 +172,54 @@ if ($loggedIn) {
'isFavorite' => (bool) $minorplanet['is_favorite'],
];
}
$stmtTrackedComets = $pdo->prepare("
SELECT
c.`id`,
c.`designation_packed`,
c.`designation_and_name`,
c.`orbit_type`,
c.`year_of_perihelion`,
c.`month_of_perihelion`,
c.`day_of_perihelion`,
c.`perihelion_dist_au`,
c.`eccentricity`,
c.`arg_perihelion_deg`,
c.`ascending_node_deg`,
c.`inclination_deg`,
c.`absolute_magnitude_h`,
c.`slope_parameter_g`,
auc.`is_favorite`
FROM `app_user_comets` auc
INNER JOIN `comets_mpc` c
ON c.`id` = auc.`comet_id`
WHERE auc.`user_id` = :user_id
ORDER BY auc.`is_favorite` DESC, c.`year_of_perihelion` ASC, c.`month_of_perihelion` ASC, c.`day_of_perihelion` ASC, c.`designation_and_name` ASC
");
$stmtTrackedComets->execute([
':user_id' => (int) $_SESSION['user_id'],
]);
foreach ($stmtTrackedComets->fetchAll() as $comet) {
$trackedCometsForJs[] = [
'id' => (int) $comet['id'],
'designation' => (string) $comet['designation_and_name'],
'packedDesignation' => (string) $comet['designation_packed'],
'orbitType' => (string) $comet['orbit_type'],
'perihelionIso' => buildCometPerihelionIso($comet),
'perihelionDistanceAu' => (float) $comet['perihelion_dist_au'],
'eccentricity' => (float) $comet['eccentricity'],
'argPerihelionDeg' => (float) $comet['arg_perihelion_deg'],
'ascendingNodeDeg' => (float) $comet['ascending_node_deg'],
'inclinationDeg' => (float) $comet['inclination_deg'],
'absoluteMagnitudeH' => $comet['absolute_magnitude_h'] !== null ? (float) $comet['absolute_magnitude_h'] : null,
'photometricIndex' => $comet['slope_parameter_g'] !== null ? (float) $comet['slope_parameter_g'] : null,
'isFavorite' => (bool) $comet['is_favorite'],
];
}
} catch (Throwable $e) {
$trackedMinorplanetsForJs = [];
$trackedCometsForJs = [];
}
}
@@ -219,6 +299,34 @@ $bodyClass = 'solarsystem-page';
</div>
</div>
<div class="solarsystem-sidebar-block">
<div class="solarsystem-label">Komet</div>
<div class="solarsystem-input-row">
<details class="solarsystem-multiselect<?= empty($trackedCometsForJs) ? ' is-disabled' : '' ?>" id="solarCometSelectWrap"<?= empty($trackedCometsForJs) ? ' open' : '' ?>>
<summary id="solarCometSelectSummary">
<?= empty($trackedCometsForJs) ? 'Keine Kometen verfuegbar' : 'Kometen auswaehlen' ?>
</summary>
<div class="solarsystem-multiselect-panel" id="solarCometSelectPanel">
<?php if (!empty($trackedCometsForJs)): ?>
<?php foreach ($trackedCometsForJs as $comet): ?>
<label class="solarsystem-multiselect-option">
<input
type="checkbox"
class="solar-comet-checkbox"
value="<?= (int) $comet['id'] ?>"
>
<span><?= h(formatCometDisplayName($comet)) ?></span>
</label>
<?php endforeach; ?>
<?php endif; ?>
</div>
</details>
</div>
<div class="solarsystem-current-date solarsystem-minorplanet-hint" id="solarCometHint">
<?= empty($trackedCometsForJs) ? 'Fuege in den Favoriten Kometen hinzu, um hier ihre Orbits anzuzeigen.' : 'Mehrere Kometen koennen gleichzeitig per Haken eingeblendet werden.' ?>
</div>
</div>
<div class="solarsystem-sidebar-block">
<div class="solarsystem-label">Hinweise</div>
<ul class="solarsystem-notes">
@@ -283,6 +391,7 @@ const AstronomyEngine = window.Astronomy;
const AU_IN_KM = 149597870.7;
const DISPLAY_SCALE = 34;
const USER_MINOR_PLANETS = <?= json_encode($trackedMinorplanetsForJs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const USER_COMETS = <?= json_encode($trackedCometsForJs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const JUPITER_MIN_VISIBLE_SIZE = 1.42;
const MOON_DISPLAY_DISTANCE_MULTIPLIER = 2200;
@@ -330,6 +439,9 @@ const infoPanel = document.getElementById('solarInfoPanel');
const minorPlanetSelectWrap = document.getElementById('solarMinorplanetSelectWrap');
const minorPlanetSelectSummary = document.getElementById('solarMinorplanetSelectSummary');
const minorPlanetCheckboxes = Array.from(document.querySelectorAll('.solar-minorplanet-checkbox'));
const cometSelectWrap = document.getElementById('solarCometSelectWrap');
const cometSelectSummary = document.getElementById('solarCometSelectSummary');
const cometCheckboxes = Array.from(document.querySelectorAll('.solar-comet-checkbox'));
if (!AstronomyEngine) {
overlay.textContent = 'Astronomy Engine konnte nicht geladen werden.';
@@ -352,6 +464,7 @@ let moonMesh = null;
let moonOrbitLine = null;
let moonLabelElement = null;
let minorPlanetObjects = null;
let cometObjects = null;
const planetDynamicsCache = new Map();
let showOrbits = true;
@@ -362,6 +475,8 @@ let speedDaysPerSecond = 1;
let focusedPlanetKey = null;
let selectedMinorPlanetIds = [];
let focusedMinorPlanetId = null;
let selectedCometIds = [];
let focusedCometId = null;
let simDate = new Date();
let lastAnimationTime = performance.now();
let orbitRefreshCursor = 0;
@@ -470,6 +585,14 @@ function getSelectedMinorPlanetSet() {
return new Set(selectedMinorPlanetIds);
}
function getCometMeta(id) {
return USER_COMETS.find((comet) => comet.id === Number(id)) || null;
}
function getSelectedCometSet() {
return new Set(selectedCometIds);
}
const MINOR_PLANET_COLOR_PALETTE = [
{ line: '#ff6b6b', body: '#ff5a5a', emissive: '#d63b3b' },
{ line: '#ff5252', body: '#ff4747', emissive: '#c62828' },
@@ -484,6 +607,20 @@ function getMinorPlanetColor(id) {
return MINOR_PLANET_COLOR_PALETTE[safeId % MINOR_PLANET_COLOR_PALETTE.length];
}
const COMET_COLOR_PALETTE = [
{ line: '#63e6be', body: '#46d9a5', emissive: '#1d7f63' },
{ line: '#7ad8ff', body: '#58c8f4', emissive: '#1f6e8d' },
{ line: '#8ce99a', body: '#69db7c', emissive: '#2b8a3e' },
{ line: '#b2f2bb', body: '#8ce99a', emissive: '#2f9e44' },
{ line: '#66d9e8', body: '#3bc9db', emissive: '#0b7285' },
{ line: '#95d5b2', body: '#74c69d', emissive: '#2d6a4f' },
];
function getCometColor(id) {
const safeId = Math.abs(Number(id) || 0);
return COMET_COLOR_PALETTE[safeId % COMET_COLOR_PALETTE.length];
}
function updateMinorPlanetSummary() {
if (!minorPlanetSelectSummary) {
return;
@@ -508,6 +645,30 @@ function updateMinorPlanetSummary() {
: `${selectedNames.length} Kleinplaneten ausgewaehlt`;
}
function updateCometSummary() {
if (!cometSelectSummary) {
return;
}
if (cometCheckboxes.length === 0) {
cometSelectSummary.textContent = 'Keine Kometen verfuegbar';
return;
}
if (selectedCometIds.length === 0) {
cometSelectSummary.textContent = 'Kometen auswaehlen';
return;
}
const selectedNames = selectedCometIds
.map((id) => formatCometName(getCometMeta(id)))
.filter((name) => name && name !== '--');
cometSelectSummary.textContent = selectedNames.length <= 2
? selectedNames.join(', ')
: `${selectedNames.length} Kometen ausgewaehlt`;
}
function formatMinorPlanetName(minorPlanet) {
if (!minorPlanet) {
return '--';
@@ -520,6 +681,14 @@ function formatMinorPlanetName(minorPlanet) {
: designation;
}
function formatCometName(comet) {
if (!comet) {
return '--';
}
return String(comet.designation || comet.packedDesignation || '--').trim();
}
function getHelioPosition(key, date) {
const body = getPlanetBodyKey(key);
const vectorEqj = AstronomyEngine.HelioVector(body, date);
@@ -650,6 +819,7 @@ function updateCamera() {
function clearFocus() {
focusedPlanetKey = null;
focusedMinorPlanetId = null;
focusedCometId = null;
cameraTarget.set(0, 0, 0);
camRadiusTarget = camRadius;
document.querySelectorAll('.solarsystem-planet-chip').forEach((button) => {
@@ -731,8 +901,51 @@ function updateMinorPlanetInfoPanel(id) {
document.getElementById('solarInfoType').textContent = `Kleinplanet${minorPlanet.absoluteMagnitudeH !== null ? ` | H ${minorPlanet.absoluteMagnitudeH.toFixed(1)}` : ''}`;
}
function updateCometInfoPanel(id) {
const comet = getCometMeta(id);
if (!comet) {
infoPanel.hidden = true;
return;
}
const position = getCometHelioPosition(comet, simDate);
if (!position) {
infoPanel.hidden = true;
return;
}
const earthPosition = getHelioPosition('Earth', simDate);
const earthDistanceAu = Math.hypot(
position.x - earthPosition.x,
position.y - earthPosition.y,
position.z - earthPosition.z
);
const apparentMagnitude = estimateCometMagnitude(comet, position, earthDistanceAu);
const color = getCometColor(id);
const semimajorAxisAu = comet.eccentricity < 1 ? comet.perihelionDistanceAu / (1 - comet.eccentricity) : null;
const periodDays = semimajorAxisAu !== null ? 365.25 * Math.sqrt(semimajorAxisAu ** 3) : null;
infoPanel.hidden = false;
document.getElementById('solarInfoName').textContent = formatCometName(comet);
document.getElementById('solarInfoName').style.color = color.line;
document.getElementById('solarInfoDistanceAu').textContent = `${position.r.toFixed(4)} AU`;
document.getElementById('solarInfoDistanceKm').textContent = `${(position.r * AU_IN_KM).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, '.')} km`;
document.getElementById('solarInfoEarthDistanceAu').textContent = `${earthDistanceAu.toFixed(4)} AU`;
document.getElementById('solarInfoEarthDistanceKm').textContent = `${(earthDistanceAu * AU_IN_KM).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, '.')} km`;
document.getElementById('solarInfoMagnitude').textContent = apparentMagnitude === null ? '--' : `${apparentMagnitude.toFixed(1)} mag (geschaetzt)`;
document.getElementById('solarInfoPeriod').textContent = periodDays === null
? '--'
: periodDays >= 365
? `${(periodDays / 365.25).toFixed(2)} Jahre`
: `${periodDays.toFixed(2)} Tage`;
document.getElementById('solarInfoAxis').textContent = semimajorAxisAu === null ? '--' : `${semimajorAxisAu.toFixed(3)} AU`;
document.getElementById('solarInfoType').textContent = `Komet${comet.absoluteMagnitudeH !== null ? ` | H ${comet.absoluteMagnitudeH.toFixed(1)}` : ''}${comet.photometricIndex !== null ? ` | n ${comet.photometricIndex.toFixed(1)}` : ''}`;
}
function focusPlanet(key) {
focusedPlanetKey = key;
focusedMinorPlanetId = null;
focusedCometId = null;
document.querySelectorAll('.solarsystem-planet-chip').forEach((button) => {
button.classList.toggle('is-focused', button.dataset.planet === key);
});
@@ -992,6 +1205,187 @@ function getMinorPlanetHelioPosition(minorPlanet, date) {
};
}
function ensureCometObjects() {
if (!cometObjects) {
cometObjects = new Map();
}
}
function createCometObjects(id) {
ensureCometObjects();
if (cometObjects.has(id)) {
return cometObjects.get(id);
}
const color = getCometColor(id);
const orbitLine = new THREE.Line(
new THREE.BufferGeometry(),
new THREE.LineBasicMaterial({
color: new THREE.Color(color.line),
transparent: true,
opacity: 0.85
})
);
orbitLine.visible = false;
scene.add(orbitLine);
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 24, 24),
new THREE.MeshStandardMaterial({
color: new THREE.Color(color.body),
emissive: new THREE.Color(color.emissive),
emissiveIntensity: 0.08,
roughness: 0.5,
metalness: 0.03,
})
);
mesh.visible = false;
mesh.userData.baseScale = 1;
mesh.userData.minScaleMultiplier = 1;
mesh.userData.cometId = id;
scene.add(mesh);
const labelElement = document.createElement('div');
labelElement.className = 'solarsystem-planet-label';
labelElement.style.color = color.line;
labelElement.hidden = true;
labelsLayer.appendChild(labelElement);
const objects = { orbitLine, mesh, labelElement };
cometObjects.set(id, objects);
return objects;
}
function removeCometObjects(id) {
if (!cometObjects || !cometObjects.has(id)) {
return;
}
const { orbitLine, mesh, labelElement } = cometObjects.get(id);
orbitLine.geometry.dispose();
orbitLine.material.dispose();
scene.remove(orbitLine);
mesh.geometry.dispose();
mesh.material.dispose();
scene.remove(mesh);
labelElement.remove();
cometObjects.delete(id);
}
function syncCometObjects() {
ensureCometObjects();
const selectedSet = getSelectedCometSet();
selectedCometIds.forEach((id) => {
createCometObjects(id);
});
Array.from(cometObjects.keys()).forEach((id) => {
if (!selectedSet.has(id)) {
removeCometObjects(id);
}
});
}
const GAUSSIAN_GRAVITATIONAL_CONSTANT = 0.01720209895;
const PARABOLIC_ECCENTRICITY_TOLERANCE = 1e-6;
function solveHyperbolicAnomaly(meanAnomaly, eccentricity) {
let anomaly = meanAnomaly === 0 ? 0 : Math.asinh(meanAnomaly / eccentricity);
for (let i = 0; i < 40; i += 1) {
const sinhValue = Math.sinh(anomaly);
const coshValue = Math.cosh(anomaly);
const delta = (eccentricity * sinhValue - anomaly - meanAnomaly) / (eccentricity * coshValue - 1);
anomaly -= delta;
if (Math.abs(delta) < 1e-12) {
break;
}
}
return anomaly;
}
function solveParabolicParameter(deltaDays, perihelionDistanceAu) {
const scale = GAUSSIAN_GRAVITATIONAL_CONSTANT * deltaDays / Math.sqrt(2 * perihelionDistanceAu ** 3);
let parameter = scale;
for (let i = 0; i < 40; i += 1) {
const delta = (parameter + (parameter ** 3) / 3 - scale) / (1 + parameter ** 2);
parameter -= delta;
if (Math.abs(delta) < 1e-12) {
break;
}
}
return parameter;
}
function getCometTrueAnomalyAndRadius(deltaDays, perihelionDistanceAu, eccentricity) {
if (eccentricity < 1 - PARABOLIC_ECCENTRICITY_TOLERANCE) {
const semimajorAxis = perihelionDistanceAu / (1 - eccentricity);
const meanMotion = GAUSSIAN_GRAVITATIONAL_CONSTANT / (semimajorAxis ** 1.5);
const meanAnomaly = normalizeRadians(meanMotion * deltaDays);
const eccentricAnomaly = solveKeplerEquation(meanAnomaly, eccentricity);
const radiusAu = semimajorAxis * (1 - eccentricity * Math.cos(eccentricAnomaly));
const trueAnomaly = 2 * Math.atan2(
Math.sqrt(1 + eccentricity) * Math.sin(eccentricAnomaly / 2),
Math.sqrt(1 - eccentricity) * Math.cos(eccentricAnomaly / 2)
);
return { trueAnomaly, radiusAu };
}
if (eccentricity > 1 + PARABOLIC_ECCENTRICITY_TOLERANCE) {
const semimajorAxisAbs = perihelionDistanceAu / (eccentricity - 1);
const meanAnomaly = GAUSSIAN_GRAVITATIONAL_CONSTANT * deltaDays / (semimajorAxisAbs ** 1.5);
const hyperbolicAnomaly = solveHyperbolicAnomaly(meanAnomaly, eccentricity);
const radiusAu = semimajorAxisAbs * (eccentricity * Math.cosh(hyperbolicAnomaly) - 1);
const trueAnomaly = 2 * Math.atan2(
Math.sqrt(eccentricity + 1) * Math.sinh(hyperbolicAnomaly / 2),
Math.sqrt(eccentricity - 1) * Math.cosh(hyperbolicAnomaly / 2)
);
return { trueAnomaly, radiusAu };
}
const parameter = solveParabolicParameter(deltaDays, perihelionDistanceAu);
return {
trueAnomaly: 2 * Math.atan(parameter),
radiusAu: perihelionDistanceAu * (1 + parameter ** 2),
};
}
function estimateCometMagnitude(comet, helioPosition, earthDistanceAu) {
if (!comet || comet.absoluteMagnitudeH === null || comet.photometricIndex === null) {
return null;
}
const r = Math.max(helioPosition.r, 1e-9);
const delta = Math.max(earthDistanceAu, 1e-9);
return comet.absoluteMagnitudeH + (5 * Math.log10(delta)) + (2.5 * comet.photometricIndex * Math.log10(r));
}
function getCometHelioPosition(comet, date) {
if (!comet || !comet.perihelionIso) {
return null;
}
const perihelionMs = Date.parse(comet.perihelionIso);
if (Number.isNaN(perihelionMs)) {
return null;
}
const deltaDays = (date.getTime() - perihelionMs) / 86400000;
const orbit = getCometTrueAnomalyAndRadius(deltaDays, comet.perihelionDistanceAu, comet.eccentricity);
const inclination = comet.inclinationDeg * Math.PI / 180;
const ascendingNode = comet.ascendingNodeDeg * Math.PI / 180;
const argPerihelion = comet.argPerihelionDeg * Math.PI / 180;
const argumentOfLatitude = orbit.trueAnomaly + argPerihelion;
return {
x: orbit.radiusAu * ((Math.cos(ascendingNode) * Math.cos(argumentOfLatitude)) - (Math.sin(ascendingNode) * Math.sin(argumentOfLatitude) * Math.cos(inclination))),
y: orbit.radiusAu * ((Math.sin(ascendingNode) * Math.cos(argumentOfLatitude)) + (Math.cos(ascendingNode) * Math.sin(argumentOfLatitude) * Math.cos(inclination))),
z: orbit.radiusAu * (Math.sin(argumentOfLatitude) * Math.sin(inclination)),
r: orbit.radiusAu,
};
}
function rebuildMinorPlanetOrbit(id) {
const minorPlanet = getMinorPlanetMeta(id);
const objects = createMinorPlanetObjects(id);
@@ -1067,6 +1461,88 @@ function updateMinorPlanetVisual() {
}
}
function rebuildCometOrbit(id) {
const comet = getCometMeta(id);
const objects = createCometObjects(id);
if (!comet || !objects) {
return;
}
const positions = [];
const steps = 720;
const beforeDays = comet.eccentricity < 1 ? 3650 : 1200;
const afterDays = comet.eccentricity < 1 ? 3650 : 1200;
for (let step = 0; step <= steps; step += 1) {
const fraction = step / steps;
const centered = (fraction * 2) - 1;
const curved = Math.sign(centered) * Math.pow(Math.abs(centered), 2.2);
const offsetDays = curved < 0
? curved * beforeDays
: curved * afterDays;
const orbitDate = new Date(simDate.getTime() + offsetDays * 86400000);
const heliocentric = getCometHelioPosition(comet, orbitDate);
if (!heliocentric) {
continue;
}
const display = toDisplayPosition(heliocentric);
positions.push(display.x, display.y, display.z);
}
objects.orbitLine.geometry.dispose();
objects.orbitLine.geometry = new THREE.BufferGeometry();
objects.orbitLine.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
objects.orbitLine.visible = showOrbits;
}
function updateCometVisual() {
syncCometObjects();
if (!cometObjects || cometObjects.size === 0) {
if (!focusedPlanetKey && focusedMinorPlanetId === null) {
infoPanel.hidden = true;
}
return;
}
const selectedSet = getSelectedCometSet();
cometObjects.forEach((objects, id) => {
const comet = getCometMeta(id);
const isSelected = selectedSet.has(id);
if (!comet || !isSelected) {
objects.orbitLine.visible = false;
objects.mesh.visible = false;
objects.labelElement.hidden = true;
return;
}
const heliocentric = getCometHelioPosition(comet, simDate);
if (!heliocentric) {
objects.orbitLine.visible = false;
objects.mesh.visible = false;
objects.labelElement.hidden = true;
return;
}
const display = toDisplayPosition(heliocentric);
objects.mesh.visible = true;
objects.mesh.position.copy(display);
objects.mesh.rotation.y = (simDate.getTime() / 1000) * 0.0002;
objects.orbitLine.visible = showOrbits;
if (focusedCometId === id) {
updateCometInfoPanel(id);
}
});
if (focusedCometId !== null && !selectedSet.has(focusedCometId)) {
focusedCometId = null;
if (!focusedPlanetKey && focusedMinorPlanetId === null) {
infoPanel.hidden = true;
}
}
}
function createOrbitLine(planet) {
const periodDays = getPlanetDynamics(planet.key).periodDays ?? 365;
const steps = planet.key === 'Pluto' ? 300 : 220;
@@ -1271,6 +1747,14 @@ function updateBodyDisplayScales() {
}
});
}
if (cometObjects) {
cometObjects.forEach(({ mesh }) => {
if (mesh.visible) {
applyMinimumDisplayScale(mesh, 220, 10);
}
});
}
}
function updateLabels() {
@@ -1312,12 +1796,34 @@ function updateLabels() {
}
if (!minorPlanetObjects) {
} else {
minorPlanetObjects.forEach(({ mesh, labelElement }, id) => {
const minorPlanet = getMinorPlanetMeta(id);
labelElement.hidden = !showLabels || !minorPlanet || !mesh.visible;
if (labelElement.hidden) {
return;
}
const projected = mesh.position.clone().project(camera);
if (projected.z > 1) {
labelElement.style.opacity = '0';
return;
}
labelElement.textContent = formatMinorPlanetName(minorPlanet);
labelElement.style.opacity = '1';
labelElement.style.left = `${(projected.x * 0.5 + 0.5) * stageWrap.clientWidth}px`;
labelElement.style.top = `${(-projected.y * 0.5 + 0.5) * stageWrap.clientHeight}px`;
});
}
if (!cometObjects) {
return;
}
minorPlanetObjects.forEach(({ mesh, labelElement }, id) => {
const minorPlanet = getMinorPlanetMeta(id);
labelElement.hidden = !showLabels || !minorPlanet || !mesh.visible;
cometObjects.forEach(({ mesh, labelElement }, id) => {
const comet = getCometMeta(id);
labelElement.hidden = !showLabels || !comet || !mesh.visible;
if (labelElement.hidden) {
return;
}
@@ -1328,7 +1834,7 @@ function updateLabels() {
return;
}
labelElement.textContent = formatMinorPlanetName(minorPlanet);
labelElement.textContent = formatCometName(comet);
labelElement.style.opacity = '1';
labelElement.style.left = `${(projected.x * 0.5 + 0.5) * stageWrap.clientWidth}px`;
labelElement.style.top = `${(-projected.y * 0.5 + 0.5) * stageWrap.clientHeight}px`;
@@ -1367,6 +1873,24 @@ function pickPlanet(clientX, clientY) {
}
}
if (cometObjects) {
const visibleCometMeshes = Array.from(cometObjects.entries())
.filter(([, objects]) => objects.mesh.visible)
.map(([id, objects]) => ({ id, mesh: objects.mesh }));
const cometHits = raycaster.intersectObjects(visibleCometMeshes.map((item) => item.mesh), false);
if (cometHits.length > 0) {
const hitItem = visibleCometMeshes.find((item) => item.mesh === cometHits[0].object);
if (hitItem) {
focusedPlanetKey = null;
focusedMinorPlanetId = null;
focusedCometId = hitItem.id;
updateCometInfoPanel(hitItem.id);
return;
}
}
}
if (hits.length === 0) {
clearFocus();
return;
@@ -1374,6 +1898,7 @@ function pickPlanet(clientX, clientY) {
const hit = meshes.find((item) => item.mesh === hits[0].object);
if (hit) {
focusedCometId = null;
focusedMinorPlanetId = null;
focusPlanet(hit.key);
}
@@ -1468,6 +1993,11 @@ function attachUiHandlers() {
orbitLine.visible = showOrbits;
});
}
if (cometObjects) {
cometObjects.forEach(({ orbitLine }) => {
orbitLine.visible = showOrbits;
});
}
if (moonOrbitLine) {
moonOrbitLine.visible = showOrbits;
}
@@ -1522,6 +2052,28 @@ function attachUiHandlers() {
}
});
});
cometCheckboxes.forEach((checkbox) => {
checkbox.addEventListener('change', () => {
selectedCometIds = cometCheckboxes
.filter((input) => input.checked)
.map((input) => Number(input.value));
if (focusedCometId !== null && !selectedCometIds.includes(focusedCometId)) {
focusedCometId = null;
}
syncCometObjects();
selectedCometIds.forEach((id) => {
rebuildCometOrbit(id);
});
updateCometSummary();
if (selectedCometIds.length === 0 && !focusedPlanetKey && focusedMinorPlanetId === null && focusedCometId === null) {
infoPanel.hidden = true;
}
});
});
}
function animate() {
@@ -1538,6 +2090,7 @@ function animate() {
updatePlanetPositions();
updateMoonVisual();
updateMinorPlanetVisual();
updateCometVisual();
// Aim shadow light from sun toward Saturn
const saturnMesh = planetMeshes.get('Saturn');
@@ -1567,6 +2120,14 @@ function animate() {
}
}
if (focusedCometId && cometObjects && cometObjects.has(focusedCometId)) {
const focusedCometObjects = cometObjects.get(focusedCometId);
if (focusedCometObjects.mesh.visible) {
cameraTarget.lerp(focusedCometObjects.mesh.position, 0.06);
updateCometInfoPanel(focusedCometId);
}
}
camRadius += (camRadiusTarget - camRadius) * 0.18;
updateCamera();
updateBodyDisplayScales();
@@ -1580,6 +2141,11 @@ function animate() {
if (playing && orbitRefreshCursor % 45 === 0) {
rebuildMoonOrbitLine();
}
if (playing && orbitRefreshCursor % 60 === 0) {
selectedCometIds.forEach((id) => {
rebuildCometOrbit(id);
});
}
renderer.render(scene, camera);
}
@@ -1605,6 +2171,7 @@ async function initSolarSystem() {
cameraTarget = new THREE.Vector3(0, 0, 0);
rotationEqjToEcl = AstronomyEngine.Rotation_EQJ_ECL();
updateMinorPlanetSummary();
updateCometSummary();
const ambientLight = new THREE.AmbientLight(0x334466, 0.8);
scene.add(ambientLight);