开源|cesium自主漫游实战教程(附完整源码)

该文章已生成可运行项目,

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

图片

原文地址以及完整代码点这里查看https://www.bilibili.com/opus/1123447699605028884

实现思路

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() {
    const result = Cesium.Transforms.headingPitchRollQuaternion(
      this.position,
      this.hpRoll
    );
    return result;
  }

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);
}

keyUpCallback(e) {
	this.setFlagStatus(e, false);
}

关于汽车的位置计算

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

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,方便调用

由于文章篇幅有限

需要完整代码的同学

+博主【yaogis888】无偿分享

图片

使用方法

const drawTool=new DrawTool(viewer)
const roam=new autoRoam(viewer);
drawTool.active(drawTool.DrawTypes.Point)
drawTool.DrawEndEvent.addEventListener((ent,positions)=>{
	drawTool.removeAllDrawEnts()
	if(positions.length){
		roam.addModel(positions[0])
		roam.startRoam()
	}
})

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

👇👇👇

针对有就业需求的同学,可以系统学习我们三维WebGIS开发特训营。

由浅入深、循序渐进,从零基础必学的 HTML 网页结构搭建起步,逐步深入 CSS 样式设计、JavaScript 交互逻辑,再到 WebGIS 核心技术(如 OpenLayers、Mapbox地图框架),最终聚焦三维 GIS 开发实战,通过 Cesium 引擎实现地形建模、三维场景渲染、空间分析等核心功能。

全程以项目驱动教学,每阶段配套真实企业级案例(如智慧城市三维可视化、智慧地铁地图开发),学完不仅能独立完成三维 GIS 项目开发,更能具备解决实际业务问题的能力,轻松向实战型三维 GIS 开发工程师转型,感兴趣的同学别错过这波入门好机会!

GIS开发特训营全年开班 可同步试听,+yaogis888(咨询/试听);

☑0基础可学   ☑助力考研   ☑优选赛道

本文学习前提需要具备一定的GIS开发能力,若你还不熟悉 Cesium 基础,建议学习《Cesium 零基础入门教程》,掌握坐标转换、图元操作等知识,助力理解标绘原理!

【Cesium入门教程】第一课:Cesium简介与快速入门,零基础适用-CSDN博客

【Cesium入门教程】第二课:基础操作与地图控制-CSDN博客

【Cesium零基础入门教程】第三课:添加实体(Entity)点、线、面、模型_new cesium.entity 所有参数-CSDN博客

【Cesium零基础入门教程】第四课:删除实体的三种方法-CSDN博客

【Cesium零基础入门教程】第五课:Cesium中四种常见地理数据格式(GeoJSON、TopoJSON、KML、CZML)的加载方法_cesium.geojsondatasource-CSDN博客

【Cesium入门教程】第六课:四种常见的地理数据格式GeoJSON、TopoJSON、KML 、CZML在Cesium中的使用_cesium.geojsondatasource默认坐标系-CSDN博客

【Cesium零基础入门教程】第七课:Cesium提供了两种可视化API:高级的Entity API和底层的Primitive API的实例应用_cesium primitive api-CSDN博客

【Cesium零基础入门教程】第八课:使用Cesium.js库创建一个3D地球视图,加载一个GLTF飞机模型(3D模型)-CSDN博客

本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值