Geocron 3D verbessert
This commit is contained in:
+297
-40
@@ -25,6 +25,14 @@ require __DIR__ . '/header.php';
|
|||||||
<input type="checkbox" id="geocron3dLabelsToggle">
|
<input type="checkbox" id="geocron3dLabelsToggle">
|
||||||
<span>Satelliten-Beschriftungen</span>
|
<span>Satelliten-Beschriftungen</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="geocron3d-time-controls" aria-label="Zeitsteuerung">
|
||||||
|
<button type="button" class="geocron3d-time-button is-active" data-time-scale="1">Live</button>
|
||||||
|
<button type="button" class="geocron3d-time-button" data-time-scale="25">25x</button>
|
||||||
|
<button type="button" class="geocron3d-time-button" data-time-scale="60">60x</button>
|
||||||
|
<button type="button" class="geocron3d-time-button" data-time-scale="120">120x</button>
|
||||||
|
<button type="button" class="geocron3d-time-button" data-time-scale="0">Pause</button>
|
||||||
|
</div>
|
||||||
|
<div class="geocron3d-time-readout" id="geocron3dTimeReadout">Simulationszeit: --</div>
|
||||||
<div class="geocron3d-status" id="geocron3dStatus">Lade 3D-Szene…</div>
|
<div class="geocron3d-status" id="geocron3dStatus">Lade 3D-Szene…</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="geocron3d-view" id="geocron3dView">
|
<div class="geocron3d-view" id="geocron3dView">
|
||||||
@@ -113,12 +121,44 @@ require __DIR__ . '/header.php';
|
|||||||
font-size: 0.92rem;
|
font-size: 0.92rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.geocron3d-time-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geocron3d-time-button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid rgba(108, 145, 220, 0.28);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(10, 16, 32, 0.72);
|
||||||
|
color: rgba(220, 232, 255, 0.92);
|
||||||
|
font: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.16s ease, border-color 0.16s ease, transform 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geocron3d-time-button:hover {
|
||||||
|
border-color: rgba(125, 170, 255, 0.55);
|
||||||
|
background: rgba(18, 28, 52, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geocron3d-time-button.is-active {
|
||||||
|
border-color: rgba(150, 190, 255, 0.78);
|
||||||
|
background: rgba(38, 66, 122, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.geocron3d-time-readout {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: rgba(192, 214, 255, 0.88);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.geocron3d-view {
|
.geocron3d-view {
|
||||||
position: relative;
|
position: relative;
|
||||||
background:
|
background: #000;
|
||||||
radial-gradient(circle at 50% 36%, rgba(70, 110, 190, 0.12), transparent 30%),
|
|
||||||
radial-gradient(circle at 50% 68%, rgba(8, 12, 20, 0.1), transparent 44%),
|
|
||||||
#02050b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.geocron3d-view canvas {
|
.geocron3d-view canvas {
|
||||||
@@ -178,6 +218,7 @@ require __DIR__ . '/header.php';
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script src="./js/astronomy.browser.min.js"></script>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
@@ -190,6 +231,8 @@ require __DIR__ . '/header.php';
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { GLTFLoader } from './js/GLTFLoader.js';
|
import { GLTFLoader } from './js/GLTFLoader.js';
|
||||||
|
|
||||||
|
const AstronomyEngine = window.Astronomy;
|
||||||
|
|
||||||
const EARTH_RADIUS = 6;
|
const EARTH_RADIUS = 6;
|
||||||
const EARTH_REAL_RADIUS_KM = 6371;
|
const EARTH_REAL_RADIUS_KM = 6371;
|
||||||
const ORBIT_WINDOW_MINUTES = 90;
|
const ORBIT_WINDOW_MINUTES = 90;
|
||||||
@@ -197,14 +240,24 @@ const ORBIT_STEP_SECONDS = 10;
|
|||||||
const ORBIT_REFRESH_MS = 30_000;
|
const ORBIT_REFRESH_MS = 30_000;
|
||||||
const MONTH_INDEX = new Date().getMonth() + 1;
|
const MONTH_INDEX = new Date().getMonth() + 1;
|
||||||
const EARTH_LONGITUDE_OFFSET_DEG = 0;
|
const EARTH_LONGITUDE_OFFSET_DEG = 0;
|
||||||
|
const EARTH_AXIAL_TILT_DEG = 23.439281;
|
||||||
|
|
||||||
const canvas = document.getElementById('geocron3dCanvas');
|
const canvas = document.getElementById('geocron3dCanvas');
|
||||||
const host = document.getElementById('geocron3dView');
|
const host = document.getElementById('geocron3dView');
|
||||||
const statusEl = document.getElementById('geocron3dStatus');
|
const statusEl = document.getElementById('geocron3dStatus');
|
||||||
const labelsToggle = document.getElementById('geocron3dLabelsToggle');
|
const labelsToggle = document.getElementById('geocron3dLabelsToggle');
|
||||||
const labelsLayer = document.getElementById('geocron3dLabels');
|
const labelsLayer = document.getElementById('geocron3dLabels');
|
||||||
|
const timeReadoutEl = document.getElementById('geocron3dTimeReadout');
|
||||||
|
const timeButtons = Array.from(document.querySelectorAll('[data-time-scale]'));
|
||||||
let labelsEnabled = false;
|
let labelsEnabled = false;
|
||||||
let issModelTemplatePromise = null;
|
let issModelTemplatePromise = null;
|
||||||
|
let simulatedTime = new Date();
|
||||||
|
let lastFrameTimeMs = performance.now();
|
||||||
|
let timeScale = 1;
|
||||||
|
|
||||||
|
if (!AstronomyEngine) {
|
||||||
|
throw new Error('Astronomy Engine konnte nicht geladen werden.');
|
||||||
|
}
|
||||||
|
|
||||||
function setStatus(text, isError = false) {
|
function setStatus(text, isError = false) {
|
||||||
statusEl.textContent = text;
|
statusEl.textContent = text;
|
||||||
@@ -252,6 +305,64 @@ function wrapLongitude(lon) {
|
|||||||
return ((lon + 180) % 360 + 360) % 360 - 180;
|
return ((lon + 180) % 360 + 360) % 360 - 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getJulianDate(date) {
|
||||||
|
return date.getTime() / 86400000 + 2440587.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGreenwichMeanSiderealTime(date) {
|
||||||
|
const julianDate = getJulianDate(date);
|
||||||
|
const t = (julianDate - 2451545.0) / 36525;
|
||||||
|
let gmstDeg = 280.46061837
|
||||||
|
+ 360.98564736629 * (julianDate - 2451545.0)
|
||||||
|
+ 0.000387933 * t * t
|
||||||
|
- (t * t * t) / 38710000;
|
||||||
|
gmstDeg = ((gmstDeg % 360) + 360) % 360;
|
||||||
|
return THREE.MathUtils.degToRad(gmstDeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSunSubsolarPoint(date) {
|
||||||
|
const sunVector = AstronomyEngine.GeoVector(AstronomyEngine.Body.Sun, date, true);
|
||||||
|
const radius = Math.hypot(sunVector.x, sunVector.y, sunVector.z);
|
||||||
|
const declinationRad = Math.asin(sunVector.z / radius);
|
||||||
|
const rightAscensionRad = Math.atan2(sunVector.y, sunVector.x);
|
||||||
|
const gmstRad = getGreenwichMeanSiderealTime(date);
|
||||||
|
const subsolarLongitudeRad = THREE.MathUtils.euclideanModulo(rightAscensionRad - gmstRad + Math.PI, Math.PI * 2) - Math.PI;
|
||||||
|
|
||||||
|
return {
|
||||||
|
latDeg: THREE.MathUtils.radToDeg(declinationRad),
|
||||||
|
lonDeg: THREE.MathUtils.radToDeg(subsolarLongitudeRad),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSimulationTime(date) {
|
||||||
|
return new Intl.DateTimeFormat('de-DE', {
|
||||||
|
dateStyle: 'medium',
|
||||||
|
timeStyle: 'medium',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
}).format(date) + ' UTC';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimeReadout() {
|
||||||
|
const modeLabel = timeScale === 0 ? 'Pausiert' : (timeScale === 1 ? 'Live' : `${timeScale}x`);
|
||||||
|
timeReadoutEl.textContent = `Simulationszeit: ${formatSimulationTime(simulatedTime)} (${modeLabel})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTimeScale(nextScale) {
|
||||||
|
timeScale = nextScale;
|
||||||
|
if (nextScale === 1) {
|
||||||
|
simulatedTime = new Date();
|
||||||
|
lastFrameTimeMs = performance.now();
|
||||||
|
if (satelliteLib && satellites.length > 0) {
|
||||||
|
refreshOrbitLines(satellites, satelliteLib, simulatedTime);
|
||||||
|
nextOrbitRefreshAt = performance.now() + ORBIT_REFRESH_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const button of timeButtons) {
|
||||||
|
button.classList.toggle('is-active', Number(button.dataset.timeScale) === nextScale);
|
||||||
|
}
|
||||||
|
updateTimeReadout();
|
||||||
|
}
|
||||||
|
|
||||||
function createOrbitGeometry(samples) {
|
function createOrbitGeometry(samples) {
|
||||||
const positions = [];
|
const positions = [];
|
||||||
for (const sample of samples) {
|
for (const sample of samples) {
|
||||||
@@ -272,8 +383,7 @@ renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
|||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
scene.background = new THREE.Color(0x02050b);
|
scene.background = new THREE.Color(0x000000);
|
||||||
scene.fog = new THREE.FogExp2(0x02050b, 0.04);
|
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 500);
|
const camera = new THREE.PerspectiveCamera(42, 1, 0.1, 500);
|
||||||
camera.position.set(0, 4.8, 18);
|
camera.position.set(0, 4.8, 18);
|
||||||
@@ -281,6 +391,13 @@ camera.position.set(0, 4.8, 18);
|
|||||||
const root = new THREE.Group();
|
const root = new THREE.Group();
|
||||||
scene.add(root);
|
scene.add(root);
|
||||||
|
|
||||||
|
const earthTiltGroup = new THREE.Group();
|
||||||
|
earthTiltGroup.rotation.z = THREE.MathUtils.degToRad(-EARTH_AXIAL_TILT_DEG);
|
||||||
|
root.add(earthTiltGroup);
|
||||||
|
|
||||||
|
const earthRotationGroup = new THREE.Group();
|
||||||
|
earthTiltGroup.add(earthRotationGroup);
|
||||||
|
|
||||||
const starsGeometry = new THREE.BufferGeometry();
|
const starsGeometry = new THREE.BufferGeometry();
|
||||||
const starPositions = [];
|
const starPositions = [];
|
||||||
for (let i = 0; i < 1500; i++) {
|
for (let i = 0; i < 1500; i++) {
|
||||||
@@ -353,7 +470,7 @@ const earthMesh = new THREE.Mesh(
|
|||||||
new THREE.SphereGeometry(EARTH_RADIUS, 96, 96),
|
new THREE.SphereGeometry(EARTH_RADIUS, 96, 96),
|
||||||
earthMaterial
|
earthMaterial
|
||||||
);
|
);
|
||||||
root.add(earthMesh);
|
earthRotationGroup.add(earthMesh);
|
||||||
|
|
||||||
const atmosphereMesh = new THREE.Mesh(
|
const atmosphereMesh = new THREE.Mesh(
|
||||||
new THREE.SphereGeometry(EARTH_RADIUS * 1.035, 64, 64),
|
new THREE.SphereGeometry(EARTH_RADIUS * 1.035, 64, 64),
|
||||||
@@ -364,23 +481,12 @@ const atmosphereMesh = new THREE.Mesh(
|
|||||||
side: THREE.BackSide,
|
side: THREE.BackSide,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
root.add(atmosphereMesh);
|
earthRotationGroup.add(atmosphereMesh);
|
||||||
|
|
||||||
const earthGlow = new THREE.Sprite(
|
|
||||||
new THREE.SpriteMaterial({
|
|
||||||
color: 0x7aa7ff,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.16,
|
|
||||||
depthWrite: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
earthGlow.scale.setScalar(17);
|
|
||||||
root.add(earthGlow);
|
|
||||||
|
|
||||||
const satelliteGroup = new THREE.Group();
|
const satelliteGroup = new THREE.Group();
|
||||||
const orbitGroup = new THREE.Group();
|
const orbitGroup = new THREE.Group();
|
||||||
root.add(orbitGroup);
|
earthRotationGroup.add(orbitGroup);
|
||||||
root.add(satelliteGroup);
|
earthRotationGroup.add(satelliteGroup);
|
||||||
|
|
||||||
const dragState = {
|
const dragState = {
|
||||||
active: false,
|
active: false,
|
||||||
@@ -503,34 +609,158 @@ function loadIssModelTemplate() {
|
|||||||
return issModelTemplatePromise;
|
return issModelTemplatePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSatelliteFallback(color, isIss = false) {
|
function createSatelliteFallback(color, isIss = false, variantSeed = 0) {
|
||||||
if (!isIss) {
|
|
||||||
return new THREE.Mesh(
|
|
||||||
new THREE.SphereGeometry(0.11, 16, 16),
|
|
||||||
new THREE.MeshBasicMaterial({ color })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const group = new THREE.Group();
|
const group = new THREE.Group();
|
||||||
|
|
||||||
const bodyMaterial = new THREE.MeshStandardMaterial({
|
const bodyMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0xd7dde8,
|
color: isIss ? 0xd7dde8 : new THREE.Color(color).lerp(new THREE.Color(0xd9e1ef), 0.45),
|
||||||
roughness: 0.65,
|
roughness: 0.65,
|
||||||
metalness: 0.35,
|
metalness: 0.35,
|
||||||
});
|
});
|
||||||
const panelMaterial = new THREE.MeshStandardMaterial({
|
const panelMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0x587fc8,
|
color: isIss ? 0x587fc8 : new THREE.Color(color).offsetHSL(0.02, -0.05, -0.12),
|
||||||
roughness: 0.55,
|
roughness: 0.55,
|
||||||
metalness: 0.2,
|
metalness: 0.2,
|
||||||
emissive: 0x112244,
|
emissive: 0x112244,
|
||||||
emissiveIntensity: 0.18,
|
emissiveIntensity: 0.18,
|
||||||
});
|
});
|
||||||
const trussMaterial = new THREE.MeshStandardMaterial({
|
const trussMaterial = new THREE.MeshStandardMaterial({
|
||||||
color: 0xb8c3d9,
|
color: isIss ? 0xb8c3d9 : 0xb9c4d8,
|
||||||
roughness: 0.7,
|
roughness: 0.7,
|
||||||
metalness: 0.28,
|
metalness: 0.28,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isIss) {
|
||||||
|
const variant = Math.abs(variantSeed) % 4;
|
||||||
|
|
||||||
|
if (variant === 0) {
|
||||||
|
const bus = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.18, 0.14, 0.14),
|
||||||
|
bodyMaterial
|
||||||
|
);
|
||||||
|
group.add(bus);
|
||||||
|
|
||||||
|
const mast = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.012, 0.012, 0.62, 10),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
mast.rotation.z = Math.PI / 2;
|
||||||
|
group.add(mast);
|
||||||
|
|
||||||
|
const panelGeometry = new THREE.BoxGeometry(0.24, 0.012, 0.14);
|
||||||
|
const leftPanel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||||
|
leftPanel.position.x = -0.43;
|
||||||
|
group.add(leftPanel);
|
||||||
|
|
||||||
|
const rightPanel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||||
|
rightPanel.position.x = 0.43;
|
||||||
|
group.add(rightPanel);
|
||||||
|
|
||||||
|
const antenna = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.008, 0.008, 0.16, 10),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
antenna.position.y = 0.14;
|
||||||
|
group.add(antenna);
|
||||||
|
} else if (variant === 1) {
|
||||||
|
const body = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.09, 0.09, 0.22, 12),
|
||||||
|
bodyMaterial
|
||||||
|
);
|
||||||
|
body.rotation.z = Math.PI / 2;
|
||||||
|
group.add(body);
|
||||||
|
|
||||||
|
const dish = new THREE.Mesh(
|
||||||
|
new THREE.SphereGeometry(0.085, 16, 12, 0, Math.PI * 2, 0, Math.PI / 2),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
dish.rotation.z = -Math.PI / 2;
|
||||||
|
dish.position.x = 0.18;
|
||||||
|
group.add(dish);
|
||||||
|
|
||||||
|
const panelGeometry = new THREE.BoxGeometry(0.18, 0.012, 0.11);
|
||||||
|
const topPanel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||||
|
topPanel.position.set(-0.05, 0.2, 0);
|
||||||
|
topPanel.rotation.z = Math.PI / 2;
|
||||||
|
group.add(topPanel);
|
||||||
|
|
||||||
|
const bottomPanel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||||
|
bottomPanel.position.set(-0.05, -0.2, 0);
|
||||||
|
bottomPanel.rotation.z = Math.PI / 2;
|
||||||
|
group.add(bottomPanel);
|
||||||
|
} else if (variant === 2) {
|
||||||
|
const core = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.16, 0.16, 0.2),
|
||||||
|
bodyMaterial
|
||||||
|
);
|
||||||
|
group.add(core);
|
||||||
|
|
||||||
|
const armX = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.58, 0.018, 0.018),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
group.add(armX);
|
||||||
|
|
||||||
|
const armY = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.018, 0.58, 0.018),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
group.add(armY);
|
||||||
|
|
||||||
|
const panelGeometry = new THREE.BoxGeometry(0.14, 0.012, 0.12);
|
||||||
|
const panelOffsets = [
|
||||||
|
[0.34, 0, 0],
|
||||||
|
[-0.34, 0, 0],
|
||||||
|
[0, 0.34, 0],
|
||||||
|
[0, -0.34, 0],
|
||||||
|
];
|
||||||
|
for (const [x, y, z] of panelOffsets) {
|
||||||
|
const panel = new THREE.Mesh(panelGeometry, panelMaterial);
|
||||||
|
panel.position.set(x, y, z);
|
||||||
|
panel.rotation.z = Math.abs(y) > 0 ? Math.PI / 2 : 0;
|
||||||
|
group.add(panel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const bus = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.18, 0.11, 0.24),
|
||||||
|
bodyMaterial
|
||||||
|
);
|
||||||
|
group.add(bus);
|
||||||
|
|
||||||
|
const leftWing = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.32, 0.012, 0.12),
|
||||||
|
panelMaterial
|
||||||
|
);
|
||||||
|
leftWing.position.set(-0.28, 0.03, 0);
|
||||||
|
leftWing.rotation.z = 0.16;
|
||||||
|
group.add(leftWing);
|
||||||
|
|
||||||
|
const rightWing = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.32, 0.012, 0.12),
|
||||||
|
panelMaterial
|
||||||
|
);
|
||||||
|
rightWing.position.set(0.28, -0.03, 0);
|
||||||
|
rightWing.rotation.z = -0.16;
|
||||||
|
group.add(rightWing);
|
||||||
|
|
||||||
|
const boom = new THREE.Mesh(
|
||||||
|
new THREE.CylinderGeometry(0.01, 0.01, 0.28, 10),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
boom.position.y = -0.18;
|
||||||
|
group.add(boom);
|
||||||
|
|
||||||
|
const sensor = new THREE.Mesh(
|
||||||
|
new THREE.BoxGeometry(0.05, 0.05, 0.09),
|
||||||
|
trussMaterial
|
||||||
|
);
|
||||||
|
sensor.position.y = -0.33;
|
||||||
|
group.add(sensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
const core = new THREE.Mesh(
|
const core = new THREE.Mesh(
|
||||||
new THREE.BoxGeometry(0.24, 0.14, 0.14),
|
new THREE.BoxGeometry(0.24, 0.14, 0.14),
|
||||||
bodyMaterial
|
bodyMaterial
|
||||||
@@ -612,7 +842,7 @@ async function buildSatellites(rows, satelliteLib) {
|
|||||||
const isIss = row.norad_cat_id === 25544;
|
const isIss = row.norad_cat_id === 25544;
|
||||||
const mesh = isIss && issTemplate
|
const mesh = isIss && issTemplate
|
||||||
? issTemplate.clone(true)
|
? issTemplate.clone(true)
|
||||||
: createSatelliteFallback(color, isIss);
|
: createSatelliteFallback(color, isIss, row.norad_cat_id + index);
|
||||||
mesh.scale.setScalar(isIss && issTemplate ? 0.0035 : (isIss ? 1.35 : 1));
|
mesh.scale.setScalar(isIss && issTemplate ? 0.0035 : (isIss ? 1.35 : 1));
|
||||||
satelliteGroup.add(mesh);
|
satelliteGroup.add(mesh);
|
||||||
|
|
||||||
@@ -621,7 +851,7 @@ async function buildSatellites(rows, satelliteLib) {
|
|||||||
labelEl.textContent = row.object_name ?? String(row.norad_cat_id);
|
labelEl.textContent = row.object_name ?? String(row.norad_cat_id);
|
||||||
labelsLayer.appendChild(labelEl);
|
labelsLayer.appendChild(labelEl);
|
||||||
|
|
||||||
const orbitSamples = buildOrbitSamples(satrec, satelliteLib, new Date());
|
const orbitSamples = buildOrbitSamples(satrec, satelliteLib, simulatedTime);
|
||||||
let orbitLine = null;
|
let orbitLine = null;
|
||||||
if (orbitSamples.length > 1) {
|
if (orbitSamples.length > 1) {
|
||||||
orbitLine = new THREE.Line(
|
orbitLine = new THREE.Line(
|
||||||
@@ -698,8 +928,8 @@ function updateSatellitePositions(items, satelliteLib, now) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelPos = pos.clone().multiplyScalar(1.035);
|
const labelPos = item.mesh.getWorldPosition(new THREE.Vector3()).multiplyScalar(1.035);
|
||||||
const ndc = labelPos.project(camera);
|
const ndc = labelPos.clone().project(camera);
|
||||||
const isOffscreen = ndc.z < -1 || ndc.z > 1 || ndc.x < -1 || ndc.x > 1 || ndc.y < -1 || ndc.y > 1;
|
const isOffscreen = ndc.z < -1 || ndc.z > 1 || ndc.x < -1 || ndc.x > 1 || ndc.y < -1 || ndc.y > 1;
|
||||||
|
|
||||||
if (isOffscreen) {
|
if (isOffscreen) {
|
||||||
@@ -708,7 +938,7 @@ function updateSatellitePositions(items, satelliteLib, now) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const viewDir = camera.position.clone().normalize();
|
const viewDir = camera.position.clone().normalize();
|
||||||
const satNormal = pos.clone().normalize();
|
const satNormal = labelPos.clone().normalize();
|
||||||
if (satNormal.dot(viewDir) <= 0) {
|
if (satNormal.dot(viewDir) <= 0) {
|
||||||
item.labelEl.classList.remove('is-visible');
|
item.labelEl.classList.remove('is-visible');
|
||||||
continue;
|
continue;
|
||||||
@@ -722,6 +952,15 @@ function updateSatellitePositions(items, satelliteLib, now) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateEarthOrientation(now) {
|
||||||
|
const gmst = getGreenwichMeanSiderealTime(now);
|
||||||
|
earthRotationGroup.rotation.y = gmst + THREE.MathUtils.degToRad(EARTH_LONGITUDE_OFFSET_DEG);
|
||||||
|
|
||||||
|
const subsolarPoint = getSunSubsolarPoint(now);
|
||||||
|
const sunDirectionLocal = lonLatAltToVector3(subsolarPoint.latDeg, subsolarPoint.lonDeg, 0).normalize().multiplyScalar(28);
|
||||||
|
sunLight.position.copy(earthRotationGroup.localToWorld(sunDirectionLocal));
|
||||||
|
}
|
||||||
|
|
||||||
let satellites = [];
|
let satellites = [];
|
||||||
let satelliteLib = null;
|
let satelliteLib = null;
|
||||||
let nextOrbitRefreshAt = 0;
|
let nextOrbitRefreshAt = 0;
|
||||||
@@ -736,8 +975,16 @@ labelsToggle.addEventListener('change', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const button of timeButtons) {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
setTimeScale(Number(button.dataset.timeScale));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
try {
|
try {
|
||||||
|
simulatedTime = new Date();
|
||||||
|
updateTimeReadout();
|
||||||
satelliteLib = await loadSatelliteJs();
|
satelliteLib = await loadSatelliteJs();
|
||||||
setStatus('Lade TLE-Daten und ISS-Modell…');
|
setStatus('Lade TLE-Daten und ISS-Modell…');
|
||||||
|
|
||||||
@@ -766,12 +1013,22 @@ async function init() {
|
|||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
const frameNowMs = performance.now();
|
||||||
|
const deltaMs = Math.min(frameNowMs - lastFrameTimeMs, 1000);
|
||||||
|
lastFrameTimeMs = frameNowMs;
|
||||||
|
|
||||||
|
if (timeScale !== 0) {
|
||||||
|
simulatedTime = new Date(simulatedTime.getTime() + deltaMs * timeScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimeReadout();
|
||||||
|
updateEarthOrientation(simulatedTime);
|
||||||
|
|
||||||
if (satelliteLib && satellites.length > 0) {
|
if (satelliteLib && satellites.length > 0) {
|
||||||
const now = new Date();
|
updateSatellitePositions(satellites, satelliteLib, simulatedTime);
|
||||||
updateSatellitePositions(satellites, satelliteLib, now);
|
|
||||||
|
|
||||||
if (performance.now() >= nextOrbitRefreshAt) {
|
if (performance.now() >= nextOrbitRefreshAt) {
|
||||||
refreshOrbitLines(satellites, satelliteLib, now);
|
refreshOrbitLines(satellites, satelliteLib, simulatedTime);
|
||||||
nextOrbitRefreshAt = performance.now() + ORBIT_REFRESH_MS;
|
nextOrbitRefreshAt = performance.now() + ORBIT_REFRESH_MS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user