<!DOCTYPE html>
<html>
<head>
<title>Asteroid MG1 Passing Earth - July 12, 2025</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
#timeline {
width: 100%;
padding: 10px;
background: rgba(255, 255, 255, 0.8);
border-radius: 5px;
}
#timeLabel {
color: white;
font-family: Arial, sans-serif;
cursor: default;
}
#controls {
position: absolute;
bottom: 20px; left: 50%;
transform: translateX(-50%);
width: 80%; max-width: 600px;
text-align: center;
padding: 10px;
color: white;
font-family: Arial, sans-serif;
}
#controls2 {
position: absolute;
top: 10px; right: 10px;
color: lime; background: rgba(0, 0, 0, 0.5);
text-align: right;
padding: 10px;
font-family: Arial, sans-serif;
}
#controls2 > span {
cursor: default;
}
#cameraInfo {
position: absolute;
top: 10px; left: 10px;
color: lime; background: rgba(0, 0, 0, 0.5);
font-family: Arial, sans-serif; font-size: 14px;
padding: 5px;
border-radius: 3px;
cursor: default;
}
#mF1, #mF2, #mF3 {
display:none;
}
</style>
</style>
</head>
<body>
<div id="cameraInfo">
<div id="infoDynamic"></div><br />
<div id="infoStatic">
Earth speed ~ 107 000 km/h<br />
Moon speed ~ 3 600 km/h<br />
Asteroid speed ~ 25 000 km/h<br />
<!--(Sun ~ 800 000 km/h)<br />
(Milky Way ~ 2.1 million km/h)</br>-->
</div>
</div>
<div id="controls">
<div id="timeLabel">July 12, 2025 12:00 UTC</div>
<input type="range" id="timeline" min="0" max="100" value="50" step=".01">
<br />
<button id="play1x">Play 1x</button>
<button id="play100x">Play 100x</button>
<button id="play1000x">Play 1000x</button>
<button id="play10000x">Play 10000x</button>
<button id="play100000x">Play 100000x</button>
<button id="stop">Stop</button>
</div>
<div id="controls2">
<span id="mF0">></span><button id="modeF">Free mode</button><br />
<span id="lockLabel">Lock mode:</span><br />
<span id="mF1">></span><button id="modeL1">Asteroid</button><br />
<span id="mF2">></span><button id="modeL2">Moon</button><br />
<span id="mF3">></span><button id="modeL3">Earth</button><br />
</div>
<!--<script src="three.min.js"></script>
<script src="OrbitControls.js"></script>-->
<script type="importmap">
{
"imports": {
"three": "./build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls2.js';
//import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
//import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
//THREE.Object3D._threexDomEvent.camera(camera);
scene.add(camera);
// Lighting
const sunLight = new THREE.PointLight(0xffffff, 50000, 1000);
sunLight.position.set(0, 0, 0);
scene.add(sunLight);
const ambientLight = new THREE.AmbientLight(0x404040, 0.1); // Adjusted up from 0.1
scene.add(ambientLight);
// Define the shaders
const vertexShaderSun = `
varying vec3 vPosition;
void main() {
vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderSun = `
uniform float time;
varying vec3 vPosition;
float random(vec3 p) {
return fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
}
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float n0 = random(i);
float n1 = random(i + vec3(1.0, 0.0, 0.0));
float n2 = random(i + vec3(0.0, 1.0, 0.0));
float n3 = random(i + vec3(1.0, 1.0, 0.0));
float n4 = random(i + vec3(0.0, 0.0, 1.0));
float n5 = random(i + vec3(1.0, 0.0, 1.0));
float n6 = random(i + vec3(0.0, 1.0, 1.0));
float n7 = random(i + vec3(1.0, 1.0, 1.0));
return mix(
mix(mix(n0, n1, u.x), mix(n2, n3, u.x), u.y),
mix(mix(n4, n5, u.x), mix(n6, n7, u.x), u.y),
u.z
);
}
float noise4D(vec3 p, float t) {
return noise(p + vec3(t));
}
void main() {
vec3 pos = normalize(vPosition);
// Base scale increased for higher resolution (finer details)
float baseScale = 10.0; // Doubled from 5.0 for more detail
float detailScale = 20.0; // High-frequency detail layer
// Three main flow layers with increased frequency
vec3 flow1 = pos * baseScale + vec3(time * 0.1);
vec3 flow2 = pos * baseScale - vec3(time * 0.15);
vec3 flow3 = pos * baseScale + vec3(time * 0.05, time * 0.05, 0.0);
// Additional high-frequency detail layer
vec3 flowDetail = pos * detailScale + vec3(time * 0.2);
// Generate noise layers
float n1 = noise4D(flow1, time * 0.1);
float n2 = noise4D(flow2, time * 0.15);
float n3 = noise4D(flow3, time * 0.05);
float nDetail = noise4D(flowDetail, time * 0.2);
// Combine noise layers with detail
float n = (n1 + n2 + n3) / 3.0 + nDetail * 0.2; // Detail contributes subtly
// Define lava colors
vec3 color1 = vec3(1.0, 1.0, 0.2); // Bright yellow/orange
vec3 color2 = vec3(1.0, 0.25, 0.0); // Darker orange/red
vec3 color3 = vec3(0.25, 0.0, 0.15); // Near-black
// Blend colors based on noise
vec3 color;
if (n < 0.3) {
color = mix(color3, color2, n / 0.3);
} else {
color = mix(color2, color1, (n - 0.3) / 0.7);
}
// Add glow for hot spots
float glow = smoothstep(0.6, 1.0, n);
color += vec3(1.0, 0.5, 0.0) * glow * 0.5;
gl_FragColor = vec4(color, 1.0);
}
`;
const vertexShaderStars = `
varying vec2 vUv;
varying float vNoise;
varying vec3 vPosition;
//varying float vDensity;
//varying float vColorNoise;
void main() {
vUv = uv;
vPosition = position;
// Simple 3D noise for clustering
vec3 p = position * 0.01; // Scale for noise frequency
float n = fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
vNoise = n;
//vDensity = smoothstep(0.3, 0.7, (noise(p) + noise(p + vec3(5.0))) * 0.5);
//vColorNoise = noise(p + vec3(10.0));
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_PointSize = 2.0; // Adjust star size
}
`;
const fragmentShaderStars = `
varying vec2 vUv;
varying float vNoise;
varying vec3 vPosition;
//varying float vDensity;
//varying float vColorNoise;
// Simple 3D noise function
float random(vec3 p) {
return fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
}
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float n0 = random(i);
float n1 = random(i + vec3(1.0, 0.0, 0.0));
float n2 = random(i + vec3(0.0, 1.0, 0.0));
float n3 = random(i + vec3(1.0, 1.0, 0.0));
float n4 = random(i + vec3(0.0, 0.0, 1.0));
float n5 = random(i + vec3(1.0, 0.0, 1.0));
float n6 = random(i + vec3(0.0, 1.0, 1.0));
float n7 = random(i + vec3(1.0, 1.0, 1.0));
return mix(
mix(mix(n0, n1, u.x), mix(n2, n3, u.x), u.y),
mix(mix(n4, n5, u.x), mix(n6, n7, u.x), u.y),
u.z
);
}
void main() {
// Cluster density using noise
vec3 pos = vPosition * 0.075; // Adjust noise scale
float n1 = noise(pos);
float n2 = noise(pos + vec3(5.0)); // Offset for variation
float density = smoothstep(0.03, 0.7, (n1 + n2) * 0.5);
// Noise-based color map
vec3 color;
float colorNoise = noise(pos + vec3(10.0));
if (colorNoise < 0.2) {
color = vec3(0.9, 0.0, 0.0); // Reddish clusters
} else if (colorNoise < 0.4) {
color = vec3(0.0, 0.3, 0.6); // Bluish clusters
} else if (colorNoise < 0.6) {
color = vec3(0.6, 0.3, 0.0); // Yellowish clusters
} else {
color = vec3(1.0, 1.0, 1.0); // White for major spaces
}
// Star visibility based on density
float alpha = density * 0.8;
if (density < 0.4) {
discard; // Skip low-density areas for sparse stars
}
gl_FragColor = vec4(color, alpha);
}
`;
const vertexShaderGlow = `
varying vec2 vUv;
varying float vNoise;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
vec3 p = position * 0.01;
vNoise = fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
gl_PointSize = 10.0 * vNoise; // Vary size by noise
}
`;
const fragmentShaderGlow = `
uniform sampler2D glowTexture;
varying vec2 vUv;
varying float vNoise;
varying vec3 vPosition;
float random(vec3 p) {
return fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
}
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float n0 = random(i);
float n1 = random(i + vec3(1.0, 0.0, 0.0));
float n2 = random(i + vec3(0.0, 1.0, 0.0));
float n3 = random(i + vec3(1.0, 1.0, 0.0));
float n4 = random(i + vec3(0.0, 0.0, 1.0));
float n5 = random(i + vec3(1.0, 0.0, 1.0));
float n6 = random(i + vec3(0.0, 1.0, 1.0));
float n7 = random(i + vec3(1.0, 1.0, 1.0));
return mix(
mix(mix(n0, n1, u.x), mix(n2, n3, u.x), u.y),
mix(mix(n4, n5, u.x), mix(n6, n7, u.x), u.y),
u.z
);
}
void main() {
vec3 pos = vPosition * 0.075;
float n1 = noise(pos);
float n2 = noise(pos + vec3(5.0));
float density = smoothstep(0.03, 0.7, (n1 + n2) * 0.5);
vec3 color;
float colorNoise = noise(pos + vec3(10.0));
if (colorNoise < 0.2) {
color = vec3(1.0, 0.3, 0.3); // Reddish glow
} else if (colorNoise < 0.4) {
color = vec3(0.3, 0.3, 1.0); // Bluish glow
} else if (colorNoise < 0.6) {
color = vec3(1.0, 1.0, 0.3); // Yellowish glow
} else {
color = vec3(1.0, 1.0, 1.0); // White glow
}
float alpha = density * texture2D(glowTexture, gl_PointCoord).a * 0.5;
if (density < 0.4) {
discard;
}
gl_FragColor = vec4(color, alpha);
}
`;
const vertexShaderEarth = `
uniform mat4 mvpMatrix;
uniform mat3 nMatrix; // CPU-computed normal matrix
uniform mat4 vMatrix; // View matrix (for view-space lighting)
uniform vec3 lightPosition;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec3 v_vertToLight;
varying vec3 vViewDir;
void main() {
vUv = uv;
vNormal = normal;
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
vec3 worldNormal = normalize(nMatrix * normal);
vNormal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
vec4 viewSunPos = vMatrix * vec4(lightPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - vViewPosition.xyz);
vViewDir = normalize(-vViewPosition);
gl_Position = mvpMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderEarth = `
uniform sampler2D diffuseMap; // Day/night texture
uniform vec3 lightPosition; // Sun at (0, 0, 0)
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec3 v_vertToLight;
varying vec3 vViewDir;
void main() {
// Sample diffuse texture
vec4 diffuseColor = texture2D(diffuseMap, vUv);
// Water/land detection (simplified: blue = water, others = land)
float isWater = step(0.5, diffuseColor.b); // High blue = water
float colorRG = (texture2D(diffuseMap, vUv).r + texture2D(diffuseMap, vUv).g) * 0.5;
isWater = clamp(isWater - (colorRG * .5), 0.0, 1.0);
float isLand = 1.0 - isWater;
// Bump mapping (fake height from green channel)
float height = colorRG * 0.1; // Use green for elevation
vec2 uvOffset = vec2(dFdx(vUv.x), dFdy(vUv.y)) * height * 10.0; // Approximate normal perturbation
vec3 bumpNormal = normalize(vNormal + vec3(uvOffset, 0.0));
// Lighting (view space)
//vec3 lightDir = normalize((viewMatrix * vec4(lightPosition, 1.0)).xyz - vViewPosition);
vec3 lightDir = normalize(v_vertToLight);
float diffuse = max(dot(bumpNormal, lightDir * 1.5), 0.02); //ambient
// Specular (Phong model)
//vec3 vViewDir = normalize(-vViewPosition);
vec3 reflectDir = reflect(-lightDir, bumpNormal);
float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 32.0); // Shininess = 32
float gloss = mix(0.05, 0.8, isWater); // Water glossy, land matte
float specular = spec * (gloss * 5.0);
// Combine
vec3 color = diffuseColor.rgb * diffuse + vec3(1.0, 1.0, 1.0) * specular;
gl_FragColor = vec4(color, 1.0);
}
`;
const vertexShaderMoon = `
uniform mat4 mvpMatrix;
uniform mat3 nMatrix; // CPU-computed normal matrix
uniform mat4 vMatrix; // View matrix (for view-space lighting)
uniform vec3 lightPosition;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec3 v_vertToLight;
varying vec3 vViewDir;
void main() {
vUv = uv;
vNormal = normal;
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
vec3 worldNormal = normalize(nMatrix * normal);
vNormal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
vec4 viewSunPos = vMatrix * vec4(lightPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - vViewPosition.xyz);
vViewDir = normalize(-vViewPosition);
gl_Position = mvpMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderMoon = `
uniform sampler2D diffuseMap; // Moon texture
uniform vec3 lightPosition;
varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vViewPosition;
varying vec3 v_vertToLight;
varying vec3 vViewDir;
void main() {
vec4 diffuseColor = texture2D(diffuseMap, vUv);
float height = texture2D(diffuseMap, vUv).r * 0.15; // Use red for crater depth
vec2 uvOffset = vec2(dFdx(vUv.x), dFdy(vUv.y)) * height * 5.0;
vec3 bumpNormal = normalize(vNormal + vec3(uvOffset, 0.0));
//vec3 lightDir = normalize((viewMatrix * vec4(lightPosition, 1.0)).xyz - vViewPosition);
vec3 lightDir = normalize(v_vertToLight);
//float lightIntensity = max(dot(vNormal, lightDir), 0.0);
float diffuse = max(dot(bumpNormal, lightDir * 1.5), 0.02);// ambient
//vec3 vViewDir = normalize(-vViewPosition);
vec3 reflectDir = reflect(-lightDir, bumpNormal);
float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 16.0); // Lower shininess for moon
float specular = spec * (0.05 * 5.0);
vec3 color = diffuseColor.rgb * diffuse + vec3(1.0, 1.0, 1.0) * specular;
gl_FragColor = vec4(color, 1.0);
}
`;
const vertexShaderClouds = `
uniform mat4 mvpMatrix;
varying vec3 vPosition;
varying vec3 vLocalPosition;
uniform mat3 nMatrix; // CPU-computed normal matrix
uniform mat4 vMatrix; // View matrix (for view-space lighting)
varying vec3 vViewPosition; // View-space position
varying vec2 TexCoord;
varying vec3 v_Normal;
uniform vec3 lightPosition; // Sun at (0, 0, 0)
varying vec3 v_vertToLight;
void main() {
vLocalPosition = position;
vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; // View-space position
TexCoord = vec2(vPosition.x, vPosition.z);
vec3 worldNormal = normalize(nMatrix * normal);
v_Normal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
vec4 viewSunPos = vMatrix * vec4(lightPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - vViewPosition.xyz);
gl_Position = mvpMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderClouds = `
uniform float time;
//uniform mat4 invView; // Inverse view matrix
//uniform mat4 invProjection; // Inverse view matrix
varying vec3 vPosition;
varying vec3 vLocalPosition;
varying vec3 vViewPosition;
varying vec2 TexCoord;
varying vec3 v_Normal;
varying vec3 v_vertToLight;
// Simple random function
float random(vec3 p) {
return fract(sin(dot(p, vec3(12.9898, 78.233, 45.5432))) * 43758.5453123);
}
// 3D noise function
float noise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float n0 = random(i);
float n1 = random(i + vec3(1.0, 0.0, 0.0));
float n2 = random(i + vec3(0.0, 1.0, 0.0));
float n3 = random(i + vec3(1.0, 1.0, 0.0));
float n4 = random(i + vec3(0.0, 0.0, 1.0));
float n5 = random(i + vec3(1.0, 0.0, 1.0));
float n6 = random(i + vec3(0.0, 1.0, 1.0));
float n7 = random(i + vec3(1.0, 1.0, 1.0));
return mix(
mix(mix(n0, n1, u.x), mix(n2, n3, u.x), u.y),
mix(mix(n4, n5, u.x), mix(n6, n7, u.x), u.y),
u.z
);
}
// 4D noise
float noise4D(vec3 p, float t) {
return noise(p + vec3(t));
}
//float linearizeDepth(float depth) {
// float near = 0.1;
// float far = 500.0; // Adjust to match your camera
// float z = depth * 2.0 - 1.0; // NDC
// return 2.0 * near * far / (far + near - z * (far - near));
//}
//
//vec3 getWorldPosition(float depth, vec2 uv) {
// float z = linearizeDepth(depth);
// vec4 clipSpace = vec4(uv * 2.0 - 1.0, z, 1.0);
// vec4 viewSpace = invProjection * clipSpace;
// viewSpace /= viewSpace.w;
// vec4 worldSpace = invView * viewSpace;
// return worldSpace.xyz;
//}
void main() {
// Noise for cloud pattern
vec3 pos = normalize(vLocalPosition);
float scale = 10.0;
vec3 flow1 = pos * scale + vec3(time * 0.05);
vec3 flow2 = pos * scale - vec3(time * 0.03);
vec3 flow3 = pos * scale - vec3(time * 0.0015);
vec3 flow4 = pos * scale - vec3(time * 0.015);
float n1 = noise4D(flow1, time * 0.05);
float n2 = noise4D(flow2, time * 0.03);
float n3 = noise4D(flow3, time * 0.0015);
float n4 = noise4D(flow4, time * 0.25);
float n = (n2 + n3 - n1 + n4) / 3.0;
// Cloud density and fading
float cloudDensity = smoothstep(0.4, 0.9, n);
vec3 cloudColor = vec3(1.0, 1.0, 1.0);
float alpha = cloudDensity * 1.0;
//vec3 P = getWorldPosition(vPosition.z, TexCoord);
// Lighting calculation (view space)
vec3 fragToLight = normalize(v_vertToLight);
//float lightIntensity = max(dot(v_Normal, fragToLight), 0.0);
float lightIntensity = dot(v_vertToLight, v_Normal);
lightIntensity = clamp(lightIntensity * 1.5, 0.05, 1.0); // Subtle night glow
//vec3 testColor = vec3(0,.5,0);
// Apply lighting to color
cloudColor *= lightIntensity;
//testColor *= lightIntensity;
gl_FragColor = vec4(cloudColor, alpha);
//gl_FragColor = vec4(v_Normal, 1);
//gl_FragColor = vec4(testColor, 1);
//gl_FragColor = vec4(P, alpha);
}
`;
const vertexShaderAtmosphere = `
uniform mat4 mvpMatrix;
varying vec3 vPosition;
varying vec3 vViewPosition; // View-space position
uniform mat3 nMatrix; // CPU-computed normal matrix
uniform mat4 vMatrix; // View matrix (for view-space lighting)
varying vec3 vViewDir;
varying vec2 TexCoord;
varying vec3 v_Normal;
uniform vec3 lightPosition; // Sun at (0, 0, 0)
varying vec3 v_vertToLight;
void main() {
vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz; // View-space position
TexCoord = vec2(vPosition.x, vPosition.z);
vec3 worldNormal = normalize(nMatrix * normal);
v_Normal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
//v_Normal = normalize((vMatrix * vec4(mat3(0, 0, -1, 0, 1, 0, 1, 0, 0) * worldNormal, 0.0)).xyz);
vec4 viewSunPos = vMatrix * vec4(lightPosition, 1.0);
v_vertToLight = normalize(viewSunPos.xyz - vViewPosition.xyz);
//vViewDir = normalize(cameraPosition - vPosition);
vViewDir = normalize(-vViewPosition);
gl_Position = mvpMatrix * vec4(position, 1.0);
}
`;
const fragmentShaderAtmosphere = `
uniform vec3 lightPosition; // Sun at (0, 0, 0)
varying vec3 vPosition;
varying vec3 vViewDir; // fresnel
varying vec2 TexCoord;
varying vec3 v_Normal;
varying vec3 v_vertToLight;
void main() {
// Lighting calculation (view space)
vec3 fragToLight = normalize(v_vertToLight);
float lightIntensity = max(dot(v_Normal, fragToLight), 0.0);
// Fresnel effect for rim glow
//float fresnel = pow(1.0 - max(dot(v_Normal, vViewDir), 0.0), 3.0);
float fresnel = pow(1.0 - abs(dot(v_Normal, vViewDir)), 2.0);
fresnel = clamp(fresnel, 0.1, 1.0);
// Atmosphere color (blue-cyan)
vec3 atmColor = vec3(0.2, 0.6, 1.0); // Blue-cyan glow
vec3 color = atmColor * fresnel * lightIntensity;
// Transparency based on light and fresnel
float alpha = fresnel * clamp(lightIntensity * 1.5, 0.1, 0.8);
gl_FragColor = vec4(color, alpha);
//gl_FragColor = vec4(color, 1);
}
`;
function createGlowTexture() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
return new THREE.CanvasTexture(canvas);
}
const glowTexture = createGlowTexture();
// Create star field sphere
//const starGeometry = new THREE.SphereGeometry(500, 64, 64); // Large enough to enclose scene
const starMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderStars,
fragmentShader: fragmentShaderStars,
//side: THREE.BackSide // Render on inner surface
transparent: true
});
const starCount = 10000;
const starGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const theta = Math.random() * 2 * Math.PI;
const phi = Math.acos(2 * Math.random() - 1);
const r = 500; // Starfield radius
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
positions[i * 3 + 2] = r * Math.cos(phi);
}
starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const starField = new THREE.Points(starGeometry, starMaterial);
//const starField = new THREE.Mesh(starGeometry, starMaterial);
scene.add(starField);
const glowMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderGlow,
fragmentShader: fragmentShaderGlow,
uniforms: {
glowTexture: { value: glowTexture }
},
//side: THREE.BackSide // Render on inner surface
transparent: true,
blending: THREE.AdditiveBlending // Enhance glow effect
});
const glowCount = 2000; // Fewer points for glow to optimize performance
const glowGeometry = new THREE.BufferGeometry();
const glowPositions = new Float32Array(glowCount * 3);
for (let i = 0; i < glowCount; i++) {
const theta = Math.random() * 2 * Math.PI;
const phi = Math.acos(2 * Math.random() - 1);
const r = 500; // Same radius as starfield
glowPositions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
glowPositions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
glowPositions[i * 3 + 2] = r * Math.cos(phi);
}
glowGeometry.setAttribute('position', new THREE.BufferAttribute(glowPositions, 3));
const glowField = new THREE.Points(glowGeometry, glowMaterial);
scene.add(glowField);
// Texture loader
const textureLoader = new THREE.TextureLoader();
// Sun
//const sunTexture = textureLoader.load('sun_1024.jpg');
const sunGeometry = new THREE.SphereGeometry(15, 64, 64);
//const sunMaterial = new THREE.MeshBasicMaterial({ map: sunTexture });
// Create the shader material
const sunMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0.0 }
},
vertexShader: vertexShaderSun,
fragmentShader: fragmentShaderSun
});
const sun = new THREE.Mesh(sunGeometry, sunMaterial);
sun.position.set(0, 0, 0);
scene.add(sun);
// Earth
//const earthTexture = textureLoader.load('earth_atmos_2048.jpg');
const earthGeometry = new THREE.SphereGeometry(1, 64, 64);
//const earthMaterial = new THREE.MeshPhongMaterial({ map: earthTexture });
const earthMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderEarth,
fragmentShader: fragmentShaderEarth,
uniforms: {
diffuseMap: { value: new THREE.TextureLoader().load('earth_atmos_2048.jpg') },
lightPosition: { value: new THREE.Vector3(0, 0, 0) },
nMatrix: { value: new THREE.Matrix3() }, // Initialize empty mat3
vMatrix: { value: new THREE.Matrix4() }, // View matrix
mvpMatrix: { value: new THREE.Matrix4() }
}
});
earthMaterial.castShadow = true;
const earth = new THREE.Mesh(earthGeometry, earthMaterial);
earth.position.set(100, 0, 0);
scene.add(earth);
// Atmosphere sphere setup
const atmGeometry = new THREE.SphereGeometry(1.02, 64, 64); // Slightly larger than clouds
const atmMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderAtmosphere, // Atmosphere vertex shader
fragmentShader: fragmentShaderAtmosphere, // Atmosphere fragment shader
uniforms: {
lightPosition: { value: new THREE.Vector3(0, 0, 0) }, // Sun at origin
nMatrix: { value: new THREE.Matrix3() },
vMatrix: { value: new THREE.Matrix4() },
mvpMatrix: { value: new THREE.Matrix4() }
},
transparent: true,
side: THREE.BackSide // Render on inner surface for better glow
});
const atmosphere = new THREE.Mesh(atmGeometry, atmMaterial);
scene.add(atmosphere);
// Create cloud sphere
const cloudGeometry = new THREE.SphereGeometry(1.01, 64, 64); // Slightly larger than Earth (radius 100)
const cloudMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderClouds,
fragmentShader: fragmentShaderClouds,
uniforms: {
time: { value: 0.0 },
lightPosition: { value: new THREE.Vector3(0, 0, 0) }, // Sun at origin
nMatrix: { value: new THREE.Matrix3() }, // Initialize empty mat3
vMatrix: { value: new THREE.Matrix4() }, // View matrix
//invView: { value: new THREE.Matrix4() } // Inverse view matrix
mvpMatrix: { value: new THREE.Matrix4() }
},
transparent: true
});
cloudMaterial.castShadow = true;
const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial);
scene.add(clouds);
// Moon
//const moonTexture = textureLoader.load('moon_1024.jpg');
const moonGeometry = new THREE.SphereGeometry(0.27, 32, 32);
//const moonMaterial = new THREE.MeshPhongMaterial({ map: moonTexture });
const moonMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShaderMoon,
fragmentShader: fragmentShaderMoon,
uniforms: {
diffuseMap: { value: new THREE.TextureLoader().load('moon_1024.jpg') },
lightPosition: { value: new THREE.Vector3(0, 0, 0) },
nMatrix: { value: new THREE.Matrix3() }, // Initialize empty mat3
vMatrix: { value: new THREE.Matrix4() }, // View matrix
mvpMatrix: { value: new THREE.Matrix4() }
}
});
moonMaterial.castShadow = true;
const moon = new THREE.Mesh(moonGeometry, moonMaterial);
moon.position.set(105, 0, .5);
scene.add(moon);
// Asteroid MG1
const asteroidGeometry = new THREE.SphereGeometry(0.05, 16, 16);
const asteroidMaterial = new THREE.MeshBasicMaterial({ color: 0xaaaaaa });
const asteroid = new THREE.Mesh(asteroidGeometry, asteroidMaterial);
//asteroid.position.set(125, .7, -45);
asteroid.position.set(115, .7, 51);
scene.add(asteroid);
// Orbital paths
const earthOrbitCurve = new THREE.EllipseCurve(0, 0, 100, 100, 0, 2 * Math.PI, false, 0);
const earthOrbitPoints = earthOrbitCurve.getPoints(128);
const earthOrbitGeometry = new THREE.BufferGeometry().setFromPoints(earthOrbitPoints);
earthOrbitGeometry.rotateX(Math.PI / 2); // Aligns to XZ plane
const earthOrbit = new THREE.Line(earthOrbitGeometry, new THREE.LineBasicMaterial({ color: 0x0000ff }));
scene.add(earthOrbit);
const moonOrbitCurve = new THREE.EllipseCurve(0, 0, 5, 5, 0, 2 * Math.PI, false, 0);
const moonOrbitPoints = moonOrbitCurve.getPoints(64);
const moonOrbitGeometry = new THREE.BufferGeometry().setFromPoints(moonOrbitPoints);
moonOrbitGeometry.rotateX(Math.PI / 2); // Aligns to XZ plane
moonOrbitGeometry.rotateZ(5 / (Math.PI / 2)); // Aligns to XZ plane
const moonOrbit = new THREE.Line(moonOrbitGeometry, new THREE.LineBasicMaterial({ color: 0x00ff00 }));
scene.add(moonOrbit);
// Asteroid path
const asteroidPath = new THREE.Line(
new THREE.BufferGeometry().setFromPoints([
new THREE.Vector3(125, .7, -45),
new THREE.Vector3(115, .7, 51)
]),
new THREE.LineBasicMaterial({ color: 0xff0000 })
);
scene.add(asteroidPath);
const asteroidMarkGeometry = new THREE.BufferGeometry();
const asteroidMarkPositions = new Float32Array(6); // 2 vertices * 3 coordinates
asteroidMarkGeometry.setAttribute('position', new THREE.BufferAttribute(asteroidMarkPositions, 3));
const asteroidMark = new THREE.Line(
asteroidMarkGeometry,
new THREE.LineBasicMaterial({ color: 0x00ff00 })
);
asteroidMark.frustumCulled = false;
scene.add(asteroidMark);
// Camera setup
camera.position.set(97, 2, 2);
//camera.lookAt(135, 3, 0);
// OrbitControls for free movement
//const controls = new THREE.OrbitControls(camera, renderer.domElement);
const controls = new OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 100;
controls.target.set(100, 0, 2);
const helperGrids = false; // debug
if(helperGrids) {
const helper = new THREE.GridHelper(500, 500);
helper.material.opacity = 0.25;
helper.material.transparent = true;
scene.add(helper);
const axis = new THREE.AxesHelper(1000);
scene.add(axis);
}
// Timeline control
const timeline = document.getElementById('timeline');
const timeLabel = document.getElementById('timeLabel');
//const baseTime = new Date('2025-07-13T00:00:00Z').getTime();
const baseTime = new Date('2025-07-12T00:00:00Z').getTime();
const hoursRange = 192 * 24; // 192 days ~ 6.4 months / 2
const earthOrbitalPeriodMs = 365.25 * 24 * 3600 * 1000;
const moonOrbitalPeriodMs = 27.3 * 24 * 3600 * 1000;
let isPlaying = false;
let speed = 1; // 1x, 100x, 1000x, 10000x
let lastTime = null;
const play1x = document.getElementById('play1x');
const play100x = document.getElementById('play100x');
const play1000x = document.getElementById('play1000x');
const play10000x = document.getElementById('play10000x');
const play100000x = document.getElementById('play100000x');
const stop = document.getElementById('stop');
const modeF = document.getElementById('modeF');
const modeL1 = document.getElementById('modeL1'); // aster
const modeL2 = document.getElementById('modeL2'); // moon
const modeL3 = document.getElementById('modeL3'); // earth
let mFL = [];
mFL[0] = document.getElementById('mF0');
mFL[1] = document.getElementById('mF1');
mFL[2] = document.getElementById('mF2');
mFL[3] = document.getElementById('mF3');
play1x.addEventListener('click', () => { isPlaying = true; speed = 1; });
play100x.addEventListener('click', () => { isPlaying = true; speed = 100; });
play1000x.addEventListener('click', () => { isPlaying = true; speed = 1000; });
play10000x.addEventListener('click', () => { isPlaying = true; speed = 10000; });
play100000x.addEventListener('click', () => { isPlaying = true; speed = 100000; });
stop.addEventListener('click', () => { isPlaying = false; });
let lockControls = false;
let tDiff = new THREE.Vector3();
let tMode = 0; // free look mode
modeF.addEventListener('click', () => {
tMode=0;
for(let i=0;i<4;i++) mFL[i].style.display="none";
mFL[0].style.display="inline-block";
});
modeL1.addEventListener('click', () => {
tMode=1;
tDiff.x = asteroid.position.x - camera.position.x;
tDiff.y = asteroid.position.y - camera.position.y;
tDiff.z = asteroid.position.z - camera.position.z;
for(let i=0;i<4;i++) mFL[i].style.display="none";
mFL[1].style.display="inline-block";
});
modeL2.addEventListener('click', () => {
tMode=2;
tDiff.x = moon.position.x - camera.position.x;
tDiff.y = moon.position.y - camera.position.y;
tDiff.z = moon.position.z - camera.position.z;
for(let i=0;i<4;i++) mFL[i].style.display="none";
mFL[2].style.display="inline-block";
});
modeL3.addEventListener('click', () => {
tMode=3;
tDiff.x = earth.position.x - camera.position.x;
tDiff.y = earth.position.y - camera.position.y;
tDiff.z = earth.position.z - camera.position.z;
for(let i=0;i<4;i++) mFL[i].style.display="none";
mFL[3].style.display="inline-block";
});
const normalMatrix = new THREE.Matrix3(); // create once and reuse
const atmNormalMatrix = new THREE.Matrix3();
const earthNormalMatrix = new THREE.Matrix3();
const moonNormalMatrix = new THREE.Matrix3();
const mvpMatrix = new THREE.Matrix4();
const atmMvpMatrix = new THREE.Matrix4();
const earthMvpMatrix = new THREE.Matrix4();
const moonMvpMatrix = new THREE.Matrix4();
let timeOffset = 0.0;
let timeFraction = 0.0;
timeline.addEventListener('input', (event) => {
if(!isPlaying) {
timeFraction = event.target.value / 100; // 0 to 1
timeOffset = (timeFraction - 0.5) * hoursRange * 3600 * 1000; // -24 to +24 hours in ms
}
const currentTime = new Date(baseTime + timeOffset);
timeLabel.textContent = currentTime.toUTCString();
// Earth orbital position (moves 1/365th of orbit per day)
const earthAngle = (timeOffset / earthOrbitalPeriodMs) * 2 * Math.PI;
earth.position.set(100 * Math.cos(earthAngle), 0, 100 * Math.sin(earthAngle));
clouds.position.set(earth.position.x, 0, earth.position.z);
atmosphere.position.set(earth.position.x, 0, earth.position.z);
const rad = Math.PI / 180;
// Moon orbital position
const moonAngle = (timeOffset / moonOrbitalPeriodMs) * 2 * Math.PI + (45 * rad);
//const tF = -1 + (moonAngle - ( 90 / ( 180 / Math.PI )) * 2);
const moonTilt = Math.cos(moonAngle);
//const tF = -1 + (timeFraction * 2);
moon.position.set(
earth.position.x + 5 * Math.cos(moonAngle),
//Math.abs(tF * .2),
moonTilt * .2,
earth.position.z + 5 * Math.sin(moonAngle)
);
// Asteroid MG1 position
const asteroidX = 115 + (timeFraction * 5 * 2); // Moves from 14 to 8 over 24 hours
const asteroidZ = 51 - (timeFraction * 48 * 2); // Moves from 3 to 6 over 24 hours
asteroid.position.set(asteroidX, .7, asteroidZ);
// Update asteroid marker line
asteroidMarkPositions[0] = asteroidX; // Top vertex X
asteroidMarkPositions[1] = 3.0; // Top vertex Y (0.7 + 2)
asteroidMarkPositions[2] = asteroidZ; // Top vertex Z
asteroidMarkPositions[3] = asteroidX; // Bottom vertex X
asteroidMarkPositions[4] = 1.0; // Bottom vertex Y (asteroid's Y)
asteroidMarkPositions[5] = asteroidZ; // Bottom vertex Z
asteroidMarkGeometry.attributes.position.needsUpdate = true;
// Earth rotation (360 degrees in 24 hours)
const earthRotationAngle = (timeOffset / (24 * 3600 * 1000)) * 2 * Math.PI;
earth.rotation.y = earthRotationAngle + 50;
earth.rotation.x = -25;
clouds.rotation.y = earthRotationAngle + 50;
clouds.rotation.x = -25;
clouds.matrixWorldNeedsUpdate = true; // Ensure matrix updates
atmosphere.rotation.y = earthRotationAngle + 50;
atmosphere.rotation.x = -25;
atmosphere.matrixWorldNeedsUpdate = true;
// Moon rotation (tidally locked to its orbit)
let moonRotationAngle = (((timeOffset / moonOrbitalPeriodMs) * 2 * Math.PI));
while(moonRotationAngle > (Math.PI * 2)) moonRotationAngle-=Math.PI * 2;
while(moonRotationAngle < 0.0) moonRotationAngle+=Math.PI * 2;
moon.rotation.y = ((Math.PI * 2) - moonRotationAngle) + (180 * rad);
//console.log(moon.rotation.y);
moon.rotation.x = -25 * rad;
moon.rotation.z = -15 * rad;
// Update Moon orbit position to follow Earth
moonOrbit.position.set(earth.position.x, 0, earth.position.z);
// Update cloud animation
cloudMaterial.uniforms.time.value = timeFraction * (192 * 24 * 2); // Scale to hou
});
// Trigger initial state
const initialEvent = new Event('input');
timeline.value = 50; // Set to middle of timeline (12:00 UTC)
timeline.dispatchEvent(initialEvent);
const cameraInfo = document.getElementById('infoDynamic');
// Animation loop
function updateScene(time) {
if (lastTime === null) lastTime = time;
const deltaTime = (time - lastTime) / 1000; // Seconds
lastTime = time;
if (isPlaying) {
//timeFraction = event.target.value / 100; // 0 to 1
//timeOffset = (timeFraction - 0.5) * hoursRange * 3600 * 1000; // -24 to +24 hours in ms
timeOffset += deltaTime * (speed * .00025) * 3600 * 1000; // Advance by speed * hours
timeFraction = (timeOffset / hoursRange / 3600 / 1000) + .5;
if (timeFraction < 0) timeFraction = 0;
if (timeFraction > 1) timeFraction = 1;
timeline.value = timeFraction * 100;
//currentTime = new Date(baseTime + timeOffset);
//timeLabel.textContent = currentTime.toUTCString();
timeline.dispatchEvent(initialEvent);
}
//--debug
//camera.position.set(102, .5, 3); // View from a diagonal angle
//camera.lookAt(earth.position); // Look at Earth
//camera.lookAt(moon.position); // Look at Earth
//controls.target.copy(moon.position); // Update OrbitControls target
//---
controls.update();
camera.updateMatrixWorld();
//camera.matrixWorldInverse.getInverse( camera.matrixWorld );
if(tMode>0) {
if(tMode==1) {
camera.position.set(asteroid.position.x - tDiff.x, asteroid.position.y - tDiff.y, asteroid.position.z - tDiff.z);
camera.lookAt(asteroid.position);
controls.target.copy(asteroid.position); // Update OrbitControls target
} else if(tMode==2) {
camera.position.set(moon.position.x - tDiff.x, moon.position.y - tDiff.y, moon.position.z - tDiff.z);
camera.lookAt(moon.position);
controls.target.copy(moon.position); // Update OrbitControls target
} else if(tMode==3) {
camera.position.set(earth.position.x - tDiff.x, earth.position.y - tDiff.y, earth.position.z - tDiff.z);
camera.lookAt(earth.position);
controls.target.copy(earth.position); // Update OrbitControls target
}
if(!lockControls) {
controls.enabled=false;
lockControls=true;
//console.log("controls locked");
}
} else {
if(lockControls) {
controls.enabled=true;
lockControls=false;
//console.log("controls unlocked");
}
}
// Update star field position
starField.position.copy(camera.position);
//starField.position.set(camera.position.x, camera.position.y, camera.position.z);
starField.updateMatrixWorld();
glowField.position.copy(camera.position); // Sync glow layer
glowField.updateMatrixWorld();
// Update normal matrices
earth.updateMatrixWorld();
earthNormalMatrix.getNormalMatrix(earth.matrixWorld);
earthMaterial.uniforms.nMatrix.value = earthNormalMatrix;
earthMaterial.uniforms.vMatrix.value.copy(camera.matrixWorldInverse);
earthMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(earth.matrixWorld);
earthMaterial.uniforms.mvpMatrix.value = earthMvpMatrix;
moon.updateMatrixWorld();
moonNormalMatrix.getNormalMatrix(moon.matrixWorld);
moonMaterial.uniforms.nMatrix.value = moonNormalMatrix;
moonMaterial.uniforms.vMatrix.value.copy(camera.matrixWorldInverse);
moonMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(moon.matrixWorld);
moonMaterial.uniforms.mvpMatrix.value = moonMvpMatrix;
clouds.updateMatrixWorld();
normalMatrix.getNormalMatrix(clouds.matrixWorld);
cloudMaterial.uniforms.nMatrix.value = normalMatrix;
cloudMaterial.uniforms.vMatrix.value.copy(camera.matrixWorldInverse);
//cloudMaterial.uniforms.invView.value.copy(camera.matrixWorldInverse).invert();
//cloudMaterial.uniforms.invProjection = { value: new THREE.Matrix4() };
//cloudMaterial.uniforms.invProjection.value.copy(camera.projectionMatrix).invert();
mvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(clouds.matrixWorld);
cloudMaterial.uniforms.mvpMatrix.value = mvpMatrix;
atmosphere.updateMatrixWorld();
atmNormalMatrix.getNormalMatrix(atmosphere.matrixWorld);
atmMaterial.uniforms.nMatrix.value = atmNormalMatrix;
atmMaterial.uniforms.vMatrix.value.copy(camera.matrixWorldInverse);
atmMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(atmosphere.matrixWorld);
atmMaterial.uniforms.mvpMatrix.value = atmMvpMatrix;
// Update sun material time
sunMaterial.uniforms.time.value += 0.015; // Adjust speed here
// Update camera info
const pos = camera.position;
const target = controls.target; // OrbitControls look-at point
cameraInfo.innerHTML = `Camera Pos: (${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)})<br />\nLookAt: (${target.x.toFixed(2)}, ${target.y.toFixed(2)}, ${target.z.toFixed(2)})`;
renderer.render(scene, camera);
requestAnimationFrame(updateScene);
}
//updateScene();
requestAnimationFrame(updateScene);
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
var anaS=`%c
,/#(,
./%%%%(,
.*#%%%%#,
,/#%%%%%%*
./%%%%%%%%%%*
*%%%%%%%%%%%%/.
,#%%%%%%%%%%%%(.
./%%%%#*/%%%%%%/. /%(,*(
./#%%%%/. ./%%#/,,%%%%%%%%%%%/
*(%%##%%%%%%%%%%%%%%(,./%%%%%%#*.
*#%%#* ../%%%%#/. ./%%%%/.
.*(%%#*. ./%%%%#, *%%%%(,
.*#%%###%%%%%%#/, .*%%%%#, .*%%%%#, *
./#%%/.*(%%(*. *%%%%%%%%/.,#%%%%* *
.(%%%%%%%%%%/, ,*//*, ,
`
function printascii() { setTimeout(console.log.bind(console,anaS,'background: #2a1e27; color: #fbbb57;')); }
printascii();
</script>
</body>
</html>
Top