cesium 模拟地球自转
cesium 现有的机制分析
默认情况下,cesium 的球体其实在三维中是保持静止的状态。
而随着时间的流逝,默认的背景星空,是在不断地旋转的。
这个逻辑,我们从 cesium 的源码中就能发现端倪。
cesium 的 Scene 对象中,有个 render 方法,每一帧都被会调用,用于场景的重绘。

而 render 中有个 updateEnvironment 方法,用于更新环境

updateEnvironment 方法中,有一段更新 skyBox 的代码,同时我们能看到的是,同样有一段更新太阳和月亮位置的代码。

所以说,默认情况下,cesium 框架内部,通过计算,以地球为中心,真实的模拟了地球的运动。
产生地球自转可行的方案
如果现在有需求,想要实现地球自转的效果,那么我们该如何处理呢?
变与不变,其实是相对的。
所以其实我们有两种方案解决地球转动的问题:
- 地球球体自身旋转
- 旋转相机,产生自转的效果
旋转地球自身
先来讨论下我们的第一种方案。
如果我们想旋转地球自身的话,其实并不现实。
整个球包含着一套地理信息,而地理信息与实际的三维坐标信息并不一致,是有一套专门的转换机制的。
如果地球一转,就会使得三维坐标发生了变化,但是地理坐标却保持不变,如果还想使坐标对应,就必须要更新这套转换机制了。
这种做法显然是比较麻烦的。
我们知道,cesium 默认内置了一些三维对象的实现,比如 cylinder、plane、wall 等。
但是这些对象一般不是直接添加到 3d 场景中去的,而是通过附着在一个 entity 上添加的。
而且 entity 想要进行旋转操作,其实蛮复杂,必须要改变 orientation 值。
说实话,这个对新手及其不友好。
尤其是之前用过类似于 three.js 类似框架的童鞋,习惯了用 rotate 方式去旋转,但是用上 cesium 就犯难了,很难轻易的弄清楚,这个 orientation 该如何去写。
如果你查文档的话,文档也只会告诉你,这个 orientation 接受一个 Property 属性或者 undefined。

作为一枚新手,会让你一头雾水,特别是 cesium 里面的这个 Property,你刚接触还并不太好理解。

我们看看定义,大概能明白这是个什么东西。
其能够包含一组随着时间变化的值,也就是不同的时间,可以应用上不同的值。
这个功能很强大,特别是,当你了解到,原来 cesium 里面内置了一套系统时间的时候,就能感受到这么设计的魅力所在了。
咳咳,有点扯远了。
总之这种方案,如果放在其他的系统上,着实是可行的,就是麻烦了点。
但是放在地球上,显然是不太能行得通的方案。
旋转相机,产生自转的错觉
既然经过我们的分析,感觉第一种方案,过于复杂,那么现在我们来分析一下第二种方案,是否能够很好的满足我们的需求。
我们大家都知道的一个物理的基本原理是,运动是相对的。
所以,假使地球不转,我们视角(也就是这里的相机)反向转动,是能够模拟出地球转动的效果的。
那么相机,到底好不好进行旋转操作呢?
或者说的具体点,cesium 里面有没有提供这种 api,方便我们进行操作呢?
答案是肯定的!
不然,我也不会写这篇文章了。
需要注意的是,由于 cesium 支持多种视图模式,所以我们还需要针对不同的情况分别进行讨论,不能一概而论。
1. 哥伦布视图
对于哥伦布视图,讨论旋转好像没啥意义。
本身就是个 2.5 D 的视图,我很难想象,什么应用场景下,需要用到该视图模拟地球的旋转。
所以,这一块我们就先不考虑了。
2. 2d视图
由于 cesium 的球处于2d模式的时候,也是能够进行旋转缩放操作的。
因此,为了合理性,我们只考虑我们的 2d 视图一直处于类似初始化时候的平铺状态。

