Files
apt15e/src/pages/modules/gl-map2.vue
2025-08-05 14:20:40 +08:00

365 lines
10 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 class="canvas-container" :style="{'background-color': isConnected ? 'rgba(4, 33, 58, 70%)' : '#fff'}">
<div ref="container" class="point-cloud-container"></div>
<!-- <div v-if="!isConnected" class="reload_bg"><el-button type="danger">刷新</el-button></div> -->
</div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { mapGetters } from 'vuex'
import { points } from '../../config/point.js'
export default {
/* eslint-disable */
data () {
return {
isConnected: true,
points: [], // 存储所有点云数据
ws: null, // WebSocket 实例
// Three.js 相关对象
scene: null,
camera: null,
renderer: null,
controls: null,
pointCloud: null,
vehicleSprite: null,
// 渲染循环
animationFrameId: null,
// 性能优化
pointsBuffer: [],
bufferUpdateThreshold: 500,
lastUpdateTime: 0,
// 小车图片
vehicleImage: require('../../images/new/agv.png'),
// 2D视图参数
viewSize: 20,
minZoom: 0.5,
maxZoom: 5
}
},
computed: {
...mapGetters(['agvObj', 'position', 'rotation']),
},
watch: {
agvObj: {
handler() {
this.updateVehiclePosition()
},
deep: true
},
position: {
handler() {
this.updateVehiclePosition()
},
deep: true
},
rotation: {
handler() {
this.updateVehiclePosition()
},
deep: true
}
},
mounted () {
this.initThreeJS()
// this.initWebSocket()
this.init()
this.startRendering()
window.addEventListener('resize', this.handleResize)
},
beforeDestroy () {
if (this.ws) this.ws.close()
window.removeEventListener('resize', this.handleResize)
if (this.renderer) this.renderer.dispose()
if (this.controls) {
this.controls.dispose()
}
},
methods: {
initWebSocket() {
const wsHost = process.env.VUE_APP_API_BASE_URL.replace(/^https?:\/\//, '')
const sid = this.$store.getters.userInfo === 'true' ? 1 : 2
this.ws = new WebSocket(`ws://${wsHost}/webSocket/PointCloudData/${sid}`)
this.ws.onopen = () => {
console.log('WebSocket connected')
}
this.ws.onmessage = (event) => {
const newPoints = event.data
// 确保所有点都在XY平面(z=0)
const points2D = newPoints.map(p => ({ x: p.x, y: p.y, z: 0 }))
this.processNewPoints(points2D)
}
this.ws.onerror = (error) => {
console.error('WebSocket error:', error)
}
this.ws.onclose = () => {
console.log('WebSocket disconnected')
}
},
init () {
this.isConnected = true
const newPoints = points.data
const points2D = newPoints.map(p => ({ x: p.x, y: p.y, z: 0 }))
this.processNewPoints(points2D)
},
initThreeJS() {
// viewSize: 控制视图范围大小,值越小看到的范围越小(视角越近)
// camera.position.z: 相机Z轴位置值越小视角越近
// camera.zoom: 直接控制缩放级别大于1放大小于1缩小
// 1. 创建场景
this.scene = new THREE.Scene()
// 2. 创建正交相机(2D视图)
const container = this.$refs.container
const aspect = container.clientWidth / container.clientHeight
this.camera = new THREE.OrthographicCamera(
-this.viewSize * aspect / 2,
this.viewSize * aspect / 2,
this.viewSize / 2,
-this.viewSize / 2,
0.1,
1000
)
this.camera.position.set(0, 0, 40)
this.camera.lookAt(0, 0, 0)
// 3.创建渲染器 - 启用alpha通道实现透明背景
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true, // 关键启用alpha通道
transparent: true // 关键:允许透明
});
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.setSize(container.clientWidth, container.clientHeight)
// 可选设置clearAlpha确保完全透明
this.renderer.setClearAlpha(0);
container.appendChild(this.renderer.domElement)
// 4. 添加OrbitControls并配置为2D模式
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.configure2DControls()
// 5. 添加2D坐标轴辅助
const axesHelper = new THREE.AxesHelper(10)
this.scene.add(axesHelper)
// 6. 初始化点云
this.initPointCloud()
// 7. 初始化车辆精灵
this.initVehicleSprite()
},
configure2DControls() {
// 禁用旋转
this.controls.enableRotate = false
// 启用平面平移
this.controls.screenSpacePanning = true
// 设置缩放限制
this.controls.minZoom = this.minZoom
this.controls.maxZoom = this.maxZoom
// 启用阻尼效果
this.controls.enableDamping = true
this.controls.dampingFactor = 0.25
// 确保相机保持2D视角
this.controls.addEventListener('change', () => {
this.camera.position.z = 40
// this.camera.lookAt(this.controls.target)
})
// 修复触摸事件处理
this.fixTouchEvents()
},
fixTouchEvents() {
// 确保控制器的触摸处理函数存在
if (!this.controls) return;
// 备份原始触摸处理函数
const originalOnTouchStart = this.controls.onTouchStart
const originalOnTouchMove = this.controls.onTouchMove
const originalOnTouchEnd = this.controls.onTouchEnd
// 修复触摸开始事件
this.controls.onTouchStart = (event) => {
if (!event.touches) return
if (originalOnTouchStart) originalOnTouchStart(event)
}
// 修复触摸移动事件
this.controls.onTouchMove = (event) => {
if (!event.touches || event.touches.length === 0) return
if (event.touches[0] && event.touches[0].clientX !== undefined) {
if (originalOnTouchMove) originalOnTouchMove(event)
}
}
// 修复触摸结束事件
this.controls.onTouchEnd = (event) => {
if (!event.touches) return
if (originalOnTouchEnd) originalOnTouchEnd(event)
}
},
initPointCloud() {
const geometry = new THREE.BufferGeometry()
const material = new THREE.PointsMaterial({
color: 0xffffff,
size: 1,
transparent: true,
opacity: 1
})
this.pointCloud = new THREE.Points(geometry, material)
this.scene.add(this.pointCloud)
},
initVehicleSprite() {
const textureLoader = new THREE.TextureLoader()
textureLoader.load(this.vehicleImage, (texture) => {
// 97px × 67px图片的比例
const imageAspect = 97 / 67
const spriteWidth = 2
const spriteHeight = spriteWidth / imageAspect
this.vehicleSprite = new THREE.Sprite(
new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: 0.9
})
)
this.vehicleSprite.scale.set(spriteWidth, spriteHeight, 1)
// 角度偏移(根据图片初始朝向调整)
this.updateVehiclePosition()
this.scene.add(this.vehicleSprite)
})
},
processNewPoints(newPoints) {
this.pointsBuffer.push(...newPoints)
const now = performance.now()
if (this.pointsBuffer.length >= this.bufferUpdateThreshold || now - this.lastUpdateTime > 1000) {
this.updatePointCloudGeometry()
this.lastUpdateTime = now
}
},
updatePointCloudGeometry() {
if (this.pointsBuffer.length === 0) return
this.points = this.points.concat(this.pointsBuffer)
this.pointsBuffer = []
if (this.points.length > 50000) {
this.points = this.points.slice(-40000)
}
const positions = new Float32Array(this.points.length * 3)
for (let i = 0; i < this.points.length; i++) {
positions[i * 3] = this.points[i].x
positions[i * 3 + 1] = this.points[i].y
positions[i * 3 + 2] = 0
}
this.pointCloud.geometry.dispose()
this.pointCloud.geometry = new THREE.BufferGeometry()
this.pointCloud.geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
},
updateVehiclePosition() {
if (!this.vehicleSprite) return
this.vehicleSprite.position.set(
this.position.x,
this.position.y,
0
)
const radians = this.rotation * (Math.PI / 180)
this.vehicleSprite.rotation.X = radians
},
startRendering() {
const render = () => {
this.controls.update()
this.renderer.render(this.scene, this.camera)
this.animationFrameId = requestAnimationFrame(render)
}
render()
},
handleResize() {
const container = this.$refs.container
const width = container.clientWidth
const height = container.clientHeight
const aspect = width / height
this.camera.left = -this.viewSize * aspect / 2
this.camera.right = this.viewSize * aspect / 2
this.camera.top = this.viewSize / 2
this.camera.bottom = -this.viewSize / 2
this.camera.updateProjectionMatrix()
this.renderer.setSize(width, height)
},
zoomToArea(minX, maxX, minY, maxY) {
const width = maxX - minX
const height = maxY - minY
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
const margin = 0.1
const targetViewSize = Math.max(width, height) * (1 + margin)
// 计算合适的缩放级别
const zoomLevel = this.viewSize / targetViewSize
this.controls.target.set(centerX, centerY, 0)
// 通过调整camera.zoom实现平滑缩放
this.camera.zoom = Math.min(this.maxZoom, Math.max(this.minZoom, zoomLevel))
this.camera.updateProjectionMatrix()
this.controls.update()
}
}
}
</script>
<style lang="stylus" scoped>
.canvas-container
position relative
display flex
justify-content: center;
align-items: center;
height calc(100% - 1rem)
margin .14rem 0
box-shadow inset 1px 1px 7px 2px #4d9bcd
overflow hidden
.point-cloud-container
width 100%
height 100%
.reload_bg {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
}
</style>