~firefoxsimulationsmg1
4 itemsDownload ./*

..
build
jsm
textures
index.html


mg1index.html
124 KB• 121•  1 week ago•  DownloadRawClose
1 week ago•  121

{}
<!DOCTYPE html>
<!--
    Author: twily                                 2025
    Helper in making: grok 3 (ai)                   x)
    Textures: nasa earth observatory terra:blue marble
    Description: simluate asteroid in our solar system
    Keybinds available:
            w : wireframe toggle
            l : lines toggle
            u : ui toggle
            shift: swap mouseR
-->
<html>
<head>
    <title>Asteroid MG1 Passing Earth - July 12, 2025</title>
    <style>
        body { margin: 0; overflow: hidden; user-select: none; }
        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: 0px; left: 50%;
            transform: translateX(-50%);
            width: 80%; max-width: 600px;
            text-align: center;
            padding: 10px;
            color: lime;
            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;
        }
        #pF1, #pF2, #pF3, #pF4, #pF5, #pF6 {
            display:none;
        }

        .tbl { display: table; width: 100%; height: 100%; /*table-layout: fixed;*/ }
        .tr { display: table-row; }
        .td { display: table-cell; vertical-align: top; /*border: 1px solid #606163; box-shadow: inset 0 0 2px 2px #000;*/ }

        /*#tbl1 span { height: 0; line-height: 0; }*/
    </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 />
        <div class="tbl" id="tbl1"> 
        <div class="tr">
        <div class="td">
        <button id="play1x">Play 1x</button>
        </div>
        <div class="td">
        <button id="play100x">Play 100x</button>
        </div>
        <div class="td">
        <button id="play1000x">Play 1.000x</button>
        </div>
        <div class="td">
        <button id="play10000x">Play 10.000x</button>
        </div>
        <div class="td">
        <button id="play100000x">Play 100.000x</button>
        </div>
        <div class="td">
        <button id="play1000000x">Play 1.000.000x</button>
        </div>
        <div class="td">
        <button id="stop">Stop</button>
        </div>
        </div>
        <div class="tr">
        <div class="td"><span id="pF1">^</span></div>
        <div class="td"><span id="pF2">^</span></div>
        <div class="td"><span id="pF3">^</span></div>
        <div class="td"><span id="pF4">^</span></div>
        <div class="td"><span id="pF5">^</span></div>
        <div class="td"><span id="pF6">^</span></div>
        <div class="td"><span id="pF0">^</span></div>
        </div>
        </div>
        </div>
    </div>
    <div id="controls2">
        <span id="mF0">&gt;</span><button id="modeF">Free mode</button><br />
        <span id="lockLabel">Lock mode:</span><br />
        <span id="mF1">&gt;</span><button id="modeL1">Asteroid</button><br />
        <span id="mF2">&gt;</span><button id="modeL2">Moon</button><br />
        <span id="mF3">&gt;</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';
        // Add composer
        //--import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
        //--import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
        //--import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.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);
        
        const canvas = renderer.domElement;

        camera.near=.01;
        camera.far=1000;
        camera.updateProjectionMatrix()

        //--const renderScene = new RenderPass(scene, camera);
        //--const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
        //--bloomPass.threshold = 0.1; // Glow starts at bright areas
        //--bloomPass.strength = 1.0; // Intensity
        //--bloomPass.radius = 0.5; // Spread
        //--const composer = new EffectComposer(renderer);
        //--composer.addPass(renderScene);
        //--composer.addPass(bloomPass);

        // Lighting
        const sunLight = new THREE.PointLight(0xffffff, 50000, 1000);
        sunLight.position.set(0, 0, 0);
        sunLight.castShadow = true;
        sunLight.shadow.mapSize.width = 1024;
        sunLight.shadow.mapSize.height = 1024;
        sunLight.shadow.camera.near = 0.5;
        sunLight.shadow.camera.far = 500;
        sunLight.shadow.radius = 1;
        scene.add(sunLight);
        const ambientLight = new THREE.AmbientLight(0x404040, 0.1); // Adjusted up from 0.1
        scene.add(ambientLight);

//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//
// Define the shaders
const vertexShaderSun = `
    uniform mat4 mvpMatrix;
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec3 lPosition;

    void main() {
        vUv = uv;
        vNormal = normal;
        vPosition = position;
        lPosition = (modelMatrix * vec4(position, 1.0)).xyz;
        gl_Position = mvpMatrix * vec4(position, 1.0);
    }
`;
const fragmentShaderSun = `
    uniform float time; // surface
    uniform float realtime; // rotation
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec3 lPosition;

    const float PI = 3.1415926535897932384626433832795;

    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(0.0, 0.0, t)); // Use Z for time dimension
    }

    void main() {
        //float loopedRealtime = mod(realtime, 25.0 * 24.0 * 3600.0); // 25 days in seconds
        //float globalAngle = (loopedRealtime / (25.0 * 24.0 * 3600.0)) * 2.0 * PI; // 0 to 2PI

        // Latitude-dependent speed factor
        float latitude = asin(vNormal.y); // -PI/2 to PI/2
        //--float speedFactor = mix(0.1, 0.3, abs(latitude) / (PI / 2.0)); // 1.0 at poles, 1.2 at equator
        float speedFactor = mix(realtime, realtime, abs(latitude) / (PI / 2.0)); // 1.0 at poles, 1.2 at equator
        //--float localAngle = globalAngle * speedFactor;
        float localAngle = speedFactor;

        // 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
       
        vec3 pos = normalize(vPosition);

        // 3D rotation around Y-axis (horizontal)
        float cosR = cos(-localAngle);
        float sinR = sin(-localAngle);
        vec3 pos2 = vec3(
            cosR * pos.x + sinR * pos.z, // Rotate X around Y
            pos.y,                       // Y unchanged (horizontal axis)
            -sinR * pos.x + cosR * pos.z // Rotate Z around Y
        );

        // Three main flow layers with increased frequency
        vec3 flow1 = pos2 * baseScale + vec3(time * 0.1);
        vec3 flow2 = pos2 * baseScale - vec3(time * 0.15);
        vec3 flow3 = pos2 * baseScale + vec3(time * 0.05, time * 0.05, 0.0);
        
        // Additional high-frequency detail layer
        vec3 flowDetail = pos2 * 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;
        
        //color += .2; // boost brightness

        //vec2 fuv=fract(uv);
        //color *= (color + 0.5);
        
        gl_FragColor = vec4(color, 1.0);
        //gl_FragColor = vec4(fuv, 0.0, 1.0);
        //gl_FragColor = vec4(realtime, 0.0, 0.0, 1.0);
    }
`;

const vertexShaderStars = `
    uniform mat4 mvpMatrix;
    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 = mvpMatrix * 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 = `
    uniform mat4 mvpMatrix;
    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 = mvpMatrix * 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 vPosition;
    varying vec4 viewSunPos; 
    varying mat4 pMatrix;
    
    void main() {
        vUv = uv;
        vNormal = normal;
        vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
        vPosition = position;
        //vPosition = (modelMatrix * vec4(position, 1.0)).xyz; // World space use vViewPos
        pMatrix = projectionMatrix;

        vec3 worldNormal = normalize(nMatrix * normal);
        vNormal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
        viewSunPos = vMatrix * vec4(lightPosition, 1.0);

        gl_Position = mvpMatrix * vec4(position, 1.0);
    }
`;
const fragmentShaderEarth = `
    uniform sampler2D diffuseMapNightEmit;
    uniform sampler2D diffuseMapNight;
    uniform sampler2D diffuseMapJuly;
    uniform sampler2D diffuseMapDecember;
    uniform sampler2D diffuseMapAqua;
    uniform sampler2D normalMap;
    uniform sampler2D earth_night_emit;
    uniform mat4 vMatrix; // View matrix (for view-space lighting)
    uniform vec3 lightPosition;   // Sun at (0, 0, 0)
    uniform float isWinter;    // 0 = summer (July), 1 = winter (December)
    uniform float time; // For animation
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vViewPosition;
    varying vec3 vPosition;
    varying vec4 viewSunPos;
    varying mat4 pMatrix;

    // Shadow map uniforms
    //uniform sampler2D shadowMap; // for directional
    //uniform mat4 lightSpaceMatrix;
    uniform samplerCube shadowMap; // Cube map for PointLight
    uniform float shadowBias;

    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));
    }

    //----shadowmap
    //float sampleShadowMap(vec3 worldPos) {
    //    vec4 lightSpacePos = lightSpaceMatrix * vec4(worldPos, 1.0);
    //    vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
    //    projCoords = projCoords * 0.5 + 0.5; // Convert to [0,1]
    //    if (projCoords.z > 1.0) return 1.0; // Outside shadow map
    //    float closestDepth = texture2D(shadowMap, projCoords.xy).r;
    //    float currentDepth = projCoords.z - shadowBias;
    //    return currentDepth > closestDepth ? 0.5 : 1.0; // Partial shadow
    //}
    //float sampleShadowMap(vec3 worldPos) { // cube (point lights)
    //    vec3 lightToFrag = worldPos - lightPosition;
    //    float currentDepth = length(lightToFrag) - shadowBias;
    //    float closestDepth = textureCube(shadowMap, lightToFrag).r * 1000.0; // Scale to match scene
    //    // Define cloud depth range (Earth radius 100 + cloud thickness)
    //    float cloudMinDepth = 90.0; // Start of cloud layer
    //    float cloudMaxDepth = 110.0; // End of cloud layer
    //    if (closestDepth < cloudMinDepth || closestDepth > cloudMaxDepth) return 1.0; // No shadow outside range
    //    return currentDepth > closestDepth ? 0.5 : 1.0; // Partial shadow within range
    //}
    //----prepass shadow depth for alpha clip shadows
    //float sampleShadowMap(vec3 worldPos) { // custom prepass
    //    // Project world position to light's view (simplified for camera-aligned depth)
    //    vec4 lightSpacePos = pMatrix * vMatrix * vec4(worldPos, 1.0);
    //    vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
    //    projCoords = projCoords * 0.5 + 0.5; // Convert to [0,1]
    //    if (projCoords.z > 1.0 || projCoords.x < 0.0 || projCoords.x > 1.0 || projCoords.y < 0.0 || projCoords.y > 1.0) return 1.0;
    //    float closestDepth = texture2D(shadowMap, projCoords.xy).r; // Depth from render target
    //    float currentDepth = projCoords.z - shadowBias;
    //    return currentDepth > closestDepth ? 0.5 : 1.0; // Partial shadow
    //}
    float sampleShadowMap(vec3 worldPos) {
        vec3 lightToFrag = worldPos - lightPosition;
        float currentDepth = length(lightToFrag) - shadowBias;
        float closestDepth = textureCube(shadowMap, lightToFrag).r * 150.0; // Scale to far plane
        return currentDepth > closestDepth ? 0.5 : 1.0; // Partial shadow
    }
    
    void main() {
        // Sample diffuse texture
        //vec4 diffuseColor = texture2D(diffuseMap, vUv);
        // Sample textures
        vec4 nightEmit = texture2D(diffuseMapNightEmit, vUv);
        vec4 nightColor = texture2D(diffuseMapNight, vUv);
        vec4 dayColor = mix(texture2D(diffuseMapJuly, vUv), texture2D(diffuseMapDecember, vUv), isWinter);
        vec4 aquaColor = texture2D(diffuseMapAqua, vUv);
        vec3 normalMap = texture2D(normalMap, vUv).xyz * 2.0 - 1.0; // [-1,1]
        normalMap.x = -normalMap.x; // Flip X axis
        vec4 emitColor = texture2D(earth_night_emit, vUv);

        // Bump mapping with normal map
        vec3 bumpNormal = normalize(vNormal + normalMap * 0.3); // Adjust bump strength

        // Lighting (view space)
        vec3 lightDir = normalize(viewSunPos.xyz - vViewPosition.xyz);
        float dist = length((viewMatrix * vec4(lightPosition, 1.0)).xyz - vViewPosition);
        float attenuation = 1.0 / (1.0 + 0.01 * dist * dist);
        float nightFactor = 1.0 - max(dot(bumpNormal, lightDir), 0.0); // 0 = day, 1 = night
        nightFactor = smoothstep(0.2, 0.8, nightFactor); // Soften transition

        // Day/night blend
        vec4 diffuseColor = mix(dayColor, nightColor, nightFactor);

        
        // ---- water begin
        // Water/land detection
        float isWater = step(0.1, length(aquaColor.rgb)); // Non-black = water
        //float waterDepth = 1.0-((aquaColor.r+aquaColor.g+aquaColor.b)/3.0)*.2;
        float waterDepth = ((aquaColor.r+aquaColor.g+aquaColor.b)/3.0)*.33;

        vec3 pos = normalize(vPosition);

        // Base scale increased for higher resolution (finer details)
        float baseScale = 20.0; // Doubled from 5.0 for more detail
        float detailScale = 240.0; // High-frequency detail layer
        
        // Three main flow layers with increased frequency
        vec3 flow1 = pos * baseScale + vec3(time * 0.01);
        vec3 flow2 = pos * baseScale + vec3(time * 0.07);
        vec3 flow3 = pos * baseScale - vec3(time * 0.15, 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.01);
        float n2 = noise4D(flow2, time * 0.07);
        float n3 = noise4D(flow3, time * 0.15);
        float nDetail = noise4D(flowDetail, time * 0.2);
        
        // Combine noise layers with detail
        float n = (n2 + n3 + n1) * 0.5 + nDetail * 0.2; // Detail contributes subtly

        // Procedural water effect
        vec2 waterUv = vUv * 10.0 + vec2(time * 0.0015, 0.0); // Slow ripple
        float waterNoise = n * 0.2; // Subtle amplitude
        //vec3 waterColor = vec3(0.0, 0.8, 1.0) * (0.2 + (waterNoise * .8)) * isWater; // Bright cyan tint
        vec3 waterColor = vec3(0.0, 0.8, 1.0) * (waterDepth + (waterNoise * .8)) * isWater; // Bright cyan tint
        vec3 waterOffset = vec3(0.0, waterNoise, 0.0) * isWater; // Apply only to water
        vec3 waterBumpNormal = normalize(bumpNormal + waterOffset);
        // ---- water end


        float lightIntensity = 200.0;
        // Lighting calculation  with water effect
        //float diffuse = max(dot(waterBumpNormal, lightDir * (2.0 * (1.0 - isWater))), 0.2); // Minimum light for night glow
        float diffuse = max(dot(waterBumpNormal, lightDir), 0.05) * attenuation * lightIntensity; // ambient
        //float shadowFactor = sampleShadowMap(vViewPosition);
        //diffuse *= shadowFactor; // Apply shadow

        diffuse = smoothstep(0.0, 0.6, diffuse); // Softer transition, wider lit area

        // Specular (Phong model)
        vec3 vViewDir = normalize(-vViewPosition);

        float rim = 1.0 - max(dot(bumpNormal, vViewDir), 0.05); // Backside intensity
        rim = smoothstep(0.5, 1.0, rim) * 0.3; // Limit and scale rim effect  
        vec3 lighting = (diffuseColor.rgb + waterColor) * (diffuse + rim);
        vec3 reflectDir = reflect(-lightDir, bumpNormal);

    
        // Specular (Phong model)
        float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 32.0); // Shininess = 32
        float gloss = mix(0.1, 0.5, isWater); // Water glossy, land matte
        float specular = spec * gloss * attenuation * lightIntensity * 1.0;

        // Emission (only at night)
        float test = ((nightFactor * 2.0) - 1.0) * 1.0;
        vec3 emit = clamp(emitColor.rgb * test, 0.0, 1.0);
        //emit = smoothstep(0.0, 0.6, emit); // Softer transition, wider lit area

        // Combine
        vec3 color = lighting + vec3(1.0, 1.0, 1.0) * specular + emit;
        //vec3 color = (diffuseColor.rgb + waterColor) * diffuse + vec3(1.0, 1.0, 1.0) * specular + emit;
        //gl_FragColor = vec4(vec3(shadowFactor / 1000.0), 1.0); // Debug depth

        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 vPosition;
    varying vec3 vViewPosition;
    varying vec4 viewSunPos; 
    
    void main() {
        vUv = uv;
        vNormal = normal;
        vPosition = normal;
        vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;

        vec3 worldNormal = normalize(nMatrix * normal);
        vNormal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
        viewSunPos   = vMatrix * vec4(lightPosition, 1.0);

        gl_Position = mvpMatrix * vec4(position, 1.0);
    }
