Files
bdqh-course-platform-master/src/render/views/BDSatellite3DPostureControl/SatelliteModelView/index.vue
2023-11-29 16:08:12 +08:00

226 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div ref="satelliteRef" id="satellite" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useMessage } from 'naive-ui'
import * as THREE from 'three'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'
import { LineBreakParser } from '@/utils/parser'
const message = useMessage()
const accRate = 16384.0
const gyrRate = 131.072
const satelliteRef = ref(null)
let renderer = null
let scene = null
let camera = null
let satellite = null
const initSatellite = async () => {
// 创建场景对象Scene
scene = new THREE.Scene()
// 添加相机
const w = satelliteRef.value.clientWidth, h = satelliteRef.value.clientHeight
const aspect = w / h
const frustumSize = 15
camera = new THREE.OrthographicCamera(
frustumSize * aspect / -2, frustumSize * aspect / 2,
frustumSize / 2, frustumSize / -2)
camera.position.z = 100
scene.add(camera)
// 添加renderer
renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(w, h)
// 加载卫星模型
const modelDirPath = '../models/geo/'
const rgbeLoader = new RGBELoader().setPath(modelDirPath)
const environment = await rgbeLoader.loadAsync('env.hdr')
environment.mapping = THREE.EquirectangularReflectionMapping
scene.environment = environment
const gltfLoader = new GLTFLoader().setPath(modelDirPath)
const geoModel = await gltfLoader.loadAsync('geo.gltf')
geoModel.scene.traverse( function ( child ) {
if ( child.isMesh ) {
child.material.emissive = child.material.color
child.material.emissiveMap = child.material.map
}
})
satellite = geoModel.scene
scene.add(satellite)
camera.lookAt(satellite.position)
satelliteRef.value.appendChild(renderer.domElement)
}
let acc = new Array(3), gyr = new Array(3)
let firstSample = true
let lastTime = 0
let RwGyro = [0, 0, 0] // 重新读取陀螺仪
let Awz = [0, 0] // XZ/ YZ平面和Z轴(度)R的投影之间的角度
let RwEst = [0, 0, 0]
const wGyro = 10.0
const HALF_PI = Math.PI / 2
const render = () => {
if (acc && gyr) {
updateRwExt(acc, gyr)
}
if (satellite) {
satellite.rotation.x = HALF_PI * -RwEst[0]
satellite.rotation.z = HALF_PI * RwEst[1]
// satellite.rotation.y = HALF_PI * RwEst[2]
}
renderer?.render(scene, camera)
}
const updateRwExt = (acc, gyr) => {
normalize3DVec(acc)
let currentTime = Date.now()
let interval = currentTime - lastTime
lastTime = currentTime
if (firstSample || isNaN(RwEst[0])) {
for (let index = 0; index < 3; index++) {
RwEst[index] = acc[index]// 初始化加速度传感器读数
}
} else {
// 对RwGyro进行评估
if (Math.abs(RwEst[2]) < 0.1) {
// Rz值非常的小,它的作用是作为Axz与Ayz的计算参照值,防止放大的波动产生错误的结果。
// 这种情况下就跳过当前的陀螺仪数据,使用以前的。
for (let index = 0; index < 3; index++) {
RwGyro[index] = RwEst[index]
}
} else {
// ZX/ZY平面和Z轴R的投影之间的角度,基于最近一次的RwEst值
for (let index = 0; index < 2; index++) {
let tmpf = gyr[index] // 获取当前陀螺仪的deg/s
tmpf *= interval / 1000.0 // 得到角度变化值
Awz[index] = Math.atan2(RwEst[index], RwEst[2]) * 180 / Math.PI // 得到角度并转换为度
Awz[index] += tmpf // 根据陀螺仪的运动得到更新后的角度
}
// 判断RzGyro是多少,主要看Axz的弧度是多少
// 当Axz在-90 ..90 => cos(Awz) >= 0这个范围内的时候RzGyro是准确的
let signRzGyro = (Math.cos(Awz[0] * Math.PI / 180) >= 0) ? 1 : -1
// 从Awz的角度值反向计算RwGyro的公式请查看网页 http://starlino.com/imu_guide.html
for (let index = 0; index < 2; index++) {
RwGyro[0] = Math.sin(Awz[0] * Math.PI / 180)
RwGyro[0] /= Math.sqrt(1 + squared(Math.cos(Awz[0] * Math.PI / 180)) * squared(Math.tan(Awz[1] * Math.PI / 180)))
RwGyro[1] = Math.sin(Awz[1] * Math.PI / 180)
RwGyro[1] /= Math.sqrt(1 + squared(Math.cos(Awz[1] * Math.PI / 180)) * squared(Math.tan(Awz[0] * Math.PI / 180)))
}
RwGyro[2] = signRzGyro * Math.sqrt(1 - squared(RwGyro[0]) - squared(RwGyro[1]))
// 把陀螺仪与加速度传感器的值进行结合
for (let index = 0; index < 3; index++) {
RwEst[index] = (acc[index] + wGyro * RwGyro[index]) / (1 + wGyro)
}
normalize3DVec(RwEst)
}
}
}
const normalize3DVec = (vector) => {
const R = Math.sqrt(squared(vector[0]) + squared(vector[1]) + squared(vector[2]))
vector[0] /= R
vector[1] /= R
vector[2] /= R
}
const squared = x => Math.pow(x, 2)
const animate = () => {
render()
requestAnimationFrame(animate)
}
// 判断字符串是否满足协议格式
const isValidFormat = data => {
if (!data) {
return false
}
if (data.indexOf('@POSE') < 0) {
return false
}
const splitIndex = data.indexOf('*') + 1
const dataStr = data.substring(0, splitIndex)
const csStr = data.substring(splitIndex)
if (!dataStr || !csStr) {
return false
}
if (dataStr.length !== parseInt(csStr.trim())) {
return false
}
return true
}
// 将data转换成加速度和角速度
const data2AccGyr = data => {
const startIndex = data.indexOf(',') + 1
const endIndex = data.indexOf('*')
const dataArr = data.substring(startIndex, endIndex).split(',').map(parseFloat)
acc = new Array(3), gyr = new Array(3)
acc[0] = dataArr[0] / accRate
acc[1] = dataArr[1] / accRate
acc[2] = dataArr[2] / accRate
gyr[0] = dataArr[3] / gyrRate
gyr[1] = dataArr[4] / gyrRate
gyr[2] = dataArr[5] / gyrRate
}
const decoder = new TextDecoder()
const parser = new LineBreakParser()
const onSerialPortData = (value: Uint8Array) => {
const lineArr = parser.parse(decoder.decode(value))
lineArr.forEach(line => {
if (!isValidFormat(line)) return
data2AccGyr(line)
})
}
const connectErrMsg = (value: Boolean) => {
message.error('无法连接到串口设备')
}
window.ipcRenderer.receive('CANNOT_CONNECT',connectErrMsg)
window.ipcRenderer.send('COM_ON')
window.ipcRenderer.receive('COM_LISTENER',onSerialPortData)
onMounted(() => {
initSatellite()
animate()
})
</script>