发帖
 找回密码
 立即注册
搜索
5 0 0
教程文档 386 5 2025-6-13 17:19:25

upload 附件:模拟太阳系.html

93ab83a988778183b21bb00f7215dfba1d687a89.jpeg

八大行星轨道参数

行星 公转周期 (年) 半长轴 (AU) 离心率 e 轨道倾角 i (°) 近日点距离 q (AU) 远日点距离 Q (AU) 升交点经度 Ω (°) 近日点参数 ω (°) 平近点角 M (°)
水星 (Mercury) 0.241 (≈88天) 0.3871 0.2056 7.005 0.3075 0.4667 48.33 29.12 174.79
金星 (Venus) 0.615 (≈225天) 0.7233 0.0068 3.3947 0.7184 0.7282 76.68 54.85 50.45
地球 (Earth) 1.000 (365天) 1.0000 0.0167 0.0001 0.9833 1.0167 348.74 114.21 357.52
火星 (Mars) 1.881 (≈687天) 1.5237 0.0934 1.8506 1.3820 1.6660 49.58 286.46 19.41
木星 (Jupiter) 11.862 (≈4333天) 5.2034 0.0484 1.3053 4.9504 5.4563 100.56 274.20 19.65
土星 (Saturn) 29.457 (≈10747天) 9.5371 0.0542 2.4845 9.0215 10.0527 113.72 338.72 317.51
天王星 (Uranus) 84.011 (≈30589天) 19.1913 0.0472 0.7699 18.3757 20.0069 74.23 96.73 142.27
海王星 (Neptune) 164.79 (≈59800天) 30.0690 0.0086 1.7692 29.8109 30.3271 131.72 273.25 259.91

矮行星轨道参数

天体 公转周期 (年) 半长轴 (AU) 离心率 e 轨道倾角 i (°) 近日点距离 q (AU) 远日点距离 Q (AU) 升交点经度 Ω (°) 近日点参数 ω (°) 平近点角 M (°)
谷神星 (Ceres) 4.60 2.7668 0.07954 10.5864 2.552 AU 2.981 AU 80.407 72.965 301.655
冥王星 (Pluto) 247.94 39.4451 0.25025 17.0890 29.58 AU 49.31 AU 110.377 112.597 25.247
阋神星 (Eris) ~558 67.840 0.43747 44.0790 38.16 AU 97.52 AU 35.9276 151.57 198.490
鸟神星 (Makemake) ~306 45.426 0.1610 28.999° 38.13 AU 52.73 AU 79.572 295.154 151.598
妊神星 (Haumea) ~283 43.133 0.1950 28.22 34.71 AU 51.55 AU 122.103 239.184 202.675

典型天然卫星轨道参数

卫星 (母星) 公转周期 (天) 半长轴 a (km) 离心率 e 轨道倾角 i (°) 近地点距离 (km) 远地点距离 (km)
月球 (地球) 27.3217 384,400 0.0549 5.145 363,300 405,500
木卫一 Io (木星) 1.769 421,800 0.004 0.04 420,000 424,000
木卫二 Europa (木星) 3.551 670,900 0.009 0.47 664,000 678,000
木卫三 Ganymede (木星) 7.155 1,070,000 0.001 0.18 1,068,000 1,072,000
木卫四 Callisto (木星) 16.689 1,883,000 0.007 0.19 1,870,000 1,896,000
土卫六 Titan (土星) 15.945 1,222,000 0.029 0.33 1,186,000 1,258,000
海卫一 Triton (海王星) 5.877 355,000 ~0.000 157.3 355,000 355,000

典型小行星轨道参数

小行星 公转周期 (年) 半长轴 (AU) 离心率 e 轨道倾角 i (°) 近日点距离 q (AU) 远日点距离 Q (AU) 升交点经度 Ω (°) 近日点参数 ω (°) 平近点角 M (°)
灶神星 Vesta 3.63 2.362 0.0889 7.14 2.15 2.57 103.71 151.66 169.35
智神星 Pallas 4.61 2.772 0.2310 34.84 2.13 AU 3.41 AU
爱神星 Eros 1.76 1.458 0.2227 10.83 1.13 AU 1.78 AU

代表性周期彗星轨道参数

最后列出几颗著名的周期彗星的轨道数据,包括长周期的哈雷彗星 (1P/Halley) 和短周期的恩克彗星 (2P/Encke)。表中给出轨道周期、半长轴、离心率、轨道倾角(相对黄道面)、近日/远日距离,以及(如有)升交点经度、近日点参数和平近点角。

彗星 公转周期 (年) 半长轴 (AU) 离心率 e 轨道倾角 i (°) 近日点距离 q (AU) 远日点距离 Q (AU) Ω (°) ω (°) M (°)
1P/哈雷彗星 76.1 17.94 0.967 162.3 0.587 35.30 59.11 112.26 274.14
2P/恩克彗星 3.30 2.21 0.847 11.8 0.340 4.09 334.56 186.56 341.43

