Full Code of GhostCatcg/3d-earth for AI

main a931e5789c42 cached
29 files
51.2 KB
17.6k tokens
44 symbols
1 requests
Download .txt
Repository: GhostCatcg/3d-earth
Branch: main
Commit: a931e5789c42
Files: 29
Total size: 51.2 KB

Directory structure:
gitextract_nl3i_g8w/

├── .commitlintrc.js
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierrc.js
├── LICENSE
├── README.en.md
├── README.md
├── package.json
├── src/
│   ├── index.html
│   ├── shaders/
│   │   └── earth/
│   │       ├── fragment.fs
│   │       └── vertex.vs
│   └── ts/
│       ├── Utils/
│       │   ├── Sizes.ts
│       │   ├── arc.ts
│       │   └── common.ts
│       ├── global.d.ts
│       ├── index.ts
│       ├── interfaces/
│       │   ├── IEvents.ts
│       │   └── IWord.ts
│       └── world/
│           ├── Assets.ts
│           ├── Basic.ts
│           ├── Data.ts
│           ├── Earth.ts
│           ├── Resources.ts
│           └── Word.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .commitlintrc.js
================================================
module.exports = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "type-enum": [
      2,
      "always",
      [
        "feat", // 功能
        "fix", // bug
        "test", // 测试
        "perf", // 优化
        "refactor", // 重构
        "docs", // 文档
        "chore", // 辅助工具配置
        "style", // 格式 (适合lint fix...)
        "revert", // 回滚
        "merge", // 合并
        "sync", // 同步(同步主线或分支上的fix修复等)
      ],
    ],
    "type-case": [2, "always", "lower-case"],
    "type-empty": [2, "never"],
    "scope-empty": [0],
    "scope-case": [0],
    "subject-full-stop": [0, "never"],
    "subject-case": [0, "never"],
    "header-max-length": [0, "always", 72],
  },
};


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  "plugins": ['@typescript-eslint'],
  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
  rules: {
    eqeqeq: 0, // 必须使用全等
    'no-unused-vars': 1, // 不能有声明后未被使用的变量或参数
    'no-throw-literal': 0, // 0可以/2不可以 抛出字面量错误 throw "error";
    'no-sparse-arrays': 2, // 数组中不允许出现空位置
    'no-empty': 0, // 禁止出现空语句块
    'no-console': ['error', { allow: ['warn', 'error', 'info', "log"] }],
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-useless-escape': 0,
    "@typescript-eslint/no-explicit-any": "off",
    "no-async-promise-executor": 0
  },
}


================================================
FILE: .gitattributes
================================================
*.js linguist-detectable=false


================================================
FILE: .gitignore
================================================
.vscode
node_modules
dist
build

================================================
FILE: .husky/pre-commit
================================================
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged



================================================
FILE: .prettierrc.js
================================================
export default {
  "printWidth": 80,
  "semi": false,
  "singleQuote": true,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "spaceBeforeFunctionParen": true
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 GhostCat

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.en.md
================================================
# Webpack 5 + Typescript 4 + Three.js 基础模板

- Webpack 5
- Typescript 4
- Three.js 130
- lodash

================================================
FILE: README.md
================================================
# 3d-earth
本项目使用 [three-ts-webpack](https://github.com/GhostCatcg/three-ts-webpack) 构建

[Live Demo](https://gcat.cc/demo/earth)

![alt](./3d-earth.png)
## Todolist
1. - [x] 加载效果[loading...]
2. - [x] 地球、以及星空背景🌏
3. - [x] 辉光以及大气层✨
4. - [x] 地球标点以及城市标签🇨🇳
5. - [x] 卫星环绕旋转🛰
6. - [x] 国家/城市之前的飞线🪐
7. - [ ] 飞机沿飞线飞行🛫

================================================
FILE: package.json
================================================
{
  "name": "GhostCat",
  "version": "1.0.0",
  "description": "3d-earth",
  "keywords": [
    "GhostCat",
    "3d",
    "threejs",
    "typescript"
  ],
  "author": "GhostCat",
  "license": "MIT",
  "scripts": {
    "dev": "webpack serve --mode development",
    "build": "webpack --mode production",
    "lint": "eslint",
    "prepare": "husky install"
  },
  "lint-staged": {
    "src/*.{js,jsx,tsx,ts}": "eslint --fix"
  },
  "homepage": "https://gcat.cc",
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "@commitlint/config-conventional": "^15.0.0",
    "@size-limit/preset-small-lib": "^7.0.3",
    "@types/lodash": "^4.14.172",
    "@types/three": "^0.131.0",
    "@typescript-eslint/eslint-plugin": "^5.13.0",
    "@typescript-eslint/parser": "^5.13.0",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^3.0.0",
    "copy-webpack-plugin": "^9.0.1",
    "core-js": "^3.8.1",
    "css-loader": "^6.2.0",
    "eslint": "^8.10.0",
    "html-webpack-plugin": "^4.5.0",
    "husky": "^7.0.0",
    "lint-staged": "^12.1.2",
    "style-loader": "^3.2.1",
    "ts-loader": "^8.0.12",
    "ts-shader-loader": "^1.0.6",
    "typescript": "^4.1.3",
    "webpack": "^5.11.0",
    "webpack-cli": "^4.2.0",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "@tweakpane/core": "^1.0.6",
    "eslint-webpack-plugin": "^3.1.1",
    "gsap": "^3.7.1",
    "html2canvas": "^1.4.1",
    "lodash": "^4.17.21",
    "pietile-eventemitter": "^1.0.1",
    "three": "^0.143"
  }
}


================================================
FILE: src/index.html
================================================
<!DOCTYPE html>
<html lang="zh">
  <html>

<head>
	<title>GhostCat</title>
  <meta charset="utf-8">
</head>
<style>
  html,body{
    height:100%;
    width: 100%;
    padding:0;
    margin:0;
  }
  body{
    overflow: hidden;
    position: relative;
  }
  h1,h2{
    position: absolute;
    color:#fff;
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    padding-top:20vh;
    pointer-events: none;
  }
  h2{
    pointer-events: all;
    padding-top:25vh;
  }
  h2 a{
    color:rgb(19, 22, 226);
    font-size: 30px;
  }

  #earth-canvas{
    height: 100%;
    width: 100%;
    background: #010826;
  }
  
  #html2canvas {
    position: absolute;
    z-index: -1;
    left: 0;
    top:0;
    background: rgba(0, 0, 0, 0);
  }
  .css3d-wapper {
    pointer-events: none;
    color: #fff;
  }
  
  .css3d-wapper .fire-div {
    font-size: 20px;
    font-weight: 600;
    border-top: 3px solid #0cd1eb;
    padding: 6px 8px;
    min-width: 50px;
    background: rgba(40, 108, 181, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
  }
  #loading{
    position:absolute;
    left:0;
    top:0;
    right:0;
    bottom: 0;
    width: 100vw;
    height:100vh;
    z-index: 999;
    background:#010826;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    color:#409EFF;
    font-size: 15px;
    letter-spacing: 2px;
    overflow: hidden;
  }
  @keyframes zoomOut {
    0%{
      opacity:1
    }
    50%{
      opacity:0;
      transform:scale3d(1.3,1.3,1.3)
    }
    to{
      opacity:0
    }
  }
  #loading.out{
    animation:zoomOut 0.5s  linear forwards;
    pointer-events: none;
  }
  #loading.out .sk-chase-dot,
  #loading.out .sk-chase{
    animation: null;
  }
  .sk-chase {
    margin-bottom: 20px;
    width: 40px;
    height: 40px;
    position: relative;
    animation: sk-chase 2.5s infinite linear both;
  }

  .sk-chase-dot {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    animation: sk-chase-dot 2s infinite ease-in-out both;
  }

  .sk-chase-dot::before {
    content: '';
    display: block;
    width: 20%;
    height: 20%;
    background-color: #409EFF;
    border-radius: 100%;
    animation: sk-chase-dot-before 2s infinite ease-in-out both;
  }
  .sk-chase-dot:nth-child(1) {
    animation-delay: -1.1s;
  }
  .sk-chase-dot:nth-child(2) {
    animation-delay: -1s;
  }
  .sk-chase-dot:nth-child(3) {
    animation-delay: -0.9s;
  }
  .sk-chase-dot:nth-child(4) {
    animation-delay: -0.8s;
  }
  .sk-chase-dot:nth-child(5) {
    animation-delay: -0.7s;
  }
  .sk-chase-dot:nth-child(6) {
    animation-delay: -0.6s;
  }
  .sk-chase-dot:nth-child(1):before {
    animation-delay: -1.1s;
  }
  .sk-chase-dot:nth-child(2):before {
    animation-delay: -1s;
  }
  .sk-chase-dot:nth-child(3):before {
    animation-delay: -0.9s;
  }
  .sk-chase-dot:nth-child(4):before {
    animation-delay: -0.8s;
  }
  .sk-chase-dot:nth-child(5):before {
    animation-delay: -0.7s;
  }
  .sk-chase-dot:nth-child(6):before {
    animation-delay: -0.6s;
  }

  .sk-chase-dot .sk-chase-dot:nth-child(2) {
    animation-delay: -1s;
  }
  .sk-chase-dot:nth-child(3) {
    animation-delay: -0.9s;
  }
  .sk-chase-dot:nth-child(4) {
    animation-delay: -0.8s;
  }
  .sk-chase-dot:nth-child(5) {
    animation-delay: -0.7s;
  }
  .sk-chase-dot:nth-child(6) {
    animation-delay: -0.6s;
  }
  .sk-chase-dot:nth-child(1):before {
    animation-delay: -1.1s;
  }
  .sk-chase-dot:nth-child(2):before {
    animation-delay: -1s;
  }
  .sk-chase-dot:nth-child(3):before {
    animation-delay: -0.9s;
  }
  .sk-chase-dot:nth-child(4):before {
    animation-delay: -0.8s;
  }
  .sk-chase-dot:nth-child(5):before {
    animation-delay: -0.7s;
  }
  .sk-chase-dot:nth-child(6):before {
    animation-delay: -0.6s;
  }

  @keyframes sk-chase {
    100% {
      transform: rotate(360deg);
    }
  }

  @keyframes sk-chase-dot {
    80%,
    100% {
      transform: rotate(360deg);
    }
  }

  @keyframes sk-chase-dot-before {
    50% {
      transform: scale(0.4);
    }
    100%,
    0% {
      transform: scale(1);
    }
  }
