privmv / index.html
privateuserh's picture
Add 2 files
18043eb verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lunar Celestial Viewer with Magnetic Field</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.min.js"></script>
<style>
/* Custom styles for the toggle switch */
.toggle-checkbox:checked {
right: 0;
border-color: #68D391;
}
.toggle-checkbox:checked + .toggle-label {
background-color: #68D391;
}
/* Full screen canvas */
#renderCanvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
/* Control panel */
.control-panel {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 100;
background: rgba(0, 0, 0, 0.8);
padding: 15px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
width: 320px;
color: white;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Loading animation */
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 2s linear infinite;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
}
@keyframes spin {
0% { transform: translate(-50%, -50%) rotate(0deg); }
100% { transform: translate(-50%, -50%) rotate(360deg); }
}
/* Coordinate display */
.celestial-display {
position: fixed;
top: 20px;
left: 20px;
z-index: 100;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 15px;
border-radius: 8px;
font-family: 'Courier New', monospace;
border: 1px solid rgba(255, 255, 255, 0.2);
min-width: 300px;
}
.celestial-value {
color: #4ade80;
font-weight: bold;
}
/* Model selector dropdown */
.model-selector {
width: 100%;
padding: 8px;
margin-top: 10px;
border-radius: 5px;
border: 1px solid #4b5563;
background-color: #1f2937;
color: white;
}
/* Celestial grid */
.celestial-grid {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
}
/* Button styles */
.control-btn {
transition: all 0.2s ease;
border-radius: 6px;
padding: 6px 12px;
font-size: 14px;
}
.control-btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Celestial lines */
.celestial-line {
position: absolute;
background: rgba(255, 255, 255, 0.1);
}
/* Moon phase indicator */
.moon-phase {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(90deg, #333 50%, #eee 50%);
margin-right: 10px;
}
/* Pin info window */
.pin-info {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
pointer-events: none;
z-index: 200;
max-width: 200px;
display: none;
}
</style>
</head>
<body class="bg-gray-900">
<!-- Loading indicator -->
<div id="loader" class="loader"></div>
<!-- Celestial coordinate display -->
<div class="celestial-display" id="celestialDisplay">
<div class="flex items-center mb-2">
<div class="moon-phase" id="moonPhase"></div>
<h3 class="text-lg font-bold">Lunar Celestial Coordinates</h3>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<div>Right Ascension:</div>
<div class="celestial-value" id="rightAscension">22h 53m 16s</div>
<div>Declination:</div>
<div class="celestial-value" id="declination">-09° 30' 48"</div>
<div>Hour Angle:</div>
<div class="celestial-value" id="hourAngle">14h 55m 53s</div>
<div>Sidereal Time:</div>
<div class="celestial-value" id="siderealTime">13h 49m 09s</div>
<div>Azimuth:</div>
<div class="celestial-value" id="azimuth">214° 22'</div>
<div>Altitude:</div>
<div class="celestial-value" id="altitude">+32° 47'</div>
<div>Gravity:</div>
<div class="celestial-value" id="gravity">1.62 m/s²</div>
<div>Magnetic Field:</div>
<div class="celestial-value" id="magneticField">~1-100 nT</div>
</div>
</div>
<!-- VRML 3D Renderer Canvas -->
<div id="renderCanvas"></div>
<!-- Pin info window -->
<div id="pinInfo" class="pin-info"></div>
<!-- Control Panel -->
<div class="control-panel">
<div class="flex items-center justify-between mb-4">
<span class="text-gray-200 font-medium">Lunar Celestial Viewer</span>
<div class="relative inline-block w-10 mr-2 align-middle select-none">
<input type="checkbox" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/>
<label for="toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
</div>
</div>
<select id="modelSelector" class="model-selector">
<option value="0">Celestial Moon</option>
<option value="1">Lunar Surface Grid</option>
<option value="2">Moon with Craters</option>
<option value="3">Earth-Moon System</option>
<option value="4">Lunar Landing Site</option>
<option value="5">Magnetic Field View</option>
</select>
<div class="flex flex-wrap gap-2 mt-4">
<button id="resetView" class="control-btn bg-blue-600 hover:bg-blue-700 text-white">
Reset View
</button>
<button id="toggleStars" class="control-btn bg-purple-600 hover:bg-purple-700 text-white">
Toggle Stars
</button>
<button id="toggleGrid" class="control-btn bg-green-600 hover:bg-green-700 text-white">
Toggle Celestial Grid
</button>
<button id="toggleCoordinates" class="control-btn bg-yellow-600 hover:bg-yellow-700 text-white">
Toggle Coordinates
</button>
<button id="togglePins" class="control-btn bg-red-600 hover:bg-red-700 text-white">
Toggle Site Pins
</button>
<button id="toggleMagnetic" class="control-btn bg-indigo-600 hover:bg-indigo-700 text-white">
Toggle Magnetic Field
</button>
</div>
<div class="mt-4 text-xs text-gray-400">
<p>• Drag to rotate view | Scroll to zoom | Right-drag to pan</p>
<p>• Current moon phase: <span id="moonPhaseText">Waning Gibbous</span></p>
<p>• Coordinates updated in real-time</p>
<p>• Click on pins for site information</p>
</div>
</div>
<script>
// Initialize Three.js scene
let scene, camera, renderer, controls, model, stars, celestialGrid;
let isVRMLActive = true;
let isStarsVisible = true;
let isGridVisible = true;
let isCoordinatesVisible = true;
let isPinsVisible = true;
let isMagneticVisible = false;
let pins = [];
let magneticFieldLines = [];
// Current moon coordinates (from TheSkyLive)
const moonCoords = {
rightAscension: { hours: 22, minutes: 53, seconds: 16 },
declination: { degrees: -9, minutes: 30, seconds: 48 },
hourAngle: { hours: 14, minutes: 55, seconds: 53 },
siderealTime: { hours: 13, minutes: 49, seconds: 9 },
azimuth: 214.3667,
altitude: 32.7833,
gravity: 1.62,
magneticField: "~1-100 nT"
};
// Known lunar sites with coordinates (latitude, longitude)
const lunarSites = [
{ name: "Volcano Prime", lat: 16.84, long: -21.22, type: "colony", description: "Primary lunar colony located in volcanic highlands" },
{ name: "Apollo 11", lat: 0.6741, long: 23.4731, type: "landing", description: "First human landing site (1969)" },
{ name: "Apollo 17", lat: 20.1919, long: 30.7717, type: "landing", description: "Last Apollo mission landing site (1972)" },
{ name: "Tycho Crater", lat: -43.31, long: -11.36, type: "feature", description: "Prominent impact crater with ray system" },
{ name: "Mare Tranquillitatis", lat: 8.5, long: 31.4, type: "feature", description: "Sea of Tranquility - dark volcanic plain" },
{ name: "Shackleton Crater", lat: -89.9, long: 0, type: "feature", description: "South pole crater with potential water ice" },
{ name: "Luna 2", lat: 29.1, long: 0, type: "impact", description: "First human-made object to reach the Moon (1959)" },
{ name: "Chang'e 4", lat: -45.5, long: 177.6, type: "lander", description: "First spacecraft to land on far side (2019)" },
{ name: "Mons Hadley", lat: 26.5, long: 3.7, type: "feature", description: "Mountain near Apollo 15 landing site" },
{ name: "Oceanus Procellarum", lat: 18.4, long: -57.4, type: "feature", description: "Largest lunar mare (Ocean of Storms)" }
];
// Space and lunar models with celestial coordinates
const spaceModels = [
// Celestial Moon
`#VRML V2.0 utf8
Group {
children [
# Moon positioned at current celestial coordinates
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.8 0.8 0.8
specularColor 0.5 0.5 0.5
shininess 0.2
}
texture ImageTexture {
url "moon_texture.jpg"
}
}
geometry Sphere {
radius 1.5
}
}
]
}
]
},
# Celestial equator
Transform {
rotation 1 0 0 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.2 0.4 1.0
transparency 0.7
}
}
geometry Cylinder {
radius 3.0
height 0.01
side TRUE
top FALSE
bottom FALSE
}
}
]
},
# Ecliptic plane (tilted 23.5°)
Transform {
rotation 1 0 0 ${23.5 * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 0.5 0.2
transparency 0.7
}
}
geometry Cylinder {
radius 3.0
height 0.01
side TRUE
top FALSE
bottom FALSE
}
}
]
}
]
}`,
// Lunar Surface Grid with coordinates
`#VRML V2.0 utf8
Group {
children [
# Moon surface with coordinate grid
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
# Surface
Shape {
appearance Appearance {
material Material {
diffuseColor 0.6 0.6 0.6
}
}
geometry ElevationGrid {
xDimension 10
zDimension 10
xSpacing 0.5
zSpacing 0.5
height [
0.0, 0.1, 0.2, 0.1, 0.0, -0.1, -0.2, -0.1, 0.0, 0.1,
0.1, 0.2, 0.3, 0.2, 0.1, 0.0, -0.1, 0.0, 0.1, 0.2,
0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0, 0.1, 0.2, 0.3,
0.1, 0.2, 0.3, 0.2, 0.1, 0.0, -0.1, 0.0, 0.1, 0.2,
0.0, 0.1, 0.2, 0.1, 0.0, -0.1, -0.2, -0.1, 0.0, 0.1,
-0.1, 0.0, 0.1, 0.0, -0.1, -0.2, -0.3, -0.2, -0.1, 0.0,
-0.2, -0.1, 0.0, -0.1, -0.2, -0.3, -0.4, -0.3, -0.2, -0.1,
-0.1, 0.0, 0.1, 0.0, -0.1, -0.2, -0.3, -0.2, -0.1, 0.0,
0.0, 0.1, 0.2, 0.1, 0.0, -0.1, -0.2, -0.1, 0.0, 0.1,
0.1, 0.2, 0.3, 0.2, 0.1, 0.0, -0.1, 0.0, 0.1, 0.2
]
creaseAngle 0.5
}
},
# Coordinate grid lines
DEF GridLines Group {
children [
# Longitude lines (every 30 degrees)
Transform {
rotation 0 1 0 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 0.0 0.0
emissiveColor 0.8 0.0 0.0
}
}
geometry Cylinder {
radius 2.5
height 0.01
top FALSE
bottom FALSE
}
}
]
},
Transform {
rotation 0 1 0 ${30 * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 0.0 0.0
emissiveColor 0.8 0.0 0.0
}
}
geometry Cylinder {
radius 2.5
height 0.01
top FALSE
bottom FALSE
}
}
]
},
Transform {
rotation 0 1 0 ${60 * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 0.0 0.0
emissiveColor 0.8 0.0 0.0
}
}
geometry Cylinder {
radius 2.5
height 0.01
top FALSE
bottom FALSE
}
}
]
},
# Latitude lines (every 30 degrees)
Transform {
rotation 1 0 0 ${30 * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.0 0.0 1.0
emissiveColor 0.0 0.0 0.8
}
}
geometry Cylinder {
radius 2.5
height 0.01
top FALSE
bottom FALSE
}
}
]
},
Transform {
rotation 1 0 0 ${60 * Math.PI / 180}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.0 0.0 1.0
emissiveColor 0.0 0.0 0.8
}
}
geometry Cylinder {
radius 2.5
height 0.01
top FALSE
bottom FALSE
}
}
]
}
]
}
]
}
]
},
# Current position marker
Transform {
translation ${Math.cos(moonCoords.azimuth * Math.PI / 180) * 2.5}
${Math.sin(moonCoords.altitude * Math.PI / 180) * 2.5}
${Math.sin(moonCoords.azimuth * Math.PI / 180) * 2.5}
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 1.0 0.0
emissiveColor 1.0 1.0 0.2
}
}
geometry Sphere {
radius 0.1
}
}
]
}
]
}`,
// Moon with Craters (celestial positioned)
`#VRML V2.0 utf8
Group {
children [
# Moon positioned at current celestial coordinates
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
# Moon base
Shape {
appearance Appearance {
material Material {
diffuseColor 0.7 0.7 0.7
}
}
geometry Sphere {
radius 1.5
}
},
# Craters
Transform {
translation -1 0 0.5
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.5 0.5 0.5
}
}
geometry Cylinder {
radius 0.3
height 0.1
top FALSE
}
}
]
},
Transform {
translation 0.8 0 -0.8
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.5 0.5 0.5
}
}
geometry Cylinder {
radius 0.5
height 0.15
top FALSE
}
}
]
},
Transform {
translation 0.5 0 1
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.5 0.5 0.5
}
}
geometry Cylinder {
radius 0.4
height 0.12
top FALSE
}
}
]
}
]
}
]
}`,
// Earth-Moon System with celestial coordinates
`#VRML V2.0 utf8
Group {
children [
# Earth positioned at celestial coordinates
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
Transform {
translation -3 0 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.2 0.4 0.8
specularColor 0.5 0.5 0.5
shininess 0.3
}
}
geometry Sphere {
radius 1.0
}
}
]
},
# Moon
Transform {
translation 3 0 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.8 0.8 0.8
}
}
geometry Sphere {
radius 0.3
}
}
]
},
# Orbit path
Shape {
appearance Appearance {
material Material {
diffuseColor 0.8 0.8 0.8
transparency 0.7
}
}
geometry Cylinder {
radius 3.0
height 0.01
side TRUE
top FALSE
bottom FALSE
}
}
]
}
]
}
]
}`,
// Lunar Landing Site with coordinates
`#VRML V2.0 utf8
Group {
children [
# Lunar surface positioned at celestial coordinates
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
# Lunar surface
Shape {
appearance Appearance {
material Material {
diffuseColor 0.6 0.6 0.6
}
}
geometry ElevationGrid {
xDimension 5
zDimension 5
xSpacing 1.0
zSpacing 1.0
height [
0.0, 0.1, 0.2, 0.1, 0.0,
-0.1, 0.0, 0.1, 0.0, -0.1,
-0.2, -0.1, 0.0, -0.1, -0.2,
-0.1, 0.0, 0.1, 0.0, -0.1,
0.0, 0.1, 0.2, 0.1, 0.0
]
}
},
# Lunar lander
Transform {
translation 0 0.5 0
children [
# Lander base
Shape {
appearance Appearance {
material Material {
diffuseColor 0.8 0.8 0.8
}
}
geometry Box {
size 0.5 0.2 0.5
}
},
# Lander legs
Transform {
translation -0.3 -0.2 -0.3
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.7 0.7 0.7
}
}
geometry Cylinder {
radius 0.03
height 0.4
}
}
]
},
Transform {
translation 0.3 -0.2 -0.3
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.7 0.7 0.7
}
}
geometry Cylinder {
radius 0.03
height 0.4
}
}
]
},
Transform {
translation -0.3 -0.2 0.3
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.7 0.7 0.7
}
}
geometry Cylinder {
radius 0.03
height 0.4
}
}
]
},
Transform {
translation 0.3 -0.2 0.3
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.7 0.7 0.7
}
}
geometry Cylinder {
radius 0.03
height 0.4
}
}
]
},
# Lander module
Transform {
translation 0 0.3 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.9 0.9 0.9
}
}
geometry Cylinder {
radius 0.2
height 0.4
}
}
]
}
]
},
# Flag with coordinates marker
Transform {
translation 0.5 0.5 0
children [
# Flag pole
Shape {
appearance Appearance {
material Material {
diffuseColor 0.5 0.5 0.5
}
}
geometry Cylinder {
radius 0.02
height 1.0
}
},
# Flag with coordinates
Transform {
translation 0 0.6 0.1
rotation 0 1 0 0.5
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1 0 0
}
}
geometry Box {
size 0.3 0.2 0.01
}
},
Transform {
translation 0 0 0.02
scale 0.5 0.5 0.5
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0 0 0
}
}
geometry Text {
string ["RA: 22h 53m", "Dec: -09°30'"]
fontStyle FontStyle {
size 0.1
family "SANS"
style "BOLD"
}
}
}
]
}
]
}
]
}
]
}
]
}
]
}`,
// Magnetic Field View
`#VRML V2.0 utf8
Group {
children [
# Moon positioned at current celestial coordinates
Transform {
rotation 0 1 0 ${(moonCoords.hourAngle.hours / 24) * Math.PI * 2}
children [
Transform {
rotation 1 0 0 ${moonCoords.declination.degrees * Math.PI / 180}
children [
# Moon with transparent surface
Shape {
appearance Appearance {
material Material {
diffuseColor 0.8 0.8 0.8
transparency 0.7
}
}
geometry Sphere {
radius 1.5
}
},
# Magnetic poles (north and south)
Transform {
translation 0 1.5 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 1.0 0.0 0.0
emissiveColor 1.0 0.3 0.3
}
}
geometry Sphere {
radius 0.15
}
}
]
},
Transform {
translation 0 -1.5 0
children [
Shape {
appearance Appearance {
material Material {
diffuseColor 0.0 0.0 1.0
emissiveColor 0.3 0.3 1.0
}
}
geometry Sphere {
radius 0.15
}
}
]
}
]
}
]
}
]
}`
];
// Initialize the 3D scene
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000011);
// Create camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('renderCanvas').appendChild(renderer.domElement);
// Add lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Add stars background
createStars();
// Add celestial grid
createCelestialGrid();
// Add orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// Load initial model
loadVRMLModel(spaceModels[0]);
// Add lunar site pins
createLunarSitePins();
// Create magnetic field visualization
createMagneticField();
// Update moon phase display
updateMoonPhase();
// Hide loader when scene is ready
document.getElementById('loader').style.display = 'none';
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Start animation loop
animate();
// Update coordinates periodically to simulate real-time
setInterval(updateMoonCoordinates, 1000);
// Set up pin click handler
document.getElementById('renderCanvas').addEventListener('click', onCanvasClick, false);
}
// Create lunar site pins
function createLunarSitePins() {
// Remove existing pins
pins.forEach(pin => scene.remove(pin));
pins = [];
lunarSites.forEach(site => {
// Convert lat/long to 3D position on sphere
const latRad = site.lat * Math.PI / 180;
const longRad = site.long * Math.PI / 180;
const radius = 1.52; // Slightly larger than moon radius
const x = radius * Math.cos(latRad) * Math.cos(longRad);
const y = radius * Math.sin(latRad);
const z = radius * Math.cos(latRad) * Math.sin(longRad);
// Create pin based on site type
let pinMaterial, pinHeight;
if (site.type === "colony") {
pinMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
pinHeight = 0.3;
} else if (site.type === "landing") {
pinMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
pinHeight = 0.25;
} else if (site.type === "impact") {
pinMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
pinHeight = 0.2;
} else {
pinMaterial = new THREE.MeshBasicMaterial({ color: 0x00ffff });
pinHeight = 0.15;
}
// Create pin (cone on top of a cylinder)
const pinGroup = new THREE.Group();
// Pin base (cylinder)
const pinBaseGeometry = new THREE.CylinderGeometry(0.03, 0.03, pinHeight, 8);
const pinBase = new THREE.Mesh(pinBaseGeometry, pinMaterial);
pinBase.position.y = pinHeight / 2;
pinGroup.add(pinBase);
// Pin tip (cone)
const pinTipGeometry = new THREE.ConeGeometry(0.05, 0.1, 8);
const pinTip = new THREE.Mesh(pinTipGeometry, pinMaterial);
pinTip.position.y = pinHeight + 0.05;
pinGroup.add(pinTip);
// Position the pin on the moon's surface
pinGroup.position.set(x, y, z);
// Make pin point outward from moon's center
pinGroup.lookAt(new THREE.Vector3(0, 0, 0));
pinGroup.rotateX(Math.PI / 2);
// Store site data for click handling
pinGroup.userData = {
isPin: true,
siteInfo: site
};
scene.add(pinGroup);
pins.push(pinGroup);
});
}
// Create magnetic field visualization
function createMagneticField() {
// Remove existing field lines
magneticFieldLines.forEach(line => scene.remove(line));
magneticFieldLines = [];
// Moon's magnetic field is weak and localized, so we'll visualize it with field lines
// emanating from the poles (simplified representation)
// North pole position
const northPole = new THREE.Vector3(0, 1.5, 0);
// South pole position
const southPole = new THREE.Vector3(0, -1.5, 0);
// Create field lines (simplified dipole field)
const segments = 12;
const steps = 20;
const material = new THREE.LineBasicMaterial({
color: 0xff5555,
transparent: true,
opacity: 0.7
});
for (let i = 0; i < segments; i++) {
// Field lines from north pole
const northGeometry = new THREE.BufferGeometry();
const northVertices = [];
const angle = (i / segments) * Math.PI * 2;
for (let j = 0; j <= steps; j++) {
const t = j / steps;
const theta = angle;
const phi = t * Math.PI;
// Simplified dipole field line path
const r = 1.5 * Math.sin(phi) * Math.sin(phi);
const x = r * Math.cos(theta);
const y = 1.5 * Math.cos(phi);
const z = r * Math.sin(theta);
northVertices.push(x, y, z);
}
northGeometry.setAttribute('position', new THREE.Float32BufferAttribute(northVertices, 3));
const northLine = new THREE.Line(northGeometry, material);
magneticFieldLines.push(northLine);
scene.add(northLine);
// Field lines from south pole (same but inverted)
const southGeometry = new THREE.BufferGeometry();
const southVertices = [];
for (let j = 0; j <= steps; j++) {
const t = j / steps;
const theta = angle + Math.PI;
const phi = t * Math.PI;
const r = 1.5 * Math.sin(phi) * Math.sin(phi);
const x = r * Math.cos(theta);
const y = -1.5 * Math.cos(phi);
const z = r * Math.sin(theta);
southVertices.push(x, y, z);
}
southGeometry.setAttribute('position', new THREE.Float32BufferAttribute(southVertices, 3));
const southLine = new THREE.Line(southGeometry, material);
magneticFieldLines.push(southLine);
scene.add(southLine);
}
// Set initial visibility
magneticFieldLines.forEach(line => line.visible = isMagneticVisible);
}
// Create starfield background
function createStars() {
const starsGeometry = new THREE.BufferGeometry();
const starsMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.1,
transparent: true
});
const starsVertices = [];
for (let i = 0; i < 10000; i++) {
const x = (Math.random() - 0.5) * 2000;
const y = (Math.random() - 0.5) * 2000;
const z = (Math.random() - 0.5) * 2000;
starsVertices.push(x, y, z);
}
starsGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starsVertices, 3));
stars = new THREE.Points(starsGeometry, starsMaterial);
scene.add(stars);
}
// Create celestial coordinate grid
function createCelestialGrid() {
const gridGroup = new THREE.Group();
// Celestial equator (blue)
const equatorGeometry = new THREE.TorusGeometry(3, 0.01, 16, 64);
const equatorMaterial = new THREE.MeshBasicMaterial({
color: 0x4444ff,
transparent: true,
opacity: 0.5
});
const equator = new THREE.Mesh(equatorGeometry, equatorMaterial);
equator.rotation.x = Math.PI / 2;
gridGroup.add(equator);
// Ecliptic (yellow, tilted 23.5°)
const eclipticGeometry = new THREE.TorusGeometry(3, 0.01, 16, 64);
const eclipticMaterial = new THREE.MeshBasicMaterial({
color: 0xffff44,
transparent: true,
opacity: 0.5
});
const ecliptic = new THREE.Mesh(eclipticGeometry, eclipticMaterial);
ecliptic.rotation.x = Math.PI / 2 + (23.5 * Math.PI / 180);
gridGroup.add(ecliptic);
// Right ascension lines (every 30 degrees)
for (let i = 0; i < 12; i++) {
const raGeometry = new THREE.BufferGeometry();
const raMaterial = new THREE.LineBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.3
});
const raAngle = (i * 30) * Math.PI / 180;
const vertices = new Float32Array([
0, 0, 0,
Math.cos(raAngle) * 3, Math.sin(raAngle) * 3, 0
]);
raGeometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
const raLine = new THREE.Line(raGeometry, raMaterial);
gridGroup.add(raLine);
}
// Declination lines (every 15 degrees from -90 to +90)
for (let i = -6; i <= 6; i++) {
const decGeometry = new THREE.BufferGeometry();
const decMaterial = new THREE.LineBasicMaterial({
color: 0x0000ff,
transparent: true,
opacity: 0.3
});
const decAngle = (i * 15) * Math.PI / 180;
const radius = Math.cos(decAngle) * 3;
const y = Math.sin(decAngle) * 3;
const circleGeometry = new THREE.BufferGeometry();
const circleVertices = [];
const segments = 64;
for (let j = 0; j <= segments; j++) {
const theta = (j / segments) * Math.PI * 2;
circleVertices.push(
Math.cos(theta) * radius,
y,
Math.sin(theta) * radius
);
}
circleGeometry.setAttribute('position', new THREE.Float32BufferAttribute(circleVertices, 3));
const decCircle = new THREE.Line(circleGeometry, decMaterial);
gridGroup.add(decCircle);
}
celestialGrid = gridGroup;
scene.add(celestialGrid);
}
// Load VRML model
function loadVRMLModel(vrmlText) {
// Remove previous model if exists
if (model) {
scene.remove(model);
}
try {
// Parse VRML (simplified for this example)
const parser = new VRMLParser();
const vrmlObject = parser.parse(vrmlText);
// Convert VRML to Three.js objects (simplified)
model = convertVRMLToThreeJS(vrmlObject);
if (model) {
scene.add(model);
// Adjust camera position based on model
const selectedModel = document.getElementById('modelSelector').value;
if (selectedModel === "1" || selectedModel === "4") {
camera.position.z = 8;
} else if (selectedModel === "3") {
camera.position.z = 10;
} else if (selectedModel === "5") {
camera.position.z = 7;
} else {
camera.position.z = 5;
}
controls.update();
}
} catch (error) {
console.error('Error loading VRML model:', error);
// Fallback to a simple sphere if parsing fails
const geometry = new THREE.SphereGeometry(1.5, 32, 32);
const material = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
model = new THREE.Mesh(geometry, material);
scene.add(model);
}
}
// Simplified VRML to Three.js converter
function convertVRMLToThreeJS(vrmlObject) {
// This is a simplified version - a real converter would be more complex
if (vrmlObject.children && vrmlObject.children.length > 0) {
const group = new THREE.Group();
for (const child of vrmlObject.children) {
if (child.geometry) {
let geometry;
let material = new THREE.MeshStandardMaterial({
color: child.appearance?.material?.diffuseColor ?
new THREE.Color(...child.appearance.material.diffuseColor) :
0xffffff,
emissive: child.appearance?.material?.emissiveColor ?
new THREE.Color(...child.appearance.material.emissiveColor) :
0x000000,
transparent: child.appearance?.material?.transparency ? true : false,
opacity: child.appearance?.material?.transparency ?
1 - child.appearance.material.transparency : 1.0
});
if (child.geometry.type === 'Box') {
geometry = new THREE.BoxGeometry(
child.geometry.size[0] || 1,
child.geometry.size[1] || 1,
child.geometry.size[2] || 1
);
} else if (child.geometry.type === 'Sphere') {
geometry = new THREE.SphereGeometry(
child.geometry.radius || 1,
32, 32
);
} else if (child.geometry.type === 'Cone') {
geometry = new THREE.ConeGeometry(
child.geometry.bottomRadius || 1,
child.geometry.height || 1,
32
);
} else if (child.geometry.type === 'Cylinder') {
geometry = new THREE.CylinderGeometry(
child.geometry.radius || 0.5,
child.geometry.radius || 0.5,
child.geometry.height || 1,
32
);
if (child.geometry.top === false) {
// For craters, we need to flip the cylinder upside down
geometry.rotateX(Math.PI);
}
} else if (child.geometry.type === 'ElevationGrid') {
const xDimension = child.geometry.xDimension || 2;
const zDimension = child.geometry.zDimension || 2;
const xSpacing = child.geometry.xSpacing || 1;
const zSpacing = child.geometry.zSpacing || 1;
const heights = child.geometry.height || [0, 0, 0, 0];
geometry = new THREE.PlaneGeometry(
(xDimension - 1) * xSpacing,
(zDimension - 1) * zSpacing,
xDimension - 1,
zDimension - 1
);
// Apply height data to vertices
const positionAttribute = geometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
positionAttribute.setY(i, heights[i]);
}
positionAttribute.needsUpdate = true;
// Rotate to make it horizontal
geometry.rotateX(-Math.PI / 2);
} else {
// Default to sphere if geometry type not recognized
geometry = new THREE.SphereGeometry(1, 32, 32);
}
const mesh = new THREE.Mesh(geometry, material);
if (child.translation) {
mesh.position.set(
child.translation[0] || 0,
child.translation[1] || 0,
child.translation[2] || 0
);
}
if (child.rotation) {
mesh.rotation.set(
child.rotation[0] || 0,
child.rotation[1] || 0,
child.rotation[2] || 0,
child.rotation[3] || 0
);
}
group.add(mesh);
}
}
return group;
}
return null;
}
// Update moon coordinates display
function updateMoonCoordinates() {
// Simulate slight changes in coordinates over time
moonCoords.rightAscension.seconds = (moonCoords.rightAscension.seconds + 0.1) % 60;
if (moonCoords.rightAscension.seconds < 0.1) {
moonCoords.rightAscension.minutes = (moonCoords.rightAscension.minutes + 1) % 60;
if (moonCoords.rightAscension.minutes === 0) {
moonCoords.rightAscension.hours = (moonCoords.rightAscension.hours + 1) % 24;
}
}
moonCoords.declination.seconds = (moonCoords.declination.seconds + 0.05) % 60;
if (moonCoords.declination.seconds < 0.05) {
moonCoords.declination.minutes = (moonCoords.declination.minutes + 1) % 60;
}
moonCoords.hourAngle.seconds = (moonCoords.hourAngle.seconds + 0.15) % 60;
if (moonCoords.hourAngle.seconds < 0.15) {
moonCoords.hourAngle.minutes = (moonCoords.hourAngle.minutes + 1) % 60;
if (moonCoords.hourAngle.minutes === 0) {
moonCoords.hourAngle.hours = (moonCoords.hourAngle.hours + 1) % 24;
}
}
moonCoords.siderealTime.seconds = (moonCoords.siderealTime.seconds + 0.1) % 60;
if (moonCoords.siderealTime.seconds < 0.1) {
moonCoords.siderealTime.minutes = (moonCoords.siderealTime.minutes + 1) % 60;
if (moonCoords.siderealTime.minutes === 0) {
moonCoords.siderealTime.hours = (moonCoords.siderealTime.hours + 1) % 24;
}
}
// Update azimuth and altitude slightly
moonCoords.azimuth = (moonCoords.azimuth + 0.05) % 360;
moonCoords.altitude = Math.min(90, Math.max(-90, moonCoords.altitude + (Math.random() - 0.5) * 0.1));
// Update the display
document.getElementById('rightAscension').textContent =
`${Math.floor(moonCoords.rightAscension.hours)}h ${Math.floor(moonCoords.rightAscension.minutes)}m ${Math.floor(moonCoords.rightAscension.seconds)}s`;
document.getElementById('declination').textContent =
`${moonCoords.declination.degrees < 0 ? '-' : '+'}${Math.abs(Math.floor(moonCoords.declination.degrees))}° ${Math.floor(moonCoords.declination.minutes)}' ${Math.floor(moonCoords.declination.seconds)}"`;
document.getElementById('hourAngle').textContent =
`${Math.floor(moonCoords.hourAngle.hours)}h ${Math.floor(moonCoords.hourAngle.minutes)}m ${Math.floor(moonCoords.hourAngle.seconds)}s`;
document.getElementById('siderealTime').textContent =
`${Math.floor(moonCoords.siderealTime.hours)}h ${Math.floor(moonCoords.siderealTime.minutes)}m ${Math.floor(moonCoords.siderealTime.seconds)}s`;
document.getElementById('azimuth').textContent =
`${Math.floor(moonCoords.azimuth)}° ${Math.floor((moonCoords.azimuth % 1) * 60)}'`;
document.getElementById('altitude').textContent =
`${moonCoords.altitude >= 0 ? '+' : ''}${Math.floor(moonCoords.altitude)}° ${Math.floor((moonCoords.altitude % 1) * 60)}'`;
document.getElementById('gravity').textContent =
`${moonCoords.gravity.toFixed(2)} m/s²`;
document.getElementById('magneticField').textContent =
moonCoords.magneticField;
}
// Update moon phase display
function updateMoonPhase() {
const moonPhaseElement = document.getElementById('moonPhase');
const moonPhaseTextElement = document.getElementById('moonPhaseText');
// Simulate waning gibbous phase (about 70% illuminated)
moonPhaseElement.style.background = 'linear-gradient(90deg, #333 30%, #eee 30%)';
moonPhaseElement.style.borderRadius = '50%';
moonPhaseTextElement.textContent = 'Waning Gibbous (70%)';
}
// Handle canvas clicks for pin selection
function onCanvasClick(event) {
if (!isPinsVisible) return;
// Calculate mouse position in normalized device coordinates
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// Set up raycaster
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
// Calculate objects intersecting the ray
const intersects = raycaster.intersectObjects(pins);
if (intersects.length > 0) {
const clickedPin = intersects[0].object;
if (clickedPin.userData.isPin) {
const site = clickedPin.userData.siteInfo;
const pinInfo = document.getElementById('pinInfo');
// Position the info window near the pin
const pinPosition = clickedPin.position.clone().project(camera);
pinInfo.style.left = `${(pinPosition.x * 0.5 + 0.5) * window.innerWidth}px`;
pinInfo.style.top = `${(-(pinPosition.y * 0.5 - 0.5)) * window.innerHeight}px`;
// Set content based on site type
let typeColor;
switch(site.type) {
case 'colony': typeColor = 'text-green-400'; break;
case 'landing': typeColor = 'text-red-400'; break;
case 'impact': typeColor = 'text-yellow-400'; break;
default: typeColor = 'text-cyan-400';
}
pinInfo.innerHTML = `
<div class="font-bold ${typeColor}">${site.name}</div>
<div>Lat: ${site.lat}° | Long: ${site.long}°</div>
<div class="mt-1">${site.description}</div>
`;
pinInfo.style.display = 'block';
// Hide after 5 seconds
setTimeout(() => {
pinInfo.style.display = 'none';
}, 5000);
}
}
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
if (isVRMLActive) {
controls.update();
renderer.render(scene, camera);
}
}
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Toggle VRML display
document.getElementById('toggle').addEventListener('change', function(e) {
isVRMLActive = e.target.checked;
if (isVRMLActive) {
document.getElementById('renderCanvas').style.display = 'block';
} else {
document.getElementById('renderCanvas').style.display = 'none';
}
});
// Reset view
document.getElementById('resetView').addEventListener('click', function() {
if (controls) {
controls.reset();
// Adjust camera based on current model
const selectedModel = document.getElementById('modelSelector').value;
if (selectedModel === "1" || selectedModel === "4") {
camera.position.z = 8;
} else if (selectedModel === "3") {
camera.position.z = 10;
} else if (selectedModel === "5") {
camera.position.z = 7;
} else {
camera.position.z = 5;
}
}
});
// Toggle stars
document.getElementById('toggleStars').addEventListener('click', function() {
isStarsVisible = !isStarsVisible;
stars.visible = isStarsVisible;
});
// Toggle grid
document.getElementById('toggleGrid').addEventListener('click', function() {
isGridVisible = !isGridVisible;
celestialGrid.visible = isGridVisible;
});
// Toggle coordinates display
document.getElementById('toggleCoordinates').addEventListener('click', function() {
isCoordinatesVisible = !isCoordinatesVisible;
document.getElementById('celestialDisplay').style.display = isCoordinatesVisible ? 'block' : 'none';
});
// Toggle site pins
document.getElementById('togglePins').addEventListener('click', function() {
isPinsVisible = !isPinsVisible;
pins.forEach(pin => pin.visible = isPinsVisible);
});
// Toggle magnetic field
document.getElementById('toggleMagnetic').addEventListener('click', function() {
isMagneticVisible = !isMagneticVisible;
magneticFieldLines.forEach(line => line.visible = isMagneticVisible);
});
// Model selector
document.getElementById('modelSelector').addEventListener('change', function() {
const modelIndex = parseInt(this.value);
loadVRMLModel(spaceModels[modelIndex]);
// Special handling for magnetic field view
if (modelIndex === 5) {
isMagneticVisible = true;
magneticFieldLines.forEach(line => line.visible = true);
} else {
isMagneticVisible = false;
magneticFieldLines.forEach(line => line.visible = false);
}
});
// Initialize the scene when the page loads
window.onload = init;
// Mock VRMLParser class for demonstration
class VRMLParser {
parse(vrmlText) {
// This is a very simplified parser for demonstration
const lines = vrmlText.split('\n');
const result = { children: [] };
let currentChild = null;
let currentGroup = null;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.startsWith('Transform {') || trimmed.startsWith('Group {')) {
currentGroup = { children: [] };
if (trimmed.startsWith('Transform')) {
currentGroup.type = 'Transform';
} else {
currentGroup.type = 'Group';
}
}
else if (trimmed.startsWith('translation')) {
const coords = trimmed.split(' ').slice(1).map(Number);
if (currentGroup) {
currentGroup.translation = coords;
}
}
else if (trimmed.startsWith('rotation')) {
const values = trimmed.split(' ').slice(1).map(Number);
if (currentGroup) {
currentGroup.rotation = values;
}
}
else if (trimmed.startsWith('Shape {')) {
currentChild = {
appearance: {},
geometry: {}
};
}
else if (trimmed.startsWith('diffuseColor')) {
const color = trimmed.split(' ').slice(1).map(Number);
if (currentChild) {
currentChild.appearance.material = { diffuseColor: color };
}
}
else if (trimmed.startsWith('emissiveColor')) {
const color = trimmed.split(' ').slice(1).map(Number);
if (currentChild) {
currentChild.appearance.material = currentChild.appearance.material || {};
currentChild.appearance.material.emissiveColor = color;
}
}
else if (trimmed.startsWith('transparency')) {
const value = parseFloat(trimmed.split(' ')[1]);
if (currentChild) {
currentChild.appearance.material = currentChild.appearance.material || {};
currentChild.appearance.material.transparency = value;
}
}
else if (trimmed.startsWith('geometry Box')) {
if (currentChild) {
currentChild.geometry = { type: 'Box', size: [2, 2, 2] };
}
}
else if (trimmed.startsWith('geometry Sphere')) {
if (currentChild) {
currentChild.geometry = { type: 'Sphere', radius: 1.5 };
}
}
else if (trimmed.startsWith('geometry Cone')) {
if (currentChild) {
currentChild.geometry = { type: 'Cone', bottomRadius: 1.5, height: 3 };
}
}
else if (trimmed.startsWith('geometry Cylinder')) {
if (currentChild) {
currentChild.geometry = { type: 'Cylinder', radius: 0.5, height: 1, top: true };
}
}
else if (trimmed.startsWith('geometry ElevationGrid')) {
if (currentChild) {
currentChild.geometry = {
type: 'ElevationGrid',
xDimension: 10,
zDimension: 10,
xSpacing: 0.5,
zSpacing: 0.5,
height: Array(100).fill(0)
};
}
}
else if (trimmed === '}') {
if (currentChild) {
if (currentGroup) {
currentGroup.children.push(currentChild);
} else {
result.children.push(currentChild);
}
currentChild = null;
}
else if (currentGroup) {
result.children.push(currentGroup);
currentGroup = null;
}
}
}
return result;
}
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=privateuserh/privmv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>