纯前端用cesium实现无人机模拟飞行
1.效果预览
2.项目搭建
项目采用
vite + vue3 + ts
框架搭建,使用vite-plugin-cesium
插件引入cesium
vite.config.ts
配置
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import cesium from "vite-plugin-cesium"
export default {
plugins: [
vue(),
cesium()
]
}
3.搭建 cesium
<template>
<div style="width:100vw; height:100vh" ref="container"></div>
</templete>
<script setup lang="ts">
import * as cesium from "cesium"
import { onMounted, ref } from "vue"
const container = ref<HTMLElement>()
onMounted(()=>{
const configs = {
infoBox: false,
selectionIndicator: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
navigationHelpButton: false,
fullscreenButton: false,
homeButton: false,
sceneModePicker: false,
timeline: false,
shadows: true,
shouldAnimate: true,
}
const viewer = new cesium.Viewer(container.value, {
...configs,
terrainProvider: cesium.createWorldTerrain()
})
})
</script>
4.导入无人机模型
无人机模型文件放置在:项目根目录下
public/models/uav.glb
<template>
<div style="width:100vw; height:100vh" ref="container"></div>
</templete>
<script setup lang="ts">
import * as cesium from "cesium"
import { onMounted, ref } from "vue"
const container = ref<HTMLElement>()
onMounted(()=>{
const configs = {...}
const viewer = new cesium.Viewer(container.value, {...})
//模型的初始位置(经度,维度,高度)
const position = cesium.Cartesian3.fromDegrees(120, 30, 2000)
const hpr = new cesium.HeadingPitchRoll()
const orientation = cesium.Transforms.headingPitchRollQuaternion(position, hpr)
const model = viewer.entities.add({
name: "uax",
position,
orientation,
model: {
uri: "/models/uav.glb",
minimumPixelSize: 128,
maximumScale: 20000,
runAnimations: true
}
})
//摄像头跟随模型
viewer.trackedEntity = model
})
</script>
5. 完成无人机的基本动作
<template>
<div style="width:100vw; height:100vh" ref="container"></div>
</templete>
<script setup lang="ts">
import * as cesium from "cesium"
import { onMounted, ref } from "vue"
const container = ref<HTMLElement>()
//飞行姿态参数
const flightParams = reactive({
at: 30,
lng: 120,
altitude: 2000,
heading: 0,
pitch: 0,
roll: 0,
correction: 1,
speed: 1224
})
onMounted(()=>{
//...
const model = viewer.entities.add({...})
viewer.trackedEntity = model
//根据flightParams参数调整无人机的飞行姿态
const adjustFlightAttitude = () => {
// 在requestAnimationFrame中调用, 每秒更新60次
// temp 计算的是每次更新无人机移动的经纬度度数
const temp = flightParams.speed / 60 / 60 / 60 / 110
flightParams.lng += temp * Math.cos(flightParams.heading)
flightParams.lat -= temp * Math.sin(flightParams.heading)
const { lng, lat, altitude, heading, pitch, roll } = flightParams
flightParams.altitude += temp * Math.sin(pitch) * 110 * 1000 * 10
const position = cesium.Cartesian3.fromDegrees(lng, lat, altitude)
const hpr = new cesium.HeadingPitchRoll(heading, pitch, roll)
const orientation = cesium.Transforms.headingPitchRollQuaternion(position, hpr)
model.orientation = orientation
model.position = position
}
const renderer = () => {
adjustFlightAttitude()
requestAnimationFrame(renderer)
}
renderer()
})
</script>
6.完成无人机的按键监听
没有通过用户按键事件直接调整飞行参数, 是为了实现按键无冲同时姿态调整过渡更加顺畅
<template>
<div style="width:100vw; height:100vh" ref="container"></div>
</templete>
<script setup lang="ts">
import * as cesium from "cesium"
import { onMounted, ref } from "vue"
const container = ref<HTMLElement>()
const flightParams = reactive({...})
const DIRECTION_ENUM = {
UP: "w",
DOWN: "s",
LEFT: "a",
RIGHT: "d",
SPEED_UP: "q",
SPEED_DOWN: "e",
}
const keysMap = {
[DIRECTION_ENUM.UP]: false,
[DIRECTION_ENUM.DOWN]: false,
[DIRECTION_ENUM.LEFT]: false,
[DIRECTION_ENUM.RIGHT]: false,
[DIRECTION_ENUM.SPEED_UP]: false,
[DIRECTION_ENUM.SPEED_DOWN]: false,
}
onMounted(()=>{
//...
const model = viewer.entities.add({...})
viewer.trackedEntity = model
const adjustFlightAttitude = () => {...}
//开启按键监听
const openKeysListener = () => {
document.addEventListener("keydown", (e: KeyboardEvent) => {
if (Object.keys(keysMap).includes(e.key)){
keysMap[e.key] = true
}
})
document.addEventListener("keyup", (e: KeyboardEvent) => {
if (Object.keys(keysMap).includes(e.key)){
keysMap[e.key] = false
}
})
}
const renderer = () => {
adjustFlightAttitude()
requestAnimationFrame(renderer)
}
renderer()
openKeysListener()
})
</script>
7.按键监听后调整飞行参数
<template>
<div style="width:100vw; height:100vh" ref="container"></div>
</templete>
<script setup lang="ts">
import * as cesium from "cesium"
import { onMounted, ref } from "vue"
const container = ref<HTMLElement>()
const flightParams = reactive({...})
const DIRECTION_ENUM = {...}
const keysMap = {...}
onMounted(()=>{
//...
const adjustFlightAttitude = () => {...}
const openKeysListener = () => {...}
const adjustFlightParams = () => {
//无人机加速
if (keysMap[DIRECTION_ENUM.SPEED_UP]) {
flightParams.speed += 100
}
//无人机减速
if (keysMap[DIRECTION_ENUM.SPEED_DOWN]) {
flightParams.speed -= 100
}
//机体爬升
if (keysMap[DIRECTION_ENUM.UP] && flightParams.pitch <= 0.3) {
flightParams.pitch += 0.005
const { speed, pitch } = flightParams
const temp = (flightParams.speed / 60 / 60 / 60) * 110
flightParams.altitude += temp * Math.sin(pitch)
}
//机体俯冲
if (keysMap[DIRECTION_ENUM.DOWN] && flightParams.pitch >= -0.3) {
flightParams.pitch -= 0.01
const { speed, pitch } = flightParams
const temp = (flightParams.speed / 60 / 60 / 60) * 110
flightParams.altitude += temp * Math.sin(pitch)
}
//机体左转
if (keysMap[DIRECTION_ENUM.LEFT]) {
flightParams.heading -= 0.005
if (flightParams.roll > -0.785) flightParams.roll -= 0.005
}
//机体右转
if (keysMap[DIRECTION_ENUM.RIGHT]) {
flightParams.heading += 0.005
if (flightParams.roll < 0.785) flightParams.roll += 0.005
}
//方向自动回正
const { heading, pitch, roll } = flightParams
const { abs, cos } = Math
flightParams.correction = abs(cos(heading) * cos(pitch))
if (abs(heading) < 0.001) flightParams.heading = 0
if (abs(roll) < 0.001) flightParams.roll = 0
if (abs(pitch) < 0.001) flightParams.pitch = 0
if (flightParams.roll > 0) flightParams.roll -= 0.003
if (flightParams.roll < 0) flightParams.roll += 0.003
if (flightParams.pitch < 0) flightParams.pitch += 0.005
if (flightParams.pitch > 0) flightParams.pitch -= 0.003
}
const renderer = () => {
adjustFlightParams()
adjustFlightAttitude()
requestAnimationFrame(renderer)
}
renderer()
openKeysListener()
})
</script>
8.完整代码和DEMO实例
- 如果需要阅读完整代码,可以访问fengtianxi001/MF-UAV仓库, 顺便再帮我点个
star
吧! - 在线案例可以访问MF-UAV, 在线预览效果