</style>

<body>
  <div id="loading">
    <div class="sk-chase">
      <div class="sk-chase-dot"></div>
      <div class="sk-chase-dot"></div>
      <div class="sk-chase-dot"></div>
      <div class="sk-chase-dot"></div>
      <div class="sk-chase-dot"></div>
      <div class="sk-chase-dot"></div>
    </div>
    <div>加载资源中...</div>
  </div>
  <div id="html2canvas" class="css3d-wapper">
    <div class="fire-div"></div>
  </div>
  <div id="earth-canvas"></div>
</body>

</html>

================================================
FILE: src/shaders/earth/fragment.fs
================================================
uniform vec3 glowColor;
uniform float bias;
uniform float power;
uniform float time;
varying vec3 vp;
varying vec3 vNormal;
varying vec3 vPositionNormal;
uniform float scale;
// 获取纹理
uniform sampler2D map;
// 纹理坐标
varying vec2 vUv;

void main(void){
  float a = pow( bias + scale * abs(dot(vNormal, vPositionNormal)), power );
  if(vp.y > time && vp.y < time + 20.0) {
    float t =  smoothstep(0.0, 0.8,  (1.0 - abs(0.5 - (vp.y - time) / 20.0)) / 3.0  );
    gl_FragColor = mix(gl_FragColor, vec4(glowColor, 1.0), t * t );
  }
  gl_FragColor = mix(gl_FragColor, vec4( glowColor, 1.0 ), a);
  float b = 0.8;
  gl_FragColor = gl_FragColor + texture2D( map, vUv );
}

================================================
FILE: src/shaders/earth/vertex.vs
================================================

varying vec2 vUv;
varying vec3 vNormal;
varying vec3 vp;
varying vec3 vPositionNormal;
void main(void){
  vUv = uv;
  vNormal = normalize( normalMatrix * normal ); // 转换到视图空间
  vp = position;
  vPositionNormal = normalize(( modelViewMatrix * vec4(position, 1.0) ).xyz);
  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}

================================================
FILE: src/ts/Utils/Sizes.ts
================================================
/**
 * 屏幕尺寸
*/
import { EventEmitter } from 'pietile-eventemitter';
import { IEvents } from '../interfaces/IEvents';


type options = { dom: HTMLElement }

export default class Sizes {
  public width: number
  public height: number
  public viewport: {
    width: number,
    height: number
  }
  public $sizeViewport: HTMLElement
  public emitter: EventEmitter<IEvents>;

  /**
   * Constructor
   */
  constructor(options: options) {

    this.emitter = new EventEmitter<IEvents>()

    // Viewport size
    this.$sizeViewport = options.dom

    this.viewport = {
      width: 0,
      height: 0
    }

    // Resize event
    this.resize = this.resize.bind(this)
    window.addEventListener('resize', this.resize)

    this.resize()
  }

  /**
   * 目前用于监听历史记录执行 historyChange
   * @param event 事件
   * @param fun 执行
   */
  $on<T extends keyof IEvents>(event: T, fun: () => void) {
    this.emitter.on(
      event,
      () => {
        fun()
      }
    )
  }

  /**
   * Resize
   */
  resize() {
    // 可视区域大小
    this.viewport.width = this.$sizeViewport.offsetWidth
    this.viewport.height = this.$sizeViewport.offsetHeight

    this.emitter.emit('resize')
  }
}


================================================
FILE: src/ts/Utils/arc.ts
================================================

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { ArcCurve, BufferAttribute, BufferGeometry,
Color, Line, LineBasicMaterial, Points, PointsMaterial, 
Quaternion, Vector3 } from 'three';
import { lon2xyz } from './common';

/*
 * 绘制一条圆弧飞线
 * 5个参数含义:( 飞线圆弧轨迹半径, 开始角度, 结束角度)
 */
function createFlyLine(radius, startAngle, endAngle,color) {
  const geometry = new BufferGeometry(); //声明一个几何体对象BufferGeometry
  //  ArcCurve创建圆弧曲线
  const arc = new ArcCurve(0, 0, radius, startAngle, endAngle, false);
  //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
  const pointsArr = arc.getSpacedPoints(100); //分段数80,返回81个顶点
  geometry.setFromPoints(pointsArr); // setFromPoints方法从pointsArr中提取数据改变几何体的顶点属性vertices
  // 每个顶点对应一个百分比数据attributes.percent 用于控制点的渲染大小
  const percentArr = []; //attributes.percent的数据
  for (let i = 0; i < pointsArr.length; i++) {
    percentArr.push(i / pointsArr.length);
  }
  const percentAttribue = new BufferAttribute(
    new Float32Array(percentArr),
    1
  );
  // 通过顶点数据percent点模型从大到小变化,产生小蝌蚪形状飞线
  geometry.attributes.percent = percentAttribue;
  // 批量计算所有顶点颜色数据
  const colorArr = [];
  for (let i = 0; i < pointsArr.length; i++) {
    const color1 = new Color(0xec8f43); //轨迹线颜色 青色
    const color2 = new Color(0xf3ae76); //黄色
    const color = color1.lerp(color2, i / pointsArr.length);
    colorArr.push(color.r, color.g, color.b);
  }
  // 设置几何体顶点颜色数据
  geometry.attributes.color = new BufferAttribute(
    new Float32Array(colorArr),
    3
  );
  const size = 1.3;
  // 点模型渲染几何体每个顶点
  const material = new PointsMaterial({
    size, //点大小
    // vertexColors: VertexColors, //使用顶点颜色渲染
    transparent: true,
    depthWrite: false,
  });
  // 修改点材质的着色器源码(注意:不同版本细节可能会稍微会有区别,不过整体思路是一样的)
  material.onBeforeCompile = function (shader) {
    // 顶点着色器中声明一个attribute变量:百分比
    shader.vertexShader = shader.vertexShader.replace(
      "void main() {",
      [
        "attribute float percent;", //顶点大小百分比变量,控制点渲染大小
        "void main() {",
      ].join("\n") // .join()把数组元素合成字符串
    );
    // 调整点渲染大小计算方式
    shader.vertexShader = shader.vertexShader.replace(
      "gl_PointSize = size;",
      ["gl_PointSize = percent * size;"].join("\n") // .join()把数组元素合成字符串
    );
  };
  const FlyLine = new Points(geometry, material);
  material.color = new Color(color)
  FlyLine.name = "飞行线";

  return FlyLine;
}


/**输入地球上任意两点的经纬度坐标,通过函数flyArc可以绘制一个飞线圆弧轨迹
 * lon1,lat1:轨迹线起点经纬度坐标
 * lon2,lat2:轨迹线结束点经纬度坐标
 */
