在 Cesium 三维项目开发中,有时候客户并不希望将漫游的路径规划的太死,而是想要自己使用键盘的wasd或者上下左右控制物体在三维世界中漫游,那么这个功能应该如何实现呢?今天就带大家实现一下。

实现思路

Step 1:创建可动态控制的小车实体

首先我们可以想到这是一个会动的实体,所以肯定需要使用之前所学的callbackProperty属性来实现

CallbackProperty 是一种用于动态计算属性值的对象。它允许用户提供一个回调函数,该回调函数会在需要计算属性值时被调用,并返回相应的属性值。这使得属性值可以根据特定的条件或动态变化的数据进行实时计算。

所以模型的位置属性和方向属性都应该被设置为callbackProperty,并且通过我们的交互事件,不断的更新

我们可以初始化模型,并构造初始的位置和朝向变量

这里初始的位置绑定在this.position上,初始的方向写在this.hpRoll上

// 传入初始位置,笛卡尔坐标,添加小车addModel(position) {// 每次点击左右键模型旋转的角度this.radian = Cesium.Math.toRadians(1.0);this.speedVector = new Cesium.Cartesian3();this.position = position;// 小车朝向this.hpRoll = new Cesium.HeadingPitchRoll();this.hpRoll.heading =this.viewer.scene.camera.heading + Cesium.Math.toRadians(-90);this.carEntity = this.viewer.entities.add({		id: "car",		position: new Cesium.CallbackProperty(this.getPositin.bind(this), false),// 根据所提供的速度计算点		orientation: new Cesium.CallbackProperty(this.getOrientation.bind(this),false		),		model: {			uri: "/src/assets/gltf/redCar.glb",			scale: 0.04,		},	});return this.carEntity;}

  getPositin() {    return this.position;  }
  getOrientation() {

    Step2:两个关键变量的计算

    如何更新这两个关键变量

    关于汽车的方向

    直接控制this.hpRoll的朝向角即可,这个过程中可以通过参数调节朝向角的变化速度,这样我们可以得到准确的hpRoll的值

    this.roamEvent = () => {this.traceHandler();if (this.flag.moveLeft) {this.hpRoll.heading -= this.radian;	}if (this.flag.moveRight) {this.hpRoll.heading += this.radian;	}};this.viewer.clock.onTick.addEventListener(this.roamEvent);

      其中flag是一个标识,用于确定当前小车的状态,当触发对应按键的时候,会改变flag的属性,然后调整小车的方向

      flag的结构如下
      this.flag = {	moveUp: false,	moveDown: false,	moveLeft: false,	moveRight: false,};

      然后通过监听键盘事件修改flag的数据

      //  上下左右 wasd控制车辆移动setFlagStatus(key, value) {	switch (key.keyCode) {		case 37:// 左this.flag.moveLeft = value;
      break;		case 38:// 上this.flag.moveUp = value;
      break;		case 39:// 右this.flag.moveRight = value;
      break;		case 40:this.flag.moveDown = value;
      // 下break;		case 65:this.flag.moveLeft = value;
      // 左break;		case 68:this.flag.moveRight = value;
      // 右break;		case 83:this.flag.moveDown = value;
      // 下break;		case 87:this.flag.moveUp = value;
      // 下break;	}}
      keyDonwCallback(e) {this.setFlagStatus(e, true);}

      ​​

      关于汽车的位置计算

      我们可以使用之前学过的本地坐标的计算方法,首先计算前一帧小车的位置,然后根据这个坐标得到模型矩阵,然后再通过小车的朝向与速度,计算下一帧的位置

      image.png

      在监听事件中添加前进和后退的逻辑

      //   开始自主漫游startRoam() {if (this.carEntity) {		document.addEventListener("keydown", this.keyDonwCallback.bind(this));		document.addEventListener("keyup", this.keyUpCallback.bind(this));this.roamEvent = () => {this.traceHandler();
      if (this.flag.moveLeft) {this.hpRoll.heading -= this.radian;			}if (this.flag.moveRight) {this.hpRoll.heading += this.radian;			}
      if (this.flag.moveUp) {this.moveCar(1);			}if (this.flag.moveDown) {this.moveCar(-1);			}		};this.viewer.clock.onTick.addEventListener(this.roamEvent);	}}

      其中moveCar函数,处理小车的位置逻辑

      具体逻辑如下:

      1. 我们将当前小车的位置clone一份,用来计算下一帧小车会出现的位置

      2. 通过当前小车的速度,如果isUp为true,说明按下的是前进键,这时候我们会得到一个X轴方向的向量,这个向量的模为速度*时间,同理,如果是后退,我们将朝着-X轴构造一个向量

      3. 根据当前的hpRoll小车朝向,以及当前的世界坐标clonePosition,通过headingPitchRollToFixedFrame构造一个方向和hpRoll一致的模型矩阵

      4. 通过模型矩阵左乘我们刚刚构造出来的向量,得到下一帧小车的位置position

      5. 然后把position丢给sampleHeight处理一下真实的地形高度,避免小车跑到地下去

      6. 还可以对比上一帧和下一帧的位置地形高,做一个碰撞检测

      moveCar(isUp) {const clonePosition = _.clone(this.position);const {height:prevHeight}=this.setHeight(clonePosition)
      // 位移的距离const distance = this._speed / 20;let speedVectorX = new Cesium.Cartesian3();
      // 计算速度矩阵x轴方向if (isUp > 0) {		speedVectorX = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.UNIT_X,			distance,			speedVectorX		);	} else if (isUp < 0) {		speedVectorX = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.UNIT_X,			-distance,			speedVectorX		);	} else {		speedVectorX = Cesium.Cartesian3.multiplyByScalar(Cesium.Cartesian3.UNIT_X,0,			speedVectorX		);	}
      let fixedFrameTransforms =Cesium.Transforms.localFrameToFixedFrameGenerator("east", "north");let modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(		clonePosition,this.hpRoll,Cesium.Ellipsoid.WGS84,		fixedFrameTransforms	);let position = Cesium.Matrix4.multiplyByPoint(		modelMatrix,		speedVectorX,new Cesium.Cartesian3()	);
      const { lng: lng1, lat: lat1, height: real } = this.setHeight(position);const heightDiff=real-prevHeight// 碰撞检测if(heightDiff>1){return	}this.position = Cesium.Cartesian3.fromDegrees(lng1, lat1, real);}

      完整代码:

      最后我们可以将自主漫游封装为一个class,方便调用

      由于文章篇幅有限

      需要完整代码的同学

      图片

      PS:本文为新中地原创,转载请标注来源。

      Logo

      DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

      更多推荐