lanceeeeeeeee 2024-04-08 15:36 采纳率: 33.3%
浏览 29
已结题

threejs加载urdf模型实现点击变色和获取点击模组的名称

threejs + urdf-loaders加载了一个urdf模型, 想实现模型上的模组,对应的模组变色. 并且点击的时候,获取到所点击的模组名称.模组是多个STL文件,urdf文件引入的link上写有对应的name.
模型目录结构:

-cart
---meshes
------base_link.STL
------lleft_back_link.STL
------lleft_forward_link.STL
------right_back_link.STL
------rigit_forward_link.STL
---urdf
------vehicle.urdf

文件无法上传, 我的urdf是在这个开源库下载的,可以直接访问下载.
https://gitee.com/reinovo/robot_urdf/tree/master/bobac2_description/urdf

// vue2代码
模板
<template>
  <div class="page">
    <div class="container-box">
      <slider @sliderInput="sliderInput"/>
      <div id="container"></div>
    </div>
    <button @click="saveFile()">保存图片</button>
  </div>
</template>

<script>
import * as Three from 'three'
import {LoadingManager,MathUtils,} from 'three'
import URDFLoader from 'urdf-loader'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
import Slider from './components/slider.vue'
/**
 * 代表一个Three.js图形渲染组件
 */
export default {
  name: 'HelloWorld',
  components: {
    Slider,
  },
  props: {
    msg: String // 接收一个字符串类型的属性
  },
  data() {
    return {
      scene: null, // Three.js场景
      camera: null, // Three.js相机
      renderer: null, // Three.js渲染器
      mesh: null, // Three.js网格
      controls: null, // Three.js控制器
      container: null, // 容器元素
      handList : [],
      circlePosition:'',
      robot: null,
      handConfig :[
        {
          name: '2.mtl',
          rotation: {
            x: 0.7,
            y: 0.63,
            z: 0,
          },
        },
        {
          name: '3.mtl',
          rotation: {
            x: 0.1,
            y: 2.42,
            z: 0,
          },
        },
        {
          name: '4.mtl',
          rotation: {
            x: 0.15,
            y: 4.113,
            z: 0,
          },
        },
        {
          name: '5.mtl',
          rotation: {
            x: 0.65,
            y: 4.38,
            z: 0,
          },
        },
        {
          name: '6.mtl',
          rotation: {
            x: 0.88,
            y: 4.68,
            z: 0,
          },
        },
      ],
    }
  },
  methods: {
    /**
     * 初始化Three.js场景、相机、渲染器等
     */
    init() {
      this.container = document.getElementById('container');
      this.scene = new Three.Scene();
      const manager = new LoadingManager();
      const loader = new URDFLoader(manager);
      const path = '/modelFile/cart/urdf/vehicle.urdf';
      loader.load(
          path,
          result => {
            this.robot = result;
            // 模型添加点击事件
            document.addEventListener('click', (e) => {
              this._testOnClick(e, this.robot)
            })
            // 设置ROBOT在坐标原点
            this.robot.position.x = 0;
            this.robot.position.y = 0;
            // 确保模型底部位于渲染区域最下端(根据实际需求调整extraVerticalOffset)
            this.robot.position.z = 0;
            this.scene.add(this.robot);
          }
      )
      this.renderer = new Three.WebGLRenderer({antialias: true, preserveDrawingBuffer: true});
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      // this.renderer.setClearColor(0xffffff,0.0);  // 设置渲染器的背景颜色为白色且透明
      this.container.appendChild(this.renderer.domElement);
      this.createLight(-20, 30, 40); // 创建光源
      this.createLight(20, -30, -40);
      this.createCamera(); // 创建相机
      this.createControls(); // 创建控制器
      this.render(); // 进行渲染
    },
    /**
     * 创建Three.js相机
     */
    createCamera() {
      this.camera = new Three.PerspectiveCamera(70, this.container.clientWidth / this.container.clientHeight, 0.01, 1000);
      this.camera.position.set(0, -1, 0);        // 设置相机位置
      this.camera.lookAt(new Three.Vector3(0, 0, 0));         // 设置相机朝向
    },
    /**
     * 创建轨道控制器
     */
    createControls() {
      this.clock = new Three.Clock(); // 创建计时器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement)
      this.controls.autoRotate = false // 设置是否自动旋转
    },
    /**
     * 在场景中添加光源
     * @param {number} pos_x 光源x轴位置
     * @param {number} pos_y 光源y轴位置
     * @param {number} pos_z 光源z轴位置
     */
    createLight(pos_x, pos_y, pos_z) {
      // 添加环境光
      const ambientLight = new Three.AmbientLight(0x111111) // 创建环境光
      this.scene.add(ambientLight) // 将环境光添加到场景
      const directionLight = new Three.DirectionalLight(0xffffff)
      directionLight.position.set(pos_x, pos_y, pos_z)
      directionLight.intensity = 1.5
      this.scene.add(directionLight) // 添加方向光
    },
    /**
     * 实现渲染循环
     */
    render() {
      const delta = this.clock.getDelta(); // 获取自上次渲染以来的时间差
      this.controls.update(delta); // 更新控制器
      this.renderer.render(this.scene, this.camera); // 渲染场景
      requestAnimationFrame(this.render); // 请求下一个动画帧
    },
    /**
     * 保存当前渲染视图为图片
     */
    saveFile() {
      var link = document.createElement("a");
      var canvas = document.getElementById('container').children[0];
      var imgData = canvas.toDataURL({format: 'image/png', quality: 1, width: 20000, height: 4000});
      var blob = this.dataURLtoBlob(imgData);
      var objurl = URL.createObjectURL(blob);
      link.download = "grid1.png";
      link.href = objurl;
      link.click();
    },

    /**
     * 将data URL转换为Blob对象
     * @param {string} dataurl - Data URL字符串
     * @returns {Blob} 转换后的Blob对象
     */
    dataURLtoBlob(dataurl) {
      var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
          bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], {type: mime});
    },
    // 设置各个关节的角度
    sliderInput(value, name) {
      // console.log(value, name,this.robot.joints)
      // 找到要设置的关节
      name = "joint" + String(name);
      this.robot?.joints[name].setJointValue(MathUtils.degToRad(value));
    },
     /**
      * 当点击事件发生时,测试并处理点击交互。
      * @param {MouseEvent} event - 触发的鼠标事件。
      * @param {Object3D} object - 用于检测点击的对象。
      */
     _testOnClick(event, object) {
       // 将鼠标点击位置转换为标准设备坐标
       const mouse = new Three.Vector2(
           (event.clientX / window.innerWidth) * 2 - 1,
           -(event.clientY / window.innerHeight) * 2 + 1
       );
       // 创建光线投射器用于检测鼠标点击是否命中3D对象
       const raycaster = new Three.Raycaster();
       // raycaster.params.Points.threshold = 10000;
       // 设置光线投射器的起点(相机位置)和方向(基于鼠标点击位置)
       raycaster.setFromCamera(mouse, this.camera);
       // console.log(raycaster,'raycaster')
       // 调整射线检测范围
       // raycaster.far = 1000000000;
       // 执行光线投射,检测是否有对象被命中
       const intersects = raycaster.intersectObject(object, true);
       console.log(intersects,'intersects',object)
       // 如果命中了至少一个对象
       if (intersects.length > 0) {
         // 获取最接近的命中对象
         const selected = intersects[0].object;
         // 如果命中对象是一个网格,改变其材质为红色
         if (selected instanceof Three.Mesh) {
           console.log('命中',selected)
           selected.material = new Three.MeshPhysicalMaterial({
             color: 'red',
             metalness: 1,
             roughness: 0.7,
           });
           // // 获取并打印点击的模型名称或标识
           // const modelName = selected.geometry?.userData?.filename?.split('.')[0];
           // console.log('点击的模型名称:', modelName)
           // if (selected.userData && selected.userData.modelName) {
           //   console.log('点击的模型名称:', selected.userData.modelName);
           // } else if (selected.name) {
           //   console.log('点击的模型标识:', selected.name);
           // } else {
           //   console.warn('无法获取点击模型的名称或标识,确保模型实例上已附加相关属性');
           // }
         }
       }
     },


  },
  mounted() {
    this.init(); // 组件挂载时初始化Three.js场景
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.page{
  width: 100%;
  .container-box{
    display: flex;
    #container {
      width: 60vw;
      height: 90vh;
    }
  }
}