function flyArc(radius, lon1, lat1, lon2, lat2,options) {
  const sphereCoord1 = lon2xyz(radius, lon1, lat1); //经纬度坐标转球面坐标
  // startSphereCoord:轨迹线起点球面坐标
  const startSphereCoord = new Vector3(sphereCoord1.x, sphereCoord1.y, sphereCoord1.z);
  const sphereCoord2 = lon2xyz(radius, lon2, lat2);
  // startSphereCoord:轨迹线结束点球面坐标
  const endSphereCoord = new Vector3(sphereCoord2.x, sphereCoord2.y, sphereCoord2.z);

  //计算绘制圆弧需要的关于y轴对称的起点、结束点和旋转四元数
  const startEndQua = _3Dto2D(startSphereCoord, endSphereCoord)
  // 调用arcXOY函数绘制一条圆弧飞线轨迹
  const arcline = arcXOY(radius, startEndQua.startPoint, startEndQua.endPoint,options);
  arcline.quaternion.multiply(startEndQua.quaternion)
  return arcline;
}
/*
* 把3D球面上任意的两个飞线起点和结束点绕球心旋转到到XOY平面上,
* 同时保持关于y轴对称,借助旋转得到的新起点和新结束点绘制
* 一个圆弧,最后把绘制的圆弧反向旋转到原来的起点和结束点即可
*/
function _3Dto2D(startSphere, endSphere) {
  /*计算第一次旋转的四元数:表示从一个平面如何旋转到另一个平面*/
  const origin = new Vector3(0, 0, 0); //球心坐标
  const startDir = startSphere.clone().sub(origin); //飞线起点与球心构成方向向量
  const endDir = endSphere.clone().sub(origin); //飞线结束点与球心构成方向向量
  // dir1和dir2构成一个三角形,.cross()叉乘计算该三角形法线normal
  const normal = startDir.clone().cross(endDir).normalize();
  const xoyNormal = new Vector3(0, 0, 1); //XOY平面的法线
  //.setFromUnitVectors()计算从normal向量旋转达到xoyNormal向量所需要的四元数
  // quaternion表示把球面飞线旋转到XOY平面上需要的四元数
  const quaternion3D_XOY = new Quaternion().setFromUnitVectors(normal, xoyNormal);
  /*第一次旋转:飞线起点、结束点从3D空间第一次旋转到XOY平面*/
  const startSphereXOY = startSphere.clone().applyQuaternion(quaternion3D_XOY);
  const endSphereXOY = endSphere.clone().applyQuaternion(quaternion3D_XOY);

  /*计算第二次旋转的四元数*/
  // middleV3:startSphereXOY和endSphereXOY的中点
  const middleV3 = startSphereXOY.clone().add(endSphereXOY).multiplyScalar(0.5);
  const midDir = middleV3.clone().sub(origin).normalize(); // 旋转前向量midDir,中点middleV3和球心构成的方向向量
  const yDir = new Vector3(0, 1, 0); // 旋转后向量yDir,即y轴
  // .setFromUnitVectors()计算从midDir向量旋转达到yDir向量所需要的四元数
  // quaternion2表示让第一次旋转到XOY平面的起点和结束点关于y轴对称需要的四元数
  const quaternionXOY_Y = new Quaternion().setFromUnitVectors(midDir, yDir);

  /*第二次旋转:使旋转到XOY平面的点再次旋转,实现关于Y轴对称*/
  const startSpherXOY_Y = startSphereXOY.clone().applyQuaternion(quaternionXOY_Y);
  const endSphereXOY_Y = endSphereXOY.clone().applyQuaternion(quaternionXOY_Y);

  /**一个四元数表示一个旋转过程
   *.invert()方法表示四元数的逆,简单说就是把旋转过程倒过来
   * 两次旋转的四元数执行.invert()求逆,然后执行.multiply()相乘
   *新版本.invert()对应旧版本.invert()
   */
  const quaternionInverse = quaternion3D_XOY.clone().invert().multiply(quaternionXOY_Y.clone().invert())
  return {
    // 返回两次旋转四元数的逆四元数
    quaternion: quaternionInverse,
    // 范围两次旋转后在XOY平面上关于y轴对称的圆弧起点和结束点坐标
    startPoint: startSpherXOY_Y,
    endPoint: endSphereXOY_Y,
  }
}
/**通过函数arcXOY()可以在XOY平面上绘制一个关于y轴对称的圆弧曲线
 * startPoint, endPoint:表示圆弧曲线的起点和结束点坐标值,起点和结束点关于y轴对称
 * 同时在圆弧轨迹的基础上绘制一段飞线*/
function arcXOY(radius,startPoint, endPoint,options) {
  // 计算两点的中点
  const middleV3 = new Vector3().addVectors(startPoint, endPoint).multiplyScalar(0.5);
  // 弦垂线的方向dir(弦的中点和圆心构成的向量)
  const dir = middleV3.clone().normalize()
  // 计算球面飞线的起点、结束点和球心构成夹角的弧度值
  const earthRadianAngle = radianAOB(startPoint, endPoint, new Vector3(0, 0, 0))
  /*设置飞线轨迹圆弧的中间点坐标
  弧度值 * radius * 0.2:表示飞线轨迹圆弧顶部距离地球球面的距离
  起点、结束点相聚越远,构成的弧线顶部距离球面越高*/
  const arcTopCoord = dir.multiplyScalar(radius + earthRadianAngle * radius * 0.2) // 黄色飞行线的高度
  //求三个点的外接圆圆心(飞线圆弧轨迹的圆心坐标)
  const flyArcCenter = threePointCenter(startPoint, endPoint, arcTopCoord)
  // 飞线圆弧轨迹半径flyArcR
  const flyArcR = Math.abs(flyArcCenter.y - arcTopCoord.y);
  /*坐标原点和飞线起点构成直线和y轴负半轴夹角弧度值
  参数分别是:飞线圆弧起点、y轴负半轴上一点、飞线圆弧圆心*/
  const flyRadianAngle = radianAOB(startPoint, new Vector3(0, -1, 0), flyArcCenter);
  const startAngle = -Math.PI / 2 + flyRadianAngle; //飞线圆弧开始角度
  const endAngle = Math.PI - startAngle; //飞线圆弧结束角度
  // 调用圆弧线模型的绘制函数
  const arcline = circleLine(flyArcCenter.x, flyArcCenter.y, flyArcR, startAngle, endAngle, options.color)
  // const arcline = new  Group();// 不绘制轨迹线,使用 Group替换circleLine()即可
  arcline.center = flyArcCenter; //飞线圆弧自定一个属性表示飞线圆弧的圆心
  arcline.topCoord = arcTopCoord; //飞线圆弧自定一个属性表示飞线圆弧中间也就是顶部坐标

  // const flyAngle = Math.PI/ 10; //飞线圆弧固定弧度
  const flyAngle = (endAngle - startAngle) / 7; //飞线圆弧的弧度和轨迹线弧度相关
  // 绘制一段飞线,圆心做坐标原点
  const flyLine = createFlyLine(flyArcR, startAngle, startAngle + flyAngle, options.flyLineColor);
  flyLine.position.y = flyArcCenter.y; //平移飞线圆弧和飞线轨迹圆弧重合
  //飞线段flyLine作为飞线轨迹arcLine子对象,继承飞线轨迹平移旋转等变换
  arcline.add(flyLine);
  //飞线段运动范围startAngle~flyEndAngle
  flyLine.flyEndAngle = endAngle - startAngle - flyAngle;
  flyLine.startAngle = startAngle;
  // arcline.flyEndAngle:飞线段当前角度位置,这里设置了一个随机值用于演示
  flyLine.AngleZ = arcline.flyEndAngle * Math.random();
  // flyLine.rotation.z = arcline.AngleZ;
  // arcline.flyLine指向飞线段,便于设置动画是访问飞线段
  arcline.userData['flyLine'] = flyLine;

  return arcline
}
/*计算球面上两点和球心构成夹角的弧度值
参数point1, point2:表示地球球面上两点坐标Vector3
计算A、B两点和顶点O构成的AOB夹角弧度值*/
function radianAOB(A, B, O) {
  // dir1、dir2:球面上两个点和球心构成的方向向量
  const dir1 = A.clone().sub(O).normalize();
  const dir2 = B.clone().sub(O).normalize();
  //点乘.dot()计算夹角余弦值
  const cosAngle = dir1.clone().dot(dir2);
  const radianAngle = Math.acos(cosAngle); //余弦值转夹角弧度值,通过余弦值可以计算夹角范围是0~180度
  return radianAngle
}
/*绘制一条圆弧曲线模型Line
5个参数含义:(圆心横坐标, 圆心纵坐标, 飞线圆弧轨迹半径, 开始角度, 结束角度)*/
function circleLine(x, y, r, startAngle, endAngle,color) {
  const geometry = new BufferGeometry(); //声明一个几何体对象Geometry
  //  ArcCurve创建圆弧曲线
  const arc = new ArcCurve(x, y, r, startAngle, endAngle, false);
  //getSpacedPoints是基类Curve的方法,返回一个vector2对象作为元素组成的数组
  const points = arc.getSpacedPoints(80); //分段数50,返回51个顶点
  geometry.setFromPoints(points); // setFromPoints方法从points中提取数据改变几何体的顶点属性vertices
  const material = new LineBasicMaterial({
    color:color || 0xd18547,
  }); //线条材质
  const line = new Line(geometry, material); //线条模型对象
  return line;
}
//求三个点的外接圆圆心,p1, p2, p3表示三个点的坐标Vector3。
function threePointCenter(p1, p2, p3) {
  const L1 = p1.lengthSq(); //p1到坐标原点距离的平方
  const L2 = p2.lengthSq();
  const L3 = p3.lengthSq();
  const x1 = p1.x,
    y1 = p1.y,
    x2 = p2.x,
    y2 = p2.y,
    x3 = p3.x,
    y3 = p3.y;
  const S = x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2;
  const x = (L2 * y3 + L1 * y2 + L3 * y1 - L2 * y1 - L3 * y2 - L1 * y3) / S / 2;
  const y = (L3 * x2 + L2 * x1 + L1 * x3 - L1 * x2 - L2 * x3 - L3 * x1) / S / 2;
  // 三点外接圆圆心坐标
  const center = new Vector3(x, y, 0);
  return center
}


export {
  arcXOY,
  flyArc
}


================================================
FILE: src/ts/Utils/common.ts
================================================
import { CatmullRomCurve3, DoubleSide, Group, Mesh, MeshBasicMaterial, PlaneBufferGeometry, Texture, TubeGeometry, Vector3 } from "three";
import { punctuation } from "../world/Earth";


/**
 * 经纬度坐标转球面坐标  
 * @param {地球半径} R  
 * @param {经度(角度值)} longitude 
 * @param {维度(角度值)} latitude
 */