生成的HTML👇:

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <title>3D 太阳系模拟 (Three.js - 多星环 - 优化版+全屏+亮度+粗线)</title>
     <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #030308; 
            color: white;
            font-family: sans-serif;
            user-select: none;
        }

        canvas {
            display: block;
        }
  
         .panel {
             background: rgba(15, 15, 35, 0.75); 
             padding: 8px 12px;
             border-radius: 5px;
             user-select: none;
             pointer-events: auto; 
             box-sizing: border-box;
             cursor: default; 
         }

        #info {
           position: absolute; 
           top: 10px;
           left: 10px;
           padding: 10px 15px;
            background: rgba(10, 10, 20, 0.65);
        }
  
        #top-right-container {
           position: absolute;
            top: 10px;
            right: 10px;
            display: flex;
            flex-direction: column; 
            align-items: flex-end; 
            gap: 8px;
            pointer-events: none; 
         }
          #top-right-container > * {
            pointer-events: auto; 
          }


         #instructions {
             background: rgba(10, 10, 20, 0.5);
            padding: 5px 10px;
            font-size: 12px;
        }

        #bottom-wrapper {
           position: absolute;
           bottom: 0;
           left: 0;
           right: 0;
           display: flex;
           justify-content: space-between; 
           align-items: flex-end;
           padding: 10px; 
           gap: 20px; 
           pointer-events: none; /* Wrapper none, children auto */
            box-sizing: border-box;
        }
  
         #planet-selector {
            padding: 5px;
            display: flex; 
            flex-wrap: wrap; 
             justify-content: flex-start;
            gap: 4px; 
            flex-shrink: 1; 
             max-width: 70%;
            max-height: 90px; 
            overflow-y: auto; 
         }
          .planet-button {
             background: #223;
             color: #ccc;
             border: 1px solid #445;
             border-left: 4px solid #556; /* Color indicator */
             padding: 3px 8px;
             cursor: pointer;
             border-radius: 3px;
             font-size: 11px;
             transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;;
             white-space: nowrap;
          }
           .planet-button.active {
              background: #446;
              color: white;
              border-color: #779;
               font-weight: bold;
           }
          .planet-button:hover:not(.active) {
             background: #335;
             color: white;
          }

        #controls-panel {
             padding: 8px;
             flex-shrink: 0; 
             white-space: nowrap; 
             text-align: right;
        }
  
        .ui-button {
             background: #335; 
            color: white;
            border: 1px solid #668;
            padding: 5px 10px;
            cursor: pointer;
            border-radius: 3px;
             transition: background-color 0.2s ease;
             font-size: 12px; 
             font-family: sans-serif; 
              pointer-events: auto; 
        }
         .ui-button:hover {
           background: #447;
         }
          .ui-button.active {
             background: #558;
             border-color: #88b;
         }
       #controls-panel button.ui-button {
           padding: 5px 10px; 
        }


        #controls-panel label {
            margin-left: 8px;
             font-size: 13px;
        }

        #controls-panel input[type=range],
         #controls-panel input[type=checkbox] {
            vertical-align: middle;
            cursor: pointer;
        }

        .label {
            color: #FFF;
            font-size: 11px;
            background: rgba(15, 15, 35, 0.65);
            padding: 2px 4px;
            border-radius: 3px;
            text-shadow: 1px 1px 2px #000;
            pointer-events: none;
            user-select: none;
            white-space: nowrap;
             transition: opacity 0.3s ease; 
        }
         .label-hidden {
            opacity: 0;
         }

    </style>
    <!-- Import maps polyfill -->
    <script async src="https://ga.jspm.io/npm:es-module-shims@1.6.3/dist/es-module-shims.js"></script>
    <!-- Use CDN for Three.js -->
    <script type="importmap">
      {
         "imports": {
            "three": "https://unpkg.com/three@0.158.0/build/three.module.js",
            "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/"
         }
      }
   </script>
</head>

