From 8083c731f7c8c8666e7c80fa9e3f798fd0f6a167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 11 Apr 2026 11:48:35 +0200 Subject: [PATCH] Sonnensystem kann jetzt mehr Kleinplaneten --- public/css/style.css | 105 ++++++++++++- public/solarsystem.php | 340 +++++++++++++++++++++++++++++------------ 2 files changed, 345 insertions(+), 100 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 864fc8f..a8a0e8f 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -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); diff --git a/public/solarsystem.php b/public/solarsystem.php index 7a4f4af..8ed2215 100644 --- a/public/solarsystem.php +++ b/public/solarsystem.php @@ -194,15 +194,28 @@ $bodyClass = 'solarsystem-page';
Kleinplanet
- +
> + + + +
+ + + + + +
+
- +
@@ -212,7 +225,7 @@ $bodyClass = 'solarsystem-page';
  • Maus ziehen: Kamera drehen
  • Mausrad: Zoom
  • Klick auf Planet: Fokus setzen
  • -
  • Dropdown: gewaehlten Kleinplaneten-Orbit zeigen
  • +
  • Dropdown: mehrere Kleinplaneten per Haken anzeigen
  • Erneuter Klick ins Leere: Fokus loesen
  • @@ -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);