export const lon2xyz = (R:number, longitude:number, latitude:number): Vector3 => {
  let lon = longitude * Math.PI / 180; // 转弧度值
  const lat = latitude * Math.PI / 180; // 转弧度值
  lon = -lon; // js坐标系z坐标轴对应经度-90度,而不是90度

  // 经纬度坐标转球面坐标计算公式
  const x = R * Math.cos(lat) * Math.cos(lon);
  const y = R * Math.sin(lat);
  const z = R * Math.cos(lat) * Math.sin(lon);
  // 返回球面坐标
  return new Vector3(x, y, z);
}

// 创建波动光圈
export const createWaveMesh = (options: { radius, lon, lat, textures }) => {
  const geometry = new PlaneBufferGeometry(1, 1); //默认在XOY平面上
  const texture = options.textures.aperture;

  const material = new MeshBasicMaterial({
    color: 0xe99f68,
    map: texture,
    transparent: true, //使用背景透明的png贴图,注意开启透明计算
    opacity: 1.0,
    depthWrite: false, //禁止写入深度缓冲区数据
  });
  const mesh = new Mesh(geometry, material);
  // 经纬度转球面坐标
  const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat);
  const size = options.radius * 0.12; //矩形平面Mesh的尺寸
  mesh.scale.set(size, size, size); //设置mesh大小
  mesh.userData['size'] = size; //自顶一个属性,表示mesh静态大小
  mesh.userData['scale'] = Math.random() * 1.0; //自定义属性._s表示mesh在原始大小基础上放大倍数  光圈在原来mesh.size基础上1~2倍之间变化
  mesh.position.set(coord.x, coord.y, coord.z);
  const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize();
  const meshNormal = new Vector3(0, 0, 1);
  mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);
  return mesh;
}

// 创建柱状
export const createLightPillar = (options: { radius: number, lon: number, lat: number, index: number, textures: Record<string, Texture>, punctuation: punctuation }) => {
  const height = options.radius * 0.3;
  const geometry = new PlaneBufferGeometry(options.radius * 0.05, height);
  geometry.rotateX(Math.PI / 2);
  geometry.translate(0, 0, height / 2);
  const material = new MeshBasicMaterial({
    map: options.textures.light_column,
    color:
      options.index == 0
        ? options.punctuation.lightColumn.startColor
        : options.punctuation.lightColumn.endColor,
    transparent: true,
    side: DoubleSide,
    depthWrite: false, //是否对深度缓冲区有任何的影响
  });
  const mesh = new Mesh(geometry, material);
  const group = new Group();
  // 两个光柱交叉叠加
  group.add(mesh, mesh.clone().rotateZ(Math.PI / 2)); //几何体绕x轴旋转了,所以mesh旋转轴变为z
  // 经纬度转球面坐标
  const SphereCoord = lon2xyz(options.radius, options.lon, options.lat); //SphereCoord球面坐标
  group.position.set(SphereCoord.x, SphereCoord.y, SphereCoord.z); //设置mesh位置
  const coordVec3 = new Vector3(
    SphereCoord.x,
    SphereCoord.y,
    SphereCoord.z
  ).normalize();
  const meshNormal = new Vector3(0, 0, 1);
  group.quaternion.setFromUnitVectors(meshNormal, coordVec3);
  return group;
}

// 光柱底座矩形平面
export const createPointMesh = (options: {
  radius: number, lon: number,
  lat: number, material: MeshBasicMaterial
}) => {

  const geometry = new PlaneBufferGeometry(1, 1); //默认在XOY平面上
  const mesh = new Mesh(geometry, options.material);
  // 经纬度转球面坐标
  const coord = lon2xyz(options.radius * 1.001, options.lon, options.lat);
  const size = options.radius * 0.05; // 矩形平面Mesh的尺寸
  mesh.scale.set(size, size, size); // 设置mesh大小

  // 设置mesh位置
  mesh.position.set(coord.x, coord.y, coord.z);
  const coordVec3 = new Vector3(coord.x, coord.y, coord.z).normalize();
  const meshNormal = new Vector3(0, 0, 1);
  mesh.quaternion.setFromUnitVectors(meshNormal, coordVec3);
  return mesh;

}

// 获取点
export const getCirclePoints = (option) => {
  const list = [];
  for (
    let j = 0;
    j < 2 * Math.PI - 0.1;
    j += (2 * Math.PI) / (option.number || 100)
  ) {
    list.push([
      parseFloat((Math.cos(j) * (option.radius || 10)).toFixed(2)),
      0,
      parseFloat((Math.sin(j) * (option.radius || 10)).toFixed(2)),
    ]);
  }
  if (option.closed) list.push(list[0]);
  return list;
}

// 创建线

/**
 * 创建动态的线
 */
export const createAnimateLine = (option) => {
  // 由多个点数组构成的曲线 通常用于道路
  const l = [];
  option.pointList.forEach((e) =>
    l.push(new Vector3(e[0], e[1], e[2]))
  );
  const curve = new CatmullRomCurve3(l); // 曲线路径

  // 管道体
  const tubeGeometry = new TubeGeometry(
    curve,
    option.number || 50,
    option.radius || 1,
    option.radialSegments
  );
  return new Mesh(tubeGeometry, option.material);
}

================================================
FILE: src/ts/global.d.ts
================================================
// Definitions to let TS understand .vs, .fs, .glsl shader files
/**
 * 声明着色器
 */

declare module '*.fs' {
	const value: string
	export default value
}
declare module '*.vs' {
	const value: string
	export default value
}
declare module '*.glsl' {
	const value: string
	export default value
}

================================================
FILE: src/ts/index.ts
================================================
import World  from './world/Word'

// earth-canvas
const dom: HTMLElement = document.querySelector('#earth-canvas')
new World({
  dom,
})

================================================
FILE: src/ts/interfaces/IEvents.ts
================================================

export interface IEvents {
  resize: () => void
}


================================================
FILE: src/ts/interfaces/IWord.ts
================================================
export interface IWord {
  dom: HTMLElement
}

================================================
FILE: src/ts/world/Assets.ts
================================================
/**
 * 资源文件
 * 把模型和图片分开进行加载
 */

interface ITextures {
  name: string
  url: string
}

export interface IResources {
  textures?: ITextures[],
}

const filePath = './images/earth/'
const fileSuffix = [
  'gradient',
  'redCircle',
  "label",
  "aperture",
  'glow',
  'light_column',
  'aircraft'
]

const textures = fileSuffix.map(item => {
  return {
    name: item,
    url: filePath + item + '.png'
  }
})

textures.push({
  name: 'earth',
  url: filePath + 'earth.jpg'
})

const resources: IResources = {
  textures
}


export {
  resources
}

================================================
FILE: src/ts/world/Basic.ts
================================================
/**
 * 创建 threejs 四大天王
 * 场景、相机、渲染器、控制器
 */

import * as THREE from 'three';
import {
  OrbitControls
} from "three/examples/jsm/controls/OrbitControls";

export class Basic {
  public scene: THREE.Scene;
  public camera: THREE.PerspectiveCamera;
  public renderer: THREE.WebGLRenderer
  public controls: OrbitControls;
  public dom: HTMLElement;

  constructor(dom: HTMLElement) {
    this.dom = dom
    this.initScenes()
    this.setControls()
  }

  /**
   * 初始化场景
   */
  initScenes() {
    this.scene = new THREE.Scene();

    this.camera = new THREE.PerspectiveCamera(
      45,
      window.innerWidth / window.innerHeight,
      1,
      100000
    );
    this.camera.position.set(0, 30, -250)


    this.renderer = new THREE.WebGLRenderer({
      alpha: true, // 透明
      antialias: true, // 抗锯齿
    });
    this.renderer.setPixelRatio(window.devicePixelRatio); // 设置屏幕像素比
    this.renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器宽高
    this.dom.appendChild(this.renderer.domElement); // 添加到dom中
  }

  /**
   * 设置控制器
   */
  setControls() {
    // 鼠标控制      相机,渲染dom
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    
    this.controls.autoRotateSpeed = 3
    // 使动画循环使用时阻尼或自转 意思是否有惯性
    this.controls.enableDamping = true;
    // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
    this.controls.dampingFactor = 0.05;
    // 是否可以缩放
    this.controls.enableZoom = true;
    // 设置相机距离原点的最远距离
    this.controls.minDistance = 100;
    // 设置相机距离原点的最远距离
    this.controls.maxDistance = 300;
    // 是否开启右键拖拽
    this.controls.enablePan = false;
  }
}

================================================
FILE: src/ts/world/Data.ts
================================================
export default [{
  startArray: {
    name: '杭州',
    N: 30.246026,
    E: 120.210792,
  },
  endArray: [{
      name: '曼谷',
      N: 22, //维度
      E: 100.49074172973633, //经度
    },
    {
      name: '澳大利亚',
      N: -23.68477416688374,
      E: 133.857421875,
    },

    {
      name: '新疆维吾尔自治区',
      N: 41.748,
      E: 84.9023,
    },

    {
      name: '德黑兰',
      N: 35,
      E: 51,
    },
    {
      name: '德黑兰',
      N: 35,
      E: 51,
    },
    {
      name: '美国',
      N: 34.125447565116126,
      E: 241.7431640625,
    },
    {
      name: '英国',
      N: 51.508742458803326,
      E: 359.82421875,
    },
    {
      name: '巴西',
      N:  -9.96885060854611,
      E: 668.1445312499999,
    },
  ]
},
{
  startArray: {
    name: '北京',
    N: 39.89491,
    E: 116.322056,
  },
  endArray: [{
      name: '西藏',
      N: 29.660361, //维度
      E: 91.132212 //经度
    },
    {
      name: '广西',
      N: 22.830824,
      E: 108.30616
    },

    {
      name: '江西',
      N: 28.676493,
      E: 115.892151
    },

    {
      name: '贵阳',
      N: 26.647661,
      E: 106.630153
    }
  ]
}

]


