1.依赖引入

 npm install three

2.创建组件

组件代码

<template>

  <div
    v-loading="loading"
    @dblclick="onClick"
    element-loading-background="rgba(0, 0, 0, 0.8)"
    id="pcdcontainer"

  >

</div>


</template>

<script>
import * as THREE from "three";
import {Scene, PerspectiveCamera, WebGLRenderer, DirectionalLight} from 'three';
import { PCDLoader } from "three/examples/jsm/loaders/PCDLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { FormItem } from "element-ui";


export default {
  props: {
    //上传pcd文件路径
    pcdUrl: {
      type: String,
      // required: true
    },
    //颜色控制
    color: {
      type: Number,
      // required: true
    },
    //已标记点集合
    markPoints:{
      type:Array
    },
    identifying:{
      type:Boolean
    },
    deleteFlag:{
      type:Boolean
    },
    //判断是否为新增双击
    isAdd:{
      type:Boolean
    }
  },
  created () {
    this.localMarkPoints = this.markPoints.slice();
    this.init()
    this.mpoint()
    this.delete()
    console.log( this.localMarkPoints)
    // this.localMarkPoints = this.markPoints.slice();
  },
  data () {
    return {
      localMarkPoints:this.markPoints,
      clickedPointIndex :-1,
      elem: null,
      scene: null,
      camera: null,
      renderer: null,
      loader: null,
      controls: null,
      mesh: null,
      animationId: null,
      pointcloud:{},
      pointsMaterial:new THREE.PointsMaterial({ color: 0xff0000, size: 0.05 }),
      selectedPointMaterial: new THREE.PointsMaterial({ color: 0x00ff00, size: 0.1 }),
      selectedPoint:null,
      clock: new THREE.Clock(),
      mouse: new THREE.Vector2(1, 1),
      client: { clientX: 0, clientY: 0 },
      loading: true,
      line:new THREE.LineBasicMaterial({ color: 0x0000ff }),
    }
  },
  beforeMount () {

  },
  async  mounted () {
    // this.localMarkPoints = this.markPoints.slice();
    await this.init()
    //标记点加载渲染
    await this.mpoint()
    await this.cancel()
    await this.delete()
    this.localMarkPoints = this.markPoints.slice();
    console.log( this.localMarkPoints)
  },
  methods: {

    async init () {
      let elem = document.getElementById('pcdcontainer');//获取要渲染的Dom
      // 相机
      this.camera = new THREE.PerspectiveCamera(
        30, // 视野
        elem.clientWidth / elem.clientHeight, // 纵横比
        0.1, // 近平面
        1000 // 远平面
      );

      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      });
      this.renderer.setClearColor(new THREE.Color(0x303030)); // 背景色
      this.renderer.setSize(elem.clientWidth, elem.clientHeight);
      elem.appendChild(this.renderer.domElement);

      this.scene = new THREE.Scene(); // 场景
      this.loader = new PCDLoader(); //PCD加载器
      const THIS = this
        //加载PCD文件
        if(this.pcdUrl){
          THIS.loader.load(
          // this.pcdUrl,
          this.pcdUrl,
          function (points) {

            points.geometry.rotateX(0.5 * Math.PI);//旋转模型,可调
            points.material.color = new THREE.Color(THIS.color); // 模型颜色
            THIS.pointcloud=points

            THIS.scene.add(points);
            var middle = new THREE.Vector3();
            // points.material = THIS.selectedPointMaterial;
            points.geometry.computeBoundingBox();
            points.geometry.boundingBox.getCenter(middle);
             // 构造盒子
            points.applyMatrix4(
              new THREE.Matrix4().makeTranslation(
                -middle.x,
                -middle.y,
                -middle.z
              )
            );
            // 比例
            var largestDimension = Math.max(
              points.geometry.boundingBox.max.x,
              points.geometry.boundingBox.max.y,
              points.geometry.boundingBox.max.z
            );
            THIS.camera.position.y = largestDimension * 3;//相机位置,可调
            THIS.animate();
            //轨道控制器 旋转、平移、缩放
            THIS.controls = new OrbitControls(
              THIS.camera,
              THIS.renderer.domElement
            );
            THIS.controls.enableDamping = true;//旋转、平移开启阻尼
            THIS.controls.addEventListener("change", THIS.render); // 监听鼠标、键盘事件
             //放大缩小等

          },
            function (xhr) {
            let load = xhr.loaded / xhr.total
            if (load == 1) {
              THIS.loading = false
            }
          },
          function (error) {
            console.log(error);
          }
        );
        }

    },
    render () {
      this.renderer.render(this.scene, this.camera);
    },
    delete(){
      this.localMarkPoints=this.markPoints
    },
    cancel(){
      if(this.identifying){
        this.localMarkPoints=this.localMarkPoints.slice(0,-1);
      }
      console.log(this.identifying)
      console.log(this.markPoints)
      console.log(this.localMarkPoints)
      console.log(this.scene)
    },
    animate () {
      let delta = this.clock.getDelta();
      if (this.controls) {
        this.controls.update(delta);
      }
      this.animationId =requestAnimationFrame(this.animate)
      this.render();
    },
    async mpoint(){
      console.log(this.localMarkPoints)
      if(this.localMarkPoints&&this.localMarkPoints.length!=0){
        for(var i=0;i<this.localMarkPoints.length;i++){
      var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
      var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
      var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
      const point = new THREE.Vector3( this.localMarkPoints[i].xPoints,this.localMarkPoints[i].yPoints,this.localMarkPoints[i].zPoints );
      selectedSphere.position.copy(point);

      this.scene.add(selectedSphere)
      var randomColor;
      if(this.localMarkPoints[i].isColor){
         randomColor = new THREE.Color(0xFF0000);
      }else{
        randomColor = new THREE.Color(Math.random(), Math.random(), Math.random());
      }

      selectedSphere.material.color = randomColor;

    }
      this.animate();
      }

    },
    onClick(event) {
      console.log(this.scene)
      //添加点云的双击
      if(this.isAdd){
        event.preventDefault();
      //判断当前点是否在图标内
      const container = document.getElementById('pcdcontainer');
      let getBoundingClientRect = container.getBoundingClientRect()
      this.mouse.x = (event.offsetX  / container.clientWidth) * 2 - 1;
      this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
      const raycaster = new THREE.Raycaster();
      raycaster.near = 0; // 射线的起始距离
      raycaster.far = 100; // 射线的最大距离
      raycaster.params.Points.threshold = 0.1; // 设置点云的阈值
      raycaster.ray.direction.set(this.mouse.x, this.mouse.y, 0.5).unproject(this.camera).sub(this.camera.position).normalize();
      raycaster.setFromCamera(this.mouse, this.camera);
      const intersects = raycaster.intersectObject(this.scene );
      if (intersects.length <= 0) return void 0;

      let point = intersects[0].point;

      intersects[ 0 ].object.material.color.set( 0xff0000 );


      this.$emit('position',point)

      var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
      var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
      var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
      selectedSphere.position.copy(point);
      // for (const markedPoint of this.scene.children){
      //   if(markedPoint instanceof THREE.Points){
      //     markedPoint
      //   }
      // }
      this.scene.add(selectedSphere)
      const clickedPoint = {
        xPoints: point.clone().x,
        yPoints: point.clone().y,
        zPoints: point.clone().z,

    };
    this.localMarkPoints.push(clickedPoint);
    var randomColor = new THREE.Color(Math.random(), Math.random(), Math.random());
    selectedSphere.material.color = randomColor;
      }
      else{
      event.preventDefault();
      const container = document.getElementById('pcdcontainer');
      this.mouse.x = (event.offsetX  / container.clientWidth) * 2 - 1;
      this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
      const raycaster = new THREE.Raycaster();
      raycaster.setFromCamera(this.mouse, this.camera);
      const intersects = raycaster.intersectObject(this.scene);
      if (intersects.length <= 0) return void 0;

      let point = intersects[0].point;
      let clickedPoint = intersects[0].point;
      console.log(point)
      console.log(this.markPoints)
      //取已标记点最接近的点
      let closestPoint = null;
      let closestDistance = Infinity;
      for (const markedPoint of this.scene.children) {
        const markedVector = new THREE.Vector3(markedPoint.position
        .x, markedPoint.position
        .y,markedPoint.position
        .z);
        const distance = clickedPoint.distanceTo(markedVector);

        if (distance < closestDistance) {
          closestDistance = distance;
          closestPoint = markedPoint;
        }
      }
      if (closestPoint) {
        //把标记点变红
      var sphereGeometry = new THREE.TetrahedronGeometry(0.02, 8, 8);
      var sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xFF0000 });
      var selectedSphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
      selectedSphere=closestPoint

      var color = new THREE.Color(0xFF0000);
      selectedSphere.material.color = color;
      this.scene.add(selectedSphere)
      this.$emit('position',closestPoint)
      }
      }
    },
  },

  beforeDestroy() {
  clearTimeout(); // 这一行需要提供具体的定时器ID或函数,以清除定时器。例如:clearTimeout(this.timerId);

  try {
    // 清除场景中的子对象
    this.scene.children.forEach(child => {
      this.scene.remove(child);
    });

    // 释放渲染器的资源
    this.renderer.dispose();
    this.renderer.forceContextLoss(); // 不需要强制上下文丢失
    this.renderer.domElement = null;

    // 取消动画帧
    cancelAnimationFrame(this.animationId);

    // 关闭 WebGL 上下文
    const gl = this.renderer.context;
    if (gl) {
      const loseContextExtension = gl.getExtension("WEBGL_lose_context");
      if (loseContextExtension) {
        loseContextExtension.loseContext();
      }
    }
  } catch (e) {
    console.error("An error occurred during cleanup:", e);
  }

  // 清除 Three.js 缓存
  THREE.Cache.clear();
},
  computed: {},
  watch: {
    markPoints (newValue) {
      console.log(newValue)
    // 当 markPoints prop 发生变化时,更新 localMarkPoints
    this.localMarkPoints = newValue.slice();
  },
  },
  filters: {},
  components: {}
}
</script>
<style scoped lang='scss'>
#pcdcontainer {
  width: 960px;
  height:686px;
}
</style>

 3.引用组件页面及效果展示