<body>
     <div id="info" class="panel" style="pointer-events: auto;">点击天体或底部按钮进行聚焦</div>
  
      <div id="top-right-container">
           <div id="instructions" class="panel">
               鼠标左键:旋转 | 鼠标滚轮:缩放 | 鼠标右键:平移<br>
               点击天体/按钮:镜头跟随 | 点击重置:返回太阳中心
           </div>
           <button id="fullscreenButton" class="ui-button panel" style="padding: 6px 12px;">进入全屏</button>
      </div>

    <div id="bottom-wrapper"> 
         <div id="planet-selector" class="panel">
            <!-- Buttons generated by JS -->
         </div>

        <div id="controls-panel" class="panel">
            <button id="resetButton" class="ui-button">重置视角 (太阳)</button>
            <label for="timeScaleSlider">速度:</label>
            <input type="range" id="timeScaleSlider" min="0.01" max="2.5" step="0.01" value="0.15">
            <label for="sizeScaleSlider">大小:</label>
            <input type="range" id="sizeScaleSlider" min="0.1" max="3" step="0.05" value="1">
            <label for="labelToggle">标签:</label>
            <input type="checkbox" id="labelToggle" checked>
        </div>
   </div>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
         // +++ 优化: 线条粗细 - 导入所需模块
        import { Line2 } from 'three/addons/lines/Line2.js';
    import { LineMaterial } from 'three/addons/lines/LineMaterial.js';
    import { LineGeometry } from 'three/addons/lines/LineGeometry.js';
        // +++ 结束

        // --- 数据定义 ---
        const distanceScale = 15; // 1 AU = 15 units
        let timeScale = 0.15; 
        let sizeScaleFactor = 1.0; // For slider
        const orbitSegments = 128;
        const basePlanetSize = 0.4;
        const minDisplaySize = 0.18; 
        let showLabels = true;
        const glowTexture = new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABZ0RVh0Q3JlYXRpb24gVGltZQAwOC8xMy8xOG+3j68AAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzbovLKMAAAE50lEQVR4nO2b224bNxCGs/n/v+j3g9/XViB4cZImxQGkQ2q7aKCoUaBA+a2I7GZ3lW/RVb7I3vRmWbZJgKJAkZR1kmb7Ab5FY8+8McyMzzE9b+Y/03w2i6IohQIQAC7/g65vLgAABAEAAAEA+P/o+gYAAAQBAAABAAD+P7q+AQAECZ4/f+7u7o5+v39w+zEAAAgCABAAgABAAABCfV2d8+fPq9Xq9ftHIaCqGgBgYwQAQgYAAAQAEKrn5/v7a29vn16vX7z/qBNEEQEArI0IQMgAAAABAIAiAQAAAwAQCAEgAIAAAAIACADQ6/Xz8/P1en15f78fAAD+f3R9AwCAIAAAIAAQAAAQAEAgAEAAAIcOABAEAAAEAPh/dP0AAIAgAAAgAIAAAIAAAIAgAAAgAIAAAIAAQAAAf/0H3dDiXyFm5l+qyv/Q9Q0AAAQBAAABAAD+P7q+AQAECR48eFA+nw+Hw7G+vh4Oh8Pj4+Pz8/Pg8Xh8/vz5Xt/j4+PDw8Plcvn4+Pjh4eHz8/Pz8/PxeDy+vr5+e3s7HA6Hqqu1Wq1WqxWPx4/FYnl/f//x8fHz8/P39/eHh4fn5+fX19fn5+cfHx+fn5/Pz8+1Wu37+/vX19fF4/Hz8/PxePz7+/v19fXp6enr6+vDw8PPz8+lUun39/f9/f3v7+/n5+f39/fn5+cvLy/Pz89PT0+Px+Pr6+v9/f3f399fX1/f39+fn5+fn5/f3t5+fn5+fn7+8fHxw8PD29vb4+Pj8/Pz5+fnr6+v39/ff39/f39/f39/f39/Px6P39/fn5+fX19f12q1fr//9fX19fX15eXlYrFYEAQfHx8/Pz9/f3+/318sFr+//37/Y8eP/09EfL9/cnKy2Wxm/gP432Xy+fzk5OTw8LB+vz85Ocmv9+l0Ojs7Ozk5OTo6Ojo6+vT0dH9/v9vt/vF4zM/P+/3+mZmZ+fl5f3//9PR0eXm5Xq+/f//+4uLi4uLix48fX15e/vjx47dv3769vf3p06f379+/ePHizs7Ozs7O29vb3759+/3796+vrx8cHNy/f39xcfHp6enFxUU+n+/v7//+/fuLFy/u7u7u7u7+9evXv//+vb29vbu7+/r6eun+0tLSo0ePnjx58uHDh0+fPn348OHPP//84sWLq6urq6urP3/+fHBw8P79+5eXlycnJ6enp6empiYnJxcXF998883t7e3t7e33b9/euXPnzp07v/7664cPH/7xxx8fHh6+fPny+fPnz58/v7i4+OWXX3548+bN/v7+58+fv3r16ubm5ubm5ps3b968efPs2bNnz559+fLlt2/f3rt3b2dnZ3d39+eff/7y5csPHjz4+PHjBwcH19fX19fXP3369N27d3fv3r1z586dO3c+ffr0+fPnX758+e3bt7e3t7e3t7fu3bt79+4nT5588+bNN2/e/Ouvv/74448fP358fX399u3bW7du3bp169atW/v7+x88ePD48eMXL15cX1//+vXrgwcPnjx58uHDh/f39+/evbu7u7u7u3v37t179+7t7e3t7e179+7t7e19+vTp58+fP3/+fHd3d3d39/379/fv3z88PPz48eNHjx7d39+/c+fO3bt379y5c//+/fv37//4449fv379+PHj7du3b926dfPmzZs3b966detv4r/8f13fAAAAAlxRVh0WW91cl9OT1RfRVhQT1JUSU5HX1RISVNfRklMRT3G9sEAAAAASUVORK5CYII=');

        const celestialData = [
           { name: "太阳 (Sun)", period: 0, a: 0, e: 0, i: 0, Omega: 0, omega: 0, M: 0, displayRadius: 4.5, color: 0xFFEC8B, type: 'star'},
           { name: "水星 (Mercury)", period: 0.241, a: 0.3871, e: 0.2056, i: 7.005, Omega: 48.33, omega: 29.12, M: 174.79, displayRadius: basePlanetSize*0.38 , color: 0xbbbbbb, type: 'planet' }, 
           { name: "金星 (Venus)",   period: 0.615, a: 0.7233, e: 0.0068, i: 3.3947, Omega: 76.68, omega: 54.85, M: 50.45,  displayRadius: basePlanetSize*0.9 , color: 0xFFDAB9, type: 'planet' },
           { name: "地球 (Earth)",   period: 1.000, a: 1.0000, e: 0.0167, i: 0.0001, Omega: 348.74,omega: 114.21,M: 357.52, displayRadius: basePlanetSize*1.0 , color: 0x4682B4, type: 'planet' },
           { name: "火星 (Mars)",    period: 1.881, a: 1.5237, e: 0.0934, i: 1.8506, Omega: 49.58, omega: 286.46,M: 19.41,  displayRadius: basePlanetSize*0.6 , color: 0xEE6C5C, type: 'planet' }, 
           { name: "木星 (Jupiter)", period: 11.862,a: 5.2034, e: 0.0484, i: 1.3053, Omega: 100.56,omega: 274.20,M: 19.65,  displayRadius: basePlanetSize*3.5 , color: 0xDEB887, type: 'planet', ring: { innerRadius: 1.2, outerRadius: 1.6, opacity: 0.2, tilt: 0 } },
           { name: "土星 (Saturn)",  period: 29.457,a: 9.5371, e: 0.0542, i: 2.4845, Omega: 113.72,omega: 338.72,M: 317.51, displayRadius: basePlanetSize*3.0 , color: 0xF0E68C, type: 'planet', ring: { innerRadius: 1.3, outerRadius: 2.2, opacity: 0.75, tilt: 0 } }, 
           { name: "天王星 (Uranus)", period: 84.011,a: 19.1913,e: 0.0472, i: 0.7699, Omega: 74.23, omega: 96.73, M: 142.27, displayRadius: basePlanetSize*2.0 , color: 0xAFEEEE, type: 'planet', ring: { innerRadius: 1.3, outerRadius: 1.8, opacity: 0.4, tilt: 98 } }, 
           { name: "海王星 (Neptune)",period: 164.79,a: 30.0690,e: 0.0086, i: 1.7692, Omega: 131.72,omega: 273.25,M: 259.91, displayRadius: basePlanetSize*1.9 , color: 0x4169E1, type: 'planet', ring: { innerRadius: 1.4, outerRadius: 1.9, opacity: 0.35, tilt: 0 }}, 
           { name: "谷神星 (Ceres)",  period: 4.60,  a: 2.7668, e: 0.07954, i: 10.5864, Omega: 80.407, omega: 72.965, M: 301.655, displayRadius: basePlanetSize*0.25, color: 0xaaaaaa, type: 'dwarf'}, 
           { name: "冥王星 (Pluto)",  period: 247.94,a: 39.4451, e: 0.25025, i: 17.0890, Omega: 110.377,omega: 112.597,M: 25.247, displayRadius: basePlanetSize*0.3, color: 0xccaa99, type: 'dwarf'}, 
           { name: "阋神星 (Eris)",   period: 558,   a: 67.840, e: 0.43747, i: 44.0790, Omega: 35.9276,omega: 151.57, M: 198.490, displayRadius: basePlanetSize*0.28, color: 0xbbbbbb, type: 'dwarf'}, 
           { name: "鸟神星 (Makemake)",period: 306,  a: 45.426, e: 0.1610,  i: 28.999,  Omega: 79.572, omega: 295.154,M: 151.598, displayRadius: basePlanetSize*0.26, color: 0xaa8a8a, type: 'dwarf'},
           { name: "妊神星 (Haumea)", period: 283,   a: 43.133, e: 0.1950,  i: 28.22,   Omega: 122.103,omega: 239.184,M: 202.675, displayRadius: basePlanetSize*0.27, color: 0xba9a9a, type: 'dwarf'},
           { name: "哈雷彗星 (Halley)",period: 76.1, a: 17.94, e: 0.967, i: 162.3, Omega: 59.11, omega: 112.26, M: 274.14, displayRadius: basePlanetSize * 0.2, color: 0x00FFFF, type: 'comet'},
           { name: "恩克彗星 (Encke)", period: 3.30, a: 2.21,  e: 0.847, i: 11.8,  Omega: 334.56,omega: 186.56, M: 341.43, displayRadius: basePlanetSize * 0.15,  color: 0xAAFFFF, type: 'comet'},
        ];

        // --- Three.js Setup ---
        let scene, camera, renderer, controls, clock;
        let labelRenderer;
        let raycaster, pointer;
        let sunMesh;
        let sunGlowSprite; 
        const celestialMeshes = [];
        const celestialObjects = []; 
        let currentTargetMesh = null;
        let isFollowing = false;
        const infoDiv = document.getElementById('info');
        const resetButton = document.getElementById('resetButton');
        const fullscreenButton = document.getElementById('fullscreenButton'); 
        const timeScaleSlider = document.getElementById('timeScaleSlider');
        const sizeScaleSlider = document.getElementById('sizeScaleSlider');
        const labelToggle = document.getElementById('labelToggle');
        const planetSelectorDiv = document.getElementById('planet-selector'); 
        const controlsPanelDiv = document.getElementById('controls-panel');
        const topRightContainer = document.getElementById('top-right-container');
        const tempTargetVector = new THREE.Vector3();

       // +++ 优化: 线条粗细 - 定义线条宽度和新的材质
        const LINE_WIDTH = 2.0; // 设置线条宽度 (单位: 像素)
        // 使用 LineMaterial 替换 LineBasicMaterial / LineDashedMaterial
        const orbitMaterial = new LineMaterial({ color: 0x555566, linewidth: LINE_WIDTH, transparent: true, opacity: 0.7, vertexColors: false });
        const dwarfOrbitMaterial = new LineMaterial({ color: 0x665555, linewidth: LINE_WIDTH * 0.9, transparent: true, opacity: 0.6, vertexColors: false });
         // 虚线材质
        const cometOrbitMaterial = new LineMaterial({ 
             color: 0x557788, 
             linewidth: LINE_WIDTH * 1.1, 
             transparent: true, 
             opacity: 0.85, // 0.8
             vertexColors: false,
             dashed: true,     // 开启虚线
             dashSize: 6,      // 调整虚线参数
             gapSize: 4,
             dashScale: 1    // 调整比例
         });
         // +++ 结束

        const degToRad = THREE.MathUtils.degToRad; 
        const backgroundColor = new THREE.Color(0x030308); 

        init();
        // +++ 优化: 线条粗细 - 初始设置所有材质分辨率
        updateLineMaterialResolution(window.innerWidth, window.innerHeight);
        animate();

       // +++ 优化: 线条粗细 - 更新材质分辨率函数
       function updateLineMaterialResolution(width, height){
           orbitMaterial.resolution.set(width, height);
           dwarfOrbitMaterial.resolution.set(width, height);
           cometOrbitMaterial.resolution.set(width, height);
           // 还需要更新场景中已存在的线条(克隆的材质)
           celestialObjects.forEach(obj => {
              if(obj.orbitLine && obj.orbitLine.material && obj.orbitLine.material.resolution){
                   obj.orbitLine.material.resolution.set(width, height);
              }
           });
       }
        // +++ 结束


        function init() {
            scene = new THREE.Scene();
             scene.background = backgroundColor; 
            scene.fog = new THREE.FogExp2( backgroundColor , 0.00007 );

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 80000); 
            camera.position.set(0, 80, 150);

            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(window.devicePixelRatio);
            document.body.appendChild(renderer.domElement);

            labelRenderer = new CSS2DRenderer();
            labelRenderer.setSize(window.innerWidth, window.innerHeight);
            labelRenderer.domElement.style.position = 'absolute';
            labelRenderer.domElement.style.top = '0px';
            labelRenderer.domElement.style.left = '0px';
            labelRenderer.domElement.style.pointerEvents = 'none';
            document.body.appendChild(labelRenderer.domElement);

            controls = new OrbitControls(camera, renderer.domElement);
            controls.enableDamping = true;
            controls.dampingFactor = 0.08;
            controls.minDistance = 5;
            controls.maxDistance = 40000; 
            controls.target.set(0, 0, 0);
            controls.update();

            const ambientLight = new THREE.AmbientLight(0x555566, 3.2); 
            scene.add(ambientLight);
            const sunLight = new THREE.PointLight(0xffffee, 2.2, 0, 2); 
            scene.add(sunLight);

            clock = new THREE.Clock();
            raycaster = new THREE.Raycaster();
            pointer = new THREE.Vector2();
   
            createStars(); 
            createSolarSystem();
            createPlanetSelector(); 

            setTarget(sunMesh, false); 

            // Event Listeners
            window.addEventListener('resize', onWindowResize, false);
            renderer.domElement.addEventListener('pointerup', onPointerUp); 
            let dragStartPos = new THREE.Vector2();
   
             renderer.domElement.addEventListener('pointerdown', (event) => {
                dragStartPos.set(event.clientX, event.clientY);
                 if (event.target.closest('.panel') || event.target.closest('#top-right-container')) {
                      controls.enabled = false;
                 } else {
                       controls.enabled = true;
                 }
             });
              window.addEventListener('pointerup', () => {
                   controls.enabled = true; 
              });

            resetButton.addEventListener('click', () => setTarget(sunMesh, false));
             setupFullscreen();

            timeScaleSlider.addEventListener('input', (e) => { timeScale = parseFloat(e.target.value); });
            sizeScaleSlider.addEventListener('input', (e) => {
                sizeScaleFactor = parseFloat(e.target.value);
                celestialObjects.forEach(obj => {
                    if (obj.mesh && obj.data) {
                        const baseRadius = obj.data.displayRadius || minDisplaySize;
                        const newScale = Math.max(minDisplaySize, baseRadius * sizeScaleFactor);
                         if(obj.data.type === 'star' && sunGlowSprite){
                             obj.mesh.scale.set(baseRadius * sizeScaleFactor, baseRadius * sizeScaleFactor, baseRadius * sizeScaleFactor);
                              sunGlowSprite.scale.set( baseRadius * 3.2 * sizeScaleFactor, baseRadius* 3.2 * sizeScaleFactor, 1.0 ); 
                         } else {
                             obj.mesh.scale.set(newScale, newScale, newScale);
                         }
                    }
                });
                if(currentTargetMesh) setTarget(currentTargetMesh, isFollowing, true); 
            });
            labelToggle.addEventListener('change', (e) => {
                showLabels = e.target.checked;
                celestialObjects.forEach(obj => {
                    if (obj.label && obj.label.element) {
                        updateLabelVisibility(obj, camera.position.distanceTo(obj.mesh.getWorldPosition(tempTargetVector)));
                     }
                });
            });
        }
   
        function setupFullscreen() {
           if (!fullscreenButton) return;
            const docEl = document.documentElement;
             if (!(docEl.requestFullscreen || docEl.webkitRequestFullscreen || docEl.mozRequestFullScreen || docEl.msRequestFullscreen)) {
                 fullscreenButton.style.display = 'none'; 
                 console.warn("Fullscreen API not supported");
                 return;
             }
           const updateButtonState = () => {
              if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {
                  fullscreenButton.textContent = '退出全屏';
                  fullscreenButton.classList.add('active');
               } else {
                   fullscreenButton.textContent = '进入全屏';
                    fullscreenButton.classList.remove('active');
               }
               setTimeout(onWindowResize, 100); 
           };
           fullscreenButton.addEventListener('click', () => {
              if (!document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) {
                   try {
                       if (docEl.requestFullscreen) docEl.requestFullscreen();
                       else if (docEl.webkitRequestFullscreen) docEl.webkitRequestFullscreen();
                       else if (docEl.mozRequestFullScreen) docEl.mozRequestFullScreen();
                       else if (docEl.msRequestFullscreen)  docEl.msRequestFullscreen();
                   } catch(err) { console.error(`Error enabling full-screen: ${err.message}`) }
              } else {
                  try {
                      if (document.exitFullscreen) document.exitFullscreen();
                       else if (document.webkitExitFullscreen) document.webkitExitFullscreen();
                       else if (document.mozCancelFullScreen) document.mozCancelFullScreen();
                       else if (document.msExitFullscreen)  document.msExitFullscreen();
                   } catch(err) { console.error(`Error exiting full-screen: ${err.message}`) }
              }
           });
           document.addEventListener('fullscreenchange', updateButtonState);
           document.addEventListener('webkitfullscreenchange', updateButtonState); 
           document.addEventListener('mozfullscreenchange', updateButtonState);   
           document.addEventListener('MSFullscreenChange', updateButtonState);   
           updateButtonState(); 
        }

        function createStars() {
             const geometry = new THREE.BufferGeometry();
             const vertices = [];
             const count = 12000; 
             const range = 40000; 
             for (let i = 0; i < count; i++) {
                  const theta = Math.random() * Math.PI * 2;
                  const phi = Math.acos((Math.random() * 2) - 1);
                  const radius = range * Math.cbrt(Math.random()) * 0.8 + range * 0.2 ; 

                  vertices.push(
                       radius * Math.sin(phi) * Math.cos(theta),
                       radius * Math.sin(phi) * Math.sin(theta),
                       radius * Math.cos(phi)
                  );
             }
             geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
             const material = new THREE.PointsMaterial({ 
                 color: 0xeeeeee,   
                 size: 1.8,   
                 sizeAttenuation: true,
                 transparent: true,
                 opacity: 0.95,  
                 fog: false 
              });
             const stars = new THREE.Points(geometry, material);
             scene.add(stars);
        }


        function createPlanetSelector() {
             if(!planetSelectorDiv) return;
             planetSelectorDiv.innerHTML = ''; // Clear existing

             celestialObjects.forEach( obj => {
                 if(!obj.mesh || !obj.data) return;

                 const button = document.createElement('button');
                 button.className = 'planet-button';
                 button.textContent = obj.data.type === 'star' ? obj.data.name : obj.data.name.split('(')[0].trim();
         
                 const hexColor = new THREE.Color(obj.data.color).getHexString();
                 button.style.borderLeftColor = `#${hexColor}`;

                 button.addEventListener('click', () =>{
                      const meshToTarget = obj.mesh;
                      const follow = meshToTarget !== sunMesh;
                      setTarget(meshToTarget, follow);
                 });
                 planetSelectorDiv.appendChild(button);
                 obj.button = button; 
             });
        }
  
        function createSolarSystem() {
  
            celestialData.forEach(data => {
                const baseRadiusValue = data.displayRadius || minDisplaySize;
                const radius = Math.max(minDisplaySize, baseRadiusValue * sizeScaleFactor);
                const geometry = new THREE.SphereGeometry(1, 32, 16); 
                let material;
                let mesh;
                let ringMesh = null;
                let label = null;

                if (data.type === 'star') {
                    material = new THREE.MeshBasicMaterial({ color: data.color });
                    mesh = new THREE.Mesh(geometry, material);
                    mesh.position.set(0, 0, 0);
                     mesh.scale.set(baseRadiusValue * sizeScaleFactor, baseRadiusValue * sizeScaleFactor, baseRadiusValue * sizeScaleFactor);
                    mesh.userData = data;
                    scene.add(mesh);
                    sunMesh = mesh;
                    celestialMeshes.push(mesh);
            
                    const spriteMaterial = new THREE.SpriteMaterial( { 
                         map: glowTexture, color: 0xfff5e0, transparent: true, blending: THREE.AdditiveBlending, opacity: 0.9, depthWrite: false 
                    } );
                  sunGlowSprite = new THREE.Sprite( spriteMaterial );
                  sunGlowSprite.scale.set( baseRadiusValue * 3.2 * sizeScaleFactor, baseRadiusValue * 3.2 * sizeScaleFactor, 1.0 ); 
                  mesh.add( sunGlowSprite ); 

                } else {
                    material = new THREE.MeshStandardMaterial({
                        color: data.color, roughness: 0.8, metalness: 0.1, emissive: data.color, emissiveIntensity: 0.25 
                    });
                    mesh = new THREE.Mesh(geometry, material);
                    mesh.scale.set(radius, radius, radius);
                    mesh.userData = data;
                    celestialMeshes.push(mesh);

                    if (data.ring) { 
                        const ringParams = data.ring;
                        const ringGeo = new THREE.RingGeometry(ringParams.innerRadius, ringParams.outerRadius, 64);
                        const ringMat = new THREE.MeshStandardMaterial({
                            color: data.color, side: THREE.DoubleSide, transparent: true, opacity: ringParams.opacity || 0.5, 
                            emissive: data.color, emissiveIntensity: 0.18, roughness: 0.6 
                        });
                        ringMesh = new THREE.Mesh(ringGeo, ringMat);
                        ringMesh.rotation.x = -Math.PI / 2 + degToRad(ringParams.tilt || 0); 
                         if (ringParams.tilt > 80 && ringParams.tilt < 100) ringMesh.rotation.y = degToRad(25);
                        mesh.add(ringMesh);
                    }
                }

                const labelDiv = document.createElement('div');
                labelDiv.className = 'label';
                labelDiv.textContent = data.name.split('(')[0].trim(); 
                label = new CSS2DObject(labelDiv);
                label.position.set(0, 1.5, 0); 
                label.center.set(0.5, 1); 
                labelDiv.classList.add('label-hidden');
                mesh.add(label);
        
                const bodyObject = { mesh: mesh, ringMesh: ringMesh, label: label, data: data,
                    curve: null, orbitGroup: null, orbitLine: null, button: null 
                };
                celestialObjects.push(bodyObject);

                if (data.type === 'star') return; 

                 const a = data.a * distanceScale;
                 const e = data.e;
                 const b = a * Math.sqrt(Math.max(0.001, 1 - e * e));
                 const center_x = - a * e;
                 const curve = new THREE.EllipseCurve( center_x, 0, Math.max(0.1,a), Math.max(0.1,b), 0, 2 * Math.PI, false, 0);
                 bodyObject.curve = curve; 

                 const nodeGroup = new THREE.Object3D();
                 nodeGroup.rotation.y = degToRad(data.Omega);
                 scene.add(nodeGroup);
                 const inclineGroup = new THREE.Object3D();
                 inclineGroup.rotation.x = degToRad(data.i);
                 nodeGroup.add(inclineGroup);
                 const orbitGroup = new THREE.Object3D();
                 orbitGroup.rotation.y = degToRad(data.omega);
                 inclineGroup.add(orbitGroup);
                 orbitGroup.add(mesh); 
                 bodyObject.orbitGroup = orbitGroup;

                 // +++ 优化: 线条粗细 - 绘制轨道线逻辑修改
                 if (a < 5000) { 
                      const pointCount = Math.min(1024, Math.max(64, Math.round(orbitSegments * Math.sqrt(Math.max(1, data.a)))));
                      const orbitPoints = curve.getPoints(pointCount);
                       // 确保闭合
                      orbitPoints.push(orbitPoints[0]); 
               
                      // 转换点数据为 [x, y, z, x, y, z, ...] 格式
                      const positions = [];
              orbitPoints.forEach( p => positions.push( p.x, 0, p.y ) ); // 注意:将2D曲线的y映射到3D的z

                      // 使用 LineGeometry
                      const orbitGeometry = new LineGeometry();
                      orbitGeometry.setPositions( positions );

                      let currentOrbitMaterial = orbitMaterial;
                      if (data.type === 'dwarf') currentOrbitMaterial = dwarfOrbitMaterial;
                      if (data.type === 'comet') currentOrbitMaterial = cometOrbitMaterial;
              
                      // 使用 Line2 和 克隆的材质
                      const clonedMaterial = currentOrbitMaterial.clone();
                       // 重要:为克隆的材质设置分辨率
                       clonedMaterial.resolution.set(window.innerWidth, window.innerHeight);

                      const orbitLine = new Line2(orbitGeometry, clonedMaterial); 
              
                      // 虚线必须计算距离
                      if (data.type === 'comet') orbitLine.computeLineDistances();

                      // orbitLine.rotation.x = Math.PI / 2; // <<< 移除! positions.push(p.x, 0, p.y) 已正确放置在XZ平面
                      orbitGroup.add(orbitLine); 
                      bodyObject.orbitLine = orbitLine;
                 } 
                 // +++ 结束
            });
        }

       let dragStartPos = new THREE.Vector2();
  
        function onPointerUp(event) { 
             const dragEndPos = new THREE.Vector2(event.clientX, event.clientY);
             if (event.target.closest('.panel') || event.target.closest('#top-right-container') || dragEndPos.distanceTo(dragStartPos) > 5) {
                 return; 
            }
  
            pointer.x = (event.clientX / renderer.domElement.clientWidth) * 2 - 1;
            pointer.y = - (event.clientY / renderer.domElement.clientHeight) * 2 + 1;
            raycaster.setFromCamera(pointer, camera);
            // +++ 优化: 线条粗细 - orbitLine 是 Line2, 不是 mesh, 不参与射线检测
            const intersects = raycaster.intersectObjects(celestialMeshes, true); 

            if (intersects.length > 0) {
                 let selectedObject = intersects[0].object;
                 // 查找父级,直到找到 celestialMeshes 中的成员或 scene
                 while(selectedObject && selectedObject.parent && !celestialMeshes.includes(selectedObject) && selectedObject.parent !== scene){
                       // 额外检查,防止 selectedObject 变为空
                      if(!selectedObject.parent) break;
                      selectedObject = selectedObject.parent;
                 }

                if (selectedObject && selectedObject.userData && selectedObject.userData.type) {
                    if(selectedObject === currentTargetMesh) return;
                    const follow = selectedObject !== sunMesh;
                    setTarget(selectedObject, follow);
                }
            }
        }
   
        function setTarget(mesh, follow, updateDistanceOnly = false) {
            if (!mesh || !mesh.userData) return;
     
            const previousTargetMesh = currentTargetMesh;
            currentTargetMesh = mesh;
            isFollowing = follow;

             if (!updateDistanceOnly) { 
                infoDiv.textContent = `聚焦: ${mesh.userData.name}`;
                if (!isFollowing) {
                     // +++ 优化: 线条粗细 - 切换目标时如果不是跟踪,使用lerp平滑过渡到0,0,0
                    // controls.target.set(0, 0, 0); 
                     tempTargetVector.set(0,0,0);
                    infoDiv.textContent += ` (中心)`;
                }
                 const currentObj = celestialObjects.find(o => o.mesh === currentTargetMesh);
                 const previousObj = celestialObjects.find(o => o.mesh === previousTargetMesh);
                 if(previousObj && previousObj.button) previousObj.button.classList.remove('active');
                 if(currentObj && currentObj.button) currentObj.button.classList.add('active');
                 if(mesh === sunMesh) resetButton.classList.add('active');
                 else resetButton.classList.remove('active');
            }

             const scaledRadius = mesh.scale.x; 
             const effectiveRadius = mesh.userData.ring ? scaledRadius * mesh.userData.ring.outerRadius : scaledRadius;
             const minComfortDist = effectiveRadius * 2.8; 

              if(follow ) { 
                  controls.minDistance = Math.max(0.5, minComfortDist) ;
              } else {
                   controls.minDistance = Math.max(5, minComfortDist); 
              }
            controls.update();
        }

       function updateLabelVisibility(obj, dist) {
             if (!obj.label || !obj.label.element) return;
             const shouldBeVisible = showLabels && (dist < 2200 || obj.mesh === currentTargetMesh); 
             if (shouldBeVisible) {
                  obj.label.element.classList.remove('label-hidden');
                   obj.label.visible = true; 
             } else {
                 obj.label.element.classList.add('label-hidden');
             }
       }

        function onWindowResize() {
            const width = window.innerWidth;
            const height = window.innerHeight;
            camera.aspect = width / height;
            camera.updateProjectionMatrix();
            renderer.setSize(width, height);
            labelRenderer.setSize(width, height);
            // +++ 优化: 线条粗细 - resize时必须更新所有材质的分辨率
            updateLineMaterialResolution(width, height);
             // +++ 结束
        }

        function animate() {
            requestAnimationFrame(animate);

            const delta = clock.getDelta(); 
            const scaledDeltaTime = delta * timeScale;
            const elapsedTime = clock.getElapsedTime(); 

            celestialObjects.forEach(obj => {
                 const dist = camera.position.distanceTo(obj.mesh.getWorldPosition(new THREE.Vector3())); // use new vector to avoid conflict with lerp target

                 if (obj.mesh && obj.label) {
                     updateLabelVisibility(obj, dist);
                 }

                if (!obj.curve || !obj.mesh || obj.data.type === 'star') {
                    if(obj.data.type === 'star') obj.mesh.rotation.y += 0.05 * scaledDeltaTime;
                    return;
                };

                const data = obj.data;
                const period = Math.max(0.05, data.period); 
                const offset = degToRad(data.M) / (2 * Math.PI);
                const t = ((elapsedTime * timeScale / period) + offset) % 1.0;

                const point = obj.curve.getPointAt(t); 
                 // +++ 优化: 线条粗细 - 确保位置点与LineGeometry中的坐标映射一致 (x, 0, y)
                obj.mesh.position.set(point.x, 0, point.y); 
                obj.mesh.rotation.y += (0.5 / Math.max(1, Math.sqrt(data.period))) * scaledDeltaTime; 
        
                if (obj.orbitLine) {
                    const orbitVisibleDistance = 5000; 
                     const isTarget = obj.mesh === currentTargetMesh;
                    const shouldShowOrbit = (dist < orbitVisibleDistance || isTarget) && data.a * distanceScale < 8000;
                    obj.orbitLine.visible = shouldShowOrbit;
                     // 确保材质存在且是 LineMaterial
                    if(shouldShowOrbit && obj.orbitLine.material && obj.orbitLine.material.isLineMaterial){
                         // 查找基础透明度
                         let baseMaterial = orbitMaterial;
                          if (data.type === 'dwarf') baseMaterial = dwarfOrbitMaterial;
                          if (data.type === 'comet') baseMaterial = cometOrbitMaterial;
                         const baseOpacity = baseMaterial.opacity;

                        if(!isTarget) {
                             obj.orbitLine.material.opacity = THREE.MathUtils.clamp( baseOpacity * (1 - dist / orbitVisibleDistance) * 2.5, 0.1 , baseOpacity) ;
                        } else {
                             obj.orbitLine.material.opacity = baseOpacity; // 恢复为基础材质的透明度
                        }
                     }
                }
            });

            // +++ 优化: 线条粗细 - 修改lerp逻辑
            if (currentTargetMesh ) {
                 if(isFollowing) {
                    currentTargetMesh.getWorldPosition(tempTargetVector);
                 }
                  // 无论是否following,都向 tempTargetVector (跟随目标或 0,0,0) 进行 lerp
                  controls.target.lerp(tempTargetVector, 0.07); 
            }
     
            controls.update();
            renderer.render(scene, camera);
            labelRenderer.render(scene, camera);
        }

    </script>

</body>
</html>
──── 0人觉得很赞 ────

使用道具 举报

2025-6-13 17:30:00
是谷歌那个kingfall吗
2025-6-13 17:30:58
FineRIk 发表于 2025-6-13 17:30
是谷歌那个kingfall吗

包是的呀
2025-6-14 15:23:19
数据看不懂
ouyang2008
2025-6-18 16:59
这些都是星球的数据 是固定的 看不看得懂不重要  详情 回复
2025-6-18 16:59:23

这些都是星球的数据 是固定的 看不看得懂不重要
2025-6-18 16:59:42
这种做出来还挺适合拿来课堂教学的
2025-6-21 18:00:04
数据
2025-6-27 19:30:05
2025-7-6 20:00:05
数据
数据
您需要登录后才可以回帖 立即登录
高级模式