================================================
FILE: src/ts/world/Earth.ts
================================================
import {
  BufferAttribute, BufferGeometry, Color, DoubleSide, Group, Material, Mesh, MeshBasicMaterial, NormalBlending,
  Object3D,
  Points, PointsMaterial, ShaderMaterial,
  SphereBufferGeometry, Sprite, SpriteMaterial, Texture, TextureLoader, Vector3
} from "three";

import html2canvas from "html2canvas";

import earthVertex from '../../shaders/earth/vertex.vs';
import earthFragment from '../../shaders/earth/fragment.fs';
import { createAnimateLine, createLightPillar, createPointMesh, createWaveMesh, getCirclePoints, lon2xyz } from "../Utils/common";
import gsap from "gsap";
import { flyArc } from "../Utils/arc";

export type punctuation = {
  circleColor: number,
  lightColumn: {
    startColor: number, // 起点颜色
    endColor: number, // 终点颜色
  },
}

type options = {
  data: {
    startArray: {
      name: string,
      E: number, // 经度
      N: number, // 维度
    },
    endArray: {
      name: string,
      E: number, // 经度
      N: number, // 维度
    }[]
  }[]
  dom: HTMLElement,
  textures: Record<string, Texture>, // 贴图
  earth: {
    radius: number, // 地球半径
    rotateSpeed: number, // 地球旋转速度
    isRotation: boolean // 地球组是否自转
  }
  satellite: {
    show: boolean, // 是否显示卫星
    rotateSpeed: number, // 旋转速度
    size: number, // 卫星大小
    number: number, // 一个圆环几个球
  },
  punctuation: punctuation,
  flyLine: {
    color: number, // 飞线的颜色
    speed: number, // 飞机拖尾线速度
    flyLineColor: number // 飞行线的颜色
  },
}
type uniforms = {
  glowColor: { value: Color; }
  scale: { type: string; value: number; }
  bias: { type: string; value: number; }
  power: { type: string; value: number; }
  time: { type: string; value: any; }
  isHover: { value: boolean; };
  map: { value: Texture }
}

export default class earth {

  public group: Group;
  public earthGroup: Group;

  public around: BufferGeometry
  public aroundPoints: Points<BufferGeometry, PointsMaterial>;

  public options: options;
  public uniforms: uniforms
  public timeValue: number;

  public earth: Mesh<SphereBufferGeometry, ShaderMaterial>;
  public punctuationMaterial: MeshBasicMaterial;
  public markupPoint: Group;
  public waveMeshArr: Object3D[];

  public circleLineList: any[];
  public circleList: any[];
  public x: number;
  public n: number;
  public isRotation: boolean;
  public flyLineArcGroup: Group;

  constructor(options: options) {

    this.options = options;

    this.group = new Group()
    this.group.name = "group";
    this.group.scale.set(0, 0, 0)
    this.earthGroup = new Group()
    this.group.add(this.earthGroup)
    this.earthGroup.name = "EarthGroup";

    // 标注点效果
    this.markupPoint = new Group()
    this.markupPoint.name = "markupPoint"
    this.waveMeshArr = []

    // 卫星和标签
    this.circleLineList = []
    this.circleList = [];
    this.x = 0;
    this.n = 0;

    // 地球自转
    this.isRotation = this.options.earth.isRotation

    // 扫光动画 shader
    this.timeValue = 100
    this.uniforms = {
      glowColor: {
        value: new Color(0x0cd1eb),
      },
      scale: {
        type: "f",
        value: -1.0,
      },
      bias: {
        type: "f",
        value: 1.0,
      },
      power: {
        type: "f",
        value: 3.3,
      },
      time: {
        type: "f",
        value: this.timeValue,
      },
      isHover: {
        value: false,
      },
      map: {
        value: null,
      },
    };

  }

  async init(): Promise<void> {
    return new Promise(async (resolve) => {

      this.createEarth(); // 创建地球
      this.createStars(); // 添加星星
      this.createEarthGlow() // 创建地球辉光
      this.createEarthAperture() // 创建地球的大气层
      await this.createMarkupPoint() // 创建柱状点位
      await this.createSpriteLabel() // 创建标签
      this.createAnimateCircle() // 创建环绕卫星
      this.createFlyLine() // 创建飞线

      this.show()
      resolve()
    })
  }

  createEarth() {
    const earth_geometry = new SphereBufferGeometry(
      this.options.earth.radius,
      50,
      50
    );

    const earth_border = new SphereBufferGeometry(
      this.options.earth.radius + 10,
      60,
      60
    );

    const pointMaterial = new PointsMaterial({
      color: 0x81ffff, //设置颜色,默认 0xFFFFFF
      transparent: true,
      sizeAttenuation: true,
      opacity: 0.1,
      vertexColors: false, //定义材料是否使用顶点颜色,默认false ---如果该选项设置为true,则color属性失效
      size: 0.01, //定义粒子的大小。默认为1.0
    })
    const points = new Points(earth_border, pointMaterial); //将模型添加到场景

    this.earthGroup.add(points);

    this.uniforms.map.value = this.options.textures.earth;

    const earth_material = new ShaderMaterial({
      // wireframe:true, // 显示模型线条
      uniforms: this.uniforms,
      vertexShader: earthVertex,
      fragmentShader: earthFragment,
    });

    earth_material.needsUpdate = true;
    this.earth = new Mesh(earth_geometry, earth_material);
    this.earth.name = "earth";
    this.earthGroup.add(this.earth);

  }

