前言
之前我在
Cesium 中的 Camera(一)
一文中复现了官方的示例,演示了如何通过
监听键盘按键
或通过
监听鼠标事件
来控制 Camera,当时为了演示方便,修改了
ScreenSpaceCameraController
部分属性以禁用默认的处理程序。其实 Cesium 已经定义了默认的鼠标动作。具体如下图所示,通过单击鼠标
左键 + 拖拽
来
平移
视图,通过
右键单击 + 拖拽
或
鼠标滚轮滚动
来
缩放
视图,还有通过鼠标
中键 + 拖拽
或者
Ctrl + 左键或右键 + 拖拽
来
旋转
视图。
鼠标操纵视图的方法
本文将继续借助官方的示例来演示 Camera 的一些常用方法的实现效果,并对个别地方进行了修改,方便更好地理解。废话不多说,下面直入正题。
Camera 可视化
Cesium 提供了可视化 Camera 的专门类
DebugCameraPrimitive
,具体如下所示:
DebugCameraPrimitive 类
为了方便演示,我们需要更改一下 Camera 默认视图范围,就是地球的
默认视图区域
,具体如下所示:我们需要在初始化 Viewer 对象前更改 Camera 的
DEFAULT_VIEW_RECTANGLE
属性。
注: 需要在初始化 Viewer 对象前修改Camera的上述属性,否则无法生效。另外下述区域范围参考的第三方库定义的一个中国区域的四至范围,具体参考:
https://github.com/wandergis/coordtransform/blob/master/index.js#L144
。
Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(
73.66,
3.86,
135.05,
53.55
);
最终实现效果如下:
实现代码如下所示:
注:我们通过
viewer.camera
或
viewer.scene.camera
都可以获取 camera 对象,实际上根据我测试没有什么区别。
/**
* 绘制出中国范围四至
*/
function drawChinaExtent() {
if (chinaExtentEntity) {
viewer.entities.remove(chinaExtentEntity);
}
viewer.entities.add({
rectangle: {
coordinates: Cesium.Rectangle.fromDegrees(73.66, 3.86, 135.05, 53.55),
fill
: false,
outline: true,
outlineColor: Cesium.Color.RED,
},
});
}
/**
* 可视化相机视锥体
*/
function visualCameraFrustum() {
//开启地表透明
scene.globe.translucency.enabled = true;
scene.globe.translucency.frontFaceAlpha = 0.5;
//绘制是椎体
camera.frustum.near = 200000.0;
cameraPrimitive = scene.primitives.add(
new Cesium.DebugCameraPrimitive({
camera,
updateOnChange: false,
})
);
}
我们曾在
Cesium 中的 Camera(一)
一文中展示了键盘操作Camera的
moveXcc
一系列的移动方法,方法使用很简单,但这里我们详细对比一下
moveXcc()
、
lookXcc()
、
rotateXcc()
、
twistXcc()
系列方法的区别,我们以
left
方向为例:
-
moveLeft
:沿摄像机的左侧方向平移(直线移动),相当于
沿摄像机的局部坐标系 X 轴负方向移动
,其参数单位为
米
,表示移动的距离。
-
lookLeft
:让摄像机
围绕其上向量
(up 向)
向左旋转
,相当于
改变摄像机的视线方向
,但
位置保持不变
,参数单位为
弧度
。
-
rotateLeft
:让摄像机围
绕其参考框架的中心
(通常是地球中心或地面上的某个目标点)
向左旋转
,实现绕参考点的圆弧旋转,参数单位为
弧度
。
注意:上面括号里说通常是地球的中心,可以认为相机初始的目标点位于地球中心,因此可以认为其参考框架中心就是目标点。
-
twistLeft
:让摄像机
围绕自身的视线方向
(视轴,即下图的 direcition 方向)
逆时针旋转
(从摄像机视角看是向左扭转),参数单位为
弧度
。
为了方便理解,我们顺便更正一下曾在
Cesium 中的 Camera(二)
一文中的手势图,其实我们文末评论处做了更正,即使用如下图所示的左手坐标系来标明 Camera 的方向。
左手坐标系
另外,关于上述方法还有不带方向的对应方法,如下图所示:不管是
axis
还是
direction
都可以用一个
Cartesian3
来表示,例如
Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
,表示这个轴或方向为
连接笛卡尔坐标系原点与该点(位于地表上方)的一个向量
。
Camera 常用方法
下面的演示是使用的官方示例,个别地方稍加修改,疑难的地方都作了注释,请注意看代码中添加的注释,这里仅放几个比较典型的效果,其余的代码请点击文末
阅读原文
链接访问即可。
在一个城市飞行
效果如下图所示:
camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
-73.98580932617188,
40.74843406689482,
363.34038727246224
),
duration: 2,
complete: () => {
setTimeout(() => {
camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
-73.98585975679403,
40.75759944127251,
186.50838555841779
),
orientation: {
heading: Cesium.Math.toRadians(200.0),
pitch: Cesium.Math.toRadians(-50.0),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
});
}, 1000);
},
});
飞往我的位置
效果如下所示:
这里用的定位图标使用的官方提供的,其实源码中有图标名称:官方的PinBuilder类提供了相应的添加方法。
另外,这里教大家一个技巧:有些类官方在 api 文档处附了对应的示例链接,我们直接点击学习即可,具体如下所示:
navigator.geolocation.getCurrentPosition(
(pos) => {
//绘制一个定位图标
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
pos.coords.longitude,
pos.coords.latitude,
0.0
),
billboard: {
image: pinBuilder.fromMakiIconId("hospital", Cesium.Color.RED, 48),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 图标对齐方式
},
});
camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
pos.coords.longitude,
pos.coords.latitude,
1000.0
),
});
},
(err) => {
ElMessage.error("错误(" + err.code + "): " + err.message);
},
{
enableHighAccuracy: true,
}
);
飞向一个矩形
效果如下图所示:
注:这里的这个效果,我们可以直接调用
Viewer
的
flyTo
方法也可,具体参考官方入门教程的
Fly to an asset
:
https://cesium.com/learn/cesiumjs-learn/cesiumjs-camera#fly-to-an-asset
,具体如下图所示:[图片]
const west = -90.0;
const south = 38.0;
const east = -87.0;
const north = 40.0;
const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north);
camera.flyTo({
destination: rectangle,
});
//这里是为了展示方便,所以绘制了一个矩形
viewer.entities.add({
rectangle: {
coordinates: rectangle,
material: Cesium.Color.RED.withAlpha(0.5),
},
});
给相机设置一个局部坐标系——东北天坐标系
效果如下所示:
//局部坐标系(ENU)原点的坐标
const center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883);
/**
* 计算从ENU坐标系转换为全球通用坐标系的转换矩阵
* ENU,全称为:East north up coordinate,即东北天坐标系,参考:
* https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates#Local_east,_north,_up_(ENU)_coordinates
*/
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(center);
camera.constrainedAxis = Cesium.Cartesian3.UNIT_Z;
//使用目标和变换矩阵设置摄像机位置和方向
camera.lookAtTransform(
transform,
new Cesium.Cartesian3(-120000.0, -120000.0, 120000.0)
);
referenceFramePrimitive = scene.primitives.add(
new Cesium.DebugModelMatrixPrimitive({
modelMatrix: transform,
length: 100000.0,
})
);
//计算当前局部坐标系的原点转为世界坐标系坐标
const enuOrigin = Cesium.Matrix4.multiplyByPoint(
transform,
new Cesium.Cartesian3(0, 0, 0),
new Cesium.Cartesian3()
);
viewer.entities.add({
position: enuOrigin,
billboard: {
image: pinBuilder.fromMakiIconId("hospital", Cesium.Color.RED, 48),
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 图标对齐方式
},
});
地球实时旋转
效果如下所示:
let removePostUpdate;
function viewInICRF() {
//摄像机飞往默认的主视图(即Camera#.DEFAULT_VIEW_RECTANGLE),参数为持续时间,这里设置为0秒
camera.flyHome(0);
//multiplier默认值为1.0,获取或设置调用 Clock#tick 时前进的时间量,由于单位为秒,因此这里设置每次时间的前进时长为3小时
clock.multiplier = 3 * 60 * 60;
//添加在更新场景后和渲染场景之前执行的事件监听器。
//事件的订阅者接收 Scene 实例作为第一个参数,将当前时间作为第二个参数。
removePostUpdate = scene.postUpdate.addEventListener((scene, time) => {
console.log("time参数类型为JulianDate: ", time instanceof Cesium.JulianDate);
if (scene.mode !== Cesium.SceneMode.SCENE3D) {
return;
}
//计算旋转矩阵,以在给定时间将点或矢量从国际天体参考系 (GCRF/ICRF) 惯性系轴变换为地球固定系轴 (ITRF)。如果尚未加载执行转换所需的数据,则此函数可能会返回 undefined。
const icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(time);
if (Cesium.defined(icrfToFixed)) {
const offset = Cesium.Cartesian3.clone(camera.position);
//从表示旋转的 Matrix3 和表示平移的 Cartesian3 计算 Matrix4 实例。这里只指定了第一个旋转参数,第二个平移参数默认值为:Cartesian3.ZERO
const transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed);
//使用目标和变换矩阵设置摄像机位置和方向。
camera.lookAtTransform(transform, offset);
}
});
}