Lie_- 2024-03-09 14:42 采纳率: 0%
浏览 9
已结题

three.js如何实现类似BLK2GO Live 实时绘制设备传输的数据及视角移动 并且绘制移动轨迹

现在点云渲染出来了 就是视角切换有问题 机器每次给我点云数据的时候会把机器相对于一开始的位置和朝向的坐标给我 然后我用这两个字段设置相机的位置和lookAt 但是呈现出来的效果是不对的 是思路不对吗 three.js的相机把我搞懵了


<template>
  <div class="warp">
    <!-- 顶部 -->
    <div class="top">
      <img src="./assets/image/cha.svg" @click="showModel = true" />
      <span>{{ name }}</span>
      <div>
        <img src="./assets/image/dl.svg" />
        {{ dl }}%
      </div>
      <div>
        <img src="./assets/image/nc.svg" />
        {{ nc }}%
      </div>
      <img src="./assets/image/menu.svg" @click="postMessage(3)" />
    </div>

    <!-- 按钮 -->
    <div class="btn" @click="postMessage(4)" v-if="btmShow">
      <div>{{ lang != 2 ? '启动' : 'start-up' }}</div>
    </div>

    <!-- 底部 -->
    <ul class="btm">
      <li :class="{ 'active': type == 1 }" @click="type = 1">
        <img src="./assets/image/2d.svg" />
        2D
      </li>
      <li :class="{ 'active': type == 2 }" @click="type = 2">
        <img src="./assets/image/3d.svg" />
        3D
      </li>
      <li :class="{ 'active': type == 3 }" @click="type = 3">
        <img src="./assets/image/hf.svg" />
        {{ lang != 2 ? '数据回放' : 'Data playback' }}
      </li>
      <li @click="postMessage(2)">
        <img src="./assets/image/ls.svg" />
        {{ lang != 2 ? '历史记录' : 'History' }}
      </li>
    </ul>

    <!-- 弹窗 -->
    <transition>
      <div class="model" v-if="showModel">
        <h3>{{ lang != 2 ? '提示' : 'prompt' }}</h3>

        <p>{{ lang != 2 ? '您确定要退出该界面吗?' : 'Are you sure you want to exit this interface?' }}</p>

        <div class="btns">
          <div class="btnn" @click="showModel = false">{{ lang != 2 ? '取消' : 'cancel' }}</div>
          <div class="btnn" @click="esc">{{ lang != 2 ? '退出' : 'quit' }}</div>
        </div>
      </div>
    </transition>

    <!-- 进度 -->
    <div id="jd" :style="{ 'right': (type != 3) ? '-5%' : '5%' }">
      <c-progress class="c-progress" type="#FF9800" :percent="percent" @percentChange="onPercentChange"
        :show-per-text="false" />
    </div>

    <!-- 底部状态灯 -->
    <div class="btmm"></div>

    <!-- 点云 -->
    <div id="content"></div>
  </div>
</template>

<script>
import CProgress from "./components/cop.vue";
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
import Stats from '@/utils/stats';
import { hslToRgb } from "@/utils";
import service from "./utils/service";

/* 场景/相机/渲染器/控件/性能监视/轨迹管道 */
let scene = null, camera = null, renderer = null, controls = null, stats = null, mesh = null;

/* 相机位置/朝向 */
let position = [0, -14, 15], lookAt = [0, 0, 0];

/* 窗口宽度 / 窗口高度 / 窗口宽高比 / 三维场景显示范围控制系数,系数越大,显示的范围越大 */
let width = window.innerWidth, height = window.innerHeight, k = width / height, s = 5;

let path = [];