  createStars() {

    const vertices = []
    const colors = []
    for (let i = 0; i < 500; i++) {
      const vertex = new Vector3();
      vertex.x = 800 * Math.random() - 300;
      vertex.y = 800 * Math.random() - 300;
      vertex.z = 800 * Math.random() - 300;
      vertices.push(vertex.x, vertex.y, vertex.z);
      colors.push(new Color(1, 1, 1));
    }

    // 星空效果
    this.around = new BufferGeometry()
    this.around.setAttribute("position", new BufferAttribute(new Float32Array(vertices), 3));
    this.around.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3));

    const aroundMaterial = new PointsMaterial({
      size: 2,
      sizeAttenuation: true, // 尺寸衰减
      color: 0x4d76cf,
      transparent: true,
      opacity: 1,
      map: this.options.textures.gradient,
    });

    this.aroundPoints = new Points(this.around, aroundMaterial);
    this.aroundPoints.name = "星空";
    this.aroundPoints.scale.set(1, 1, 1);
    this.group.add(this.aroundPoints);
  }

  createEarthGlow() {
    const R = this.options.earth.radius; //地球半径

    // TextureLoader创建一个纹理加载器对象,可以加载图片作为纹理贴图
    const texture = this.options.textures.glow; // 加载纹理贴图

    // 创建精灵材质对象SpriteMaterial
    const spriteMaterial = new SpriteMaterial({
      map: texture, // 设置精灵纹理贴图
      color: 0x4390d1,
      transparent: true, //开启透明
      opacity: 0.7, // 可以通过透明度整体调节光圈
      depthWrite: false, //禁止写入深度缓冲区数据
    });

    // 创建表示地球光圈的精灵模型
    const sprite = new Sprite(spriteMaterial);
    sprite.scale.set(R * 3.0, R * 3.0, 1); //适当缩放精灵
    this.earthGroup.add(sprite);
  }

  createEarthAperture() {

    const vertexShader = [
      "varying vec3	vVertexWorldPosition;",
      "varying vec3	vVertexNormal;",
      "varying vec4	vFragColor;",
      "void main(){",
      "	vVertexNormal	= normalize(normalMatrix * normal);", //将法线转换到视图坐标系中
      "	vVertexWorldPosition	= (modelMatrix * vec4(position, 1.0)).xyz;", //将顶点转换到世界坐标系中
      "	// set gl_Position",
      "	gl_Position	= projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
      "}",
    ].join("\n");

    //大气层效果
    const AeroSphere = {
      uniforms: {
        coeficient: {
          type: "f",
          value: 1.0,
        },
        power: {
          type: "f",
          value: 3,
        },
        glowColor: {
          type: "c",
          value: new Color(0x4390d1),
        },
      },
      vertexShader: vertexShader,
      fragmentShader: [
        "uniform vec3	glowColor;",
        "uniform float	coeficient;",
        "uniform float	power;",

        "varying vec3	vVertexNormal;",
        "varying vec3	vVertexWorldPosition;",

        "varying vec4	vFragColor;",

        "void main(){",
        "	vec3 worldCameraToVertex = vVertexWorldPosition - cameraPosition;", //世界坐标系中从相机位置到顶点位置的距离
        "	vec3 viewCameraToVertex	= (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;", //视图坐标系中从相机位置到顶点位置的距离
        "	viewCameraToVertex= normalize(viewCameraToVertex);", //规一化
        "	float intensity	= pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);",
        "	gl_FragColor = vec4(glowColor, intensity);",
        "}",
      ].join("\n"),
    };
    //球体 辉光 大气层
    const material1 = new ShaderMaterial({
      uniforms: AeroSphere.uniforms,
      vertexShader: AeroSphere.vertexShader,
      fragmentShader: AeroSphere.fragmentShader,
      blending: NormalBlending,
      transparent: true,
      depthWrite: false,
    });
    const sphere = new SphereBufferGeometry(
      this.options.earth.radius + 0,
      50,
      50
    );
    const mesh = new Mesh(sphere, material1);
    this.earthGroup.add(mesh);
  }

  async createMarkupPoint() {

    await Promise.all(this.options.data.map(async (item) => {

      const radius = this.options.earth.radius;
      const lon = item.startArray.E; //经度
      const lat = item.startArray.N; //纬度

      this.punctuationMaterial = new MeshBasicMaterial({
        color: this.options.punctuation.circleColor,
        map: this.options.textures.label,
        transparent: true, //使用背景透明的png贴图,注意开启透明计算
        depthWrite: false, //禁止写入深度缓冲区数据
      });

      const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }); //光柱底座矩形平面
      this.markupPoint.add(mesh);
      const LightPillar = createLightPillar({
        radius: this.options.earth.radius,
        lon,
        lat,
        index: 0,
        textures: this.options.textures,
        punctuation: this.options.punctuation,
      }); //光柱
      this.markupPoint.add(LightPillar);
      const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }); //波动光圈
      this.markupPoint.add(WaveMesh);
      this.waveMeshArr.push(WaveMesh);

      await Promise.all(item.endArray.map((obj) => {
        const lon = obj.E; //经度
        const lat = obj.N; //纬度
        const mesh = createPointMesh({ radius, lon, lat, material: this.punctuationMaterial }); //光柱底座矩形平面
        this.markupPoint.add(mesh);
        const LightPillar = createLightPillar({
          radius: this.options.earth.radius,
          lon,
          lat,
          index: 1,
          textures: this.options.textures,
          punctuation: this.options.punctuation
        }); //光柱
        this.markupPoint.add(LightPillar);
        const WaveMesh = createWaveMesh({ radius, lon, lat, textures: this.options.textures }); //波动光圈
        this.markupPoint.add(WaveMesh);
        this.waveMeshArr.push(WaveMesh);
      }))
      this.earthGroup.add(this.markupPoint)
    }))
  }

  async createSpriteLabel() {
    await Promise.all(this.options.data.map(async item => {
      let cityArry = [];
      cityArry.push(item.startArray);
      cityArry = cityArry.concat(...item.endArray);
      await Promise.all(cityArry.map(async e => {
        const p = lon2xyz(this.options.earth.radius * 1.001, e.E, e.N);
        const div = `<div class="fire-div">${e.name}</div>`;
        const shareContent = document.getElementById("html2canvas");
        shareContent.innerHTML = div;
        const opts = {
          backgroundColor: null, // 背景透明
          scale: 6,
          dpi: window.devicePixelRatio,
        };
        const canvas = await html2canvas(document.getElementById("html2canvas"), opts)
        const dataURL = canvas.toDataURL("image/png");
        const map = new TextureLoader().load(dataURL);
        const material = new SpriteMaterial({
          map: map,
          transparent: true,
        });
        const sprite = new Sprite(material);
        const len = 5 + (e.name.length - 2) * 2;
        sprite.scale.set(len, 3, 1);
        sprite.position.set(p.x * 1.1, p.y * 1.1, p.z * 1.1);
        this.earth.add(sprite);
      }))
    }))
  }

  createAnimateCircle() {
    // 创建 圆环 点
    const list = getCirclePoints({
      radius: this.options.earth.radius + 15,
      number: 150, //切割数
      closed: true, // 闭合
    });
    const mat = new MeshBasicMaterial({
      color: "#0c3172",
      transparent: true,
      opacity: 0.4,
      side: DoubleSide,
    });
    const line = createAnimateLine({
      pointList: list,
      material: mat,
      number: 100,
      radius: 0.1,
    });
    this.earthGroup.add(line);

    // 在clone两条线出来
    const l2 = line.clone();
    l2.scale.set(1.2, 1.2, 1.2);
    l2.rotateZ(Math.PI / 6);
    this.earthGroup.add(l2);

    const l3 = line.clone();
    l3.scale.set(0.8, 0.8, 0.8);
    l3.rotateZ(-Math.PI / 6);
    this.earthGroup.add(l3);

    /**
     * 旋转的球
     */
    const ball = new Mesh(
      new SphereBufferGeometry(this.options.satellite.size, 32, 32),
      new MeshBasicMaterial({
        color: "#e0b187", // 745F4D
      })
    );

    const ball2 = new Mesh(
      new SphereBufferGeometry(this.options.satellite.size, 32, 32),
      new MeshBasicMaterial({
        color: "#628fbb", // 324A62
      })
    );

    const ball3 = new Mesh(
      new SphereBufferGeometry(this.options.satellite.size, 32, 32),
      new MeshBasicMaterial({
        color: "#806bdf", //6D5AC4
      })
    );

    this.circleLineList.push(line, l2, l3);
    ball.name = ball2.name = ball3.name = "卫星";

    for (let i = 0; i < this.options.satellite.number; i++) {
      const ball01 = ball.clone();
      // 一根线上总共有几个球,根据数量平均分布一下
      const num = Math.floor(list.length / this.options.satellite.number)
      ball01.position.set(
        list[num * (i + 1)][0] * 1,
        list[num * (i + 1)][1] * 1,
        list[num * (i + 1)][2] * 1
      );
      line.add(ball01);

      const ball02 = ball2.clone();
      const num02 = Math.floor(list.length / this.options.satellite.number)
      ball02.position.set(
        list[num02 * (i + 1)][0] * 1,
        list[num02 * (i + 1)][1] * 1,
        list[num02 * (i + 1)][2] * 1
      );
      l2.add(ball02);

      const ball03 = ball2.clone();
      const num03 = Math.floor(list.length / this.options.satellite.number)
      ball03.position.set(
        list[num03 * (i + 1)][0] * 1,
        list[num03 * (i + 1)][1] * 1,
        list[num03 * (i + 1)][2] * 1
      );
      l3.add(ball03);
    }
  }

  createFlyLine() {

    this.flyLineArcGroup = new Group();
    this.flyLineArcGroup.userData['flyLineArray'] = []
    this.earthGroup.add(this.flyLineArcGroup)

    this.options.data.forEach((cities) => {
      cities.endArray.forEach(item => {

        // 调用函数flyArc绘制球面上任意两点之间飞线圆弧轨迹
        const arcline = flyArc(
          this.options.earth.radius,
          cities.startArray.E,
          cities.startArray.N,
          item.E,
          item.N,
          this.options.flyLine
        );

        this.flyLineArcGroup.add(arcline); // 飞线插入flyArcGroup中
        this.flyLineArcGroup.userData['flyLineArray'].push(arcline.userData['flyLine'])
      });

    })

  }

  show() {
    gsap.to(this.group.scale, {
      x: 1,
      y: 1,
      z: 1,
      duration: 2,
      ease: "Quadratic",
    })
  }

  render() {

    this.flyLineArcGroup?.userData['flyLineArray']?.forEach(fly => {
      fly.rotation.z += this.options.flyLine.speed; // 调节飞线速度
      if (fly.rotation.z >= fly.flyEndAngle) fly.rotation.z = 0;
    })

    if (this.isRotation) {
      this.earthGroup.rotation.y += this.options.earth.rotateSpeed;
    }

    this.circleLineList.forEach((e) => {
      e.rotateY(this.options.satellite.rotateSpeed);
    });

    this.uniforms.time.value =
      this.uniforms.time.value < -this.timeValue
        ? this.timeValue
        : this.uniforms.time.value - 1;

    if (this.waveMeshArr.length) {
      this.waveMeshArr.forEach((mesh: Mesh) => {
        mesh.userData['scale'] += 0.007;
        mesh.scale.set(
          mesh.userData['size'] * mesh.userData['scale'],
          mesh.userData['size'] * mesh.userData['scale'],
          mesh.userData['size'] * mesh.userData['scale']
        );
        if (mesh.userData['scale'] <= 1.5) {
          (mesh.material as Material).opacity = (mesh.userData['scale'] - 1) * 2; //2等于1/(1.5-1.0),保证透明度在0~1之间变化
        } else if (mesh.userData['scale'] > 1.5 && mesh.userData['scale'] <= 2) {
          (mesh.material as Material).opacity = 1 - (mesh.userData['scale'] - 1.5) * 2; //2等于1/(2.0-1.5) mesh缩放2倍对应0 缩放1.5被对应1
        } else {
          mesh.userData['scale'] = 1;
        }
      });
    }

  }

}

================================================
FILE: src/ts/world/Resources.ts
================================================
/**
 * 资源管理和加载
 */
import { LoadingManager, Texture, TextureLoader } from 'three';
import { resources } from './Assets'
export class Resources {
  private manager: LoadingManager
  private callback: () => void;
  private textureLoader!: InstanceType<typeof TextureLoader>;
  public textures: Record<string, Texture>;
  constructor(callback: () => void) {
    this.callback = callback // 资源加载完成的回调

    this.textures = {} // 贴图对象

    this.setLoadingManager()
    this.loadResources()
  }

