<script lang="ts" setup name="ThreeMap"> import { reactive, ref } from 'vue' import useSettingsStore from "@/store/modules/settings"; // three import { MeshLine, MeshLineMaterial, MeshLineRaycast } from 'three.meshline'; import * as THREE from 'three' import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader' import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js' import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import useWebsocketStore from "@/store/modules/websocket"; import {TDHeatMap} from "@/utils/heat/heat"; import {getRobotList, robotWV} from "@/api/home/robot/robot"; import {ElMessage} from "element-plus"; import {uploadPcd} from "@/api/home/task/task"; import runPoint from "@/store/modules/runPoint"; import { Refresh, Crop } from '@element-plus/icons-vue' const pointStore = runPoint() const clock= ref(new THREE.Clock()) const loading= ref(true) const animationId: any = ref('') let scene: any = reactive(null) let camera: any = reactive(null) const controls: any = ref(null) const loader: any = ref(null) const renderer: any = ref(null) const pointList = ref<any[]>([]) const settingsStore = useSettingsStore() const websocket = useWebsocketStore() let plane: any = null let isNewPoint = false const props = defineProps({ id: { type: String, default: 'pcdcontainer-', required: true, }, pcdUrl: { type: String, default: '', required: true, }, gridWidth: { type: Number, default: 1, }, gridHeight: { type: Number, default: 1, }, resolution: { type: Number, default: 1, }, isMeasure: { type: Boolean, default: false, } }) // const measureSize = ref(200) // const measureNum = ref(4) const gridHeight = ref(10) const gridWidth = ref(20) const resolution = ref(0.5) const newX = ref('***') const newY = ref('***') function initMap() { init3D() } function save() { if(newX.value === '***') { ElMessage.warning('请选择网格截取坐标') return } robotWV({ id: settingsStore.robot.id, gridHeight: gridHeight.value, gridWidth: gridWidth.value, resolution: resolution.value, newX: newX.value, newY: newY.value, }).then(() => { ElMessage.success('提交成功') // getWV() }) } // socket更新数据 const unwatch = watch(websocket, (newVal: any) => { if (newVal.positionData && Object.keys(newVal.positionData).length >= 1 && isNewPoint) { makeLine(newVal.positionData.x, newVal.positionData.y, newVal.positionData.z) } // else if (newVal.heatData && Object.keys(newVal.heatData).length >= 1 ) { // if(!props.isMeasure) // makeHeat(newVal.heatData.repetitionList) // } }) const isCutting = ref(false) const isCuttingMin = ref(false) const unwatchCut = watch(isCutting, (newVal) => { if(newVal) { isCuttingMin.value = false controls.value.enableRotate = false controls.value.enablePan = false setTimeout(() => { cut_click_point = [] squares = [] document.addEventListener('click', onMouseClick, false); }, 100) } else { controls.value.enableRotate = true controls.value.enablePan = true document.removeEventListener('click', onMouseClick, false); } }) const unwatchCutMin = watch(isCuttingMin, (newVal) => { if(newVal) { isCutting.value = false recovery() controls.value.enableRotate = false controls.value.enablePan = false setTimeout(() => { document.addEventListener('click', onMouseClickMin, false); }, 100) } else { controls.value.enableRotate = true controls.value.enablePan = true document.removeEventListener('click', onMouseClickMin, false); } }) var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); let cut_click_point: any[] = [] let squares: any[] = [] // 去除正方形范围内的点 function cutPcd(minX: any, maxX: any, minY: any, maxY: any) { try { scene.remove(pcd) }catch (e) {} try { scene.remove(pcd_tmp) }catch (e) {} var geometry = pcd_tmp.geometry; // 定义裁剪条件,这里假设对 x 轴进行裁剪 var threshold = 0; // 裁剪阈值 var clippedPositions = []; var positions = geometry.attributes.position.array; for (var i = 0; i < positions.length; i += 3) { // if (positions[i] > threshold) { if (!((positions[i] >= minX && positions[i] <= maxX) && (positions[i + 1] >= minY && positions[i + 1] <= maxY))) { clippedPositions.push(positions[i], positions[i + 1], positions[i + 2]); } } // 创建新的 BufferGeometry 来存储裁剪后的点云 var clippedGeometry = new THREE.BufferGeometry(); clippedGeometry.setAttribute('position', new THREE.Float32BufferAttribute(clippedPositions, 3)); // 创建新的点云对象,并替换原始的点云 pcd_tmp = new THREE.Points(clippedGeometry, points_material); scene.add(pcd_tmp); } function getMousePosiotion(event) { const target = event.target; if(target.nodeName !== 'CANVAS') { return } // 将鼠标位置转换为三维空间中的坐标 mouse.x = ((event.clientX - 430 - 180) / 920 ) * 2 - 1; mouse.y = - ((event.clientY - 62) / 630 ) * 2 + 1; // 使用raycaster检查鼠标点击的位置 raycaster.setFromCamera(mouse, camera); // 计算鼠标点击的交叉点 var intersects = raycaster.intersectObjects([plane]); // 如果存在交叉点 if (intersects.length > 0) { console.log(intersects[0].point) // 获取第一个交叉点 return intersects[0].point } return null } function onMouseClickMin(event: any) { let point = getMousePosiotion(event) if(point === null || point === undefined ) { return } newX.value = Number(point.x.toFixed(2)) newY.value = Number(point.y.toFixed(2)) let x = pos.x // 左下角 let y = pos.y // 左下角 let nx = Math.floor((-pos.x + newX.value) / resolution.value) x += nx * resolution.value let ny = Math.floor((-pos.y + newY.value) / resolution.value) y += ny * resolution.value newX.value = Number(x.toFixed(2)) newY.value = Number(y.toFixed(2)) changeMap() } function onMouseClick(event: any) { let point = getMousePosiotion(event) if(point === null ) { ElMessage.warning('请选择点云图中正方形对角线两点进行删除') cut_click_point = [] return } if(point === undefined ) { cut_click_point = [] return } cut_click_point.push(point) if(cut_click_point.length === 2) { if((cut_click_point[0].x === cut_click_point[1].x) || (cut_click_point[0].y === cut_click_point[1].y)) { cut_click_point = [] ElMessage.warning('请选择点云图中正方形对角线两点进行删除') return } // 创建正方形 cut_click_point.push(point) var geometry = new THREE.PlaneGeometry( Math.abs(cut_click_point[0].x - cut_click_point[1].x), Math.abs(cut_click_point[0].y - cut_click_point[1].y) ); var material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, opacity: 1, wireframe: true, wireframeLinewidth: 2 }); var square = new THREE.Mesh(geometry, material); // 将正方形的中心设置为两点的中点 square.position.x = (cut_click_point[0].x + cut_click_point[1].x) / 2; square.position.y = (cut_click_point[0].y + cut_click_point[1].y) / 2; squares.push(square) // 将正方形添加到场景中 scene.add(square); const minX = cut_click_point[0].x > cut_click_point[1].x ? cut_click_point[1].x: cut_click_point[0].x const maxX = cut_click_point[0].x < cut_click_point[1].x ? cut_click_point[1].x: cut_click_point[0].x const minY = cut_click_point[0].y > cut_click_point[1].y ? cut_click_point[1].y: cut_click_point[0].y const maxY = cut_click_point[0].y < cut_click_point[1].y ? cut_click_point[1].y: cut_click_point[0].y // 删 cutPcd(minX, maxX, minY, maxY) // 重新画 cut_click_point = [] // 渲染场景 renderer.value.render(scene, camera); } } onBeforeUnmount(() => { unwatch() unwatchCut() unwatchCutMin() }) let HeatMesh = null function makeHeat( arr: any[] ) { const n = Math.ceil(props.gridWidth / props.resolution) arr = arr.map(item => Number(item)) const data = arr.map((item, index) => { return { x: index % n, y: Math.floor(index / n), value: item } }) const origin = { x: pos.x + 0.5 * Math.ceil(props.gridWidth / props.resolution) * props.resolution, y: pos.y + 0.5 * Math.ceil(props.gridHeight / props.resolution) * props.resolution, z: 0, } const prop = { gridWidth: props.gridWidth, gridHeight: props.gridHeight, resolution: props.resolution, } if(HeatMesh !== null) { scene.remove(HeatMesh) } HeatMesh = TDHeatMap( data, Math.max(...arr), origin, prop ); scene.add(HeatMesh); } // 线材质参数 var params = { // 数量 amount : 100, // 线条宽度为10 lineWidth : 10, // 设置虚线的空隙大小,设置为零时,线段会连成一条线 dashArray : 0, // 定义虚线开始的位置 dashOffset : 0, // 虚线的空隙比例 dashRatio : 0.9, // 设置为抛物线,none , linear, wavy taper : 'none', // 是否有透视效果(false - 透视,true - 不透视) sizeAttenuation : true, }; const pointMaterial = new THREE.PointsMaterial({ color: 0xffa124, size: 0.5 }) let resolutionLet = '' let lineMaterial = '' const nowMaterial = new THREE.PointsMaterial({ color: 0xfa0c0c, size: 1 }) // 点 let nowP = new THREE.BufferGeometry(); let nowPoint: any = null let initPos = {x: 0, y: 0,z: 0} let car: any = null let runAll:any = [] function clearRun () { pointList.value = [] runAll.forEach((item: any) => scene.remove(item)) runAll = [] } function makeLine( x: any, y: any, z: any ) { pointList.value.push(new THREE.Vector3(x,y,z)) // 点 var geometryP = new THREE.BufferGeometry(); geometryP.setFromPoints(pointList.value.slice(-1)); var point = new THREE.Points( geometryP, pointMaterial ); scene.add(point) runAll.push(point) // 线 const arr = pointList.value.slice(-2) var geometry = new THREE.BufferGeometry(); geometry.setFromPoints(arr); var g = new MeshLine(); switch( params.taper ) { // 设置线段宽度,setGeometry第一个参数是三维点的集合,第二个参数是每两个点之间的线段宽度变化函数 // 没有 case 'none': g.setGeometry( geometry ); break; // 线性 case 'linear': g.setGeometry( geometry, function( p: any ) { return 1 - p; } ); break; // 抛物线 case 'parabolic': g.setGeometry( geometry, function( p: any ) { return 1 * Math.pow( 4 * p * ( 1 - p ), 1 )} ); break; // 波浪 case 'wavy': g.setGeometry( geometry, function( p: any ) { return 2 + Math.sin( 50 * p ) } ); break; } // 设置线段材质 var mesh = new THREE.Mesh( g.geometry, lineMaterial ) scene.add(mesh) runAll.push(mesh) car.position.set(x, y, z) // //机器人当前 // if(nowPoint === null && pointList.value.length !== 0) { // nowP.setFromPoints(pointList.value.slice(-1)); // nowPoint = new THREE.Points( nowP, nowMaterial ); // initPos = {x: x, y: y,z: z} // scene.add(nowPoint) // } else { // nowPoint.position.set(x - initPos.x, y - initPos.y, z - initPos.z) // } } let pos: any = {x: 0,y: 0,z: 0} function animate () { let delta = clock.value.getDelta() if (controls.value) { controls.value.update(delta) } animationId.value =requestAnimationFrame(animate) render() } const color = 0x7a745d let pcd: any = null let points_material: any = null let pcd_tmp: any = null let largestDimension = 0 let OricameraUp: any let OricamerPosition: any let OricameraQuaternion: any let OricontrolTarget: any const $router = useRouter() function newGrid() { getRobotList({}).then((response) => { if(response.code === 200) { const robot = response.data.filter((item: any) => item.id === settingsStore.robot.id)[0] if(props.isMeasure) { pos.x = Number(robot.originX).toFixed(2) pos.y = Number(robot.originY).toFixed(2) pos.z = Number(robot.originZ).toFixed(2) } else { pos.x = Number(robot.newX).toFixed(2) pos.y = Number(robot.newY).toFixed(2) pos.z = 0 } if(props.isMeasure) { gridHeight.value = props.gridHeight gridWidth.value = props.gridWidth resolution.value = props.resolution changeMap() } else { drawGrid() } } }) } function loadPcd() { loader.value.load( // './pcd/finalCloud_1.pcd', props.pcdUrl + '?t=' + new Date().getTime(), function (points: any) { // 点云 pcd = points; pcd_tmp = points; points_material = points.material scene.add(points); points.geometry.computeBoundingBox(); largestDimension = Math.max(points.geometry.boundingBox.max.x, points.geometry.boundingBox.max.y, points.geometry.boundingBox.max.z); camera.position.z = largestDimension * 5; //相机位置,可调 const target = new THREE.Vector3(0, 0, 0); // 平面上的点 camera.lookAt(target); // 让摄像机对准目标 animate(); //轨道控制器 旋转、平移、缩放 controls.value = new OrbitControls( camera, renderer.value.domElement ); controls.value.enableDamping = true //旋转平移开启阻尼 controls.value.addEventListener("change", render) // 监听鼠标、键盘事件 OricameraUp = scene.up.clone() OricamerPosition = scene.position.clone() OricameraQuaternion = scene.quaternion.clone() OricontrolTarget = controls.value.target.clone() }, function (xhr: any) { let load = xhr.loaded / xhr.total if (load == 1) { loading.value = false } }, function (error: any) { console.log(error); } ); } // const isFirstEnter = ref(true); // onActivated(()=>{ // if($router.currentRoute.value.fullPath === '/run') { // if (!isFirstEnter.value) { // setTimeout(() => { // if (pcd !== null) { // try { // scene.remove(pcd) // } catch (e) { // } // } // loadPcd() // newGrid() // },1000) // } // isFirstEnter.value = false; // } // }) function init3D() { console.log('init3D') let elem: any = document.getElementById(props.id) camera = new THREE.PerspectiveCamera( 30, // 视野 elem.clientWidth / elem.clientHeight, // 纵横比 0.1, // 近平面 1000 // 远平面 ) renderer.value = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.value.setClearColor(new THREE.Color(0x303030)); // 背景色 renderer.value.setSize(elem.clientWidth, elem.clientHeight); elem.appendChild(renderer.value.domElement); scene = new THREE.Scene(); // 场景 loader.value = new PCDLoader(); //PCD加载器 resolutionLet = new THREE.Vector2(document.getElementById(props.id).clientWidth, document.getElementById(props.id).clientHeight); lineMaterial = new MeshLineMaterial( { // 随机颜色 color: 0x00e1ff, // 透明度 opacity: 1, // 虚线的线段之前空隙,为零则为实线 dashArray: 0, // 虚线开始的位置 dashOffset: 0, // 虚线 的线段与空隙比例 dashRatio: 0.9, // 二维向量指定画布大小,必需 resolution: resolutionLet, // 线宽是否衰减(是否有透视效果) sizeAttenuation: false, // 线宽 lineWidth: 10, // 摄像机近剪裁平面距离,跟随相机(sizeAttenuation为false时必须设置) near: camera.near, // 相机远剪裁平面距离,跟随相机(sizeAttenuation为false时必须设置) far: camera.far, // transparent: true }) try{ // 网格 newGrid() } catch (e) {} // 坐标系 const axisHelper = new THREE.AxesHelper(250) scene.add(axisHelper) //加载PCD文件 loadPcd() const loader1 = new GLTFLoader(); loader1.load('./pcd/car.glb', (gltf: any) => { car = gltf.scene; const material = new THREE.MeshStandardMaterial({ color: 0xafafaf, metalness: 0.7, roughness: 0.5 }); car.traverse((child: any) => { if (child.isMesh) { child.position.set(0,0,0) child.scale.set(0.0004, 0.0004, 0.0004); // 将每个子网格缩小为原始大小的一半 child.material = material; child.material.emissive = child.material.color; child.material.emissiveMap = child.material.map; } }) car.rotation.x = -90 * Math.PI / 180; //转90度 scene.add(car) if($router.currentRoute.value.fullPath === '/run') { const arr = pointStore.points console.log(arr, '缓存点位') setTimeout(() => { arr.forEach((item: any) => { makeLine(item.x, item.y, item.z) }) isNewPoint = true }, 1000) } else { isNewPoint = true } }) const geometry = new THREE.PlaneGeometry(100, 100); // 创建材质,设置透明度为0.5 const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0, }); // 创建网格对象 plane = new THREE.Mesh(geometry, material); // 将平面添加到场景 scene.add(plane); scene.add( new THREE.HemisphereLight( 0x443333, 0x111122 ) ); addShadowedLight( 1, 1, 1, 0xffffff, 1.35 ); addShadowedLight( 0.5, 1, - 1, 0xffffff, 1 ); } function render () { renderer.value.render(scene, camera); } defineExpose({ initMap, render, clearRun }) function addShadowedLight( x: any, y: any, z: any, color: any, intensity: any ) { const directionalLight = new THREE.DirectionalLight( color, intensity ); directionalLight.position.set( x, y, z ); scene.add( directionalLight ); directionalLight.castShadow = true; const d = 1; directionalLight.shadow.camera.left = - d; directionalLight.shadow.camera.right = d; directionalLight.shadow.camera.top = d; directionalLight.shadow.camera.bottom = - d; directionalLight.shadow.camera.near = 1; directionalLight.shadow.camera.far = 4; directionalLight.shadow.bias = - 0.002; } // 参数生成 function drawGrid() { lines = [] var translationVector = new THREE.Vector3(pos.x, pos.y, 0); // 沿着 x,y 轴平移,后台返回发的起止位 // 栅格 let geometry = new THREE.BufferGeometry(); geometry.setFromPoints([new THREE.Vector3(0, 0, 0),new THREE.Vector3(props.gridWidth, 0, 0)]); let y = 0 while (y < props.gridHeight) { var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.y = y; line.rotation.x = -90 * Math.PI / 180; //转90度 line.position.add(translationVector); scene.add(line) lines.push(line) y += props.resolution } var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.y = props.gridHeight; line.rotation.x = -90 * Math.PI / 180; //转90度 line.position.add(translationVector); scene.add(line) lines.push(line) geometry = new THREE.BufferGeometry(); geometry.setFromPoints([new THREE.Vector3(0, 0, 0),new THREE.Vector3(props.gridHeight, 0, 0)]); let x = 0 while (x < props.gridWidth) { var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.x = x; line.rotation.z = 90 * Math.PI / 180; line.position.add(translationVector); scene.add(line) lines.push(line) x += props.resolution } var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.x = props.gridWidth; line.rotation.z = 90 * Math.PI / 180; line.position.add(translationVector); scene.add(line) lines.push(line) } function drawGridOld() { // var geometry = new THREE.BufferGeometry(); // geometry.setFromPoints([new THREE.Vector3(0, 0, 0),new THREE.Vector3(props.mapSize, 0, 0)]); // var translationVector = new THREE.Vector3(pos.x, pos.y, 0); // 例如沿着 x 轴平移 5 个单位 // for (var i = 0; i <= props.mapNum; i++) { // var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x // line.position.y = (i * props.mapSize/props.mapNum); // line.rotation.x = -90 * Math.PI / 180; //转90度 // line.position.add(translationVector); // scene.add(line) // lines.push(line) // // var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // z // line.position.x = (i * props.mapSize/props.mapNum); // line.rotation.z = 90 * Math.PI / 180; //转90度 // line.position.add(translationVector); // scene.add(line) // lines.push(line) // } } // 自己画 let lines: any[] = [] function changeMap() { lines.forEach(item => scene.remove(item)) lines = [] var translationVector = newX.value === '***' ? new THREE.Vector3(Number(pos.x), Number(pos.y), 0) : new THREE.Vector3(Number(newX.value), Number(newY.value), 0) // 沿着 x,y 轴平移,后台返回发的起止位 // 栅格 let geometry = new THREE.BufferGeometry(); geometry.setFromPoints([new THREE.Vector3(0, 0, 0),new THREE.Vector3(gridWidth.value, 0, 0)]); let y = 0 while (y < gridHeight.value) { var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.y = y; line.rotation.x = -90 * Math.PI / 180; //转90度 line.position.add(translationVector); scene.add(line) lines.push(line) y += resolution.value } var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.y = gridHeight.value; line.rotation.x = -90 * Math.PI / 180; //转90度 line.position.add(translationVector); scene.add(line) lines.push(line) geometry = new THREE.BufferGeometry(); geometry.setFromPoints([new THREE.Vector3(0, 0, 0),new THREE.Vector3(gridHeight.value, 0, 0)]); let x = 0 while (x < gridWidth.value) { var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.x = x; line.rotation.z = 90 * Math.PI / 180; line.position.add(translationVector); scene.add(line) lines.push(line) x += resolution.value } var line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: color, opacity: 0.5 })); // x line.position.x = gridWidth.value; line.rotation.z = 90 * Math.PI / 180; line.position.add(translationVector); scene.add(line) lines.push(line) console.log('finish') } function uploadPcdFile() { // 上传 const points = pcd_tmp.geometry.attributes.position.array; const numPoints = points.length / 3; let pcdContent = 'VERSION 0.7\n'; pcdContent += 'FIELDS x y z\n'; pcdContent += 'SIZE 4 4 4\n'; pcdContent += 'TYPE F F F\n'; pcdContent += 'COUNT 1 1 1\n'; pcdContent += 'WIDTH ' + numPoints + '\n'; pcdContent += 'HEIGHT 1\n'; pcdContent += 'VIEWPOINT 0 0 0 1 0 0 0\n'; pcdContent += 'POINTS ' + numPoints + '\n'; pcdContent += 'DATA ascii\n'; for (let i = 0; i < points.length; i += 3) { pcdContent += points[i].toFixed(3) + ' ' + points[i + 1].toFixed(3) + ' ' + points[i + 2].toFixed(3) + '\n'; } const blob = new Blob([pcdContent], { type: 'text/plain' }); // 创建一个新的File对象用于上传 const fileObj = new File([blob], "finalCloud.pcd", { lastModified: new Date().getTime(), type: 'text/plain' }); // // 下载到本地 // const url = URL.createObjectURL(blob); // const a = document.createElement('a'); // a.href = url; // a.download = 'tmp.pcd'; // document.body.appendChild(a); // a.click(); // document.body.removeChild(a); try { const formData = new FormData(); formData.append('file', fileObj); formData.set('robotId', settingsStore.robot.id.toString()); console.log(formData) uploadPcd(formData, settingsStore.robot.id.toString()).then((response) => { if (response.code === 200) { ElMessage.success('pcd文件已上传更新') pcd = pcd_tmp squares.forEach((item:any) => { scene.remove(item) }) isCutting.value = false } else { ElMessage.error('pcd文件已上传失败') } }) } catch (e) {} // pcd = pcd_tmp // squares.forEach((item:any) => { // scene.remove(item) // }) // isCutting.value = false } function recovery() { try { squares.forEach((item:any) => { scene.remove(item) }) } catch (e) {} try { scene.remove(pcd) } catch (e) {} try { scene.remove(pcd_tmp) } catch (e) {} scene.add(pcd) pcd_tmp = pcd cut_click_point = [] squares = [] } // 点击裁剪按钮 function cut() { // 摄像头位置变换 camera.up.copy(OricameraUp); camera.position.copy(OricamerPosition); camera.quaternion.copy(OricameraQuaternion); camera.lookAt(OricontrolTarget); controls.value.target.copy(OricontrolTarget); controls.value.update(); camera.zoom = 1.0; camera.updateProjectionMatrix(); camera.position.set(0, 0, largestDimension * 5) const target = new THREE.Vector3(0, 0, 0); // 平面上的点 camera.lookAt(target); // 让摄像机对准目标 renderer.value.render(scene, camera); // 锁定屏幕 isCutting.value = true // 开始剪裁 } function cancel() { isCutting.value = false recovery() } </script> <template> <div class="map-common" > <div class="map-common" v-loading="loading" :id="id"/> <div id="heatmap" style="visibility: hidden" :style="`width:${Math.ceil(props.gridWidth / props.resolution) * props.resolution}px;height:${Math.ceil(props.gridHeight / props.resolution) * props.resolution}px;`"/> <div id="greymap" style="visibility: hidden" :style="`width:${Math.ceil(props.gridWidth / props.resolution) * props.resolution}px;height:${Math.ceil(props.gridHeight / props.resolution) * props.resolution}px;`"/> <div class="measure" v-if="isMeasure"> <div> 场景长度: <el-input-number v-model="gridWidth" :min="0" :max="400" :step="1" @change="changeMap" /> </div> <div style="margin: 5px 0"> 场景宽度: <el-input-number v-model="gridHeight" :min="0" :max="400" :step="1" @change="changeMap" /> </div> <div style="margin: 5px 0"> 栅格长度: <el-input-number v-model="resolution" :min="0.2" :max="50" :step="0.1" @change="changeMap" /> </div> <div style="margin: 10px 0"> 网格截取坐标:{{ newX }} , {{ newY }} </div> <div style="text-align: center"> <el-button :type="!isCuttingMin?'primary':'info'" @click="isCuttingMin=!isCuttingMin" title="左下角截取" circle > <el-icon><BottomLeft /></el-icon> </el-button> <el-button type="primary" @click="save"> <el-icon style="margin: 0 2px 0 -2px"><CircleCheck /></el-icon> 提 交 </el-button> </div> <div style="width: 100%;height: 1px;background-color: #008cffee;margin: 10px 0"/> <div style="text-align: center"> <el-button v-show="!isCutting" type="primary" @click="cut"> <el-icon style="vertical-align: middle"> <Crop /> </el-icon> <span style="vertical-align: middle"> 点云图裁剪 </span> </el-button> <el-button v-show="isCutting" type="primary" :icon="Refresh" circle title="恢复" @click="recovery"/> <el-button v-show="isCutting" type="primary" @click="uploadPcdFile"> <el-icon style="margin: 0 2px 0 -2px"><CircleCheck /></el-icon> 提交 </el-button> <el-button v-show="isCutting" type="primary" @click="cancel"> <el-icon style="margin: 0 2px 0 -2px"><CircleClose/></el-icon> 取消 </el-button> </div> </div> </div> </template> <style lang="scss" scoped> .measure { position: absolute; right: 20px; top: 20px; width: 280px; height: 260px; background-color: rgba(26, 33, 38, 0.56); z-index: 1111111; color: white; border-radius: 10px; padding: 15px; font-size: 16px; font-weight: bold; letter-spacing: 1px; border: #008cff 1px solid; font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; } .map-common { width: 100%; height: 100%; position: relative; overflow: hidden; } .table-yx { width: 30%; height: 100%; padding-left: 10px; } </style> <style lang="scss" > .el-table .e-row { background: #f0f4fe; } </style>