Spaces:
Running
Running
<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> |