我正在参加中秋创意投稿大赛,详情请看:中秋创意投稿大赛
天不生牛顿,万古如长夜 —— 中秋佳节念牛顿
1.真实的世界
从亚里士多德的“地心说”,到哥白尼的“日心说”,再到牛顿的“万有引力”,最后到爱因斯坦的“相对论”,人类越发了解了天体运动的运行的规律。而对于一般的低速系统,使用“万有引力”描述已经可以有很好的精度。
1.1.以因推果
若“以因推果”,“因”是“万有引力”,“果”是“天体运动的规律”,那么在代码实现中,应该模拟天体和力,从而得到“地球围绕太阳做椭圆运动”,“月球围绕地球亦做椭圆运动”的结论。
原理:
- 每一个时刻,每一个天体都有一个状态,这个状态包括速度矢量、位置矢量、质量等。
- 记 时刻太阳、地球、月亮的状态为 。
- 计算 时刻,每个天体受到的万有引力合理,如地球 (太阳与月亮对其的合力矢量),此时,可以得到地球的瞬时加速度 。
- 过一个极短的时间 到了 时刻,由 ,可以得到 时刻地球的状态 ,同理于太阳、月球。
- 那么经过不断的迭代,就可以模拟出“果”。
PS:这里假定了世界是不连续的, 可以认为是普朗克时间 秒。
1.2.拉普拉斯兽
这一小节是题话外
我们的世界(宇宙)本质上是有粒子组成的,由“以因推果”的原理可知:
- 假定 时刻,宇宙怪兽(拉普拉斯)知道了当前所有粒子的运动状态。
- 那么在一个普拉克时间之后的 , 拉普拉斯兽应该可以推算出当前时刻的粒子运动状态。
- 不断的迭代,拉普拉斯兽知道了未来所有时刻粒子的运动状态。
那么我们的人生就是确定的,因为人是由粒子组成,从你出生那一刻起,粒子的状态是确定的,那么在拉普拉斯兽眼里,命运也就决定了。
但是由于量子力学的不确定性,每个人好似又有把握命运的机会。就拿“双缝干涉实验”来说。
假设干涉后的条纹代表不同的阶级,从概率学上来说,落在中间阶级的概率较大,而且这个概率是确定的。由于光具有波粒二象性,再假设我们每个人都是一个光子,每个光子怎么会知道落到哪个阶级呢?光子的命运又是谁来把控?可能这便是人生的乐趣吧。
PS:你信命吗?
2.模拟的世界
真实的世界用代码比较难模拟,不妨以果推果。
- 已知地球围绕太阳转,月球围绕地球转。
- 为了更简单点,将椭圆轨道修改为圆形(偷懒)。
- 所以只需要用圆锥方程来描述天体的运动。
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:这里假定太阳不动,且占据天元之位。
4.SimpleScene介绍
因为之前每次写关于three.js
的应用,总会写一些相同的代码,比如照相机、光照、渲染器,还有一些初始化流程,所以我写了一个基于react
的npm包
simple-scene-react。
SimpleScene
内置了全局光照、摄像机、渲染器,除了渲染器外。可以通过useDefaultLight={false}
useDefaultCamera={false}
禁用配置,具体的使用方法可以参考/example
。
示例
PS:欢迎大家使用。
5.光照
SimpleScene
内置了全局环境光new THREE.AmbientLight(0xffffff, 0.8)
,为了模拟太阳光的效果,在初增加点光源,为了更好的质感,增加了平行光。
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);
};
addLight
在beforeRender
回调中执行即可。
6.animate动画
animate
回调在每次requestAnimationFrame
中执行。
let delta = clock.getDelta();
delta
每帧间隔时间,大约为
6.1.自转
为了简单(懒),每个天体都是相同的自转速度。
stars.forEach(star => {
if (star.mesh) {
star.mesh.rotation.y += delta;
}
});
6.2.地球绕太阳公转
由于运动在xz
平面中进行,所以y
坐标始终为0。
圆的坐标方程为:,旋转周期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.结语
上帝说:人间要有白月光,于是便有了牛顿。