最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

CesiumJS 源码杂谈 - 时间与时钟系统_前沿热点

来源:博客园
目录
  • 1. 时间的“诞生”
  • 2. 时间的推进
  • 3. Entity API 与 Property API 的更新动力源
  • 4. 简单应用
    • 4.1. 使用原生 JS Date 对象创建 JulianDate
    • 4.2. 使用时间字符串(ISO8601标准的时间字符串或 UTC 时间字符串)创建 JulianDate
    • 4.3. 为时钟设置起止时间和速率
    • 4.4. 调整时钟的循环情况

你知道吗?


(相关资料图)

  • Cesium 是元素 的英文单词,而 铯原子钟具有世界上最高的计时精度

  • 时间,是时刻间隔的意思,时刻是静态的点;而时间就指有起止时刻的一段范围

  • 很多应用都要有一个时钟,例如 GPS 授时、实时渲染系统,时间可以测量很多事物,万物运动也体现了时间在流逝

1. 时间的“诞生”

首次创建时间是出现在 Scene 的构造函数中:

function Scene (/**/) {  // ...  updateFrameNumber(this, 0.0, JulianDate.now());  // ...}function updateFrameNumber(scene, frameNumber, time) {  const frameState = scene._frameState;  frameState.frameNumber = frameNumber;  frameState.time = JulianDate.clone(time, frameState.time);}

源于此,很多自己应用 CesiumJS 着色器的文章中就用 FrameState上的 frameNumber就近似表达了“时间”的概念,因为在 60FPS 的屏幕上,可以通过 frameNumber / 60粗略获得时间值(秒),但是一旦浏览器的帧速率变化,比如 144 FPS,这个获得的时间就会不准确。

CesiumJS 使用 JulianDate类来表示整个程序中的时间,它是一种天文时间系统,叫作“儒略”日期,它有两个成员字段,一个是自儒略第一天(公元前 4713 年 1 月 1 日)到现在的天数 dayNumber,另一个是今天已经走过的秒数(零点起算)secondsOfDay

注:我们所说的公历时间,即 GregorianDate(格里日历记法),在 CesiumJS 中也是有的,是作为 JS 原生类 Date 的高精度替代品。

根据上面的 Scene 类构造函数,使用 JulianDate.now方法,无论什么时候初始化 CesiumJS,获取的时间值永远都是程序运行的那个时刻:

JulianDate.now = function (result) {  return JulianDate.fromDate(new Date(), result);}

所以,真正的时间值在帧状态对象 scene._frameStatetime字段上。

2. 时间的推进

CesiumJS 内部的时间是如何更新的?

CesiumJS 的渲染源头是 CesiumWidget对象,它每一帧都会运行 CesiumWidget.prototype.render方法,会让此对象上的时钟 tick一次(也就是跳一下),返回的时间就作为这一帧的时间,传递给 Scene.prototype.render,进而调用 updateFrameNumber函数更新累计帧数、时间值:

CesiumWidget.prototype.render = function () {  if (this._canRender) {    this._scene.initializeFrame();    const currentTime = this._clock.tick();    this._scene.render(currentTime);  } else {    this._clock.tick();  }}

所以要看时间是如何更新的,就要看 Clock对象的 tick方法。

初始化 Clock 时,默认就以当前的 JulianDate 为时钟起点时刻,往后一天为终点时刻。

每当调用 tick时,会获取当前的时刻 clock.currentTime,然后调用 JulianDate.addSeconds()方法把时间往前推。在所有默认条件下,调用的逻辑分支是:

const milliseconds = currentSystemTime - this._lastSystemTime;currentTime = JulianDate.addSeconds(  currentTime,  multiplier * (milliseconds / 1000.0),  currentTime);

而这个 currentSystemTime即时间戳,来自 Performance API(浏览器高精度性能 API)或 Date API,能获取当前的毫秒数。

最后,把计算的 currentTime(类型是 JulianDate)返回给调用者,也就是 CesiumWidget.prototype.render方法,继续更新一帧。

3. Entity API 与 Property API 的更新动力源

在之前写源码系列的时候,就提过 Entity API 是怎么运作的。

首先,EntityAPI 挂载于 Viewer上,若无 Viewer那默认的 Entity 容器就得自己实现一套,很麻烦。

其次,Viewer拥有 _onTick事件,它监听了 CesiumWidgetclockonTick事件,通过 EventHelper完成:

eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);

往后就是 DataSourceDisplay、CustomDataSource 等内容了,较为复杂,请移步源码解析文章。

引自源码解析文章,以参数化几何的 Entity为例,它用的是 GeometryVisualizer,当 GeometryVisualizer调用 fireChangedEvent函数后,Visualizer 就会拿到最新的 Entity 定义,进而借助 Property API、Updater 等复杂架构更新数据。

总之,若无时钟的 onTick跳动,也就没有办法根据当前时间去更新 Entity,也就拿不到最新的 Property,更别说动态更新场景中的三维 Entity 了。

4. 简单应用

4.1. 使用原生 JS Date 对象创建 JulianDate

这个最好的说明就是 JulianDate.now了,在上面第 1 节已经列出源码。当然,也可以自己来搞一个:

const myDate = JulianDate.fromDate(new Date())

4.2. 使用时间字符串(ISO8601标准的时间字符串或 UTC 时间字符串)创建 JulianDate

以北京时间为例:

const myDate = JulianDate.fromIso8601("2023-05-01T13:15:21+08:00")

注意日期和时间之间有一个大写字母 T。我在尾部加上了 +08:00表示东八区北京时间。

4.3. 为时钟设置起止时间和速率

这个就很简单了:

clock.startTime = JulianDate.fromIso8601("2023-05-01T00:00:00+08:00")clock.stopTime = JulianDate.fromDate(new Date("2023/05/02 00:00:00")) // Date 会默认使用当前时区,当然你也可以手动 +8,格式按 Date 的文档来就可以clock.multiplier = 3600 // 3600倍速,一秒过一小时

注意,设置倍数要配合参数 clock.clockStep === ClockStep.SYSTEM_CLOCK_MULTIPLIERClockStep.TICK_DEPENDENT才有效。

4.4. 调整时钟的循环情况

clock.clockRange = ClockRange.LOOP_STOP

LOOP_STOP是默认的,到终点不会停止,会继续往前走,但是会重新回到起点时刻,类似于 重播效果

CLAMPED会在终点时刻停下来,类似于 播完就停在那里

UNBOUNDED即使超过终点时刻,也不会停下来,类似 直播效果

关键词: