Newer
Older
intelligentRobot / src / components / ThreeMap / index.vue
wangxitong on 3 Sep 28 KB first commit
<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>