`;
const fragmentShaderMoon = `
    uniform sampler2D diffuseMap;
    uniform sampler2D normalMap;
    uniform vec3 lightPosition;
    //uniform mat4 vMatrix; // View matrix (for view-space lighting)
    varying vec2 vUv;
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec3 vViewPosition;
    varying vec4 viewSunPos; 
    
    void main() {
        vec4 diffuseColor = texture2D(diffuseMap, vUv);
        
        // Sample normal map and convert to normal
        vec3 normalMap = texture2D(normalMap, vUv).xyz * 2.0 - 1.0; // Convert [0,1] to [-1,1]
        normalMap.x = -normalMap.x; // Flip X axis
        vec3 bumpNormal = normalize(vNormal + normalMap * 0.5); // Adjust bump strength
    
        vec3 lightDir = normalize(viewSunPos.xyz - vViewPosition.xyz);
        float diffuse = max(dot(bumpNormal, lightDir * 1.5), 0.1);// ambient
        diffuse = smoothstep(0.0, 0.6, diffuse); // Softer transition, wider lit area

        // Specular (Phong model)
        vec3 vViewDir = normalize(-vViewPosition);

        float rim = 1.0 - max(dot(bumpNormal, vViewDir), 0.05); // Backside intensity
        rim = smoothstep(0.5, 1.0, rim) * 0.3; // Limit and scale rim effect  
        //vec3 baseColor = vec3(0.6, 0.6, 0.6); // Adjust base color
        vec3 lighting = diffuseColor.rgb * (diffuse + rim);

        vec3 reflectDir = reflect(-lightDir, bumpNormal);
        float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 16.0); // Lower shininess for moon
        float specular = spec * 0.1;
    
        vec3 color = lighting + 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;
    varying vec4 viewSunPos;
    
    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);
        viewSunPos   = vMatrix * vec4(lightPosition, 1.0);

        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;
    varying vec4 viewSunPos;
    
    const float PI = 3.1415926535897932384626433832795;

    // Noise functions
    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);
        return mix(mix(mix(random(i), random(i + vec3(1.0, 0.0, 0.0)), u.x),
                       mix(random(i + vec3(0.0, 1.0, 0.0)), random(i + vec3(1.0, 1.0, 0.0)), u.x), u.y),
                   mix(mix(random(i + vec3(0.0, 0.0, 1.0)), random(i + vec3(1.0, 0.0, 1.0)), u.x),
                       mix(random(i + vec3(0.0, 1.0, 1.0)), random(i + vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z);
    }
    
    float fbm(vec3 p) {
        float v = 0.0;
        float a = 0.5;
        vec3 shift = vec3(100.0);
        for (int i = 0; i < 6; ++i) {
            v += a * noise(p);
            p = p * 2.0 + shift;
            a *= 0.5;
        }
        return v;
    }

    //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 loopedRealtime = mod(time, 365.0 * 24.0 * 3600.0); // 365 days
       //float globalAngle = (loopedRealtime / (365.0 * 24.0 * 3600.0)) * 2.0 * PI;
       //float localAngle = globalAngle; // Optional latitude factor if desired

       //-- float cosR = cos(localAngle);
       //-- float sinR = sin(localAngle);
       //-- vec3 pos2 = vec3(
       //--     cosR * pos.x + sinR * pos.z,
       //--     pos.y,
       //--     -sinR * pos.x + cosR * pos.z
       //-- );


        float scale = 10.0;
        vec3 flow1 = pos * scale + vec3(time * 0.10);
        vec3 flow2 = pos * scale - vec3(time * 0.06);
        vec3 flow3 = pos * scale + vec3(time * 0.03);
        float n1 = noise(flow1);
        float n2 = noise(flow2);
        float n3 = noise(flow3);
        float baseNoise = (n2 + n3 - n1) * 0.5; // Base detail
        float detailNoise = fbm(pos * scale * 10.0 + vec3(time * 0.01)); // Smudged variation
        float cloudDensity = smoothstep(0.3, .7, baseNoise + detailNoise * 0.3); // Increased coverage
        vec3 cloudColor = vec3(.5, .5, .5);
        float alpha = cloudDensity * 0.9;

        // Edge tint based on alpha and nightFactor (approximated from light)
        vec3 lightDir = normalize(viewSunPos.xyz - vViewPosition.xyz);
        float edgeFactor = smoothstep(0.2, 0.8, 1.0 - alpha); // Higher alpha = less edge
        float nightFactor = 1.0 - max(dot(v_Normal, lightDir), 0.0); // Approx. day/night
        nightFactor = smoothstep(0.2, 0.8, nightFactor);
        vec3 edgeTint = mix(
            vec3(0.2, 0.2, 0.2), // Grey
            mix(
                vec3(0.5, 0.6, 0.7),
                vec3(0.8, 0.5, 0.5),
            nightFactor * 5.0), // Blue to red
        0.5 - (nightFactor * .25)); // Darker at edges
        cloudColor = mix(cloudColor, edgeTint, edgeFactor) * 2.0; // Apply tint

        //vec3 P = getWorldPosition(vPosition.z, TexCoord); 
    
        
        // Bump mapping (fake height from green channel)
        float height = ((cloudColor.r+cloudColor.g+cloudColor.b)/3.0) * 1.0; 
        vec2 uvOffset = vec2(dFdx(TexCoord.x), dFdy(TexCoord.y)) * height * 10.0; // Approximate normal perturbation
        vec3 bumpNormal = normalize(v_Normal + vec3(uvOffset, 0.0));


        float diffuse = max(dot(bumpNormal, lightDir * 1.5), 0.05);// ambient
        diffuse = smoothstep(0.0, 0.3, diffuse); // Softer transition, wider lit area

        // Specular (Phong model)
        vec3 vViewDir = normalize(-vViewPosition);

        float rim = 1.0 - max(dot(bumpNormal, vViewDir), 0.05); // Backside intensity
        rim = smoothstep(0.5, 1.0, rim) * 0.1; // Limit and scale rim effect  
        vec3 lighting = cloudColor.rgb * (diffuse + rim);

        vec3 reflectDir = reflect(-lightDir, bumpNormal);
        float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 16.0); // Lower shininess for moon
        float specular = spec * 0.1;
    
        vec3 color = lighting + specular;

    
        //vec3 testColor = vec3(0,.5,0);
        // Apply lighting to color
        //testColor *= lightIntensity;

        //uniform float alphaThreshold;
        float alphaThreshold = 0.3;
        //if (alpha < alphaThreshold) discard;
    
        gl_FragColor = vec4(color, alpha);
        //gl_FragColor = vec4(cloudColor, alpha);
        //gl_FragColor = vec4(cloudColor, 1);
        //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() {
        // Surface distance (from Earth center, radius 100)
        float surfaceDist = length(v_vertToLight);
        float atmosphereThickness = 10.0; // Thin layer beyond Earth radius
        float depthFactor = clamp((surfaceDist - 100.0) / atmosphereThickness, 0.0, 1.0); // 0 at surface, 1 at edge
        

        // Lighting calculation (view space)
        vec3 fragToLight = normalize(v_vertToLight);
        float lightIntensity = max(dot(v_Normal, fragToLight), 0.0);

        // Front-face volumetric effect
        float frontFactor = 1.0 - dot(v_Normal, vViewDir); // 1 when facing camera, 0 when edge-on
        frontFactor = smoothstep(0.0, 0.3, frontFactor); // Fade out at edges
        vec3 volumetricColor = vec3(0.0, 0.5, 0.8) * depthFactor * frontFactor; // Blue tint

        // Backside Fresnel effect
        float fresnel = pow(1.0 - abs(dot(v_Normal, vViewDir)), 2.0);
        fresnel = clamp(fresnel, 0.1, 1.0);
        vec3 fresnelColor = vec3(0.2, 0.6, 1.0) * fresnel; // Bright blue outline

        // Combine effects
        vec3 color = ((volumetricColor + fresnelColor) * 1.) * clamp(lightIntensity * 1.5, 0.1, 1.8);
    
        // Transparency based on light and fresnel
        float alpha = ((fresnel + frontFactor) * 1.) * clamp(lightIntensity * 1.5, 0.4, 1.8);
    
        gl_FragColor = vec4(color, alpha);
        //gl_FragColor = vec4(fresnelColor, 1.0); // debug
        //gl_FragColor = vec4(color, 1);
    }