import StationPC3D from '@/components/StationPointCloud3D'

        <StationPC3D :pcdUrl="pcdUrl"
       @position="position" :key="key" :color="color" :markPoints="markPoints" :identifying="identifying" :deleteFlag="deleteFlag" :isAdd="isAdd"></StationPC3D>

    //拿到点击坐标,给相关参数赋值

    position(mouse){


 

      this.workInfoModel = {

            xPoints: mouse.x + "",

            yPoints: mouse.y + "",

            zPoints: mouse.z + "",

            angle: 0,

            deviceId: undefined,

            direction: undefined,

            addrId: this.currentPoint.id,

            addrName: this.currentPoint.label,

            remark: undefined,

            stationCode: undefined,

            stationId: "-1",

            typeId: undefined,

            stationName: undefined,

          };

          this.$nextTick(() => {

            this.$refs["workInfoModelForm"].clearValidate();

          });

          this.ableOpen=true

          this.selectItem = null;

          this.selectMove = false;

          this.clickRight = false;

    },

4.流程分析 

通过上传pcd文件,加载pcd文件,用户双击可视化图像上的点,然后对xyz坐标进行保存及渲染.每次加载pcd文件,可以把保存好的点,进行上传渲染

5.效果 

 

最后,欢迎在评论区交流学习以及提问 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