Cesium 键盘鼠标控制相机漫游(源码+原理讲解)

Cesium 键盘鼠标控制相机漫游(源码+原理讲解)

在各大博客平台上,Cesium使用键盘控制相机漫游的源码已经有不少人贴出源码,本人在浏览这些源码的过程中发现大家采用的方式基本一致,大部分代码都是一个模子,甚至有一部分博文对代码进行部分删除,导致出现看不懂的情况。这些博文有个共同的问题就是无任何说明,对新手很不友好。
本文使用同样的思路完成键盘鼠标同时控制相机漫游功能,并附带必要说明,建议收藏。

1 达到的效果

  • 键盘控制:w,相机前进; s,相机后退;a,相机左移;d,相机右移;q,相机上移;e,相机下移。
  • 鼠标控制:按住鼠标左键向某个方向移动鼠标使相机镜头转向该方向。

2 实现思路

开启键盘鼠标控制相机漫游:

  • 将默认的相机操作模式禁用,防止鼠标动作冲突;
  • 设置相机漫游的标记;
  • 添加鼠标监听事件,监听按住鼠标移动情况来改变漫游标记;
  • 添加键盘监听事件,监听键盘来改变漫游标记;
  • 添加渲染事件,根据漫游标记的改变来渲染。

关闭键盘鼠标控制相机漫游:

  • 移除鼠标监听事件;
  • 移除键盘监听事件;
  • 移除渲染事件;
  • 将默认的相机操作模式启用。

3 源码及说明

3.1 HTML代码

<div id="cesiumContainer" style="width:100%;height:100%"></div>

3.2 JavaScript代码

// 初始化地球小部件
let viewer = new Cesium.Viewer('cesiumContainer');
// 声明变量,以下代码可能会多次用到
let scene = viewer.scene;
let canvas = viewer.canvas; // 此处用viewer.canvas或viewer.scene.canvas都可以,是同一个canvas对象
let camera = viewer.camera;
let ellipsoid = viewer.scene.globe.ellipsoid;
// 声明相机漫游标记
let flags = null;
// 声明handler
let handler = null;

/**
 * 进入键盘鼠标漫游模式
 */