  /**
   * 管理加载状态
   */
  private setLoadingManager() {

    this.manager = new LoadingManager()
    // 开始加载
    this.manager.onStart = () => {
      console.log('开始加载资源文件')
    }
    // 加载完成
    this.manager.onLoad = () => {
      this.callback()
    }
    // 正在进行中
    this.manager.onProgress = (url) => {
      console.log(`正在加载:${url}`)
    }

    this.manager.onError = url => {
      console.log('加载失败:' + url)
    }

  }

  /**
   * 加载资源
   */
  private loadResources(): void {
    this.textureLoader = new TextureLoader(this.manager)
    resources.textures?.forEach((item) => {
      this.textureLoader.load(item.url, (t) => {
        this.textures[item.name] = t
      })
    })
  }
}


================================================
FILE: src/ts/world/Word.ts
================================================
import {
  MeshBasicMaterial, PerspectiveCamera,
  Scene, ShaderMaterial, WebGLRenderer
} from "three";
import {
  OrbitControls
} from "three/examples/jsm/controls/OrbitControls";

// interfaces
import { IWord } from '../interfaces/IWord'

import { Basic } from './Basic'
import Sizes from '../Utils/Sizes'
import { Resources } from './Resources';

// earth 
import Earth from './Earth'
import Data from './Data'

export default class World {
  public basic: Basic;
  public scene: Scene;
  public camera: PerspectiveCamera;
  public renderer: WebGLRenderer
  public controls: OrbitControls;
  public sizes: Sizes;
  public material: ShaderMaterial | MeshBasicMaterial;
  public resources: Resources;
  public option: IWord;
  public earth: Earth;


  constructor(option: IWord) {
    /**
     * 加载资源
     */
    this.option = option

    this.basic = new Basic(option.dom)
    this.scene = this.basic.scene
    this.renderer = this.basic.renderer
    this.controls = this.basic.controls
    this.camera = this.basic.camera

    this.sizes = new Sizes({ dom: option.dom })

    this.sizes.$on('resize', () => {
      this.renderer.setSize(Number(this.sizes.viewport.width), Number(this.sizes.viewport.height))
      this.camera.aspect = Number(this.sizes.viewport.width) / Number(this.sizes.viewport.height)
      this.camera.updateProjectionMatrix()
    })

    this.resources = new Resources(async () => {
      await this.createEarth()
      // 开始渲染
      this.render()
    })
  }

  async createEarth() {

    // 资源加载完成,开始制作地球,注释在new Earth()类型里面
    this.earth = new Earth({
      data: Data,
      dom: this.option.dom,
      textures: this.resources.textures,
      earth: {
        radius: 50,
        rotateSpeed: 0.002,
        isRotation: true
      },
      satellite: {
        show: true,
        rotateSpeed: -0.01,
        size: 1,
        number: 2
      },
      punctuation: {
        circleColor: 0x3892ff,
        lightColumn: {
          startColor: 0xe4007f, // 起点颜色
          endColor: 0xffffff, // 终点颜色
        },
      },
      flyLine: {
        color: 0xf3ae76, // 飞线的颜色
        flyLineColor: 0xff7714, // 飞行线的颜色
        speed: 0.004, // 拖尾飞线的速度
      }
    })

    this.scene.add(this.earth.group)

    await this.earth.init()

    // 隐藏dom
    const loading = document.querySelector('#loading')
    loading.classList.add('out')

  }

  /**
   * 渲染函数
   */
  public render() {
    requestAnimationFrame(this.render.bind(this))
    this.renderer.render(this.scene, this.camera)
    this.controls && this.controls.update()
    this.earth && this.earth.render()
  }
}

================================================
FILE: tsconfig.json
================================================
{
  "include": [
      "src/ts/**/*"
  ],
  "compilerOptions": {
      "module": "ESNext",
      "target": "ESNext",
      "moduleResolution": "node",
      "declaration": true,
      "declarationDir": "build/types",
      "baseUrl": ".",
      "noImplicitAny": false
  }
}

================================================
FILE: tslint.json
================================================
{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {
		"one-line": false,
		"no-empty": false,
		"no-console": false,
		"indent": [true, "tabs"],
		"object-literal-shorthand": false,
        "quotemark": [true, "single"],
        "prefer-const": false,
        "eofline": false,
        "max-line-length": false,
        "no-trailing-whitespace": false,
        "ordered-imports": false,
        "import-spacing": false,
        "trailing-comma": false,
        "curly": false,
        "object-literal-sort-keys": false,
        "object-literal-key-quotes": false,
        "variable-name": false,
        "typedef": [
            true,
            "call-signature",
            "parameter",
            "property-declaration",
            "variable-declaration-ignore-function",
            "member-variable-declaration",
            "object-destructuring",
            "array-destructuring"
          ]
    },
    "linterOptions": {
        "exclude": [
            "build"
        ]
    },
    "rulesDirectory": []
}

================================================
FILE: webpack.config.js
================================================
const path = require('path')
// 引入html插件
const HTMLWebpackPlugin = require('html-webpack-plugin')
// 把整个目录copy过去
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 引入clean插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const ESLintPlugin = require('eslint-webpack-plugin')

// webpack中的所有的配置信息都应该写在module.exports中
module.exports = {
  devServer: {
    port: '8088'
  },
  // 指定入口文件
  entry: './src/ts/index.ts',
  // 指定打包文件所在目录
  output: {
    // 指定打包文件的目录
    path: path.resolve(__dirname, 'dist'),
    // 打包后文件的文件
    filename: 'bundle.js',
    libraryTarget: 'umd',
    // 告诉webpack不使用箭头
    // 默认打包后是一个立即执行的箭头函数,在IE 11中也是无法执行的!
    // 加上下面的配置,可以在webpack打包时,最外层不再是箭头函数
    // webpack新版本已经不想兼容IE了!233
    environment: {
      arrowFunction: false,
    },
  },
  // 指定webpack打包时要使用模块
  module: {
    // 指定要加载的规则
    rules: [
      {
        // test指定的是规则生效的文件
        test: /\.ts$/,
        // 要使用的loader
        // Webpack在加载时是"从后向前"加载!
        use: [
          // 配置babel
          {
            // 指定加载器
            loader: 'babel-loader',
            // 设置babel
            options: {
              // 设置预定义的环境
              presets: [
                [
                  // 指定环境的插件
                  '@babel/preset-env',
                  // 配置信息
                  {
                    // 要兼容的目标浏览器
                    targets: {
                      chrome: '58',
                      ie: '11',
                    },
                    // 指定corejs的版本
                    // package.json中的版本为3.8.1
                    corejs: '3',
                    // 使用corejs的方式,"usage" 表示按需加载
                    useBuiltIns: 'usage',
                  },
                ],
              ],
            },
          },
          'ts-loader',
        ],
        // 要排除的文件
        exclude: /node-modules/,
      },
      {
        test: /\.(css|scss|sass)$/i,
        use: ['style-loader', 'css-loader'],
      },
      // Shaders
      {
        test: /\.(glsl|vs|fs)$/,
        loader: 'ts-shader-loader',
      },
    ],
  },

  // 配置Webpack插件
  plugins: [
    // new CleanWebpackPlugin(),
    new HTMLWebpackPlugin({
      template: './src/index.html',
    }),
    // 把整个目录copy过去
    new CopyWebpackPlugin({
      patterns: [{ from: path.resolve(__dirname, './static') }],
    }),

    new ESLintPlugin({
      context: './src', // 检查目录
      extensions: ['js', 'jsx', 'ts', 'tsx'], // 文件扩展
    }),
  ],
  // 用来设置引用模块
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  // 包太大会提示你
  performance: {
    hints: false,
  },
}
Download .txt
gitextract_nl3i_g8w/

├── .commitlintrc.js
├── .eslintrc.js
├── .gitattributes
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierrc.js
├── LICENSE
├── README.en.md
├── README.md
├── package.json
├── src/
│   ├── index.html
│   ├── shaders/
│   │   └── earth/
│   │       ├── fragment.fs
│   │       └── vertex.vs
│   └── ts/
│       ├── Utils/
│       │   ├── Sizes.ts
│       │   ├── arc.ts
│       │   └── common.ts
│       ├── global.d.ts
│       ├── index.ts
│       ├── interfaces/
│       │   ├── IEvents.ts
│       │   └── IWord.ts
│       └── world/
│           ├── Assets.ts
│           ├── Basic.ts
│           ├── Data.ts
│           ├── Earth.ts
│           ├── Resources.ts
│           └── Word.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js
Download .txt
SYMBOL INDEX (44 symbols across 9 files)