</style>
  • 写回答

11条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-04-08 15:36
    关注

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

    针对您提出的问题,要在threejs加载的URDF模型中实现点击变色和获取点击模组的名称,可以按照以下步骤进行操作:

    1. 点击变色: 在您的_testOnClick方法中,已经实现了对点击事件的处理,并使用Raycaster检测与模型的交点。当检测到交点时,您已经正确地改变了选中网格(Mesh)的材质颜色为红色。如果您想实现点击模组变色,这段代码已经能满足需求。

    2. 获取点击模组的名称: 要获取点击模组的名称,通常可以从被点击对象的userData属性或name属性中提取。根据您提供的URDF模型文件,每个link元素都包含了name属性。在加载URDF模型时,这些信息应会被传递给相应的Mesh对象。您可以在_testOnClick方法中添加如下代码来获取点击模组的名称:

      if (selected instanceof Three.Mesh) {
        selected.material = new Three.MeshPhysicalMaterial({
          color: 'red',
          metalness: 1,
          roughness: 0.7,
        });
      
        // 获取点击模组的名称
        let clickedModuleName = '';
        if (selected.userData && selected.userData.linkName) {
          clickedModuleName = selected.userData.linkName;
        } else if (selected.name) {
          clickedModuleName = selected.name;
        }
      
        console.log('点击的模组名称:', clickedModuleName);
      }
      

      注意,这里假设URDF加载器(URDFLoader)将linkname属性存储在MeshuserData.linkName属性中。由于不同的URDF加载器可能有不同的实现方式,如果上述代码无法获取到正确的模组名称,请查阅您使用的URDF加载器文档或源码,确定存储模组名称的具体属性名。

    综上所述,您的现有代码已能实现点击变色功能。只需在_testOnClick方法中添加获取模组名称的相关代码即可。如果URDF加载器未将linkname属性存入MeshuserDataname属性,请根据实际情况调整代码以获取正确的模组名称。

    评论 编辑记录

报告相同问题?

问题事件

  • 已结题 (查看结题原因) 4月9日
  • 赞助了问题酬金15元 4月8日
  • 创建了问题 4月8日

悬赏问题

  • ¥15 关于#游戏策划#的问题:当浏览器输入兑换码,疯狂点击领取按钮,邮箱马上到账几十个兑换码礼包
  • ¥15 虚拟机打不开,怎么解决
  • ¥15 为什么游戏兑换码能被重复领取
  • ¥30 (急!)java实现二叉链表构建二叉树,实现相关功能
  • ¥15 C#tekloa节点插件小项
  • ¥20 脑电信号的局部场电位分析
  • ¥30 Diodes 霍尔开关AH337已经obselete,他的升级替代料【不改变现有电路图】
  • ¥15 python爬虫IndexError: list index out of range
  • ¥15 (标签-考研|关键词-set)
  • ¥15 求修改代码,图书管理系统