function enterKeyBoardMouseRoamingMode() {
  console.log('进入漫游模式');
  // 1.禁用默认相机操作模式
  scene.screenSpaceCameraController.enableRotate = false;
  scene.screenSpaceCameraController.enableTranslate = false;
  scene.screenSpaceCameraController.enableZoom = false;
  scene.screenSpaceCameraController.enableTilt = false;
  scene.screenSpaceCameraController.enableLook = false;

  // 2.初始化相机漫游的标记
  flags = {
    looking: false, // 是否正在用鼠标调整视角
    startPosition: null, // 鼠标指针开始移动位置
    endPosition: null, // 鼠标指针停止移动位置
    moveForward: false, // 是否向前移动
    moveBackward: false, // 是否向后移动
    moveLeft: false, // 是否向左移动
    moveRight: false, // 是否向右移动
    moveUp: false, // 是否向上移动
    moveDown: false, // 是否向下移动
  }; // 相机漫游标记

  // 3.添加鼠标监听事件
  handler = new Cesium.ScreenSpaceEventHandler(canvas);
  // 左键按下
  handler.setInputAction((movement) => {
    flags.looking = true;
    flags.startPosition = Cesium.Cartesian3.clone(movement.position);
    flags.endPosition = Cesium.Cartesian3.clone(movement.position);
  }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
  // 鼠标移动
  handler.setInputAction((movement) => {
    flags.endPosition = movement.endPosition;
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  // 左键弹起
  handler.setInputAction(() => {
    flags.looking = false;
  }, Cesium.ScreenSpaceEventType.LEFT_UP);

  // 4.添加键盘监听事件
  // 键盘按下事件
  document.addEventListener('keydown', keyDown, false);
  // 键盘弹起事件
  document.addEventListener('keyup', keyUp, false);

  // 5.添加渲染事件
  viewer.clock.onTick.addEventListener(renderEvent);
}

// DOM添加一个开启键盘鼠标漫游模式的按钮,使用绝对定位放在屏幕左上角,用于测试
let enterButton = document.createElement('button');
enterButton.innerText = '开启';
enterButton.style.position = 'absolute';
enterButton.style.left = '20px';
enterButton.style.top = '20px';
enterButton.onclick = enterKeyBoardMouseRoamingMode;
document.body.appendChild(enterButton);

/**
 * 退出键盘鼠标漫游模式
 */
function exitKeyBoardMouseRoamingMode() {
  console.log('退出漫游');
  // 1.移除鼠标监听事件
  if (handler) {
    handler.destroy();
    handler = null;
  }

  // 2.移除键盘监听事件
  document.removeEventListener('keydown', keyDown, false);
  document.removeEventListener('keyup', keyUp, false);

  // 3.移除渲染事件
  viewer.clock.onTick.removeEventListener(renderEvent);

  // 4.启用默认相机操作模式
  scene.screenSpaceCameraController.enableRotate = true;
  scene.screenSpaceCameraController.enableTranslate = true;
  scene.screenSpaceCameraController.enableZoom = true;
  scene.screenSpaceCameraController.enableTilt = true;
  scene.screenSpaceCameraController.enableLook = true;
}

// DOM添加一个关闭键盘鼠标漫游模式的按钮,使用绝对定位放在屏幕左上角,用于测试
let exitButton = document.createElement('button');
exitButton.innerText = '关闭';
exitButton.style.position = 'absolute';
exitButton.style.left = '70px';
exitButton.style.top = '20px';
exitButton.onclick = exitKeyBoardMouseRoamingMode;
document.body.appendChild(exitButton);

/**
 * 键盘按下
 */
function keyDown(event) {
  let flagName = getFlagFromKeyCode(event.keyCode);
  if (typeof flagName !== 'undefined') {
    flags[flagName] = true;
  }
}

/**
 * 键盘弹起
 */
function keyUp(event) {
  let flagName = getFlagFromKeyCode(event.keyCode);
  if (typeof flagName !== 'undefined') {
    flags[flagName] = false;
  }
}

/**
 * 渲染函数
 */
function renderEvent() {
  // 镜头转向
  if (flags.looking) {
    let width = viewer.canvas.clientWidth;
    let height = viewer.canvas.clientHeight;
    let lookFactor = 0.05; // 镜头转向系数,系数越大约灵敏,此处取0.05比较适中
    let x = (flags.endPosition.x - flags.startPosition.x) / width;
    let y = -(flags.endPosition.y - flags.startPosition.y) / height;
    // 计算出x,y之后,有两种方式实现镜头,经过测试感觉方式 1更流畅
    // 方式 1
    camera.lookRight(x * lookFactor);
    camera.lookUp(y * lookFactor);
    // 方式 2
    // camera.setView({
    //   orientation: {
    //     heading: camera.heading + x * lookFactor,
    //     pitch: camera.pitch + y * lookFactor,
    //     roll: 0.0,
    //   },
    // });
  }
  // 根据高度来决定镜头移动的速度
  let cameraHeight = ellipsoid.cartesianToCartographic(camera.position).height;
  let moveRate = cameraHeight / 100.0;
  if (flags.moveForward) {
    camera.moveForward(moveRate);
  }
  if (flags.moveBackward) {
    camera.moveBackward(moveRate);
  }
  if (flags.moveUp) {
    camera.moveUp(moveRate);
  }
  if (flags.moveDown) {
    camera.moveDown(moveRate);
  }
  if (flags.moveLeft) {
    camera.moveLeft(moveRate);
  }
  if (flags.moveRight) {
    camera.moveRight(moveRate);
  }
}

/**
 * 从键盘码获取flag标记
 */
function getFlagFromKeyCode(keyCode) {
  switch (keyCode) {
    case 'W'.charCodeAt(0):
      return 'moveForward';
    case 'S'.charCodeAt(0):
      return 'moveBackward';
    case 'Q'.charCodeAt(0):
      return 'moveUp';
    case 'E'.charCodeAt(0):
      return 'moveDown';
    case 'D'.charCodeAt(0):
      return 'moveRight';
    case 'A'.charCodeAt(0):
      return 'moveLeft';
    default:
      return undefined;
  }
}

4 无需鼠标控制的情况

如果我们项目中只需要键盘控制则可以对代码精简如下:

4.1 HTML代码

<div id="cesiumContainer" style="width:100%;height:100%"></div>

4.2 JavaScript代码

// 初始化地球小部件
let viewer = new Cesium.Viewer('cesiumContainer');
// 声明变量,以下代码可能会多次用到
let camera = viewer.camera;
let ellipsoid = viewer.scene.globe.ellipsoid;
// 声明相机漫游标记
let flags = null;

/**
 * 进入键盘漫游模式
 */
function enterKeyBoardMouseRoamingMode() {
  console.log('进入漫游模式');
  // 1.初始化相机漫游的标记
  flags = {
    moveForward: false, // 是否向前移动
    moveBackward: false, // 是否向后移动
    moveLeft: false, // 是否向左移动
    moveRight: false, // 是否向右移动
    moveUp: false, // 是否向上移动
    moveDown: false, // 是否向下移动
  }; // 相机漫游标记

  // 2.添加键盘监听事件
  // 键盘按下事件
  document.addEventListener('keydown', keyDown, false);
  // 键盘弹起事件
  document.addEventListener('keyup', keyUp, false);

  // 3.添加渲染事件
  viewer.clock.onTick.addEventListener(renderEvent);
}

// DOM添加一个开启键盘鼠标漫游模式的按钮,使用绝对定位放在屏幕左上角,用于测试
let enterButton = document.createElement('button');
enterButton.innerText = '开启';
enterButton.style.position = 'absolute';
enterButton.style.left = '20px';
enterButton.style.top = '20px';
enterButton.onclick = enterKeyBoardMouseRoamingMode;
document.body.appendChild(enterButton);

/**
 * 退出键盘漫游模式
 */
function exitKeyBoardMouseRoamingMode() {
  console.log('退出漫游');
  // 1.移除键盘监听事件
  document.removeEventListener('keydown', keyDown, false);
  document.removeEventListener('keyup', keyUp, false);

  // 2.移除渲染事件
  viewer.clock.onTick.removeEventListener(renderEvent);
}

// DOM添加一个关闭键盘鼠标漫游模式的按钮,使用绝对定位放在屏幕左上角,用于测试
let exitButton = document.createElement('button');
exitButton.innerText = '关闭';
exitButton.style.position = 'absolute';
exitButton.style.left = '70px';
exitButton.style.top = '20px';
exitButton.onclick = exitKeyBoardMouseRoamingMode;
document.body.appendChild(exitButton);

/**
 * 键盘按下
 */
function keyDown(event) {
  let flagName = getFlagFromKeyCode(event.keyCode);
  if (typeof flagName !== 'undefined') {
    flags[flagName] = true;
  }
}

/**
 * 键盘弹起
 */
function keyUp(event) {
  let flagName = getFlagFromKeyCode(event.keyCode);
  if (typeof flagName !== 'undefined') {
    flags[flagName] = false;
  }
}

/**
 * 渲染函数
 */
function renderEvent() {
  // 根据高度来决定镜头移动的速度
  let cameraHeight = ellipsoid.cartesianToCartographic(camera.position).height;
  let moveRate = cameraHeight / 100.0;
  if (flags.moveForward) {
    camera.moveForward(moveRate);
  }
  if (flags.moveBackward) {
    camera.moveBackward(moveRate);
  }
  if (flags.moveUp) {
    camera.moveUp(moveRate);
  }
  if (flags.moveDown) {
    camera.moveDown(moveRate);
  }
  if (flags.moveLeft) {
    camera.moveLeft(moveRate);
  }
  if (flags.moveRight) {
    camera.moveRight(moveRate);
  }
}

/**
 * 从键盘码获取flag标记
 */
function getFlagFromKeyCode(keyCode) {
  switch (keyCode) {
    case 'W'.charCodeAt(0):
      return 'moveForward';
    case 'S'.charCodeAt(0):
      return 'moveBackward';
    case 'Q'.charCodeAt(0):
      return 'moveUp';
    case 'E'.charCodeAt(0):
      return 'moveDown';
    case 'D'.charCodeAt(0):
      return 'moveRight';
    case 'A'.charCodeAt(0):
      return 'moveLeft';
    default:
      return undefined;
  }
}

5 延伸阅读

如果对这篇博文理解困难,请浏览以下博文,帮助您理解Cesium中的事件。
Cesium 事件详解
Cesium 核心类Viewer-查看器详解

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GISer小辉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值