`;

const vertexShaderAsteroid = `
    uniform mat4 mvpMatrix;
    uniform mat3 nMatrix; // CPU-computed normal matrix
    uniform mat4 vMatrix; // View matrix (for view-space lighting)
    varying vec3 vNormal;
    varying vec3 vPosition;
    uniform vec3 lightPosition;
    varying vec3 vViewPosition;
    varying vec4 viewSunPos;
    varying vec2 vUv;

    void main() {
        //vNormal = normal;
        vec3 worldNormal = normalize(nMatrix * normal);
        vNormal = normalize((vMatrix * vec4(worldNormal, 0.0)).xyz);
        vPosition = position;
        vUv = uv;
        vViewPosition = (modelViewMatrix * vec4(position, 1.0)).xyz;
        viewSunPos   = vMatrix * vec4(lightPosition, 1.0);
        //lPosition = (modelMatrix * vec4(position, 1.0)).xyz;
        gl_Position = mvpMatrix * vec4(position, 1.0);
    }
`;

const fragmentShaderAsteroid= `
    uniform float time;
    varying vec3 vNormal;
    varying vec3 vPosition;
    varying vec3 vViewPosition;
    varying vec4 viewSunPos;
    varying vec2 vUv;
            
    const float PI = 3.1415926535897932384626433832795;

    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(0.0, 0.0, t)); // Use Z for time dimension
    }

    void main() {
        //vec2 uv = vNormal.xy * 0.5 + 0.5;
        vec2 uv = vUv * 0.5 + 0.5;
        vec3 pos=normalize(vPosition);
        float n = noise4D(pos * 10.0, time); // Lower scale to reduce noise grain
        vec3 color = mix(vec3(0.34, 0.29, 0.24), vec3(0.49, 0.47, 0.44), smoothstep(0.0, 1.0, n)); // Smoother transition

        // Bump mapping (fake height from green channel)
        float height = ((color.r+color.g+color.b)/3.0) * 1.0; 
        vec2 uvOffset = vec2(dFdx(uv.x), dFdy(uv.y)) * height * 10.0; // Approximate normal perturbation
        vec3 bumpNormal = normalize(vNormal + vec3(uvOffset, 0.0));


        vec3 lightDir = normalize(viewSunPos.xyz - vViewPosition.xyz);

        float diffuse = max(dot(bumpNormal, lightDir * 1.5), 0.1);// ambient
        diffuse = smoothstep(0.0, 0.6, diffuse); // Softer transition, wider lit area

        // Specular (Phong model)
        vec3 vViewDir = normalize(-vViewPosition);

        float rim = 1.0 - max(dot(bumpNormal, vViewDir), 0.05); // Backside intensity
        rim = smoothstep(0.5, 1.0, rim) * 0.3; // Limit and scale rim effect  
        //vec3 baseColor = vec3(0.6, 0.6, 0.6); // Adjust base color
        vec3 lighting = color.rgb * (diffuse + rim);
        vec3 reflectDir = reflect(-lightDir, bumpNormal);
        float spec = pow(max(dot(vViewDir, reflectDir), 0.0), 16.0); // Lower shininess for moon
        float specular = spec * 0.1;

        color = lighting + specular;
        //color = color.rgb * diffuse + vec3(1.0, 1.0, 1.0) * specular;
        //gl_FragColor = vec4(height, 0.0, 0.0, 1.0);
        gl_FragColor = vec4(color, 1.0);
    }