export default {
  components: { CProgress },
  data() {
    return {
      lang: 1,
      type: 2,
      showModel: false,
      percent: 100, // 进度控制

      name: "zzcomm",
      dl: 100,
      nc: 100,
      isCatmull: true,
      btmShow: true,
    }
  },

  async mounted() {
    this.createWebGL();
    // this.GuiInit(camera);
    // this.createStats();

    /* 使用本地点云 */
    // const res = await service.getData2();
    // let datas = res.data;

    // let i = 0;
    // let timer = setInterval(function () {
    //   if (!datas[i]?.pointcloud) {
    //     clearInterval(timer);
    //     timer = null;
    //     return;
    //   }

    //   let pointcloud = datas[i].pointcloud.map((item, index) => {
    //     return {
    //       x: item[0],
    //       y: item[1],
    //       z: item[2],
    //       i: item[3]
    //     }
    //   });

    //   let dsd = {
    //     data: pointcloud,
    //     position: {
    //       x: datas[i].position[0][0],
    //       y: datas[i].position[0][1],
    //       z: datas[i].position[0][2],
    //     },
    //     orientation: {
    //       x: datas[i].orientation[0][0],
    //       y: datas[i].orientation[0][1],
    //       z: datas[i].orientation[0][2],
    //     }
    //   }

    //   this.uniEvent(JSON.stringify(dsd));

    //   if (i >= 295) clearInterval(timer);
    //   timer = null;
    //   console.log(i, datas.length);
    //   i += 1;
    // }, 200);
  },
  methods: {
    /* 创建three三大件及控件对象 */
    createWebGL() {
      /* 创建场景 */
      scene = new THREE.Scene();
      /* 向场景中添加环境光 */
      scene.add(new THREE.AmbientLight(0x444444));

      // 创建相机
      camera = new THREE.OrthographicCamera((-s * k), (s * k), s, -s, -2, 300);

      // 设置相机位置
      camera.position.set(...position);

      // 设置相机朝向(指向的场景对象)
      camera.lookAt(new THREE.Vector3(...lookAt));

      // 添加坐标指示器
      scene.add(new THREE.AxesHelper(10));

      // const helper = new THREE.CameraHelper(camera);
      // scene.add(helper);

      /* 创建渲染器 */
      renderer = new THREE.WebGLRenderer({ antialias: true });
      // 设置渲染区域尺寸
      renderer.setSize(window.innerWidth, window.innerHeight);
      // 设置背景颜色
      renderer.setClearColor(0x000000, 1);

      // DOM元素中插入渲染器
      document.getElementById("content").appendChild(renderer.domElement);

      /* 创建控件对象 */
      controls = new OrbitControls(camera, renderer.domElement);
      // 监听鼠标、键盘事件

      let timer = null;
      controls.addEventListener('change', () => {
        this.isCatmull = false;
        this.render();

        if (timer != null) {
          clearTimeout(timer);
          timer = null;
        }

        timer = setTimeout(() => {
          this.isCatmull = true;
          clearTimeout(timer);
          timer = null;
        }, 10000);
      });
    },

    /* 执行渲染操作 指定场景、相机作为参数 */
    render() {
      renderer.render(scene, camera);
      
      // 更新性能插件
      // stats.update();
    },

    /* 创建性能监视插件 */
    createStats() {
      stats = new Stats();

      // 把stats对象生成的dom,添加到页面中(这样就能在页面中看到性能监视器了)
      document.getElementById("content").appendChild(stats.dom);
    },

    /* 创建gui */
    GuiInit(camera, material) {
      const gui = new GUI({ title: document.title });


      /* 相机位置 */
      const position = { 'X': camera.position.x, 'Y': camera.position.y, 'Z': camera.position.z };
      const POS = gui.addFolder('相机位置');

      POS.add(position, 'X', -50, 50, 0.2).onChange((x) => {
        camera.position.set(x, camera.position.y, camera.position.z);
        this.render();
      });
      POS.add(position, 'Y', -50, 50, 0.2).onChange((y) => {
        camera.position.set(camera.position.x, y, camera.position.z);
        this.render();
      });
      POS.add(position, 'Z', -50, 50, 0.2).onChange((z) => {
        camera.position.set(camera.position.x, camera.position.y, z);
        this.render();
      });

      /* 相机朝向 */
      const look = { 'X': 0, 'Y': 0, 'Z': 0 };
      const LOK = gui.addFolder('相机朝向');

      LOK.add(look, 'X', -50, 50, 0.2).onChange((x) => {
        camera.lookAt(new THREE.Vector3(x, look['Y'], look['Z']));
        this.render();
      });

      LOK.add(look, 'Y', -50, 50, 0.2).onChange((y) => {
        camera.lookAt(new THREE.Vector3(look['X'], y, look['Z']));
        this.render();
      });

      LOK.add(look, 'Z', -50, 50, 0.2).onChange((z) => {
        camera.lookAt(new THREE.Vector3(look['X'], look['Y'], z));
        this.render();
      });


      /* 材质对象 */
      // const mater = { "顶点大小": material.size };
      // const MAT = gui.addFolder('材质对象');

      // MAT.add(mater, '顶点大小', 0.1, 2, 0.1).onChange((size) => {
      //   material.size = size;
      //   this.render();
      // });
    },

    /* 创建几何图形对象 */
    createBufferGeometry(data, colors) {
      // 创建几何图形对象
      let geometry = new THREE.BufferGeometry();

      // 设置顶点数据 3个为一组,作为一个顶点的xyz坐标
      geometry.attributes.position = new THREE.BufferAttribute(new Float32Array(data), 3);
      geometry.attributes.color = new THREE.BufferAttribute(new Float32Array(colors), 3);

      // 材质对象  模型颜色/模型大小
      let material = new THREE.PointsMaterial({ vertexColors: true, size: 2 });

      let model = new THREE.Points(geometry, material);

      // 将图形对象配置到点模型对象上
      scene.add(model);
      this.render();
    },

    /* 创建曲线轨迹 利用管道模型 */
    createCatmullRomCurve3(data) {
      path.push(new THREE.Vector3(data.x, data.y, data.z));

      if (path.length <= 1) return;

      // 三维样条曲线
      const curve3 = new THREE.CatmullRomCurve3([path[path.length - 2], path[path.length - 1]]);

      // 路径/沿着轨迹细分数/管道半径/管道截面圆细分数
      const geometry = new THREE.TubeGeometry(curve3, 10, 0.05, 10);

      // 创建材质对象
      const material = new THREE.MeshBasicMaterial({ color: 0x00FF00 });

      // 创建网格模型
      const mesh = new THREE.Mesh(geometry, material);
      mesh.sxl = "sxl";

      // 将图形对象配置到点模型对象上
      scene.add(mesh);
    },

    /* 根据设备的位置和朝向信息设置视角 */
    setCatmull(position, orientation) {
      // 设置相机位置
      camera.position.set(position.x, position.y, position.z);

      // 设置相机朝向(指向的场景对象)
      camera.lookAt(new THREE.Vector3(orientation.x, orientation.y, orientation.z));
    },

    /* 接受app的消息 */
    uniEvent(data) {
      this.btmShow = false;
      let da = JSON.parse(data);

      /* 设备名称/语言/内存/电量 */
      if (da.name) {
        this.name = da.name;
        this.lang = da.lang;
        this.nc = da.nc;
        this.dl = da.dl;
      };

      /* 视角 */
      if (da.orientation && da.position && this.isCatmull) this.setCatmull(da.position, da.orientation);

      /* 轨迹 */
      if (da.position != null && da.position != {}) {
        da.position.x *= 1;
        da.position.y *= 1;
        da.position.z *= 1;

        this.createCatmullRomCurve3(da.position);
      }

      /* 点云数据 */
      if (da.data) {
        let { sxl, colors } = this.setData(da.data);
        this.createBufferGeometry(sxl, colors);
      }
    },

    /* 格式化数据 */
    setData(data) {
      let sxl = [], colors = [];
      data.forEach((item) => {
        sxl.push(item.x);
        sxl.push(item.y);
        sxl.push(item.z);

        /* 根据强度生成颜色 */
        let rgb = hslToRgb((240 - item.i) / 330, 1, 0.5);

        colors.push(rgb.r);
        colors.push(rgb.g);
        colors.push(rgb.b);
      });

      return { sxl, colors }
    },

    /* 向app传递消息 */
    postMessage(type) {
      uni.postMessage({ data: { type } });
    },

    /* 退出 */
    esc() {
      this.showModel = false;
      this.postMessage(1);
    },

    /* 进度变化 */
    onPercentChange(e) {
      // this.isCatmull = false;

      /* 当前画面上传存在的points */
      let se = scene.children.filter((item) => item.type == "Points");
      let asas = parseInt(se.length * (e / 100));
      se.forEach((item, index) => item.visible = (index <= asas));
      this.render();
    },
  },

  created() {
    let rem = (window.innerWidth / 390) * 100;
    document.querySelector("html").style.fontSize = `${rem}px`;

    window.uniEvent = this.uniEvent;
  },

  watch: {
    type: function (val) {
      if (val == 1) {
        camera.position.set(0.03222047045112021, -0.020101109662445632, 20.518249384064756);
        camera.lookAt(new THREE.Vector3(-0.0015703296445703431, 0.000979668141083902, -0.9999982871561036));
        this.render();
      }

      if (val == 2) {
        camera.position.set(0, -14, 15);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
        this.render();
      }
    }
  },
}
</script>

