Sonnensystem kann jetzt mehr Kleinplaneten

This commit is contained in:
2026-04-11 11:48:35 +02:00
parent ddbda72c68
commit 8083c731f7
2 changed files with 345 additions and 100 deletions
+103 -2
View File
@@ -3831,9 +3831,9 @@ body.solarsystem-page main > .container {
}
.solarsystem-input-row input[type="date"],
.solarsystem-input-row select {
.solarsystem-input-row select,
.solarsystem-multiselect {
min-height: 2.6rem;
padding: 0.55rem 0.8rem;
border-radius: 6px;
border: 1px solid rgba(200, 168, 75, 0.14);
background: rgba(10, 16, 36, 0.8);
@@ -3843,6 +3843,11 @@ body.solarsystem-page main > .container {
flex: 1 1 170px;
}
.solarsystem-input-row input[type="date"],
.solarsystem-input-row select {
padding: 0.55rem 0.8rem;
}
.solarsystem-input-row select {
appearance: none;
-webkit-appearance: none;
@@ -3882,6 +3887,102 @@ body.solarsystem-page main > .container {
box-shadow: none;
}
.solarsystem-multiselect {
position: relative;
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.02), 0 10px 24px rgba(0, 0, 0, 0.22);
overflow: hidden;
}
.solarsystem-multiselect:hover {
border-color: rgba(201, 168, 76, 0.28);
background-color: rgba(14, 22, 44, 0.92);
box-shadow: inset 0 0 0 1px rgba(201, 168, 76, 0.05), 0 14px 28px rgba(0, 0, 0, 0.26);
}
.solarsystem-multiselect[open] {
border-color: rgba(201, 168, 76, 0.4);
box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.12), 0 14px 28px rgba(0, 0, 0, 0.28);
}
.solarsystem-multiselect summary {
list-style: none;
cursor: pointer;
min-height: 2.6rem;
padding: 0.55rem 2.9rem 0.55rem 0.8rem;
display: flex;
align-items: center;
position: relative;
}
.solarsystem-multiselect summary::-webkit-details-marker {
display: none;
}
.solarsystem-multiselect summary::before,
.solarsystem-multiselect summary::after {
content: "";
position: absolute;
right: 1rem;
top: 50%;
width: 0.5rem;
height: 0.5rem;
border-right: 2px solid rgba(240, 217, 144, 0.92);
border-bottom: 2px solid rgba(240, 217, 144, 0.92);
transform-origin: center;
}
.solarsystem-multiselect summary::before {
transform: translateY(-60%) rotate(45deg);
}
.solarsystem-multiselect[open] summary::before {
transform: translateY(-20%) rotate(225deg);
}
.solarsystem-multiselect summary::after {
display: none;
}
.solarsystem-multiselect-panel {
display: grid;
gap: 0.3rem;
max-height: 15rem;
overflow-y: auto;
padding: 0 0.45rem 0.45rem;
}
.solarsystem-multiselect-option {
display: flex;
align-items: center;
gap: 0.55rem;
padding: 0.45rem 0.5rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.solarsystem-multiselect-option:hover {
background: rgba(255, 255, 255, 0.04);
}
.solarsystem-multiselect-option input {
margin: 0;
accent-color: #f0d990;
}
.solarsystem-multiselect-option span {
min-width: 0;
}
.solarsystem-multiselect.is-disabled {
opacity: 0.7;
box-shadow: none;
}
.solarsystem-multiselect.is-disabled summary {
cursor: default;
}
.solarsystem-current-date,
.solarsystem-speed-value {
color: var(--gold-pale);
+242 -98
View File
@@ -194,15 +194,28 @@ $bodyClass = 'solarsystem-page';
<div class="solarsystem-sidebar-block">
<div class="solarsystem-label">Kleinplanet</div>
<div class="solarsystem-input-row">
<select id="solarMinorplanetSelect"<?= empty($trackedMinorplanetsForJs) ? ' disabled' : '' ?>>
<option value=""><?= empty($trackedMinorplanetsForJs) ? 'Keine Kleinplaneten ausgewaehlt' : 'Kein Kleinplanet' ?></option>
<?php foreach ($trackedMinorplanetsForJs as $minorplanet): ?>
<option value="<?= (int) $minorplanet['id'] ?>"><?= h(formatMinorplanetDisplayName($minorplanet)) ?></option>
<?php endforeach; ?>
</select>
<details class="solarsystem-multiselect<?= empty($trackedMinorplanetsForJs) ? ' is-disabled' : '' ?>" id="solarMinorplanetSelectWrap"<?= empty($trackedMinorplanetsForJs) ? ' open' : '' ?>>
<summary id="solarMinorplanetSelectSummary">
<?= empty($trackedMinorplanetsForJs) ? 'Keine Kleinplaneten verfuegbar' : 'Kleinplaneten auswaehlen' ?>
</summary>
<div class="solarsystem-multiselect-panel" id="solarMinorplanetSelectPanel">
<?php if (!empty($trackedMinorplanetsForJs)): ?>
<?php foreach ($trackedMinorplanetsForJs as $minorplanet): ?>
<label class="solarsystem-multiselect-option">
<input
type="checkbox"
class="solar-minorplanet-checkbox"
value="<?= (int) $minorplanet['id'] ?>"
>
<span><?= h(formatMinorplanetDisplayName($minorplanet)) ?></span>
</label>
<?php endforeach; ?>
<?php endif; ?>
</div>
</details>
</div>
<div class="solarsystem-current-date solarsystem-minorplanet-hint" id="solarMinorplanetHint">
<?= empty($trackedMinorplanetsForJs) ? 'Fuege in den Favoriten Kleinplaneten hinzu, um hier ihren Orbit anzuzeigen.' : 'Der Orbit des gewaehlten Kleinplaneten wird eingeblendet.' ?>
<?= empty($trackedMinorplanetsForJs) ? 'Fuege in den Favoriten Kleinplaneten hinzu, um hier ihre Orbits anzuzeigen.' : 'Mehrere Kleinplaneten koennen gleichzeitig per Haken eingeblendet werden.' ?>
</div>
</div>
@@ -212,7 +225,7 @@ $bodyClass = 'solarsystem-page';
<li>Maus ziehen: Kamera drehen</li>
<li>Mausrad: Zoom</li>
<li>Klick auf Planet: Fokus setzen</li>
<li>Dropdown: gewaehlten Kleinplaneten-Orbit zeigen</li>
<li>Dropdown: mehrere Kleinplaneten per Haken anzeigen</li>
<li>Erneuter Klick ins Leere: Fokus loesen</li>
</ul>
</div>
@@ -314,7 +327,9 @@ const btnEcliptic = document.getElementById('solarBtnEcliptic');
const speedSlider = document.getElementById('solarSpeedSlider');
const speedLabel = document.getElementById('solarSpeedLabel');
const infoPanel = document.getElementById('solarInfoPanel');
const minorPlanetSelect = document.getElementById('solarMinorplanetSelect');
const minorPlanetSelectWrap = document.getElementById('solarMinorplanetSelectWrap');
const minorPlanetSelectSummary = document.getElementById('solarMinorplanetSelectSummary');
const minorPlanetCheckboxes = Array.from(document.querySelectorAll('.solar-minorplanet-checkbox'));
if (!AstronomyEngine) {
overlay.textContent = 'Astronomy Engine konnte nicht geladen werden.';
@@ -336,9 +351,7 @@ let rotationEqjToEcl;
let moonMesh = null;
let moonOrbitLine = null;
let moonLabelElement = null;
let minorPlanetOrbitLine = null;
let minorPlanetMesh = null;
let minorPlanetLabelElement = null;
let minorPlanetObjects = null;
const planetDynamicsCache = new Map();
let showOrbits = true;
@@ -347,7 +360,7 @@ let showEcliptic = false;
let playing = false;
let speedDaysPerSecond = 1;
let focusedPlanetKey = null;
let selectedMinorPlanetId = null;
let selectedMinorPlanetIds = [];
let focusedMinorPlanetId = null;
let simDate = new Date();
let lastAnimationTime = performance.now();
@@ -453,6 +466,48 @@ function getMinorPlanetMeta(id) {
return USER_MINOR_PLANETS.find((minorPlanet) => minorPlanet.id === Number(id)) || null;
}
function getSelectedMinorPlanetSet() {
return new Set(selectedMinorPlanetIds);
}
const MINOR_PLANET_COLOR_PALETTE = [
{ line: '#ff6b6b', body: '#ff5a5a', emissive: '#d63b3b' },
{ line: '#ff5252', body: '#ff4747', emissive: '#c62828' },
{ line: '#ff7a5c', body: '#ff6b4a', emissive: '#d9482c' },
{ line: '#ff4d6d', body: '#ff3f5f', emissive: '#c81f45' },
{ line: '#ff8a65', body: '#ff7043', emissive: '#d4512c' },
{ line: '#f0625d', body: '#eb554f', emissive: '#bf3c36' },
];
function getMinorPlanetColor(id) {
const safeId = Math.abs(Number(id) || 0);
return MINOR_PLANET_COLOR_PALETTE[safeId % MINOR_PLANET_COLOR_PALETTE.length];
}
function updateMinorPlanetSummary() {
if (!minorPlanetSelectSummary) {
return;
}
if (minorPlanetCheckboxes.length === 0) {
minorPlanetSelectSummary.textContent = 'Keine Kleinplaneten verfuegbar';
return;
}
if (selectedMinorPlanetIds.length === 0) {
minorPlanetSelectSummary.textContent = 'Kleinplaneten auswaehlen';
return;
}
const selectedNames = selectedMinorPlanetIds
.map((id) => formatMinorPlanetName(getMinorPlanetMeta(id)))
.filter((name) => name && name !== '--');
minorPlanetSelectSummary.textContent = selectedNames.length <= 2
? selectedNames.join(', ')
: `${selectedNames.length} Kleinplaneten ausgewaehlt`;
}
function formatMinorPlanetName(minorPlanet) {
if (!minorPlanet) {
return '--';
@@ -658,9 +713,10 @@ function updateMinorPlanetInfoPanel(id) {
);
const apparentMagnitude = estimateMinorPlanetMagnitude(minorPlanet, position, earthPosition, earthDistanceAu);
const periodDays = minorPlanet.meanMotionDegPerDay > 0 ? 360 / minorPlanet.meanMotionDegPerDay : null;
const color = getMinorPlanetColor(id);
infoPanel.hidden = false;
document.getElementById('solarInfoName').textContent = formatMinorPlanetName(minorPlanet);
document.getElementById('solarInfoName').style.color = '#ff2a2a';
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`;
@@ -773,39 +829,86 @@ function updateMoonVisual() {
}
function ensureMinorPlanetObjects() {
if (minorPlanetOrbitLine && minorPlanetMesh && minorPlanetLabelElement) {
return;
if (!minorPlanetObjects) {
minorPlanetObjects = new Map();
}
}
function createMinorPlanetObjects(id) {
ensureMinorPlanetObjects();
if (minorPlanetObjects.has(id)) {
return minorPlanetObjects.get(id);
}
const color = getMinorPlanetColor(id);
const lineMaterial = new THREE.LineBasicMaterial({
color: 0xff3333,
color: new THREE.Color(color.line),
transparent: true,
opacity: 0.85
});
minorPlanetOrbitLine = new THREE.Line(new THREE.BufferGeometry(), lineMaterial);
minorPlanetOrbitLine.visible = false;
scene.add(minorPlanetOrbitLine);
const orbitLine = new THREE.Line(new THREE.BufferGeometry(), lineMaterial);
orbitLine.visible = false;
scene.add(orbitLine);
minorPlanetMesh = new THREE.Mesh(
const mesh = new THREE.Mesh(
new THREE.SphereGeometry(0.58, 28, 28),
new THREE.MeshStandardMaterial({
color: 0xff3030,
emissive: new THREE.Color(0xcc0000),
color: new THREE.Color(color.body),
emissive: new THREE.Color(color.emissive),
emissiveIntensity: 0.06,
roughness: 0.65,
metalness: 0.08,
})
);
minorPlanetMesh.visible = false;
minorPlanetMesh.userData.baseScale = 1;
minorPlanetMesh.userData.minScaleMultiplier = 1;
scene.add(minorPlanetMesh);
mesh.visible = false;
mesh.userData.baseScale = 1;
mesh.userData.minScaleMultiplier = 1;
mesh.userData.minorPlanetId = id;
scene.add(mesh);
minorPlanetLabelElement = document.createElement('div');
minorPlanetLabelElement.className = 'solarsystem-planet-label';
minorPlanetLabelElement.style.color = '#ff2a2a';
minorPlanetLabelElement.hidden = true;
labelsLayer.appendChild(minorPlanetLabelElement);
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 };
minorPlanetObjects.set(id, objects);
return objects;
}
function removeMinorPlanetObjects(id) {
if (!minorPlanetObjects || !minorPlanetObjects.has(id)) {
return;
}
const { orbitLine, mesh, labelElement } = minorPlanetObjects.get(id);
orbitLine.geometry.dispose();
orbitLine.material.dispose();
scene.remove(orbitLine);
mesh.geometry.dispose();
mesh.material.dispose();
scene.remove(mesh);
labelElement.remove();
minorPlanetObjects.delete(id);
}
function syncMinorPlanetObjects() {
ensureMinorPlanetObjects();
const selectedSet = getSelectedMinorPlanetSet();
selectedMinorPlanetIds.forEach((id) => {
createMinorPlanetObjects(id);
});
Array.from(minorPlanetObjects.keys()).forEach((id) => {
if (!selectedSet.has(id)) {
removeMinorPlanetObjects(id);
}
});
}
function normalizeRadians(value) {
@@ -890,13 +993,9 @@ function getMinorPlanetHelioPosition(minorPlanet, date) {
}
function rebuildMinorPlanetOrbit(id) {
ensureMinorPlanetObjects();
const minorPlanet = getMinorPlanetMeta(id);
if (!minorPlanet) {
minorPlanetOrbitLine.visible = false;
minorPlanetMesh.visible = false;
minorPlanetLabelElement.hidden = true;
const objects = createMinorPlanetObjects(id);
if (!minorPlanet || !objects) {
return;
}
@@ -914,42 +1013,57 @@ function rebuildMinorPlanetOrbit(id) {
positions.push(display.x, display.y, display.z);
}
minorPlanetOrbitLine.geometry.dispose();
minorPlanetOrbitLine.geometry = new THREE.BufferGeometry();
minorPlanetOrbitLine.geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
minorPlanetOrbitLine.visible = showOrbits;
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 updateMinorPlanetVisual() {
ensureMinorPlanetObjects();
syncMinorPlanetObjects();
const minorPlanet = getMinorPlanetMeta(selectedMinorPlanetId);
if (!minorPlanet) {
minorPlanetOrbitLine.visible = false;
minorPlanetMesh.visible = false;
minorPlanetLabelElement.hidden = true;
if (!minorPlanetObjects || minorPlanetObjects.size === 0) {
if (!focusedPlanetKey) {
infoPanel.hidden = true;
}
return;
}
const heliocentric = getMinorPlanetHelioPosition(minorPlanet, simDate);
if (!heliocentric) {
minorPlanetOrbitLine.visible = false;
minorPlanetMesh.visible = false;
minorPlanetLabelElement.hidden = true;
return;
}
const selectedSet = getSelectedMinorPlanetSet();
minorPlanetObjects.forEach((objects, id) => {
const minorPlanet = getMinorPlanetMeta(id);
const isSelected = selectedSet.has(id);
if (!minorPlanet || !isSelected) {
objects.orbitLine.visible = false;
objects.mesh.visible = false;
objects.labelElement.hidden = true;
return;
}
const display = toDisplayPosition(heliocentric);
minorPlanetMesh.visible = true;
minorPlanetMesh.position.copy(display);
minorPlanetMesh.rotation.y = (simDate.getTime() / 1000) * 0.0002;
minorPlanetOrbitLine.visible = showOrbits;
const heliocentric = getMinorPlanetHelioPosition(minorPlanet, simDate);
if (!heliocentric) {
objects.orbitLine.visible = false;
objects.mesh.visible = false;
objects.labelElement.hidden = true;
return;
}
if (focusedMinorPlanetId === selectedMinorPlanetId) {
updateMinorPlanetInfoPanel(selectedMinorPlanetId);
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 (focusedMinorPlanetId === id) {
updateMinorPlanetInfoPanel(id);
}
});
if (focusedMinorPlanetId !== null && !selectedSet.has(focusedMinorPlanetId)) {
focusedMinorPlanetId = null;
if (!focusedPlanetKey) {
infoPanel.hidden = true;
}
}
}
@@ -1150,8 +1264,12 @@ function updateBodyDisplayScales() {
applyMinimumDisplayScale(moonMesh, 220, 10);
}
if (minorPlanetMesh && minorPlanetMesh.visible) {
applyMinimumDisplayScale(minorPlanetMesh, 220, 10);
if (minorPlanetObjects) {
minorPlanetObjects.forEach(({ mesh }) => {
if (mesh.visible) {
applyMinimumDisplayScale(mesh, 220, 10);
}
});
}
}
@@ -1193,26 +1311,28 @@ function updateLabels() {
}
}
if (!minorPlanetLabelElement || !minorPlanetMesh) {
if (!minorPlanetObjects) {
return;
}
const minorPlanet = getMinorPlanetMeta(selectedMinorPlanetId);
minorPlanetLabelElement.hidden = !showLabels || !minorPlanet || !minorPlanetMesh.visible;
if (minorPlanetLabelElement.hidden) {
return;
}
minorPlanetObjects.forEach(({ mesh, labelElement }, id) => {
const minorPlanet = getMinorPlanetMeta(id);
labelElement.hidden = !showLabels || !minorPlanet || !mesh.visible;
if (labelElement.hidden) {
return;
}
const projected = minorPlanetMesh.position.clone().project(camera);
if (projected.z > 1) {
minorPlanetLabelElement.style.opacity = '0';
return;
}
const projected = mesh.position.clone().project(camera);
if (projected.z > 1) {
labelElement.style.opacity = '0';
return;
}
minorPlanetLabelElement.textContent = formatMinorPlanetName(minorPlanet);
minorPlanetLabelElement.style.opacity = '1';
minorPlanetLabelElement.style.left = `${(projected.x * 0.5 + 0.5) * stageWrap.clientWidth}px`;
minorPlanetLabelElement.style.top = `${(-projected.y * 0.5 + 0.5) * stageWrap.clientHeight}px`;
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`;
});
}
function pickPlanet(clientX, clientY) {
@@ -1230,13 +1350,20 @@ function pickPlanet(clientX, clientY) {
});
const hits = raycaster.intersectObjects(meshes.map((item) => item.mesh), false);
if (minorPlanetMesh && minorPlanetMesh.visible) {
const minorPlanetHits = raycaster.intersectObject(minorPlanetMesh, false);
if (minorPlanetHits.length > 0 && selectedMinorPlanetId) {
focusedPlanetKey = null;
focusedMinorPlanetId = selectedMinorPlanetId;
updateMinorPlanetInfoPanel(selectedMinorPlanetId);
return;
if (minorPlanetObjects) {
const visibleMinorPlanetMeshes = Array.from(minorPlanetObjects.entries())
.filter(([, objects]) => objects.mesh.visible)
.map(([id, objects]) => ({ id, mesh: objects.mesh }));
const minorPlanetHits = raycaster.intersectObjects(visibleMinorPlanetMeshes.map((item) => item.mesh), false);
if (minorPlanetHits.length > 0) {
const hitItem = visibleMinorPlanetMeshes.find((item) => item.mesh === minorPlanetHits[0].object);
if (hitItem) {
focusedPlanetKey = null;
focusedMinorPlanetId = hitItem.id;
updateMinorPlanetInfoPanel(hitItem.id);
return;
}
}
}
@@ -1336,8 +1463,10 @@ function attachUiHandlers() {
orbitLines.forEach((line) => {
line.visible = showOrbits;
});
if (minorPlanetOrbitLine) {
minorPlanetOrbitLine.visible = showOrbits && !!getMinorPlanetMeta(selectedMinorPlanetId);
if (minorPlanetObjects) {
minorPlanetObjects.forEach(({ orbitLine }) => {
orbitLine.visible = showOrbits;
});
}
if (moonOrbitLine) {
moonOrbitLine.visible = showOrbits;
@@ -1372,16 +1501,27 @@ function attachUiHandlers() {
refreshCurrentDateUi();
});
if (minorPlanetSelect) {
minorPlanetSelect.addEventListener('change', () => {
selectedMinorPlanetId = minorPlanetSelect.value ? Number(minorPlanetSelect.value) : null;
focusedMinorPlanetId = null;
rebuildMinorPlanetOrbit(selectedMinorPlanetId);
if (!selectedMinorPlanetId && !focusedPlanetKey) {
minorPlanetCheckboxes.forEach((checkbox) => {
checkbox.addEventListener('change', () => {
selectedMinorPlanetIds = minorPlanetCheckboxes
.filter((input) => input.checked)
.map((input) => Number(input.value));
if (focusedMinorPlanetId !== null && !selectedMinorPlanetIds.includes(focusedMinorPlanetId)) {
focusedMinorPlanetId = null;
}
syncMinorPlanetObjects();
selectedMinorPlanetIds.forEach((id) => {
rebuildMinorPlanetOrbit(id);
});
updateMinorPlanetSummary();
if (selectedMinorPlanetIds.length === 0 && !focusedPlanetKey && focusedMinorPlanetId === null) {
infoPanel.hidden = true;
}
});
}
});
}
function animate() {
@@ -1419,9 +1559,12 @@ function animate() {
}
}
if (focusedMinorPlanetId && minorPlanetMesh && minorPlanetMesh.visible) {
cameraTarget.lerp(minorPlanetMesh.position, 0.06);
updateMinorPlanetInfoPanel(focusedMinorPlanetId);
if (focusedMinorPlanetId && minorPlanetObjects && minorPlanetObjects.has(focusedMinorPlanetId)) {
const focusedMinorPlanetObjects = minorPlanetObjects.get(focusedMinorPlanetId);
if (focusedMinorPlanetObjects.mesh.visible) {
cameraTarget.lerp(focusedMinorPlanetObjects.mesh.position, 0.06);
updateMinorPlanetInfoPanel(focusedMinorPlanetId);
}
}
camRadius += (camRadiusTarget - camRadius) * 0.18;
@@ -1461,6 +1604,7 @@ async function initSolarSystem() {
pointer = new THREE.Vector2();
cameraTarget = new THREE.Vector3(0, 0, 0);
rotationEqjToEcl = AstronomyEngine.Rotation_EQJ_ECL();
updateMinorPlanetSummary();
const ambientLight = new THREE.AmbientLight(0x334466, 0.8);
scene.add(ambientLight);