原文链接及内容

效果如下视频所示:针对该示例的具体实现,请看下面js代码中详细的注释。

示例代码如下:

  1. 场景设置的监听器是有执行顺序的:viewer.scene.preUpdate → (场景更新) → viewer.scene.preRenderviewer.scene.preUpdate是在每一帧开始时触发,而viewer.scene.preRender在场景更新完成后、但在渲染之前触发。
  2. planePrimitive.readyEvent是在图元(例如一个3D模型)完成加载并准备好在场景中使用时触发。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay">
<h1>加载中...</h1>
</div>
<div id="toolbar">
<table class="infoPanel">
<tbody>
<tr>
<td>
点击加载Cesium地球的主窗口,<br />
然后使用键盘更改飞机的设置(参数),<br />
如航向、俯仰角度、飞行速度等。
</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>调整航向: <span id="heading"></span>°</td>
</tr>
<tr>
<td><kbd></kbd> 向左转/<kbd></kbd> 向右转</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>调整俯仰角: <span id="pitch"></span>°</td>
</tr>
<tr>
<td><kbd></kbd> 向上仰/<kbd></kbd> 向下俯冲</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>调整翻滚角: <span id="roll"></span>°</td>
</tr>
<tr>
<td><kbd>Shift + ←</kbd> 向左翻滚/<kbd>Shift + →</kbd> 向右翻滚</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>飞机速度: <span id="speed"></span>m/s</td>
</tr>
<tr>
<td><kbd>Shift + ↑</kbd> 加速/<kbd>Shift + ↓</kbd> 减速</td>
</tr>
<tr>
<td style="border-bottom: 2px solid white"></td>
</tr>
<tr>
<td>
跟随飞机
<input id="fromBehind" type="checkbox" />
</td>
</tr>
</tbody>
</table>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
const viewer = new Cesium.Viewer("cesiumContainer", {
geocoder: false,
sceneModePicker: false,
homeButton: false,
navigationHelpButton: false,
baseLayerPicker: false,
navigationInstructionsInitiallyVisible: false,
fullscreenButton: false,
selectionIndicator: false,
skyBox: false,
timeline: false,
animation: false,
shouldAnimate: true,
infoBox: false,
});
viewer.cesiumWidget.creditContainer.style.display = "none";

const canvas = viewer.canvas;
canvas.setAttribute("tabindex", "0"); // 将焦点聚焦到画布上
canvas.addEventListener("click", function () {
canvas.focus();
});
canvas.focus();

const scene = viewer.scene;

/**
* SampledPositionProperty类:用于表示随时间变化的位置,
* 通过为特定时间点添加样本(例如经纬度和高度),
* 可以定义一个物体在时间轴上的移动路径。
*
* pathPosition:是该类的一个实例,初始化时为空,
* 后续需要通过添加样本数据来指定实体的位置变化。
*/
const pathPosition = new Cesium.SampledPositionProperty();
const entityPath = viewer.entities.add({
//将实体的位置绑定到pathPosition 对象,实体的位置将由 pathPosition 中的样本数据动态决定
position: pathPosition,
name: "path",
path: {
show: true, //路径可见
/**
* leadTime表示路径显示的未来时间(单位通常为秒)。
* 这里设置为 0,意味着不显示实体的未来路径。
*/
leadTime: 0,
trailTime: 60, //表示路径将展示实体过去 60 秒的移动轨迹。
width: 10, //路径宽度
/**
* resolution: 定义路径的采样分辨率(单位通常为秒)。
* 值越小,路径越平滑,但计算开销可能增加。这里设置为 1,表示每秒采样一次。
*/
resolution: 1,
//使用 PolylineGlowMaterialProperty 类可以创建一个发光效果的线条材质
material: new Cesium.PolylineGlowMaterialProperty({
//glowPower:控制发光强度,为总线宽的百分比,这里设置为 0.3,表示中等发光效果。
glowPower: 0.3,
/**
* taperPower: 控制线条末端的锥化效果的强度,使路径两端逐渐淡出,为总线长的百分比。
* 值为 0.3 表示适度的渐变。如果该值大于或等于 1.0,则不使用锥化效果。
*/
taperPower: 0.3,
color: Cesium.Color.PALEGOLDENROD, //淡黄色
}),
},
});