由于,cesium 2d 平面状态的时候,做了连续向左向右无限滚动的处理,因此,我们完全是可以采用平移镜头,而模拟地球自转的效果的。
刚好,cesium 的 camera 也提供了 moveLeft、moveRight api, 可以刚好用在这个地方,起到平移镜头的作用。
为了模拟真实的自转,有几点是我们需要考虑的问题。
- 系统时间,每过24小时,我们的地球需要转一圈
- 为了模拟地球真实的转速,当相机高度不同时,相机左移或者右移的幅度也是不一样的
- 只在 2d 模式,且需要转的时候,才提供转动支持
- 为了效果平滑,我们每一帧都需要通过计算得出平移距离,然后根据这个距离来平移相机
基于以上几点考量,第一步,我们需要构建一个每一帧都需要被调用的方法。
我们知道,在 cesium 系统中,我们可以通过 viewer.clock.onTick.addEventListener
的方式添加。
// 每一帧都会调用的方法
const icrf = () => {
};
viewer.clock.onTick.addEventListener(icrf);
接下来,就是填充这个方法内部的逻辑了。
首先我们过滤掉关于一些不执行平移操作的情况:
// 地球自转关键代码
const icrf = () => {
if (viewer.scene.mode !== SceneMode.SCENE2D || !viewer.isRotation) {
return;
}
const { camera, clock, scene } = viewer;
if (!clock.shouldAnimate) return;
};
然后我们加上一段计算逻辑,算出我们相隔的时间,相机需要平移的距离:
// 地球自转关键代码
// 存储上一帧需要移动的距离
let prev = viewer.clock.currentTime;
const icrf = () => {
const { camera, clock, scene } = this;
if (
this.scene.mode !== SceneMode.SCENE2D
|| !this.isRotation
|| this.mod !== 0
|| !clock.shouldAnimate
) {
prev = clock.currentTime;
return;
}
// 获取相机高度
const { height } = scene.globe.ellipsoid.cartesianToCartographic(
camera.position,
);
// 根据高度、地球半径等参数,计算出每秒钟相机需要平移的值
const a = (465.2 / (6371 * 1000)) * (height + 6371 * 1000);
const { currentTime } = clock;
// 算出前后两次的时间间隔
const interval = JulianDate.toDate(currentTime) - JulianDate.toDate(prev);
prev = currentTime;
// 调用api平移镜头
camera.moveLeft((interval * a) / 1000);
};
最后这段代码应用上以后,是这样的效果:

这样的效果,放在球面上,可能不太能看得出来有啥作用,但是如果该平面是满铺在一个球体上,就能够看出来,刚好是我们想要的地球自转的效果了。
3. 3d视图
对于 cesium 的 3d 模式,旋转起来,好像更为简单点。
cesium 的 camera 对象,提供了 rotate api, 可以直接绕 z 轴(即地球上任意一点的法线方向)对相机进行旋转,就能产生地球自转的效果了。
话不多说,直接放代码吧。
// 地球自转关键代码
let prev = this.clock.currentTime;
const icrf = () => {
const { camera, clock, scene } = this;
if (
this.scene.mode !== SceneMode.SCENE3D
|| !this.isRotation
|| this.mod !== 0
|| !clock.shouldAnimate
) {
prev = clock.currentTime;
return;
}
const { height } = scene.globe.ellipsoid.cartesianToCartographic(
camera.position,
);
const a = (465.2 / (6371 * 1000)) * (height + 6371 * 1000);
const { currentTime } = this.clock;
const interval = JulianDate.toDate(currentTime) - JulianDate.toDate(prev);
prev = currentTime;
camera.rotate(
Cartesian3.UNIT_Z,
(Math.PI / (24 * 60 * 60)) * (interval / 1000),
);
};
this.clock.onTick.addEventListener(icrf);
可以看到,与我们的 2d 视图下的自转基本类似,唯一有差别的地方是,我们旋转的时候,传入的是旋转角度,而不再像之前一样是距离了。
应用上这段代码以后,我们的 3d 模式下,球的旋转效果最终就形成了。