FILE: src/ts/Utils/Sizes.ts
  type options (line 8) | type options = { dom: HTMLElement }
  class Sizes (line 10) | class Sizes {
    method constructor (line 23) | constructor(options: options) {
    method $on (line 47) | $on<T extends keyof IEvents>(event: T, fun: () => void) {
    method resize (line 59) | resize() {

FILE: src/ts/Utils/arc.ts
  function createFlyLine (line 13) | function createFlyLine(radius, startAngle, endAngle,color) {
  function flyArc (line 80) | function flyArc(radius, lon1, lat1, lon2, lat2,options) {
  function _3Dto2D (line 100) | function _3Dto2D(startSphere, endSphere) {
  function arcXOY (line 145) | function arcXOY(radius,startPoint, endPoint,options) {
  function radianAOB (line 192) | function radianAOB(A, B, O) {
  function circleLine (line 203) | function circleLine(x, y, r, startAngle, endAngle,color) {
  function threePointCenter (line 217) | function threePointCenter(p1, p2, p3) {

FILE: src/ts/interfaces/IEvents.ts
  type IEvents (line 2) | interface IEvents {

FILE: src/ts/interfaces/IWord.ts
  type IWord (line 1) | interface IWord {

FILE: src/ts/world/Assets.ts
  type ITextures (line 6) | interface ITextures {
  type IResources (line 11) | interface IResources {

FILE: src/ts/world/Basic.ts
  class Basic (line 11) | class Basic {
    method constructor (line 18) | constructor(dom: HTMLElement) {
    method initScenes (line 27) | initScenes() {
    method setControls (line 51) | setControls() {

FILE: src/ts/world/Earth.ts
  type punctuation (line 16) | type punctuation = {
  type options (line 24) | type options = {
  type uniforms (line 57) | type uniforms = {
  class earth (line 67) | class earth {
    method constructor (line 91) | constructor(options: options) {
    method init (line 148) | async init(): Promise<void> {
    method createEarth (line 165) | createEarth() {
    method createStars (line 206) | createStars() {
    method createEarthGlow (line 239) | createEarthGlow() {
    method createEarthAperture (line 260) | createEarthAperture() {
    method createMarkupPoint (line 328) | async createMarkupPoint() {
    method createSpriteLabel (line 380) | async createSpriteLabel() {
    method createAnimateCircle (line 411) | createAnimateCircle() {
    method createFlyLine (line 501) | createFlyLine() {
    method show (line 528) | show() {
    method render (line 538) | render() {

FILE: src/ts/world/Resources.ts
  class Resources (line 6) | class Resources {
    method constructor (line 11) | constructor(callback: () => void) {
    method setLoadingManager (line 23) | private setLoadingManager() {
    method loadResources (line 48) | private loadResources(): void {

FILE: src/ts/world/Word.ts
  class World (line 20) | class World {
    method constructor (line 33) | constructor(option: IWord) {
    method createEarth (line 60) | async createEarth() {
    method render (line 105) | public render() {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (63K chars).
[
  {
    "path": ".commitlintrc.js",
    "chars": 684,
    "preview": "module.exports = {\n  extends: [\"@commitlint/config-conventional\"],\n  rules: {\n    \"type-enum\": [\n      2,\n      \"always\""
  },
  {
    "path": ".eslintrc.js",
    "chars": 616,
    "preview": "module.exports = {\n  \"plugins\": ['@typescript-eslint'],\n  extends: [\"eslint:recommended\", \"plugin:@typescript-eslint/rec"
  },
  {
    "path": ".gitattributes",
    "chars": 31,
    "preview": "*.js linguist-detectable=false\n"
  },
  {
    "path": ".gitignore",
    "chars": 31,
    "preview": ".vscode\nnode_modules\ndist\nbuild"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 59,
    "preview": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpx lint-staged\n\n"
  },
  {
    "path": ".prettierrc.js",
    "chars": 165,
    "preview": "export default {\n  \"printWidth\": 80,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"trailingComma\": \"es5\",\n  \"bracketSpacing"
  },
  {
    "path": "LICENSE",
    "chars": 1065,
    "preview": "MIT License\n\nCopyright (c) 2022 GhostCat\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
  },
  {
    "path": "README.en.md",
    "chars": 94,
    "preview": "# Webpack 5 + Typescript 4 + Three.js 基础模板\n\n- Webpack 5\n- Typescript 4\n- Three.js 130\n- lodash"
  },
  {
    "path": "README.md",
    "chars": 305,
    "preview": "# 3d-earth\n本项目使用 [three-ts-webpack](https://github.com/GhostCatcg/three-ts-webpack) 构建\n\n[Live Demo](https://gcat.cc/demo"
  },
  {
    "path": "package.json",
    "chars": 1533,
    "preview": "{\n  \"name\": \"GhostCat\",\n  \"version\": \"1.0.0\",\n  \"description\": \"3d-earth\",\n  \"keywords\": [\n    \"GhostCat\",\n    \"3d\",\n   "
  },
  {
    "path": "src/index.html",
    "chars": 4654,
    "preview": "<!DOCTYPE html>\n<html lang=\"zh\">\n  <html>\n\n<head>\n\t<title>GhostCat</title>\n  <meta charset=\"utf-8\">\n</head>\n<style>\n  ht"
  },
  {
    "path": "src/shaders/earth/fragment.fs",
    "chars": 664,
    "preview": "uniform vec3 glowColor;\nuniform float bias;\nuniform float power;\nuniform float time;\nvarying vec3 vp;\nvarying vec3 vNorm"
  },
  {
    "path": "src/shaders/earth/vertex.vs",
    "chars": 348,
    "preview": "\nvarying vec2 vUv;\nvarying vec3 vNormal;\nvarying vec3 vp;\nvarying vec3 vPositionNormal;\nvoid main(void){\n  vUv = uv;\n  v"
  },
  {
    "path": "src/ts/Utils/Sizes.ts",
    "chars": 1172,
    "preview": "/**\n * 屏幕尺寸\n*/\nimport { EventEmitter } from 'pietile-eventemitter';\nimport { IEvents } from '../interfaces/IEvents';\n\n\nt"
  },
  {
    "path": "src/ts/Utils/arc.ts",
    "chars": 8917,
    "preview": "\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-nocheck\nimport { ArcCurve, BufferAttribute, Buffer"
  },
  {
    "path": "src/ts/Utils/common.ts",
    "chars": 4524,
    "preview": "import { CatmullRomCurve3, DoubleSide, Group, Mesh, MeshBasicMaterial, PlaneBufferGeometry, Texture, TubeGeometry, Vecto"
  },
  {
    "path": "src/ts/global.d.ts",
    "chars": 291,
    "preview": "// Definitions to let TS understand .vs, .fs, .glsl shader files\n/**\n * 声明着色器\n */\n\ndeclare module '*.fs' {\n\tconst value:"
  },
  {
    "path": "src/ts/index.ts",
    "chars": 137,
    "preview": "import World  from './world/Word'\n\n// earth-canvas\nconst dom: HTMLElement = document.querySelector('#earth-canvas')\nnew "
  },
  {
    "path": "src/ts/interfaces/IEvents.ts",
    "chars": 51,
    "preview": "\nexport interface IEvents {\n  resize: () => void\n}\n"
  },
  {
    "path": "src/ts/interfaces/IWord.ts",
    "chars": 45,
    "preview": "export interface IWord {\n  dom: HTMLElement\n}"
  },
  {
    "path": "src/ts/world/Assets.ts",
    "chars": 547,
    "preview": "/**\n * 资源文件\n * 把模型和图片分开进行加载\n */\n\ninterface ITextures {\n  name: string\n  url: string\n}\n\nexport interface IResources {\n  t"
  },
  {
    "path": "src/ts/world/Basic.ts",
    "chars": 1575,
    "preview": "/**\n * 创建 threejs 四大天王\n * 场景、相机、渲染器、控制器\n */\n\nimport * as THREE from 'three';\nimport {\n  OrbitControls\n} from \"three/exam"
  },
  {
    "path": "src/ts/world/Data.ts",
    "chars": 1109,
    "preview": "export default [{\n  startArray: {\n    name: '杭州',\n    N: 30.246026,\n    E: 120.210792,\n  },\n  endArray: [{\n      name: '"
  },
  {
    "path": "src/ts/world/Earth.ts",
    "chars": 16073,
    "preview": "import {\n  BufferAttribute, BufferGeometry, Color, DoubleSide, Group, Material, Mesh, MeshBasicMaterial, NormalBlending,"
  },
  {
    "path": "src/ts/world/Resources.ts",
    "chars": 1183,
    "preview": "/**\n * 资源管理和加载\n */\nimport { LoadingManager, Texture, TextureLoader } from 'three';\nimport { resources } from './Assets'\n"
  },
  {
    "path": "src/ts/world/Word.ts",
    "chars": 2593,
    "preview": "import {\n  MeshBasicMaterial, PerspectiveCamera,\n  Scene, ShaderMaterial, WebGLRenderer\n} from \"three\";\nimport {\n  Orbit"
  },
  {
    "path": "tsconfig.json",
    "chars": 273,
    "preview": "{\n  \"include\": [\n      \"src/ts/**/*\"\n  ],\n  \"compilerOptions\": {\n      \"module\": \"ESNext\",\n      \"target\": \"ESNext\",\n   "
  },
  {
    "path": "tslint.json",
    "chars": 1086,
    "preview": "{\n    \"defaultSeverity\": \"error\",\n    \"extends\": [\n        \"tslint:recommended\"\n    ],\n    \"jsRules\": {},\n    \"rules\": {"
  },
  {
    "path": "webpack.config.js",
    "chars": 2561,
    "preview": "const path = require('path')\n// 引入html插件\nconst HTMLWebpackPlugin = require('html-webpack-plugin')\n// 把整个目录copy过去\nconst C"
  }
]

About this extraction

This page contains the full source code of the GhostCatcg/3d-earth GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (51.2 KB), approximately 17.6k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!