`;
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//

        const rad = Math.PI / 180; 


        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,
            uniforms: {
                mvpMatrix: { value: new THREE.Matrix4() }
            },
            //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 },
                mvpMatrix: { value: new THREE.Matrix4() }
            },
            //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, 32);
        //const sunMaterial = new THREE.MeshBasicMaterial({ map: sunTexture });
        // Create the shader material
        const sunMaterial = new THREE.ShaderMaterial({
            uniforms: {
                time: { value: 0.0 },
                realtime: { value: 0.0 },
                mvpMatrix: { value: new THREE.Matrix4() }
            },
            vertexShader: vertexShaderSun,
            fragmentShader: fragmentShaderSun
        });

        const sun = new THREE.Mesh(sunGeometry, sunMaterial);
        sun.position.set(0, 0, 0);
        sun.rotation.z = -23.44 * rad;
        sun.receiveShadow = false;
        sun.castShadow = false;
        scene.add(sun);


        // Flame geometry (simple spikes)
        function createFlames(count) {
            const flameGeometry = new THREE.BufferGeometry();
            const positions = [];
            const scales = [];
        
            for (let i = 0; i < count; i++) {
                const theta = Math.random() * Math.PI * 2;
                const phi = Math.acos(2 * Math.random() - 1); // Uniform on sphere
                const x = Math.sin(phi) * Math.cos(theta);
                const y = Math.sin(phi) * Math.sin(theta);
                const z = Math.cos(phi);
        
                // Spike from surface outward
                positions.push(0, 0, 0); // Base at surface (will be offset to sun surface)
                positions.push(x * 2, y * 2, z * 2); // Tip extends 2 units
                scales.push(1.0); // Initial scale
            }
        
            flameGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
            flameGeometry.setAttribute('scale', new THREE.BufferAttribute(new Float32Array(scales), 1));
        
            const flameMaterial = new THREE.LineBasicMaterial({ color: 0xff4500, transparent: true }); // Orange-red, transparent for fade
            const flames = new THREE.LineSegments(flameGeometry, flameMaterial);
            flames.position.copy(sun.position); // Ensure flames start at sun position
            flames.frustumCulled = false;
            scene.add(flames);
        
            return flames;
        }
        
        const flames = createFlames(20); // 20 random flames
        let flameTime = 0.0;

        //console.dir(flames.geometry.attributes.position.array);

        // Earth
        //const earthTexture = textureLoader.load('earth_atmos_2048.jpg');
        const earthGeometry = new THREE.SphereGeometry(1, 128, 64);
        //const earthMaterial = new THREE.MeshPhongMaterial({ map: earthTexture });
        const earthMaterial = new THREE.ShaderMaterial({
            vertexShader: vertexShaderEarth,
            fragmentShader: fragmentShaderEarth,
            uniforms: {
                diffuseMapNightEmit: { value: new THREE.TextureLoader().load('textures/earth_night_emit.png') },
                diffuseMapNight: { value: new THREE.TextureLoader().load('textures/earth_night.jpg') },
                //diffuseMapJuly: { value: new THREE.TextureLoader().load('textures/earth_july_aqua.jpg') },
                diffuseMapJuly: { value: new THREE.TextureLoader().load('textures/earth_july.jpg') },
                //diffuseMapDecember: { value: new THREE.TextureLoader().load('textures/earth_december_aqua.jpg') },
                diffuseMapDecember: { value: new THREE.TextureLoader().load('textures/earth_december.jpg') },
                diffuseMapAqua: { value: new THREE.TextureLoader().load('textures/earth_aqua.jpg') },
                normalMap: { value: new THREE.TextureLoader().load('textures/earth_normal.png') },
                lightPosition: { value: new THREE.Vector3(0, 0, 0) },
                isWinter: { value: 0.0 },
                time: { value: 0.0 },
                shadowMap: { value: null }, // Will be set from light
                //lightSpaceMatrix: { value: new THREE.Matrix4() },
                shadowBias: { value: 0.005 },
                nMatrix: { value: new THREE.Matrix3() }, // Initialize empty mat3
                vMatrix: { value: new THREE.Matrix4() }, // View matrix
                mvpMatrix: { value: new THREE.Matrix4() }
            }
        });
        const earth = new THREE.Mesh(earthGeometry, earthMaterial);
        earth.position.set(100, 0, 0);
        earth.castShadow = true;
        earth.receiveShadow = true;
        scene.add(earth);

        // Atmosphere sphere setup
        const atmGeometry = new THREE.SphereGeometry(1.02, 128, 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,
            blending: THREE.AdditiveBlending, // Enhance glow effect
            side: THREE.DoubleSide, // Render both views
            //side: THREE.BackSide, // Render on inner surface for better glow
        });
        const atmosphere = new THREE.Mesh(atmGeometry, atmMaterial);
        atmosphere.castShadow = false;
        atmosphere.receiveShadow = false;
        scene.add(atmosphere);


        // Create cloud sphere
        const cloudGeometry = new THREE.SphereGeometry(1.01, 128, 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, // Disable blending for clipping
            alphaTest: 0.3,     // Threshold for clipping
            side: THREE.DoubleSide
        });
        const clouds = new THREE.Mesh(cloudGeometry, cloudMaterial);
        clouds.castShadow = true;
        clouds.receiveShadow = true;
        scene.add(clouds);


        earth.renderOrder = 0; // Render first
        clouds.renderOrder = 1; // Render second
        atmosphere.renderOrder = 2; // Render last


        // Create a render target and depth material
        const shadowMapSize = 1024;
        const pars = {
            minFilter: THREE.LinearFilter,
            magFilter: THREE.LinearFilter,
            format: THREE.RGBAFormat
        };
        const renderTargetDepth = new THREE.WebGLRenderTarget(shadowMapSize, shadowMapSize, pars);
        
        const depthMaterial = new THREE.MeshDepthMaterial({
            depthPacking: THREE.RGBADepthPacking,
            alphaTest: 0.3 // Match cloud's alphaTest to clip correctly
        });
        
        // Store original cloud material for restoration
        const originalCloudMaterial = clouds.material;
        
        // Function to set up depth pass objects
        function setupDepthPass() {
            scene.traverse((object) => {
                if (object === clouds) {
                    object.material = depthMaterial; // Apply depth material to clouds
                } else if (object === earth ||
                            object === atmosphere || 
                            object === sun) {
                    object.visible = false; // Hide Earth during depth pass
                }
            });
        }
        // Cube camera for PointLight depth
        const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(shadowMapSize, { format: THREE.RGBAFormat });
        const cubeCamera = new THREE.CubeCamera(0.5, 150.0, cubeRenderTarget); // Near/far match light
        cubeCamera.position.copy(sunLight.position);

        
        // Function to restore original materials
        function restoreMaterials() {
            scene.traverse((object) => {
                if (object === clouds) {
                    object.material = originalCloudMaterial;
                } else if (object === earth || 
                            object === atmosphere || 
                            object === sun) {
                    object.visible = true;
                }
            });
        }


        // Moon
        //const moonTexture = textureLoader.load('moon_1024.jpg');
        const moonGeometry = new THREE.SphereGeometry(0.27, 48, 32);
        //const moonMaterial = new THREE.MeshPhongMaterial({ map: moonTexture });
        const moonMaterial = new THREE.ShaderMaterial({
            vertexShader: vertexShaderMoon,
            fragmentShader: fragmentShaderMoon,
            uniforms: {
                diffuseMap: { value: new THREE.TextureLoader().load('textures/moon_diffuse_2k.png') },
                normalMap: { value: new THREE.TextureLoader().load('textures/moon_normal_2k.png') },
                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() }
            }
        });
        const moon = new THREE.Mesh(moonGeometry, moonMaterial);
        moon.castShadow = true;
        moon.receiveShadow = true;
        moon.position.set(105, 0, .5);
        scene.add(moon);

        // Asteroid MG1
        const asteroidGeometry = new THREE.SphereGeometry(0.05, 24, 16);
        //const asteroidMaterial = new THREE.MeshBasicMaterial({ color: 0xaaaaaa });
        const asteroidMaterial = new THREE.ShaderMaterial({ 
            vertexShader: vertexShaderAsteroid,
            fragmentShader: fragmentShaderAsteroid,
            uniforms: {
                mvpMatrix: { value: new THREE.Matrix4() },
                nMatrix: { value: new THREE.Matrix3() }, // Initialize empty mat3
                vMatrix: { value: new THREE.Matrix4() }, // View matrix
                time: { value: 0.0 }
            }
        });
        const asteroid = new THREE.Mesh(asteroidGeometry, asteroidMaterial);
        //asteroid.position.set(135, -40.7, -65);
        scene.add(asteroid);


        function randomizeAsteroidShape(geometry) {
            const positionAttribute = geometry.attributes.position;
            const normalAttribute = geometry.attributes.normal;
            const vertex = new THREE.Vector3();
            const normal = new THREE.Vector3();
            const neighborOffset = new THREE.Vector3();
        
            const poleThreshold = 0.98; // Lock vertices near poles
            const seamThreshold = 0.001; // Detect seam vertices
            const stretchFactor = 0.3; // Outward stretch bias
            const smoothFactor = 0.4; // Smoothing with neighbors
            const strength = 20.0;
        
            const originalPositions = new Float32Array(positionAttribute.array); // Store original for output
        
            for (let i = 0; i < positionAttribute.count; i++) {
                vertex.fromBufferAttribute(positionAttribute, i);
                normal.fromBufferAttribute(normalAttribute, i);
                const latitudeFactor = Math.abs(normal.z);
        
                if (latitudeFactor > poleThreshold) {
                    continue; // Lock poles
                }
        
                // Base outward offset with reduced deviation
                let offsetMagnitude = (Math.random() - 0.3) * (0.002 * strength); // Range: -0.06 to 0.14 (less deviation)
                offsetMagnitude = Math.max(0, offsetMagnitude) * stretchFactor; // Ensure outward bias
        
                // Smooth with neighbors
                neighborOffset.set(0, 0, 0);
                let neighborCount = 0;
                for (let j = 0; j < positionAttribute.count; j++) {
                    if (i !== j) {
                        const otherVertex = new THREE.Vector3().fromBufferAttribute(positionAttribute, j);
                        if (vertex.distanceTo(otherVertex) < 0.5) { // Nearby vertices
                            neighborOffset.add(otherVertex);
                            neighborCount++;
                        }
                    }
                }
                if (neighborCount > 0) {
                    neighborOffset.divideScalar(neighborCount);
                    const smoothOffset = neighborOffset.clone().sub(vertex).multiplyScalar(smoothFactor);
                    offsetMagnitude += smoothOffset.length() * (Math.random() > 0.5 ? 1 : -1) * 0.1; // Slight random tweak
                }
        
                // Apply offset along normal
                const elongationDirection = new THREE.Vector3(1.4, 1.1, 2); // Stretch along Z
                const offset = normal.clone().multiplyScalar(offsetMagnitude);
                offset.projectOnVector(elongationDirection).multiplyScalar(2.0); // Double stretch along X
        
                // Seam fix
                let isSeamVertex = false;
                for (let j = 0; j < positionAttribute.count; j++) {
                    if (i !== j) {
                        const otherVertex = new THREE.Vector3().fromBufferAttribute(positionAttribute, j);
                        if (vertex.distanceTo(otherVertex) < seamThreshold && Math.abs(normal.z - normalAttribute.getZ(j)) < 0.1) {
                            isSeamVertex = true;
                            offset.add(otherVertex).divideScalar(2); // Average with seam neighbor
                            break;
                        }
                    }
                }
        
                if (!isSeamVertex) {
                    vertex.add(offset);
                }
        
                positionAttribute.setXYZ(i, vertex.x, vertex.y, vertex.z);
            }
        
            geometry.computeVertexNormals();
            geometry.computeBoundingSphere();
        
            // Output modified positions as string/array
            const modifiedPositions = Array.from(positionAttribute.array);
            const outputString = JSON.stringify(modifiedPositions);
            //console.log("Modified Positions:", modifiedPositions);
            // Optional prompt for copy-paste
            prompt("Copy asteroid vertex data:", outputString);
        }
        let bakedAsteroid=[0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,0,0.05000000074505806,0,-0.009754516184329987,0.049039263278245926,0,-0.00870803277939558,0.04960034787654877,0.0035448060370981693,-0.006100789178162813,0.05088323354721069,0.008229929953813553,-0.006751130800694227,0.04915425553917885,0.0071065607480704784,-0.004821626469492912,0.04908297210931778,0.00852713268250227,-0.0014414956094697118,0.049890317022800446,0.010969509370625019,-0.00000473903901365702,0.049035537987947464,0.009747746400535107,0.003721174318343401,0.04997938498854637,0.011131453327834606,0.004118083044886589,0.048442769795656204,0.0073631233535707,0.011041965335607529,0.05229564011096954,0.0128181716427207,0.008604726754128933,0.04916267469525337,0.005101640243083239,0.008287036791443825,0.04814739525318146,0.0009030794608406723,0.008691245689988136,0.048203837126493454,-0.0015189583646133542,0.010982540436089039,0.050265293568372726,-0.00029551071929745376,0.009328215382993221,0.04973112791776657,-0.003619320224970579,0.010612435638904572,0.05195815488696098,-0.0015904114115983248,0.005565767642110586,0.04958023503422737,-0.0074640740640461445,0.004859807435423136,0.050874024629592896,-0.006086206529289484,-0.0005390964215621352,0.04861568659543991,-0.01052465382963419,-0.00144057662691921,0.04989103972911835,-0.007873455993831158,-0.004003680776804686,0.049725644290447235,-0.007199691608548164,-0.006654378958046436,0.04923027381300926,-0.006550190970301628,-0.008952256292104721,0.04864279553294182,-0.0055981106124818325,-0.007307030260562897,0.05070113390684128,0.0004969298606738448,-0.009754516184329987,0.049039263278245926,-2.3891674767279894e-18,-0.019134171307086945,0.04619397595524788,0,-0.016878526657819748,0.047453995794057846,0.0072432346642017365,-0.015463707968592644,0.047063738107681274,0.011148471385240555,-0.012323503382503986,0.04714186117053032,0.01525332871824503,-0.008174797520041466,0.04728791490197182,0.018559660762548447,-0.00621374137699604,0.0452028326690197,0.016680113971233368,0.005800742655992508,0.0507517009973526,0.027420947328209877,0.004444079007953405,0.04579466953873634,0.017756177112460136,0.008038359694182873,0.0449928343296051,0.014386783353984356,0.015036406926810741,0.047377657145261765,0.015682050958275795,0.016148554161190987,0.045862309634685516,0.0089640524238348,0.01955985836684704,0.04704071581363678,0.006491815205663443,0.01978149078786373,0.04670258238911629,0.000924742198549211,0.018603436648845673,0.046289242804050446,-0.004779078532010317,0.015705376863479614,0.045514095574617386,-0.010803230106830597,0.014202325604856014,0.04672230780124664,-0.012569297105073929,0.009078631177544594,0.045810189098119736,-0.0172684695571661,0.0052615683525800705,0.04643698036670685,-0.018040360882878304,-0.00019642457482405007,0.0460396409034729,-0.01941477693617344,-0.004844247829169035,0.04627886414527893,-0.018327847123146057,-0.009500396437942982,0.046246375888586044,-0.016475407406687737,-0.013352852314710617,0.046333085745573044,-0.013276973739266396,-0.01670241355895996,0.046090468764305115,-0.009755278937518597,-0.018248897045850754,0.0463772751390934,-0.004619014449417591,-0.019134171307086945,0.04619397595524788,-4.68652038700443e-18,-0.027778511866927147,0.041573479771614075,0,-0.026021510362625122,0.042210280895233154,0.008347424678504467,-0.02214227244257927,0.043077826499938965,0.016624432057142258,-0.0168925691395998,0.04373404011130333,0.02357066422700882,-0.014512797817587852,0.04108355566859245,0.023166121914982796,-0.004317003767937422,0.04383052513003349,0.030935702845454216,0.0043922909535467625,0.0450245663523674,0.03405321389436722,0.008904480375349522,0.042920880019664764,0.029281800612807274,0.020536769181489944,0.0467965267598629,0.0335533432662487,0.027202541008591652,0.04751361161470413,0.03044261410832405,0.026235727593302727,0.043285418301820755,0.017001871019601822,0.031534742563962936,0.045268505811691284,0.013907833956182003,0.03037290647625923,0.043611932545900345,0.003706278745085001,0.03234431892633438,0.045904599130153656,0.0006851570215076208,0.024876846000552177,0.04221772775053978,-0.012717898003757,0.019574852660298347,0.04152042791247368,-0.019738832488656044,0.014660574495792389,0.042179517447948456,-0.022955013439059258,0.007228655740618706,0.04160416126251221,-0.026776200160384178,-0.00015396370145026594,0.04145250841975212,-0.027998460456728935,-0.00765716889873147,0.04120611026883125,-0.027499927207827568,-0.014229200780391693,0.041306380182504654,-0.024542532861232758,-0.01931541971862316,0.04183037206530571,-0.019175296649336815,-0.025303054600954056,0.04059435427188873,-0.015669483691453934,-0.026972223073244095,0.04146328940987587,-0.007389951962977648,-0.027778511866927147,0.041573479771614075,-6.803773123666079e-18,-0.0353553406894207,0.0353553406894207,0,-0.033438097685575485,0.03591518849134445,0.010168543085455894,-0.031102055683732033,0.03497549891471863,0.01698704995214939,-0.025831595063209534,0.03470194712281227,0.023812009021639824,-0.015162907540798187,0.0373312272131443,0.034211140125989914,-0.002792389364913106,0.040351103991270065,0.04323384165763855,0.0028823448810726404,0.03762004151940346,0.039472974836826324,0.014449885115027428,0.039519038051366806,0.04172099009156227,0.019635260105133057,0.03689344599843025,0.03341517597436905,0.026948697865009308,0.036886461079120636,0.02778385393321514,0.03141557797789574,0.035981521010398865,0.018816180527210236,0.0376858189702034,0.038132987916469574,0.014200901612639427,0.03810368850827217,0.03751475736498833,0.0039262110367417336,0.03335634246468544,0.03473125398159027,-0.010285339318215847,0.029883602634072304,0.03477782383561134,-0.018727697432041168,0.026095371693372726,0.03621599078178406,-0.023435184732079506,0.01763973757624626,0.035325534641742706,-0.030672810971736908,0.00930714886635542,0.035478316247463226,-0.03392704203724861,-0.0019026065710932016,0.03386043384671211,-0.0380733497440815,-0.00978921540081501,0.03485359996557236,-0.035062890499830246,-0.01843878999352455,0.03475731611251831,-0.031705934554338455,-0.027192801237106323,0.03363242745399475,-0.028132572770118713,-0.03254544734954834,0.03384140506386757,-0.020430278033018112,-0.03462645784020424,0.03498147800564766,-0.009830385446548462,-0.0353553406894207,0.0353553406894207,-8.65956027255431e-18,-0.041573479771614075,0.027778511866927147,0,-0.040174566209316254,0.02776462770998478,0.01073476392775774,-0.035129331052303314,0.02846550941467285,0.02203582599759102,-0.028655095025897026,0.028361350297927856,0.030456596985459328,-0.021920375525951385,0.026887796819210052,0.03438420966267586,-0.0034575399477034807,0.03351616486907005,0.05058899521827698,0.00596990529447794,0.03246915340423584,0.05010191723704338,0.020152440294623375,0.03515828028321266,0.053574658930301666,0.02705717273056507,0.03270528092980385,0.04496145248413086,0.027346104383468628,0.02616718038916588,0.02646719664335251,0.03515484556555748,0.027111563831567764,0.019574107602238655,0.040744196623563766,0.028239961713552475,0.011599007062613964,0.04621380195021629,0.03142447769641876,0.006629031617194414,0.040228936821222305,0.027835113927721977,-0.010657095350325108,0.03873142600059509,0.02992173470556736,-0.016889972612261772,0.02919197827577591,0.02761751040816307,-0.029689619317650795,0.020332062616944313,0.027421263977885246,-0.03665323182940483,0.011307698674499989,0.028208840638399124,-0.03937448188662529,0.0008272718405351043,0.028428511694073677,-0.04039166122674942,-0.00974445790052414,0.02857644483447075,-0.038706108927726746,-0.019686108455061913,0.0286432933062315,-0.03443136066198349,-0.03418312221765518,0.024017898365855217,-0.03623436763882637,-0.0374394915997982,0.026650384068489075,-0.022837882861495018,-0.04088444262742996,0.027206867933273315,-0.011799359694123268,-0.041573479771614075,0.027778511866927147,-1.0182566043567556e-17,-0.04619397595524788,0.019134171307086945,0,-0.044667165726423264,0.01909707672894001,0.011888435110449791,-0.04017753526568413,0.018998730927705765,0.022850733250379562,-0.031840234994888306,0.019781475886702538,0.03384099155664444,-0.02415510080754757,0.01830279640853405,0.0384935699403286,-0.009637651033699512,0.020955637097358704,0.04793171212077141,-0.0017596088582649827,0.017751621082425117,0.04368025064468384,0.013109750114381313,0.020040784031152725,0.046268340200185776,0.0251859650015831,0.020775509998202324,0.04298941045999527,0.03058600425720215,0.017501400783658028,0.029695400968194008,0.04547550901770592,0.023432303220033646,0.030911773443222046,0.04822121933102608,0.021963736042380333,0.017100544646382332,0.0456976592540741,0.0187442097812891,-0.0007090214057825506,0.044707294553518295,0.01920279674232006,-0.01183110848069191,0.040922775864601135,0.019855158403515816,-0.021786103025078773,0.032641030848026276,0.01911606639623642,-0.03269699588418007,0.023102177307009697,0.01913824863731861,-0.03999774530529976,0.012769953347742558,0.019773799926042557,-0.043456993997097015,-0.003395621431991458,0.016466183587908745,-0.05104486271739006,-0.012935880571603775,0.01836417056620121,-0.04601995646953583,-0.025855474174022675,0.016966789960861206,-0.04394585266709328,-0.0340624675154686,0.018035434186458588,-0.034661781042814255,-0.03874992951750755,0.020120423287153244,-0.021303804591298103,-0.048228587955236435,0.016298817470669746,-0.017111068591475487,-0.04619397595524788,0.019134171307086945,-1.1314260790922793e-17,-0.049039263278245926,0.009754516184329987,0,-0.048477280884981155,0.008883168920874596,0.011108027771115303,-0.04244719073176384,0.009771847166121006,0.024551142007112503,-0.033404476940631866,0.010753567330539227,0.03649245202541351,-0.0247452724725008,0.00957722682505846,0.04214690625667572,-0.00871492363512516,0.01287959422916174,0.05305025354027748,-3.002788873478761e-18,0.009754516184329987,0.049039263278245926,0.014613662846386433,0.011264162138104439,0.05011310055851936,0.026924386620521545,0.011643966659903526,0.04590461403131485,0.03670090064406395,0.0113455131649971,0.03756871819496155,0.040584221482276917,0.008273422718048096,0.021826734766364098,0.04574251547455788,0.008477121591567993,0.010369759984314442,0.05455435439944267,0.014087803661823273,0.007878703996539116,0.048079099506139755,0.010313007980585098,-0.011676856316626072,0.04303576797246933,0.01019963901489973,-0.02371031790971756,0.03483652323484421,0.009880644269287586,-0.03444667160511017,0.02515372633934021,0.010252733714878559,-0.04156339913606644,0.009777696803212166,0.007464474067091942,-0.05153200402855873,9.008366620436283e-18,0.009754516184329987,-0.049039263278245926,-0.015484345145523548,0.007560763042420149,-0.05135693401098251,-0.02880512736737728,0.0063873413018882275,-0.048591382801532745,-0.03301011398434639,0.01106342300772667,-0.03229616582393646,-0.04529714211821556,0.007532600313425064,-0.028559477999806404,-0.0486162006855011,0.008774016052484512,-0.014475023373961449,-0.049039263278245926,0.009754516184329987,-1.2011155493915043e-17,-0.05000000074505806,3.0616169246677665e-18,0,-0.05098101496696472,-0.0021094265393912792,0.009105631150305271,-0.04376108571887016,-0.0003612843865994364,0.02434311993420124,-0.034292273223400116,0.0008352676522918046,0.03687400743365288,-0.02120712399482727,0.0029801172204315662,0.04871966317296028,-0.011701573617756367,0.0009737975778989494,0.050066832453012466,-3.0616169246677665e-18,3.0616169246677665e-18,0.05000000074505806,0.0184783935546875,0.004350847098976374,0.056206922978162766,0.023093633353710175,-0.0014978591352701187,0.04057788848876953,0.037249647080898285,0.001488383742980659,0.03806149214506149,0.04376329854130745,0.0003630236315075308,0.025660043582320213,0.046810757368803024,-0.0011672057444229722,0.01081875991076231,0.05111543834209442,0.0008764150552451611,0.001593481982126832,0.050469428300857544,0.0017074650386348367,-0.009836470708251,0.04347086325287819,0.0001332509855274111,-0.02475772611796856,0.034302446991205215,-0.0008272748091258109,-0.03685947507619858,0.02087235637009144,-0.003243148559704423,-0.049197904765605927,0.009246964007616043,-0.0029024197719991207,-0.05357341840863228,9.184851394388759e-18,3.0616169246677665e-18,-0.05000000074505806,-0.01594308204948902,-0.0023588156327605247,-0.052585046738386154,-0.02690093033015728,-0.0014935871586203575,-0.0460168831050396,-0.03873726353049278,-0.002657224191352725,-0.04018665850162506,-0.04724470153450966,-0.003098410554230213,-0.0306334737688303,-0.05077118054032326,-0.0019445557845756412,-0.016476508229970932,-0.05000000074505806,3.0616169246677665e-18,-1.2246467698671066e-17,-0.049039263278245926,-0.009754516184329987,0,-0.050021201372146606,-0.01183894369751215,0.008902426809072495,-0.04373304173350334,-0.010747496038675308,0.02271421253681183,-0.03392739221453667,-0.009166328236460686,0.03574543073773384,-0.02140050195157528,-0.0073037706315517426,0.046925149857997894,-0.007533665746450424,-0.005701306741684675,0.05473776161670685,-3.002788873478761e-18,-0.009754516184329987,0.049039263278245926,0.016628213226795197,-0.006662009283900261,0.05299103260040283,0.02693003974854946,-0.007860624231398106,0.045912690460681915,0.0378524474799633,-0.0072587342001497746,0.03921378403902054,0.045856084674596786,-0.0070934295654296875,0.029357971623539925,0.050895705819129944,-0.006982975639402866,0.01773145981132984,0.05030697211623192,-0.008758459240198135,0.001811013207770884,0.04785057529807091,-0.00937557965517044,-0.012003320269286633,0.04247423633933067,-0.00975059624761343,-0.024512505158782005,0.03405778482556343,-0.010240254923701286,-0.03555915877223015,0.024071719497442245,-0.010106447152793407,-0.04310912266373634,0.0127097824588418,-0.009740777313709259,-0.047343309968709946,9.008366620436283e-18,-0.009754516184329987,-0.049039263278245926,-0.01752157136797905,-0.013548947870731354,-0.05426725745201111,-0.02922087162733078,-0.013448347337543964,-0.04918530583381653,-0.037496186792850494,-0.011970380321145058,-0.03870484232902527,-0.04816902428865433,-0.014232910238206387,-0.03266216814517975,-0.04623059928417206,-0.008860616013407707,-0.011067022569477558,-0.049039263278245926,-0.009754516184329987,-1.2011155493915043e-17,-0.04619397595524788,-0.019134171307086945,0,-0.04379911348223686,-0.018489224836230278,0.013128511607646942,-0.04169834405183792,-0.02046453207731247,0.020678149536252022,-0.03266831114888191,-0.01913749799132347,0.03265802562236786,-0.023508479818701744,-0.019457485526800156,0.039417315274477005,-0.011495649814605713,-0.018772561103105545,0.0452774278819561,0.001414150814525783,-0.01802305318415165,0.04821418970823288,0.012767361477017403,-0.018496578559279442,0.045779213309288025,0.02234853245317936,-0.019722243770956993,0.038935936987400055,0.03124258480966091,-0.02025105617940426,0.03063337504863739,0.038718145340681076,-0.020145395770668983,0.02125839702785015,0.045659586787223816,-0.018317315727472305,0.013441070914268494,0.046787478029727936,-0.01866784878075123,0.000847859715577215,0.045769598335027695,-0.018230879679322243,-0.010313531383872032,0.03978670760989189,-0.019305812194943428,-0.02340906299650669,0.03331249579787254,-0.018624698743224144,-0.031737759709358215,0.02044888213276863,-0.02121482603251934,-0.04378816485404968,0.010471895337104797,-0.020300159230828285,-0.046739935874938965,-0.0017919365782290697,-0.020542120561003685,-0.04875388368964195,-0.020199444144964218,-0.025611257180571556,-0.056396473199129105,-0.03355381265282631,-0.027350248768925667,-0.05494347959756851,-0.03116896189749241,-0.017959438264369965,-0.030528197064995766,-0.04191361740231514,-0.020633675158023834,-0.025823360309004784,-0.04627770185470581,-0.020436687394976616,-0.014324091374874115,-0.04619397595524788,-0.019134171307086945,-1.1314260790922793e-17,-0.041573479771614075,-0.027778511866927147,0,-0.042599912732839584,-0.029698023572564125,0.007269986905157566,-0.03539605066180229,-0.027301082387566566,0.021654795855283737,-0.029596500098705292,-0.027935348451137543,0.02911173179745674,-0.020871644839644432,-0.027845222502946854,0.03588239848613739,-0.009641628712415695,-0.026899784803390503,0.0417545810341835,0.001583561417646706,-0.02653428539633751,0.04383571073412895,0.01601945236325264,-0.023646092042326927,0.0476703867316246,0.023598644882440567,-0.025569159537553787,0.04002069681882858,0.0286326315253973,-0.02837900072336197,0.028305092826485634,0.0369781069457531,-0.027012899518013,0.022178763523697853,0.041716039180755615,-0.026553472504019737,0.012987352907657623,0.04327789694070816,-0.026439327746629715,0.0024348790757358074,0.04032093286514282,-0.02764962799847126,-0.010525673627853394,0.03604860603809357,-0.027743220329284668,-0.02072257176041603,0.0301646888256073,-0.027175240218639374,-0.028300033882260323,0.020514126867055893,-0.02799270674586296,-0.03639313578605652,0.008318666368722916,-0.029696708545088768,-0.04364452883601189,-0.00015723115939181298,-0.02790204994380474,-0.041798096150159836,-0.0161848496645689,-0.03204088658094406,-0.04790667071938515,-0.02549729309976101,-0.03147966042160988,-0.042733050882816315,-0.03617341071367264,-0.03310292214155197,-0.03907763212919235,-0.04183238744735718,-0.03235820308327675,-0.029113449156284332,-0.04341130331158638,-0.030335543677210808,-0.015409158542752266,-0.041573479771614075,-0.027778511866927147,-1.0182566043567556e-17,-0.0353553406894207,-0.0353553406894207,0,-0.03510556370019913,-0.03610564395785332,0.0077864499762654305,-0.03270172327756882,-0.036992065608501434,0.014701809734106064,-0.025370106101036072,-0.03564613685011864,0.024471277371048927,-0.017851024866104126,-0.03549154847860336,0.03037097118794918,-0.008656056597828865,-0.03496674448251724,0.03485717624425888,0.001140155247412622,-0.03445950523018837,0.03698413446545601,0.010991533286869526,-0.03390892222523689,0.03678048774600029,0.01975434273481369,-0.03372367098927498,0.03358529508113861,0.026966385543346405,-0.0338103249669075,0.02780912257730961,0.03012065403163433,-0.03574660047888756,0.016966290771961212,0.03472420573234558,-0.03490467742085457,0.009970021434128284,0.03562521934509277,-0.03514329344034195,0.00038553966442123055,0.034300003200769424,-0.035237979143857956,-0.008937249891459942,0.03111802227795124,-0.03496295586228371,-0.01696423999965191,0.024143552407622337,-0.036028262227773666,-0.026223497465252876,0.015515376813709736,-0.037054285407066345,-0.03370761126279831,0.005295312497764826,-0.03838452324271202,-0.039658237248659134,-0.008259779773652554,-0.0418451689183712,-0.047155026346445084,-0.011804556474089622,-0.03744056448340416,-0.03794195130467415,-0.02215423807501793,-0.03887264430522919,-0.037013716995716095,-0.03403061255812645,-0.04245081916451454,-0.03790087252855301,-0.034350063651800156,-0.03828718885779381,-0.023008305579423904,-0.03577350825071335,-0.036630455404520035,-0.011469028890132904,-0.0353553406894207,-0.0353553406894207,-8.65956027255431e-18,-0.027778511866927147,-0.041573479771614075,0,-0.030256986618041992,-0.04426455497741699,0.0022967455442994833,-0.025696521624922752,-0.042861755937337875,0.011546933092176914,-0.0210898295044899,-0.042710766196250916,0.017574578523635864,-0.014154809527099133,-0.04178212955594063,0.023677535355091095,-0.00729021243751049,-0.04165252670645714,0.026688260957598686,-0.00012213083391543478,-0.041669439524412155,0.027604039758443832,0.00769449258223176,-0.04117678478360176,0.027553247287869453,0.01476742047816515,-0.040883492678403854,0.025311416015028954,0.020060958340764046,-0.041244592517614365,0.02024035155773163,0.02430592104792595,-0.04137781634926796,0.014245004393160343,0.02698930911719799,-0.04144986718893051,0.00741436006501317,0.02786480449140072,-0.04150567948818207,0.00012327598233241588,0.026321230456233025,-0.041974782943725586,-0.0079192528501153,0.02251211181282997,-0.04278723895549774,-0.016096092760562897,0.020552869886159897,-0.04085808992385864,-0.018341664224863052,0.008622209541499615,-0.04571187496185303,-0.031581249088048935,0.005782993044704199,-0.042678676545619965,-0.028841432183980942,0.0014504241989925504,-0.0404338613152504,-0.025706477463245392,-0.00974850170314312,-0.04358403757214546,-0.0304875448346138,-0.01212258543819189,-0.04018538072705269,-0.02153308130800724,-0.022450977936387062,-0.043780241161584854,-0.023654665797948837,-0.02475455403327942,-0.04212163761258125,-0.014885908924043179,-0.034073904156684875,-0.04726355895400047,-0.01753520779311657,-0.027778511866927147,-0.041573479771614075,-6.803773123666079e-18,-0.019134171307086945,-0.04619397595524788,0,-0.020712632685899734,-0.047946467995643616,0.0017659412696957588,-0.019010305404663086,-0.048110827803611755,0.0060819038189947605,-0.015503531321883202,-0.04774468392133713,0.010710432194173336,-0.009082612581551075,-0.045813318341970444,0.017262782901525497,-0.0064285690896213055,-0.04735391214489937,0.01637321710586548,-0.00023581244749948382,-0.04637925699353218,0.018797297030687332,0.004513334948569536,-0.04653886705636978,0.017855113372206688,0.009230910800397396,-0.04645811393857002,0.016090428456664085,0.013443594798445702,-0.04626178741455078,0.013406605459749699,0.01644292287528515,-0.046294353902339935,0.009384578093886375,0.018366739153862,-0.04628468677401543,0.004787357524037361,0.018754389137029648,-0.04649237543344498,-0.0005425457493402064,0.01566522940993309,-0.04840730130672455,-0.008976518176496029,0.017308838665485382,-0.04561399295926094,-0.008512571454048157,0.014459196478128433,-0.045463815331459045,-0.012202340178191662,0.004199590999633074,-0.05041129142045975,-0.024238526821136475,0.0025056879967451096,-0.048116303980350494,-0.02197733335196972,-0.0013954887399449944,-0.04729043319821358,-0.02112772688269615,-0.007627477403730154,-0.04829591140151024,-0.022303888574242592,-0.012583334930241108,-0.0485638864338398,-0.020879605785012245,-0.01202375814318657,-0.04501057788729668,-0.011378267779946327,-0.01512359082698822,-0.04505698010325432,-0.0074998182244598866,-0.017402444034814835,-0.04534560441970825,-0.003409795230254531,-0.019134171307086945,-0.04619397595524788,-4.68652038700443e-18,-0.009754516184329987,-0.049039263278245926,0,-0.010390146635472775,-0.0497998408973217,0.0011417872738093138,-0.007573908660560846,-0.048352744430303574,0.006125473417341709,-0.006120007485151291,-0.04842839017510414,0.008008165284991264,-0.008209330961108208,-0.05165731906890869,0.003687554970383644,-0.0042000203393399715,-0.05035562068223953,0.007028759922832251,0.0005392612656578422,-0.048615556210279465,0.010524889454245567,0.0008983754087239504,-0.050317052751779556,0.007098883390426636,0.004402901511639357,-0.049411971122026443,0.007770007010549307,0.006415745243430138,-0.04941777139902115,0.006209285464137793,0.007532478775829077,-0.04975833371281624,0.0035698572173714638,0.008444665931165218,-0.04980727657675743,0.0011282642371952534,0.008093638345599174,-0.05034423992037773,-0.002372682560235262,0.010142502374947071,-0.04847326502203941,-0.0014955647056922317,0.007632223889231682,-0.0496799610555172,-0.00604216568171978,0.006470590829849243,-0.04937468096613884,-0.007507332134991884,0.005881702993065119,-0.04825005680322647,-0.007012737914919853,-0.0008444988052360713,-0.05168645456433296,-0.01423521526157856,0.0011497254017740488,-0.048135906457901,-0.008112051524221897,-0.0037178974598646164,-0.049976810812950134,-0.011126771569252014,-0.007184107322245836,-0.050851788371801376,-0.011743158102035522,-0.006955909077078104,-0.04908517003059387,-0.006980948615819216,-0.009624874219298363,-0.04996421933174133,-0.006558993831276894,-0.012594676576554775,-0.051531970500946045,-0.00705685093998909,-0.009754516184329987,-0.049039263278245926,-2.3891674767279894e-18,-6.123233849335533e-18,-0.05000000074505806,0,-5.91458994752822e-18,-0.05000000074505806,1.5848096044559123e-18,-5.302876236065149e-18,-0.05000000074505806,3.0616169246677665e-18,-4.329780136277155e-18,-0.05000000074505806,4.329780136277155e-18,-3.0616169246677665e-18,-0.05000000074505806,5.302876236065149e-18,-1.5848096044559123e-18,-0.05000000074505806,5.91458994752822e-18,-3.7493994848884817e-34,-0.05000000074505806,6.123233849335533e-18,1.5848096044559123e-18,-0.05000000074505806,5.91458994752822e-18,3.0616169246677665e-18,-0.05000000074505806,5.302876236065149e-18,4.329780136277155e-18,-0.05000000074505806,4.329780136277155e-18,5.302876236065149e-18,-0.05000000074505806,3.0616169246677665e-18,5.91458994752822e-18,-0.05000000074505806,1.5848096044559123e-18,6.123233849335533e-18,-0.05000000074505806,7.498798969776963e-34,5.91458994752822e-18,-0.05000000074505806,-1.5848096044559123e-18,5.302876236065149e-18,-0.05000000074505806,-3.0616169246677665e-18,4.329780136277155e-18,-0.05000000074505806,-4.329780136277155e-18,3.0616169246677665e-18,-0.05000000074505806,-5.302876236065149e-18,1.5848096044559123e-18,-0.05000000074505806,-5.91458994752822e-18,1.1248197995487964e-33,-0.05000000074505806,-6.123233849335533e-18,-1.5848096044559123e-18,-0.05000000074505806,-5.91458994752822e-18,-3.0616169246677665e-18,-0.05000000074505806,-5.302876236065149e-18,-4.329780136277155e-18,-0.05000000074505806,-4.329780136277155e-18,-5.302876236065149e-18,-0.05000000074505806,-3.0616169246677665e-18,-5.91458994752822e-18,-0.05000000074505806,-1.5848096044559123e-18,-6.123233849335533e-18,-0.05000000074505806,-1.4997597939553927e-33];
       
        // Apply randomization on initialization
        //randomizeAsteroidShape(asteroidGeometry);

        //geometry.attributes.position.array = new Float32Array(JSON.parse(savedString))
        asteroidGeometry.attributes.position.array = new Float32Array(bakedAsteroid);
        asteroidGeometry.attributes.position.needsUpdate = true;


        // Orbital paths
        //--const earthOrbitGeometry = new THREE.RingGeometry(99, 101, 64); // Orbital ring
        const earthOrbitCurve = new THREE.EllipseCurve(0, 0, 107.5, 92.0, 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
        earthOrbitGeometry.rotateZ(-21.66 * rad); // Tilt orbital plane by 23.44° around X-axis
        const earthOrbit = new THREE.Line(earthOrbitGeometry, new THREE.LineBasicMaterial({ color: 0x0000ff }));
        //--const earthOrbit = new THREE.LineLoop(earthOrbitGeometry, new THREE.LineBasicMaterial({ color: 0x0000ff }));
        scene.add(earthOrbit);

        const moonOrbitCurve = new THREE.EllipseCurve(0, 0, 5.4, 4.6, 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((-21.55) * rad); // Aligns to XZ plane
        const moonOrbit = new THREE.Line(moonOrbitGeometry, new THREE.LineBasicMaterial({ color: 0x888888 })); // 00ff00
        scene.add(moonOrbit);


        const asteroidOrbitCurve = new THREE.EllipseCurve(0, 0, 85.0, 60.0, 0, 2 * Math.PI, false, 0);
        //const asteroidOrbitPoints = asteroidOrbitCurve.getPoints(64);
        const asteroidOrbitPoints = asteroidOrbitCurve.getPoints(128).map(point => new THREE.Vector3(point.x, point.y, 0)); // Convert to 3D
        const asteroidOrbitGeometry = new THREE.BufferGeometry().setFromPoints(asteroidOrbitPoints);
        //asteroidOrbitGeometry.rotateX((Math.PI / 2)); // Aligns to XZ plane
        //asteroidOrbitGeometry.rotateX(75.55 * rad); // Aligns to XZ plane
        asteroidOrbitGeometry.rotateX(-21.55 * rad); // Aligns to XZ plane
        asteroidOrbitGeometry.rotateZ(-21.55 * rad); // Aligns to XZ plane
        const asteroidOrbit = new THREE.Line(asteroidOrbitGeometry, new THREE.LineBasicMaterial({ color: 0xff0000 }));
        scene.add(asteroidOrbit);
        const asteroidOrbitOffset = new THREE.Vector3(35.0, -10.0, 0.0);
        asteroidOrbit.position.copy(asteroidOrbitOffset);

        //--// Asteroid path
        //--const asteroidPath = new THREE.Line(
        //--    new THREE.BufferGeometry().setFromPoints([
        //--        new THREE.Vector3(135, -40.7, -68),
        //--        new THREE.Vector3(105, -40.7, 68)
        //--    ]),
        //--    new THREE.LineBasicMaterial({ color: 0xff0000 })
        //--);
        //--scene.add(asteroidPath);
        const asteroidMarkGeometry = new THREE.BufferGeometry();
        const asteroidMarkPositions = new Float32Array([0, 0, 0, 0, 0, 0]); // Initialize to 0
        asteroidMarkGeometry.setAttribute('position', new THREE.BufferAttribute(asteroidMarkPositions, 3));
        asteroidMarkGeometry.rotateX(-21.55 * rad); // Apply same tilt as orbit
        asteroidMarkGeometry.rotateZ(-21.55 * rad); // Apply same tilt as orbit
        const asteroidMark = new THREE.Line(
            asteroidMarkGeometry,
            new THREE.LineBasicMaterial({ color: 0x00ff00 })
        );
        asteroidMark.frustumCulled = false;
        scene.add(asteroidMark);

        const direction = new THREE.Vector3(0, 1, 0); // Start with Y-axis (sideways in 2D)
        direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), 90.0 * rad);
        direction.applyAxisAngle(new THREE.Vector3(0, 1, 0), 90.0 * rad);
        direction.applyAxisAngle(new THREE.Vector3(1, 0, 0), -21.55 * rad);
        direction.applyAxisAngle(new THREE.Vector3(0, 0, 1), -21.55 * rad);
        direction.normalize();
        const lineLength = 5; // Fixed length
        const lineOffset = .3; // Fixed length


        // Camera setup
        camera.position.set(97.65, -38.78, 1.70);
        //camera.position.set(97.5, -38.0, 0.0);
        //camera.position.set(40.57, 7.01, -178.13);
        //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, -39.78, 0);
        //controls.target.set(100, -39.78, 0);
        //controls.target.set(27.56, 18.19, -79.61);
        controls.update();

        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 = 365 * 24; // 192 days ~ 6.4 months / 2
        const earthOrbitalPeriodMs = 365.25 * 24 * 3600 * 1000;
        const moonOrbitalPeriodMs = 27.3 * 24 * 3600 * 1000;
        const sunPeriod = 25 * 24 * 3600; // 25 days in seconds (equator base)
        const cloudPeriod = 365 * 24 * 3600; // 365 days in seconds
        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 play1000000x = document.getElementById('play1000000x');
        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', () => { playSpeed(1); });
        play100x.addEventListener('click', () => { playSpeed(100); });
        play1000x.addEventListener('click', () => { playSpeed(1000); });
        play10000x.addEventListener('click', () => { playSpeed(10000); });
        play100000x.addEventListener('click', () => { playSpeed(100000); });
        play1000000x.addEventListener('click', () => { playSpeed(1000000); });
        stop.addEventListener('click', () => { stopPlay(); });

        let pFL = [];
        pFL[0] = document.getElementById('pF0');
        pFL[1] = document.getElementById('pF1');
        pFL[2] = document.getElementById('pF2');
        pFL[3] = document.getElementById('pF3');
        pFL[4] = document.getElementById('pF4');
        pFL[5] = document.getElementById('pF5');
        pFL[6] = document.getElementById('pF6');

        function playSpeed(x) {
            isPlaying = true;
            speed = x;
            let sel=0;

            for (let i = 0; i < 7; i++) pFL[i].style.display = "none";
            switch(x) {
                case 1:
                    sel = 1;
                    break;
                case 100:
                    sel = 2;
                    break;
                case 1000:
                    sel = 3;
                    break;
                case 10000:
                    sel = 4;
                    break;
                case 100000:
                    sel = 5;
                    break;
                case 1000000:
                    sel = 6;
                    break;
                default:
            }
            pFL[sel].style.display = "inline-block"; // Show speed v
        }
        function stopPlay() {
            isPlaying = false;
            speed = 0;
            
            for (let i = 0; i < 7; i++) pFL[i].style.display = "none";
            pFL[0].style.display = "inline-block"; // Show speed v
        }

        const spherical = { radius: 500, theta: 0, phi: Math.PI / 2, velocityTheta: 0, velocityPhi: 0 }; // Default values

        function updateSphericalOrbit(obj) {
            const x = spherical.radius * Math.cos(spherical.phi) * Math.cos(spherical.theta);
            const y = spherical.radius * Math.sin(spherical.phi);
            const z = spherical.radius * Math.cos(spherical.phi) * Math.sin(spherical.theta);
            camera.position.set(x, y, z).add(obj.position); // Offset from object
            camera.lookAt(obj.position);
            controls.target.copy(obj.position); // Point OrbitControls at object
            controls.update(); // Apply user input and damping
            //camera.getWorldPosition();
        }

        function targetObject(obj1, obj2) {
            tDiff.copy(obj2.position).sub(obj1.position); // Update tDiff with current positions
            isLocked = true;
            controls.enabled = false;
            controls.enableDamping = false;

            objRef = obj2;

            for (let i = 0; i < 4; i++) mFL[i].style.display = "none";
            let sel=0;
            switch(obj2) {
                case earth:
                    //objRef=earth;
                    sel=3;
                    break;
                case moon:
                    //objRef=moon;
                    sel=2;
                    break;
                case asteroid:
                    //objRef=asteroid;
                    sel=1;
                    break;
                default:
            };
            mFL[sel].style.display = "inline-block"; // Show Earth lock UI

            // Initialize spherical orbit with current distance
            const dx = obj2.position.x - obj1.position.x;
            const dy = obj2.position.y - obj1.position.y;
            const dz = obj2.position.z - obj1.position.z;
            spherical.radius = Math.sqrt(dx * dx + dy * dy + dz * dz); // Dynamic radius
            spherical.theta = Math.atan2(dz, dx); // Azimuth
            //spherical.phi = Math.acos(dy / spherical.radius) - (90.0 * rad); // Elevation, adjusted for acos range
            //spherical.phi = Math.asin(dy / spherical.radius); // Elevation
            spherical.phi = Math.asin(dy / spherical.radius) + (180.0 * rad); // Elevation
            //spherical.phi = Math.PI / 2;
            //spherical.phi = 180.0 * rad;
            //spherical.phi = angle;
            spherical.velocityTheta = 0; // Reset velocity
            spherical.velocityPhi = 0; // Reset velocity
        }

        //let lockControls = false;
        let isLocked = false;
        let tDiff = new THREE.Vector3();
        let objRef = null;
        modeF.addEventListener('click', () => {
            isLocked=false;
            objRef=null;
            controls.enabled = true;
            controls.enableDamping = true;
            for(let i=0;i<4;i++) mFL[i].style.display="none";
            mFL[0].style.display="inline-block";
        });

        modeL1.addEventListener('click', () => targetObject(camera, asteroid));
        modeL2.addEventListener('click', () => targetObject(camera, moon));
        modeL3.addEventListener('click', () => targetObject(camera, earth));

        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 astNormalMatrix = new THREE.Matrix3();

        const mvpMatrix = new THREE.Matrix4();
        const atmMvpMatrix = new THREE.Matrix4();
        
        const earthMvpMatrix = new THREE.Matrix4();
        const moonMvpMatrix = new THREE.Matrix4();
        const astMvpMatrix = new THREE.Matrix4();
        
        const sunMvpMatrix = new THREE.Matrix4();
        const starMvpMatrix = new THREE.Matrix4();
        const glowMvpMatrix = new THREE.Matrix4();

        let timeOffset = 0.0;
        let timeFraction = 0.0;
        let yToWinter = 0.0;
        let wireframeMode = false;
        let linesVisible = true;
        let uiVisible = true;
        let currentTime = new Date(baseTime + timeOffset);

        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 loopedSunTime = (currentTime * .0000001) % 360.0; // Loop within 25 days
                //sunMaterial.uniforms.realtime.value = loopedSunTime * rad;
                // Update realtime animation
                //sunMaterial.uniforms.realtime.value = timeFraction * cloudPeriod; // Scale to 25-day period
                //cloudMaterial.uniforms.time.value = timeFraction * cloudPeriod; // Scale to 365-day period
            }
            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 with 23.44° tilt
            const earthRadius = 100;
            const tiltRad = 23.44 * rad; // Axial and orbital tilt
            const earthX = earthRadius * Math.cos(earthAngle);
            const earthY = earthRadius * Math.sin(-(90*rad)+earthAngle) * Math.sin(tiltRad); // Y-offset based on tilt
            const earthZ = earthRadius * Math.sin(earthAngle) * Math.cos(tiltRad); // Adjusted Z for tilt
            earth.position.set(earthX, earthY, earthZ);

            clouds.position.set(earth.position.x, earth.position.y, earth.position.z);
            atmosphere.position.set(earth.position.x, earth.position.y, earth.position.z);

            // Update Moon orbit position to follow Earth
            moonOrbit.position.set(earth.position.x, earth.position.y, earth.position.z);

            // Moon orbital position
            const moonAngle = -(timeOffset / moonOrbitalPeriodMs) * 2 * Math.PI + (45 * rad);

            // Moon spherical coordinates (radius 5 around Earth, tilted 5°)
            const moonRadius = 5; // Distance from Earth
            const moonTiltAngle = 5 * rad; // Tilt of Moon’s orbit
            const moonX = moonRadius * Math.cos(moonAngle);
            const moonY = moonRadius * Math.sin((-90*rad)+moonAngle) * Math.sin(tiltRad); // Y-offset based on tilt
            const moonZ = moonRadius * Math.sin(moonAngle) * Math.cos(tiltRad); // Adjusted Z for tilt
            moon.position.set(earthX + moonX, earthY + moonY, earthZ + moonZ);


            // Asteroid position
            const asteroidAngle = (timeOffset / earthOrbitalPeriodMs) * 2 * Math.PI + (178.0 * rad);
            const t = (asteroidAngle + Math.PI) / (2 * Math.PI); // 0 to 1 parameter
            const pointOnEllipse = new THREE.Vector3().set(
                asteroidOrbitCurve.getPoint(t).x,
                asteroidOrbitCurve.getPoint(t).y,
                0
            );
            //pointOnEllipse.applyAxisAngle(new THREE.Vector3(0, 0, 1), 90.0 * rad);
            //pointOnEllipse.applyAxisAngle(new THREE.Vector3(0, 1, 0), 90.0 * rad);
            pointOnEllipse.applyAxisAngle(new THREE.Vector3(1, 0, 0), -21.55 * Math.PI / 180); // Apply X tilt
            pointOnEllipse.applyAxisAngle(new THREE.Vector3(0, 0, 1), -21.55 * Math.PI / 180); // Apply Z tilt
            let asteroidPos = pointOnEllipse.add(asteroidOrbitOffset);
            asteroid.position.copy(asteroidPos);

            
            // Asteroid Mark with fixed direction and offset
            const dirClone = direction.clone(); // Prevent cumulative modification
            const startPos = asteroidPos.clone().sub(dirClone.multiplyScalar(lineOffset)); // Start at -0.3
            const endPos = asteroidPos.clone().sub(dirClone.multiplyScalar(lineLength + lineOffset)); // End at -(5 + 0.3)

            // Safe buffer update with NaN check
            asteroidMarkPositions[0] = isNaN(startPos.x) ? 0 : startPos.x;
            asteroidMarkPositions[1] = isNaN(startPos.y) ? 0 : startPos.y;
            asteroidMarkPositions[2] = isNaN(startPos.z) ? 0 : startPos.z;
            asteroidMarkPositions[3] = isNaN(endPos.x) ? 0 : endPos.x;
            asteroidMarkPositions[4] = isNaN(endPos.y) ? 0 : endPos.y;
            asteroidMarkPositions[5] = isNaN(endPos.z) ? 0 : endPos.z;
            asteroidMarkGeometry.attributes.position.needsUpdate = true;
            asteroidMarkGeometry.computeBoundingSphere(); // Force recompute

            // Calculate tilted local Y-axis
            const tiltAxis = new THREE.Vector3(1, 0, 0).applyAxisAngle(new THREE.Vector3(0, 1, 0), tiltRad);
            const localYAxis = new THREE.Vector3(0, 1, 0).applyAxisAngle(tiltAxis, 0); // Simplify, adjust if needed

            // Daily rotation angle
            const earthRotationAngle = (timeOffset / (24 * 3600 * 1000)) * 2 * Math.PI;
            
            earth.rotation.y = earthRotationAngle + (350 * rad) - earthAngle; // Align UK to Sun at 12 PM GMT
            earth.rotation.x = + (5.0 * rad); // side tilt to even egypt to norway / south america to north america

            clouds.rotation.copy(earth.rotation);
            atmosphere.rotation.copy(earth.rotation);

            //--// Create transformation matrix -- not used for now
            //--const positionMatrix = new THREE.Matrix4().makeTranslation(earthX, earthY, earthZ);
            //--const tiltMatrix = new THREE.Matrix4().makeRotationZ(-tiltRad);
            //--const rotationMatrix = new THREE.Matrix4().makeRotationAxis(localYAxis, earthRotationAngle);
            //--const combinedMatrix = new THREE.Matrix4().multiplyMatrices(positionMatrix, tiltMatrix).multiply(rotationMatrix);

            //--// Apply to Earth, clouds, and atmosphere
            //--earth.matrixAutoUpdate = false;
            //--earth.matrix.copy(combinedMatrix);
            //--earth.updateMatrixWorld();

            //--clouds.matrixAutoUpdate = false;
            //--clouds.matrix.copy(combinedMatrix); // Same matrix for clouds
            //--clouds.updateMatrixWorld();

            //--atmosphere.matrixAutoUpdate = false;
            //--atmosphere.matrix.copy(combinedMatrix); // Same matrix for atmosphere
            //--atmosphere.updateMatrixWorld();
            

            // 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;
            //console.log(moon.rotation.y);
            moon.rotation.y = ((Math.PI * 2) - moonRotationAngle) + (120 * rad);
           
            // Sun rotation
            //const sunRotationPeriodMs = 25 * 24 * 3600 * 1000; // 25 days in milliseconds
            //const sunRotationAngle = (timeOffset / sunRotationPeriodMs) * 2 * Math.PI;
            //sun.rotation.y = sunRotationAngle; // Rotate around Y-axis
            //console.log(sun.rotation.y);

            
            //console.log(earth.position.y); // ~-40 to +40
            yToWinter=(earth.position.y + 20) * .025;
            if(yToWinter>1.0) yToWinter=1.0;
            else if(yToWinter<0.0) yToWinter=0.0;
            earthMaterial.uniforms.isWinter.value = yToWinter;
        });


        function updateFlames(time) {
            const positionAttribute = flames.geometry.attributes.position;
            const scaleAttribute = flames.geometry.attributes.scale;
        
            for (let i = 0; i < positionAttribute.count; i += 2) {
                const baseIndex = i / 2;
                const scale = scaleAttribute.getX(baseIndex);
                const newScale = Math.max(0, Math.sin(time + baseIndex) * 0.5 + 0.5); // Oscillate 0-1 with offset
                scaleAttribute.setX(baseIndex, newScale);
        
                // Update tip position based on scale
                const tipX = positionAttribute.getX(i + 1) * newScale;
                const tipY = positionAttribute.getY(i + 1) * newScale;
                const tipZ = positionAttribute.getZ(i + 1) * newScale;
                positionAttribute.setXYZ(i + 1, tipX, tipY, tipZ);
       
                //if(i==0) {
                //console.dir(newScale);
                //}
            }
        
            scaleAttribute.needsUpdate = true;
            positionAttribute.needsUpdate = true;
        }


        // 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');
        const controlsUI1 = document.getElementById('controls');
        const controlsUI2 = document.getElementById('controls2');

        // 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();
           

                // Update sun material realtime
                //const loopedSunTime = currentTime % sunPeriod; // Loop within 25 days
                //const loopedCloudTime = currentTime % cloudPeriod; // Loop within 365 days
                //cloudMaterial.uniforms.time.value = loopedCloudTime;

        
                timeline.dispatchEvent(initialEvent);
            }
            const loopedSunTime = (currentTime * .0000001) % 360.0; // Loop within 25 days
            sunMaterial.uniforms.realtime.value = loopedSunTime * rad;

            //--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 (isLocked && objRef) {
                //camera.position.set(objRef.position.x - tDiff.x, objRef.position.y - tDiff.y, objRef.position.z - tDiff.z);
                //camera.lookAt(objRef.position);
                //controls.target.copy(objRef.position); // Update OrbitControls target
                //spherical.theta += 0.01; // Rotate around Earth
                
                // Apply velocity with dampening
                spherical.velocityTheta += mouseVelocityX + touchVelocityX;
                spherical.velocityPhi += mouseVelocityY + touchVelocityY;
                spherical.theta += spherical.velocityTheta;
                spherical.phi += spherical.velocityPhi;
                //spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi)); // Clamp
                //spherical.phi = Math.max(0.0, Math.min(Math.PI * 2, spherical.phi)); // Clamp
                spherical.theta %= Math.PI * 2; // Clamp
                spherical.phi = Math.max((Math.PI / 2) + .1, Math.min(((Math.PI / 2) + Math.PI) - .1, spherical.phi)); // Clamp
                spherical.velocityTheta *= dampingFactor; // Exponential decay
                spherical.velocityPhi *= dampingFactor; // Exponential decay
                updateSphericalOrbit(objRef);
                if(mouseVelocityY > 0.0) mouseVelocityY *= dampingFactor;
                if(touchVelocityY > 0.0) touchVelocityY *= dampingFactor;

                //console.log(mouseVelocityX);
            }
            
            // 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;
            
            sunMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(sun.matrixWorld);
            sunMaterial.uniforms.mvpMatrix.value = sunMvpMatrix;
            
            starMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(starField.matrixWorld);
            starMaterial.uniforms.mvpMatrix.value = starMvpMatrix;
            
            glowMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(glowField.matrixWorld);
            glowMaterial.uniforms.mvpMatrix.value = glowMvpMatrix;
            
            asteroid.updateMatrixWorld();
            astNormalMatrix.getNormalMatrix(asteroid.matrixWorld);
            asteroidMaterial.uniforms.nMatrix.value = astNormalMatrix;
            asteroidMaterial.uniforms.vMatrix.value.copy(camera.matrixWorldInverse);

            astMvpMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse).multiply(asteroid.matrixWorld);
            asteroidMaterial.uniforms.mvpMatrix.value = astMvpMatrix;

            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
            if(sunMaterial.uniforms.time.value==0) {
                sunMaterial.uniforms.time.value = 1.0; // defaultset
            }
            sunMaterial.uniforms.time.value += 0.005 + (speed * .0000001); // Adjust speed here
            earthMaterial.uniforms.time.value += 0.015; // Animate water
            cloudMaterial.uniforms.time.value += 0.005 + (speed * .000001);
            
            //const loopedFlameTime = currentTime % (1.0 * 24 * 3600); // Loop within 25 days
            flameTime += .01 + (speed * .000001);
            updateFlames(flameTime);


            //if (sunLight.shadow.map) {
            //    earthMaterial.uniforms.shadowMap.value = sunLight.shadow.map.texture;
            //    // No lightSpaceMatrix needed for point light; use direct depth
            //    //earthMaterial.uniforms.lightSpaceMatrix.value.copy(sunLight.shadow.camera.matrixWorldInverse).multiply(earth.matrixWorld);
            //}
            
            // Update camera info
            const pos = camera.position;
            const target = controls.target; // OrbitControls look-at point
            if(!isLocked) {
                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)})`;
            } else {
                cameraInfo.innerHTML = `CameraPos: (${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)})<br />\ntheta: ${spherical.theta.toFixed(3)} phi: ${spherical.phi.toFixed(3)}<br />\nvelocityTheta: ${spherical.velocityTheta.toFixed(3)}, velocityPhi: ${spherical.velocityPhi.toFixed(3)}`;
            }


            //// Pre-depth pass for clouds
            //setupDepthPass();
            //renderer.setRenderTarget(renderTargetDepth);
            //renderer.clear();
            //renderer.render(scene, camera); // Render depth from camera perspective (adjust if needed)
            //renderer.setRenderTarget(null);
            //restoreMaterials();
            // Pre-depth pass with cube camera

           //-- setupDepthPass(); // disabled fow now
           //-- cubeCamera.update(renderer, scene); // Render all six faces
           //-- restoreMaterials();

           //-- // Update shadow map uniform
           //-- earthMaterial.uniforms.shadowMap.value = renderTargetDepth.texture;


            renderer.render(scene, camera);
            //--composer.render(); // Replace renderer.render
            requestAnimationFrame(updateScene);
        }
        //updateScene();
        requestAnimationFrame(updateScene);


        let isDragging = false;
        let isDragValid = false;  // Track if drag started on canvas
        let isTouchZooming = false; // to zoom while sinning on 2 finger touch
        let lastMouseX = 0, lastMouseY = 0, lastTouchX = 0, lastTouchY = 0;
        let mouseVelocityX = 0, mouseVelocityY = 0, touchVelocityX = 0, touchVelocityY = 0;
        let touchStartTime = 0;
        const dragSensitivity = 0.00025; // Adjust for speed
        const dampingFactor = 0.95; // Velocity decay
        const spinThreshold = 200; // 200ms for spin triggery

        // Mouse Listeners
        canvas.addEventListener('mousedown', (event) => {
            if (isLocked) {
                isDragging = true;
                isDragValid = true; // Mark drag as valid since it started on canvas
                lastMouseX = event.clientX;
                lastMouseY = event.clientY;
                mouseVelocityX = mouseVelocityY = 0;

                touchStartTime=new Date().getTime();
            }
        });

        document.addEventListener('mouseup', () => {
            if (isLocked) {
                if (isDragValid && Date.now() - touchStartTime > spinThreshold) {
                    mouseVelocityX = 0;
                }
                isDragging = false;
                isDragValid = false; // Reset drag validity

                mouseVelocityY = 0;
            }
        });
        
        document.addEventListener('mousemove', (event) => {
            if (isLocked && isDragging && isDragValid) {
                const deltaX = event.clientX - lastMouseX;
                const deltaY = event.clientY - lastMouseY;
                spherical.theta += deltaX * dragSensitivity; // Rotate theta with mouse
                spherical.phi -= deltaY * dragSensitivity; // Rotate theta with mouse
                lastMouseX = event.clientX;
                lastMouseY = event.clientY;
                mouseVelocityX = deltaX * (dragSensitivity * .5); // Update velocity
                mouseVelocityY = -(deltaY * (dragSensitivity * .5)); // Update velocity
            }
        });
        
        
        document.addEventListener('wheel', (event) => {
            if (isLocked) {
                let speedzoom = Math.max(1,spherical.radius);
                spherical.radius += event.deltaY * speedzoom * 0.0005; // Zoom with mouse wheel
                spherical.radius = Math.max(.1, Math.min(100, spherical.radius)); // Clamp radius
                //console.log(spherical.radius);
            }
        });


        // Touch Listeners
        canvas.addEventListener('touchstart', (event) => {
            //event.preventDefault(); // Prevent default touch behavior (e.g., scrolling)
            if (isLocked) {
                isDragValid = true; // Mark drag as valid
                touchStartTime = Date.now();
                if (event.touches.length === 1) {
                    isDragging = true;
                    const touch = event.touches[0];
                    lastTouchX = touch.clientX;
                    lastTouchY = touch.clientY;
                   // touchVelocityX = touchVelocityY = 0;
                } else if (event.touches.length === 2) {
                    isTouchZooming = true;
                    // Two touches for pinch zoom
                    updatePinchZoom(event.touches);
                }
            }
        }, { passive: false }); // Allow preventDefault

        document.addEventListener('touchend', (event) => {
            if (isLocked && (isDragging || isTouchZooming)) {
                if (isDragValid && !isTouchZooming && event.touches.length < 1 && Date.now() - touchStartTime > spinThreshold) {
                    touchVelocityX = 0;
                }
                isDragging = false;
                isDragValid = false; // Reset drag validity
                isTouchZooming = false;
                
                touchVelocityY = 0;
            }
        });
        
        document.addEventListener('touchmove', (event) => {
            if (isLocked && (isDragging || isTouchZooming) && isDragValid) {
                event.preventDefault();
                if (event.touches.length === 1 && !isTouchZooming) {
                    // Single touch movement
                    const deltaX = event.touches[0].clientX - lastTouchX;
                    const deltaY = event.touches[0].clientY - lastTouchY;
                    spherical.velocityTheta += deltaX * dragSensitivity * .5;
                    spherical.velocityPhi -= deltaY * dragSensitivity * .5;
                    lastTouchX = event.touches[0].clientX;
                    lastTouchY = event.touches[0].clientY;
                    touchVelocityX = deltaX * (dragSensitivity * .25);
                    touchVelocityY = -(deltaY * (dragSensitivity * .25));
                } else if (event.touches.length === 2 && isTouchZooming) {
                    isDragging = false;
                    // Pinch zoom
                    updatePinchZoom(event.touches);
                }
            }
        }, { passive: false });
        
        
        // Pinch Zoom Function
        function updatePinchZoom(touches) {
            const touch1 = touches[0];
            const touch2 = touches[1];
            const dx = touch2.clientX - touch1.clientX;
            const dy = touch2.clientY - touch1.clientY;
            const currentDistance = Math.sqrt(dx * dx + dy * dy);
        
            if (!spherical.lastPinchDistance) spherical.lastPinchDistance = currentDistance;
            const deltaDistance = currentDistance - spherical.lastPinchDistance;
            let speedzoom = Math.max(1, spherical.radius);
            if(deltaDistance>0.0) {
                // zoom in
                spherical.radius -= 1.0 * speedzoom * 0.02; // Adjust factor as needed
            } else {
                // zoom oout
                spherical.radius += 1.0 * speedzoom * 0.02; // Adjust factor as needed
            }
            spherical.radius = Math.max(0.1, Math.min(100, spherical.radius)); // Clamp radius
            spherical.lastPinchDistance = currentDistance;
        }


        // Key listeners
        // altered orbitcontrols2 event.ctrl key to use custom here
        const pressedKeys = new Set;

        document.addEventListener('keydown', (e) => {
            pressedKeys.add(e.key);

            if (e.key === 'Shift') {
                controls.screenSpacePanning = false;
            }
        });
        document.addEventListener('keyup', (e) => {
            pressedKeys.delete(e.key);

            if (event.key === 'w') { // Toggle with 'w' key
                wireframeMode = !wireframeMode;
                sun.material.wireframe = wireframeMode; // Toggle wireframe
                moon.material.wireframe = wireframeMode; // Toggle wireframe
                earth.material.wireframe = wireframeMode;
                clouds.material.wireframe = wireframeMode;
                atmosphere.material.wireframe = wireframeMode;
                asteroid.material.wireframe = wireframeMode;
            } else if (e.key === 'l') { // Toggle with 'l' key
                linesVisible = !linesVisible;

                earthOrbit.visible = linesVisible;
                moonOrbit.visible = linesVisible;
                //--asteroidPath.visible = linesVisible;
                //--asteroidMark.visible = linesVisible;
            } else if( e.key === 'u' ) {
                uiVisible = !uiVisible;

                if(uiVisible) {
                    cameraInfo.parentNode.style.display = "none";
                    controlsUI1.style.display = "none";
                    controlsUI2.style.display = "none";
                } else {
                    
                    cameraInfo.parentNode.style.display = "block";
                    controlsUI1.style.display = "block";
                    controlsUI2.style.display = "block";
                }

            } else if (e.key === 'Shift') {
                controls.screenSpacePanning = true;
            }

            pressedKeys.clear();
        });


        // 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
©twily.info 2013 - 2025
twily at twily dot info



2 388 249 visits
... ^ v