<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Artemis 2 High-Fidelity Simulator</title>
<style>
body { margin: 0; background-color: #000; overflow: hidden; font-family: 'Segoe UI', Arial, sans-serif; }
/* HUD / Legend Styling */
#hud {
position: absolute;
top: 20px;
left: 20px;
width: 320px;
background: rgba(10, 10, 15, 0.9);
border: 1px solid #334;
border-radius: 8px;
padding: 20px;
color: #eee;
box-shadow: 0 0 15px rgba(0, 150, 255, 0.2);
backdrop-filter: blur(5px);
}
h2 { margin: 0 0 15px 0; font-size: 1.2rem; text-transform: uppercase; letter-spacing: 2px; color: #fff; border-bottom: 1px solid #445; padding-bottom: 10px;}
.stage-item { display: flex; align-items: flex-start; margin-bottom: 12px; font-size: 0.9rem; }
.color-indicator { min-width: 15px; height: 15px; border-radius: 50%; margin-right: 15px; margin-top: 3px; box-shadow: 0 0 8px currentColor; }
/* Legend Colors */
.c-launch { background-color: #00ff00; color: #00ff00; } /* Green */
.c-orbit { background-color: #00ffff; color: #00ffff; } /* Cyan */
.c-heo { background-color: #aa00ff; color: #aa00ff; } /* Purple */
.c-tli { background-color: #ffcc00; color: #ffcc00; } /* Gold */
.c-flyby { background-color: #ff4400; color: #ff4400; } /* Orange Red */
.c-return { background-color: #ffffff; color: #ffffff; } /* White */
#controls-hint {
position: absolute; bottom: 20px; width: 100%; text-align: center; color: #556; font-size: 0.8rem; pointer-events: none;
}
</style>
</head>
<body>
<div id="hud">
<h2>Artemis II Profile</h2>
<div class="stage-item"><div class="color-indicator c-launch"></div><div><strong>Launch (KSC)</strong><br><span style="font-size:0.75em; color:#aaa;">Ascent to Parking Orbit</span></div></div>
<div class="stage-item"><div class="color-indicator c-orbit"></div><div><strong>LEO Parking</strong><br><span style="font-size:0.75em; color:#aaa;">Systems Check</span></div></div>
<div class="stage-item"><div class="color-indicator c-heo"></div><div><strong>Hohmann Transfer</strong><br><span style="font-size:0.75em; color:#aaa;">Elliptical Orbit Raising</span></div></div>
<div class="stage-item"><div class="color-indicator c-tli"></div><div><strong>Trans-Lunar Injection</strong><br><span style="font-size:0.75em; color:#aaa;">Tangent Burn to Moon</span></div></div>
<div class="stage-item"><div class="color-indicator c-flyby"></div><div><strong>Lunar Flyby</strong><br><span style="font-size:0.75em; color:#aaa;">Free Return Trajectory</span></div></div>
<div class="stage-item"><div class="color-indicator c-return"></div><div><strong>Re-entry</strong><br><span style="font-size:0.75em; color:#aaa;">Shallow Atmospheric Interface</span></div></div>
</div>
<div id="controls-hint">Left Click: Rotate • Right Click: Pan • Scroll: Zoom</div>
<script type="importmap">
{ "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } }
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// --- 1. SCENE SETUP ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x020205);
// Starfield
const starGeo = new THREE.BufferGeometry();
const starCount = 6000;
const starPos = new Float32Array(starCount * 3);
for(let i=0; i<starCount*3; i++) {
starPos[i] = (Math.random() - 0.5) * 1500;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const starMat = new THREE.PointsMaterial({color: 0xffffff, size: 0.6});
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 5000);
camera.position.set(50, 30, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// --- 2. LIGHTING ---
const sunLight = new THREE.DirectionalLight(0xffffff, 2.0);
sunLight.position.set(-150, 30, 80);
sunLight.castShadow = true;
scene.add(sunLight);
scene.add(new THREE.AmbientLight(0x404040, 0.5));
const textureLoader = new THREE.TextureLoader();
// --- 3. CELESTIAL BODIES ---
const EARTH_RADIUS = 12;
const MOON_DISTANCE = 140;
// EARTH
const earthGroup = new THREE.Group();
const earthMat = new THREE.MeshStandardMaterial({
map: textureLoader.load('https://unpkg.com/three-globe/example/img/earth-blue-marble.jpg'),
bumpMap: textureLoader.load('https://unpkg.com/three-globe/example/img/earth-topology.png'),
bumpScale: 0.5,
roughness: 0.6,
metalness: 0.1
});
const earth = new THREE.Mesh(new THREE.SphereGeometry(EARTH_RADIUS, 128, 128), earthMat);
earthGroup.add(earth);
scene.add(earthGroup);
// MOON
const moonMat = new THREE.MeshStandardMaterial({
map: textureLoader.load('https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_poles_1k.jpg'),
displacementMap: textureLoader.load('https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/ldem_16_uint.jpg'),
displacementScale: 0.3,
roughness: 0.9
});
const moon = new THREE.Mesh(new THREE.SphereGeometry(3.5, 64, 64), moonMat);
moon.position.set(MOON_DISTANCE, 0, 0);
scene.add(moon);
// --- 4. ADVANCED TRAJECTORY CALCULATION ---
// HELPER: Coordinate conversion
function getLatLon(lat, lon, radius) {
const phi = (90 - lat) * (Math.PI / 180);
const theta = (lon + 180) * (Math.PI / 180);
return new THREE.Vector3(
-(radius * Math.sin(phi) * Math.cos(theta)),
radius * Math.cos(phi),
radius * Math.sin(phi) * Math.sin(theta)
);
}
// A. LAUNCH (Cape Canaveral -> LEO)
// Cape Canaveral is approx 28.5° N, 80.6° W
const launchStart = getLatLon(28.5, -80.6, EARTH_RADIUS);
const leoAltitude = 2; // Simulating 2000km for visibility (scaled)
const parkingRadius = EARTH_RADIUS + leoAltitude;
// We calculate a point in orbit to merge into
// We simply place the "orbit insertion" point 90 degrees East of launch to simulate ascent
const launchEnd = new THREE.Vector3(parkingRadius, 0, 0); // Aligning with X-axis for simplicity later
const launchCurve = new THREE.CubicBezierCurve3(
launchStart,
new THREE.Vector3(launchStart.x + 5, launchStart.y + 10, launchStart.z), // Vertical climb
new THREE.Vector3(parkingRadius - 2, 5, 0), // Pitch over
launchEnd // Insertion
);
const launchPoints = launchCurve.getPoints(40);
// B. LEO PARKING ORBIT (Circular)
// Simple circle segment
const leoPoints = [];
for(let i=0; i<=30; i++) {
const angle = (i/30) * (Math.PI/2); // Quarter orbit checkout
leoPoints.push(new THREE.Vector3(
Math.cos(angle) * parkingRadius,
0, // Equatorial for simplicity
Math.sin(angle) * parkingRadius
));
}
// C. HOHMANN TRANSFER (Elliptical Orbit Raising)
// Perigee = parkingRadius, Apogee = 30 (Arbitrary High Earth Orbit)
const perigee = parkingRadius;
const apogee = 35;
const semiMajor = (perigee + apogee) / 2;
const eccentricity = (apogee - perigee) / (apogee + perigee);
const heoPoints = [];
// We orbit 1.5 times (Orbit raising maneuvers)
// Equation: r = a(1-e^2) / (1 + e*cos(theta))
// We start theta at PI/2 because that's where our LEO segment ended
for(let i=0; i<=150; i++) {
const theta = (Math.PI/2) + (i/100) * (Math.PI * 2); // 1.5 loops
const r = (semiMajor * (1 - eccentricity**2)) / (1 + eccentricity * Math.cos(theta - Math.PI/2)); // Phase shift to align perigee
heoPoints.push(new THREE.Vector3(
Math.cos(theta) * r,
Math.sin(theta) * r * 0.2, // Slight inclination
Math.sin(theta) * r
));
}
// D. TLI (Tangent Burn to Moon)
// Start from end of HEO. Velocity is tangent there.
const tliStart = heoPoints[heoPoints.length - 1];
// We use a Cubic Bezier to ensure the first control point aligns with the HEO velocity
// HEO velocity at that point is roughly (-1, 0, 0) direction
const tliCurve = new THREE.CubicBezierCurve3(
tliStart,
new THREE.Vector3(tliStart.x - 20, tliStart.y, tliStart.z + 10), // Tangent extension
new THREE.Vector3(80, 20, 40), // Mid-course
new THREE.Vector3(MOON_DISTANCE - 10, 5, 10) // Near Moon
);
const tliPoints = tliCurve.getPoints(50);
// E. LUNAR FLYBY
const flybyCurve = new THREE.CatmullRomCurve3([
new THREE.Vector3(MOON_DISTANCE - 10, 5, 10),
new THREE.Vector3(MOON_DISTANCE + 8, 0, 0), // Behind Moon
new THREE.Vector3(MOON_DISTANCE - 5, -5, -10) // Sling out
]);
const flybyPoints = flybyCurve.getPoints(40);
// F. RETURN (Shallow Re-entry)
// Start from flyby end
const returnStart = flybyPoints[flybyPoints.length - 1];
// Calculate Splashdown point (Pacific)
const splashdown = getLatLon(32, -117, EARTH_RADIUS);
// To make it shallow, we create a "pre-entry" point just above the atmosphere
// The vector from Pre-Entry -> Splashdown should be roughly tangent to the surface
// We cheat visually by placing the control point "skimming" the atmosphere
const returnCurve = new THREE.CubicBezierCurve3(
returnStart,
new THREE.Vector3(80, -20, -20), // Mid-return
new THREE.Vector3(splashdown.x + 10, splashdown.y + 5, splashdown.z), // Shallow approach vector
splashdown
);
const returnPoints = returnCurve.getPoints(60);
// --- 5. MERGING & COLORING ---
const allPoints = [
...launchPoints,
...leoPoints,
...heoPoints,
...tliPoints,
...flybyPoints,
...returnPoints
];
const geometry = new THREE.BufferGeometry().setFromPoints(allPoints);
const colors = [];
// Helper to push colors
function addColors(r, g, b, count) {
for(let k=0; k<count; k++) colors.push(r, g, b);
}
addColors(0, 1, 0, launchPoints.length); // Green (Launch)
addColors(0, 1, 1, leoPoints.length); // Cyan (LEO)
addColors(0.7, 0, 1, heoPoints.length); // Purple (HEO)
addColors(1, 0.8, 0, tliPoints.length); // Gold (TLI)
addColors(1, 0.3, 0, flybyPoints.length); // Orange (Flyby)
addColors(1, 1, 1, returnPoints.length); // White (Return)
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
const material = new THREE.LineBasicMaterial({ vertexColors: true, linewidth: 2 });
const trajectoryLine = new THREE.Line(geometry, material);
scene.add(trajectoryLine);
// --- 6. ANIMATION LOOP ---
function animate() {
requestAnimationFrame(animate);
controls.update();
// Rotate Earth and Moon
earth.rotation.y += 0.0005;
moon.rotation.y += 0.001;
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>