const camera = viewer.camera;
//获取用于处理相机输入的控制器。
const controller = scene.screenSpaceCameraController;
let r = 0;

const hpRoll = new Cesium.HeadingPitchRoll();
const hpRange = new Cesium.HeadingPitchRange();
let speed = 10;
const deltaRadians = Cesium.Math.toRadians(3.0);

let position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, 5000.0);
let speedVector = new Cesium.Cartesian3();
/**
* Transforms.localFrameToFixedFrameGenerator()方法:
* 生成一个函数,该函数计算从以提供原点为中心的参考系到提供的椭球体固定参考系的 4x4 变换矩阵。
* 第一个参数为north,表示局部坐标系的x轴指向北,第二个参数west表示局部坐标系的y轴指向西,
* z轴通过x和y轴的叉积确定,北 × (-东) = - (北 × 东) = -上 = 下,因此这是个北西下局部坐标系。
*/
const fixedFrameTransform = Cesium.Transforms.localFrameToFixedFrameGenerator(
"north",
"west"
);

const headingSpan = document.getElementById("heading");
const pitchSpan = document.getElementById("pitch");
const rollSpan = document.getElementById("roll");
const speedSpan = document.getElementById("speed");
const fromBehind = document.getElementById("fromBehind");

try {
const planePrimitive = scene.primitives.add(
/**
* Model.fromGltfAsync()方法:从 glTF 资产异步创建模型。此函数返回一个 Promise,
* 该 Promise 在模型准备好渲染时(即下载外部二进制文件、图像和着色器文件并创建 WebGL 资源时)进行解析。
*/
await Cesium.Model.fromGltfAsync({
url: "../SampleData/models/CesiumAir/Cesium_Air.glb",
modelMatrix: Cesium.Transforms.headingPitchRollToFixedFrame(
position,
hpRoll,
Cesium.Ellipsoid.WGS84,
fixedFrameTransform
),
minimumPixelSize: 128,
})
);

/**
* Model.readyEvent:
* 获取在加载模型并准备好渲染时(即,下载外部资源并创建 WebGL 资源时)引发的事件。
* 事件侦听器将传递 Model 的实例。
*/
planePrimitive.readyEvent.addEventListener(() => {
// 以半速播放并循环所有动画
// Model.activeAnimations:表示当前正在播放的 glTF 动画,类型为ModelAnimationCollection
// ModelAnimationCollection.addAll方法:为模型中的所有动画创建并添加具有指定初始属性的动画到集合中。
planePrimitive.activeAnimations.addAll({
multiplier: 0.5, //半速
loop: Cesium.ModelAnimationLoop.REPEAT, //循环
});

// 缩放至模型
//Model.boundingSphere: 获取模型在世界空间中的边界球体。这里是获取边界球体的半径
//这里取边界球体半径和相机视椎体近平面距离中的较大的值。
r =
2.0 * Math.max(planePrimitive.boundingSphere.radius, camera.frustum.near);
/**
* minimumZoomDistance:缩放时相机位置的最小幅度(以米为单位)。默认值为 1.0。
*/
controller.minimumZoomDistance = r * 0.5;
const center = planePrimitive.boundingSphere.center; //模型边界球体的中心点
const heading = Cesium.Math.toRadians(230.0);
const pitch = Cesium.Math.toRadians(-20.0);
hpRange.heading = heading;
hpRange.pitch = pitch;
hpRange.range = r * 50.0;
//lookAt方法:该方法用于将相机锁定到一个目标点,此时回锁定相机视角,不能通过鼠标平移场景。
camera.lookAt(center, hpRange);
});

document.addEventListener("keydown", function (e) {
switch (e.code) {
case "ArrowDown":
if (e.shiftKey) {
// 减速
speed = Math.max(--speed, 1);
} else {
// (向下)俯冲
hpRoll.pitch -= deltaRadians;
if (hpRoll.pitch < -Cesium.Math.TWO_PI) {
hpRoll.pitch += Cesium.Math.TWO_PI;
}
}
break;
case "ArrowUp":
if (e.shiftKey) {
// 加速
speed = Math.min(++speed, 100);
} else {
// 上仰
hpRoll.pitch += deltaRadians;
if (hpRoll.pitch > Cesium.Math.TWO_PI) {
hpRoll.pitch -= Cesium.Math.TWO_PI;
}
}
break;
case "ArrowRight":
if (e.shiftKey) {
// 向右滚动
hpRoll.roll += deltaRadians;
if (hpRoll.roll > Cesium.Math.TWO_PI) {
hpRoll.roll -= Cesium.Math.TWO_PI;
}
} else {
// 向右转
hpRoll.heading += deltaRadians;
if (hpRoll.heading > Cesium.Math.TWO_PI) {
hpRoll.heading -= Cesium.Math.TWO_PI;
}
}
break;
case "ArrowLeft":
if (e.shiftKey) {
// 向左滚动
hpRoll.roll -= deltaRadians;
if (hpRoll.roll < 0.0) {
hpRoll.roll += Cesium.Math.TWO_PI;
}
} else {
// 向左转
hpRoll.heading -= deltaRadians;
if (hpRoll.heading < 0.0) {
hpRoll.heading += Cesium.Math.TWO_PI;
}
}
break;
default:
}
});

/**
* scene.preUpdate:获取在场景更新或渲染之前将引发的事件,即在每帧渲染之前触发。
* 添加的监听器能确保飞机的位置、姿态和视角在每一帧都得到更新
*/
viewer.scene.preUpdate.addEventListener(function (scene, time) {
/**
* 1. 计算飞机的飞行速度和方向
* multiplyByScalar方法将Cartesian3.UNIT_X(即(1,0,0))向量按`speed/10`缩放,
* 生成速度向量speedVector,并将结果存储在 speedVector 中。
*/
speedVector = Cesium.Cartesian3.multiplyByScalar(
Cesium.Cartesian3.UNIT_X, //局部坐标系的x轴方向
speed / 10, //除以 10 是为了调整移动的步长,使其更平滑或符合场景比例。
speedVector
);

/**
* 2. 更新飞机的位置
* planePrimitive.modelMatrix:表示飞机的模型矩阵,描述了飞机在全局坐标系中的位置和姿态。
*
* multiplyByPoint()方法:将局部速度向量 speedVector 应用到模型矩阵上,
* 计算出飞机在全局坐标系中的新位置,结果存储在 position 中。
*/
position = Cesium.Matrix4.multiplyByPoint(
planePrimitive.modelMatrix,
speedVector,
position
);

/**
* 3. 记录飞机的移动路径
*
* addSample(time, position, derivatives):添加一个新的样本。time添加时间,position即当前时间的位置。
* pathPosition用于存储飞机的位置随时间变化的样本。
*/
pathPosition.addSample(Cesium.JulianDate.now(), position);

/**
* 4. 更新飞机的姿态
*
* 根据飞机的位置和姿态,计算并更新其模型矩阵,确保飞机在移动时保持正确的方向和姿态。
*/
Cesium.Transforms.headingPitchRollToFixedFrame(
position,//局部坐标系的原点
hpRoll,//航向、俯仰和滚转。
Cesium.Ellipsoid.WGS84,//用于转换的固定框架的椭球体。
fixedFrameTransform,//从当前定义的北西下局部坐标系转换到提供的椭球体的固定参考系的 4x4 变换矩阵
planePrimitive.modelMatrix//飞机的模型矩阵,描述了飞机在全局坐标系中的位置和姿态
);

/**
* 5. 控制相机视角(可选)
*/
if (fromBehind.checked) {
//若跟随飞机视角,借助lookAt方法锁定视角即可。
// 缩放至模型
const center = planePrimitive.boundingSphere.center;
hpRange.heading = hpRoll.heading;
hpRange.pitch = hpRoll.pitch;
camera.lookAt(center, hpRange);
}
});
} catch (error) {
console.log(`加载模型出错: ${error}`);
}

//scene.preRender:通过获取场景更新后和渲染前将引发的事件,实时获取航向、俯仰、翻滚及飞行速度。
viewer.scene.preRender.addEventListener(function (scene, time) {
headingSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.heading).toFixed(1);
pitchSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.pitch).toFixed(1);
rollSpan.innerHTML = Cesium.Math.toDegrees(hpRoll.roll).toFixed(1);
speedSpan.innerHTML = speed.toFixed(1);
});