<style lang="less">
* {
  margin: 0;
  padding: 0;
}

body {
  font-size: 14px;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

.warp {
  background-color: black;
}

.top {
  position: fixed;
  width: 100vw;
  left: 0;
  top: 0.67rem;
  padding: 0 0.2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #fff;
  box-sizing: border-box;
}

.btm {
  width: 100vw;
  box-sizing: border-box;
  display: flex;
  justify-content: space-between;
  color: #fff;
  padding: 0 0.2rem;
  position: fixed;
  left: 0;
  bottom: 5%;

  li {
    display: flex;
    background-color: #323232;
    border-radius: 0.08rem;
    line-height: 0.4rem;
    padding: 0 0.1rem;
    white-space: nowrap;

    img {
      margin-right: 0.06rem;
    }

    &.active {
      background-color: #FF9800;
    }
  }
}

/* 启动/关闭按钮 */
.btn {
  position: fixed;
  left: 50%;
  bottom: 15%;
  transform: translateX(-50%);

  width: 0.94rem;
  height: 0.94rem;

  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(164, 240, 189, 0.2);
  border-radius: 50%;

  div {
    width: 0.78rem;
    height: 0.78rem;
    background: linear-gradient(180deg, #67D98D 0%, #41C63B 100%);
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
    font-size: 0.2rem;
  }
}

/* 弹窗 */
.model {
  position: fixed;
  left: 50%;
  top: 40%;
  margin-left: -1.5rem;
  background-color: #fff;
  border-radius: 0.2rem;
  width: 3rem;
  padding: 0.2rem 0;
  text-align: center;

  h3 {
    font-size: 0.2rem;
  }

  p {
    padding: 0.16rem 0 0.35rem;
  }

  .btns {
    display: flex;
    justify-content: center;

    .btnn {
      border-radius: 0.08rem;
      background-color: #E2E2E2;
      line-height: 0.3rem;
      width: 0.8rem;

      &+.btnn {
        background: linear-gradient(270deg, #FF9800 0%, #FEAB31 100%);
        margin-left: 0.2rem;
        color: #fff;
      }
    }
  }
}

/* vue transition */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
  transform: scale(0);
}

/* 步进器主体 */
#jd {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 999;
  transition: all 0.3s;
}

/* btmm */
.btmm {
  width: 100%;
  height: 0.08rem;
  background: linear-gradient(180deg, #67D98D 0%, #41C63B 100%);
  position: fixed;
  bottom: 0;
  left: 0;
}
</style>

  • 写回答

17条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-03-09 14:42
    关注
    获得0.45元问题酬金

    引自免费微信小程序:皆我百晓生

    在你的Vue项目中,你已经设置好了three.js的基础环境,包括场景、相机、渲染器、OrbitControls控件等,并且也创建了接收和处理设备传输数据的逻辑。为了实现类似BLK2GO Live的效果,即实时绘制设备传输的数据及视角移动,并绘制移动轨迹,你需要关注以下几个关键点:

    1. 实时数据接收与处理: 在uniEvent方法中,当接收到设备位置信息时,调用createCatmullRomCurve3方法来构建轨迹曲线。确保每次新位置数据到来时,都在路径数组(path)中添加新的顶点。

    2. 动态更新轨迹: 为了解决实时更新轨迹,每次接收到新的位置数据时,可以考虑清除旧的轨迹网格模型(mesh),然后重新生成TubeGeometry。或者,如果需要保留历史轨迹,可以在每次添加新点后,对CatmullRomCurve3进行动态更新。

    // 清除并重建轨迹
    if (mesh) scene.remove(mesh);
    const curve3 = new THREE.CatmullRomCurve3(path);
    const geometry = new THREE.TubeGeometry(curve3, path.length - 1, 0.05, 10, false);
    mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
    
    1. 视图跟随设备移动setCatmull方法用于根据设备的位置和朝向更新相机视角,确保每次接收到新数据时都执行此方法以保持视角同步。

    2. 点云实时显示: 当接收到点云数据时,调用createBufferGeometry方法创建点云模型,并将其添加到场景中。确保每次更新点云数据时,清理或隐藏旧的点云对象,再创建并显示新的点云。

    3. 动画循环: 使用requestAnimationFrame函数实现实时渲染和更新。在mounted生命周期钩子中添加以下代码:

    async mounted() {
        // ...其他初始化代码
    
        this.createWebGL();
        // 初始化动画循环
        this.animate();
    
        // ...监听事件和其他逻辑
    }
    
    animate() {
        requestAnimationFrame(this.animate.bind(this));
        controls.update(); // 更新轨道控制器
        this.render(); // 执行渲染
    }
    

    通过以上步骤,你应该能够实现实时绘制设备传输的数据及视角移动,并绘制移动轨迹的功能。同时,记得根据实际需求调整轨迹曲线参数以及点云数据的处理方式,以达到预期效果。

    评论 编辑记录

报告相同问题?

问题事件

  • 系统已结题 3月17日
  • 修改了问题 3月9日
  • 创建了问题 3月9日

悬赏问题

  • ¥100 图像处理,如何优化使得清晰
  • ¥15 请求看看这个仿真电路哪里有问题?
  • ¥100 关于python拓展屏使用pyautogui.screenshot无法截图的问题
  • ¥15 有偿求答 无导出权限怎么快速导出企业微信微文档
  • ¥15 求解答,怎么数码管中这么加入闹钟或者传感器,这应该怎么加入相应的代码
  • ¥15 scottplot5
  • ¥30 想问问这个建模怎么编程没有思路
  • ¥15 关于imageENview(ImageEN)中新建图层并根据鼠标位置添加图标
  • ¥100 用两台电脑局域联网进行MT5的EA参数优化,但是连接不上,日志提示:
  • ¥15 FastAPI报错: AsyncSession不是有效Pydantic类型