基于three.js的太、地、月三体运动

3,852 阅读5分钟

我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛

在线预览

天不生牛顿,万古如长夜 —— 中秋佳节念牛顿

1.真实的世界

从亚里士多德的“地心说”,到哥白尼的“日心说”,再到牛顿的“万有引力”,最后到爱因斯坦的“相对论”,人类越发了解了天体运动的运行的规律。而对于一般的低速系统,使用“万有引力”描述已经可以有很好的精度。

1.1.以因推果

若“以因推果”,“因”是“万有引力”,“果”是“天体运动的规律”,那么在代码实现中,应该模拟天体和力,从而得到“地球围绕太阳做椭圆运动”,“月球围绕地球亦做椭圆运动”的结论。

原理:

  1. 每一个时刻,每一个天体都有一个状态,这个状态包括速度矢量、位置矢量、质量等。
  2. t1t_1 时刻太阳、地球、月亮的状态为 Ssun1Searth1Smoon1S_{sun_1}、S_{earth_1}、S_{moon_1}
  3. 计算 t1t_1 时刻,每个天体受到的万有引力合理,如地球 Fearth1F_{earth_1}(太阳与月亮对其的合力矢量),此时,可以得到地球的瞬时加速度 aearth1=Fearth1/Meartha_{earth_1} = F_{earth_1} / M_{earth}
  4. 过一个极短的时间 t△t 到了 t2t_2 时刻,由 vearth2=vearth1+aearth1tv_{earth_2} = v_{earth_1} + a_{earth_1} * △t,可以得到 t2t_2 时刻地球的状态 Searth2S_{earth_2},同理于太阳、月球。
  5. 那么经过不断的迭代,就可以模拟出“果”。

PS:这里假定了世界是不连续的,t△t 可以认为是普朗克时间 104410^{-44} 秒。

1.2.拉普拉斯兽

这一小节是题话外

我们的世界(宇宙)本质上是有粒子组成的,由“以因推果”的原理可知:

  1. 假定 t1t_1 时刻,宇宙怪兽(拉普拉斯)知道了当前所有粒子的运动状态。
  2. 那么在一个普拉克时间之后的 t2t_2, 拉普拉斯兽应该可以推算出当前时刻的粒子运动状态。
  3. 不断的迭代,拉普拉斯兽知道了未来所有时刻粒子的运动状态。

那么我们的人生就是确定的,因为人是由粒子组成,从你出生那一刻起,粒子的状态是确定的,那么在拉普拉斯兽眼里,命运也就决定了。

但是由于量子力学的不确定性,每个人好似又有把握命运的机会。就拿“双缝干涉实验”来说。

双缝干涉.jpg

假设干涉后的条纹代表不同的阶级,从概率学上来说,落在中间阶级的概率较大,而且这个概率是确定的。由于光具有波粒二象性,再假设我们每个人都是一个光子,每个光子怎么会知道落到哪个阶级呢?光子的命运又是谁来把控?可能这便是人生的乐趣吧。

PS:你信命吗?

2.模拟的世界

真实的世界用代码比较难模拟,不妨以果推果。

  1. 已知地球围绕太阳转,月球围绕地球转。
  2. 为了更简单点,将椭圆轨道修改为圆形(偷懒)。
  3. 所以只需要用圆锥方程来描述天体的运动。

3.天体类Star

天体类简单定义如下

class Star {
  name: string;
  image: any;
  raduis: number;
  position: number[];
  cita: number;
  T: number = 0;
  R: number = 0;
  mesh: any 
  constructor(
    name: string,
    image: any,
    raduis: number,
    position: number[],
    cita: number,
    T?: number,
    R?: number
  ) {
    this.name = name;
    this.image = image;
    this.raduis = raduis;
    this.position = position;
    this.cita = cita;
    this.T = T || 0;
    this.R = R || 0;
  }
}
属性说明
name天体名称
image贴图图片
raduis天体半径
position天体位置
cita公转角度
T公转周期
R公转半径

太地月数组

const stars = [
  new Star('Sun', require('./texture/sun.jpg'), 100, [0, 0, 0], 0),
  new Star(
    'Earth',
    require('./texture/earth.jpg'),
    50,
    [650, 0, 0],
    0,
    30,
    650
  ),
  new Star('Moon', require('./texture/moon.jpg'), 30, [800, 0, 0], 0, 5, 150),
];

PS:这里假定太阳不动,且占据天元之位[0,0,0][0,0,0]

4.SimpleScene介绍

因为之前每次写关于three.js的应用,总会写一些相同的代码,比如照相机、光照、渲染器,还有一些初始化流程,所以我写了一个基于reactnpm包simple-scene-react

SimpleScene内置了全局光照、摄像机、渲染器,除了渲染器外。可以通过useDefaultLight={false} useDefaultCamera={false} 禁用配置,具体的使用方法可以参考/example

示例

初始化

官方示例复刻

太地月三体运动

地图飞线

PS:欢迎大家使用。

5.光照

SimpleScene内置了全局环境光new THREE.AmbientLight(0xffffff, 0.8),为了模拟太阳光的效果,在[0,0,0][0,0,0]初增加点光源,为了更好的质感,增加了平行光。

  const addLight = (scene: THREE.Scene) => {
    // 默认存在环境光  new THREE.AmbientLight(0xffffff, 0.8);
    // 若不想使用可以 useDefaultLight = false
    // 点光源
    let point = new THREE.PointLight(0xedf069, 2);
    point.position.set(0, 0, 0);
    scene.add(point);
    // 平行光
    let directionalLight = new THREE.DirectionalLight(0xffffff, 0.4);
    directionalLight.position.set(600, 600, 600);
    scene.add(directionalLight);
  };

addLightbeforeRender回调中执行即可。

6.animate动画

animate回调在每次requestAnimationFrame中执行。

 let delta = clock.getDelta();

delta每帧间隔时间,大约为1/600.01671/60≈0.0167

6.1.自转

为了简单(懒),每个天体都是相同的自转速度。

    stars.forEach(star => {
      if (star.mesh) {
        star.mesh.rotation.y += delta;
      }
    });

6.2.地球绕太阳公转

由于运动在xz平面中进行,所以y坐标始终为0。 圆的坐标方程为:[Rcos(cita),Rsin(cita)][R*cos(cita),R*sin(cita)],旋转周期T对应2*Math.PI,在每个循环周期里改变cita值即可,然后根据cita的值更新地球的位置。

    // 地球绕太阳转
    let earth = stars[1];
    if (earth.mesh) {
      earth.cita += (delta * 2 * Math.PI) / earth.T;
      earth.mesh.position.set(
        earth.R * Math.cos(earth.cita),
        0,
        earth.R * Math.sin(earth.cita)
      );
    }

6.3.月球绕地球公转

月球的位置是相对于地球的,所以position中需要加一个地球的位置。

    // 月球绕地球转
    let moon = stars[2];
    if (moon.mesh) {
      moon.cita += (delta * 2 * Math.PI) / moon.T;
      moon.mesh.position.set(
        moon.R * Math.cos(moon.cita) + earth.R * Math.cos(earth.cita),
        0,
        moon.R * Math.sin(moon.cita) + earth.R * Math.sin(earth.cita)
      );
    }

详细代码

7.结语

上帝说:人间要有白月光,于是便有了牛顿。