Javascript 使用raycaster选择gltf的特定元素&;添加事件侦听器

Javascript 使用raycaster选择gltf的特定元素&;添加事件侦听器,javascript,three.js,Javascript,Three.js,我正在构建一个交互式web应用程序。我已将glb文件加载到场景中,现在我想添加两个功能: onMouseover在对象中的一个元素上,我想更改该元素的颜色 3D对象由纹理组[Object3D]组成,如下面的控制台日志所示。纹理组本质上是拼写HAJDUK的3D字母(对象),但我已将它们作为一个分组的.glb文件导入,我想添加一个事件侦听器,以便在单击单个字母(对象)时为每个单独的对象加载不同的链接 我的困惑是,我是否可以通过使用raycaster以某种方式实现这一点,或者我是否需要进一步遍历Tex

我正在构建一个交互式web应用程序。我已将glb文件加载到场景中,现在我想添加两个功能:

  • onMouseover
    在对象中的一个元素上,我想更改该元素的颜色
  • 3D对象由纹理组[Object3D]组成,如下面的控制台日志所示。纹理组本质上是拼写HAJDUK的3D字母(对象),但我已将它们作为一个分组的.glb文件导入,我想添加一个事件侦听器,以便在单击单个字母(对象)时为每个单独的对象加载不同的链接
  • 我的困惑是,我是否可以通过使用raycaster以某种方式实现这一点,或者我是否需要进一步遍历Texture_组并为此开发一种if-else逻辑,以及如何实现这些功能?
    我猜我需要使用光线投射器来穿越场景,但我有点卡住了。谢谢你的帮助,如果我还需要什么,现在让我回答,这是我的第一个问题

    我已经在console.log中遍历了我的场景,借助于我在网上找到的一些代码,并根据我的情况对其进行了一些调整

    这是我的app.js代码:

    /*jshint esversion: 6 */
    let scene, camera, renderer, h, controls, raycaster, mouse;
    
    function init() {
      scene = new THREE.Scene();
    
      //Camera
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(0, 6, 6);
    
      const myCanvas = document.getElementById('myCanvas');
    
      //Renderer
      renderer = new THREE.WebGLRenderer({
        canvas: myCanvas,
        antialias: true,
      });
      renderer.setClearColor(0xcfd4d8);
      renderer.setPixelRatio(window.deviceSPixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      document.body.appendChild(renderer.domElement);
    
      //Controls
      controls = new THREE.OrbitControls(camera, myCanvas);
      controls.target.set(0, 6, 0);
      controls.update();
    
      controls.enableDamping = true;
    
      controls.dampingFactor = 0.25;
    
      controls.screenSpacePanning = false;
    
      controls.minDistance = -20;
      controls.maxDistance = 100;
    
      controls.maxPolarAngle = Math.PI;
      controls.enableZoom = true;
    
      controls.rotateSpeed = 0.3;
      controls.zoomSpeed = 10.0;
    
      //Lights
      const light = new THREE.AmbientLight(0xffffff, 0.5);
      scene.add(light);
    
      const light2 = new THREE.PointLight(0xffffff, 0.5);
      scene.add(light2);
    
      //Loader
      const loader = new THREE.GLTFLoader();
      loader.load(
        'content/hajdukzagltf.glb',
        function (gltf) {
          h = scene.add(gltf.scene);
          h.position.set(6, -2, 1);
          console.log(dumpObject(h).join('\n'));
          console.log(gltf);
        }
      );
    
    }
    
    init();
    
    //Render Loop
    render();
    
    function render() {
    
      requestAnimationFrame(render);
    
      controls.update();
      renderer.render(scene, camera);
    }
    
    //windowResize
    function windowResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
    
    window.addEventListener('resize', windowResize, false);
    
    //consol log traverse
    function dumpVec3(v3, precision = 3) {
      return `${v3.x.toFixed(precision)}, ${v3.y.toFixed(precision)}, ${v3.z.toFixed(precision)}`;
    }
    
    function dumpObject(obj, lines = [], isLast = true, prefix = '') {
      const localPrefix = isLast ? '└─' : '├─';
      lines.push(`${prefix}${prefix ? localPrefix : ''}${obj.name || '*no-name*'} [${obj.type}]`);
      const dataPrefix = obj.children.length
         ? (isLast ? '  │ ' : '│ │ ')
         : (isLast ? '    ' : '│   ');
      lines.push(`${prefix}${dataPrefix}  pos: ${dumpVec3(obj.position)}`);
      lines.push(`${prefix}${dataPrefix}  rot: ${dumpVec3(obj.rotation)}`);
      lines.push(`${prefix}${dataPrefix}  scl: ${dumpVec3(obj.scale)}`);
      const newPrefix = prefix + (isLast ? '  ' : '│ ');
      const lastNdx = obj.children.length - 1;
      obj.children.forEach((child, ndx) => {
        const isLast = ndx === lastNdx;
        dumpObject(child, lines, isLast, newPrefix);
      });
    
      return lines;
    }
    
    console.log:

    *no-name* [Scene]
      │   pos: 6.0000, -2.0000, 1.0000
      │   rot: 0.0000, 0.0000, 0.0000
      │   scl: 1.0000, 1.0000, 1.0000
      ├─*no-name* [AmbientLight]
      │     pos: 0.0000, 0.0000, 0.0000
      │     rot: 0.0000, 0.0000, 0.0000
      │     scl: 1.0000, 1.0000, 1.0000
      ├─*no-name* [PointLight]
      │     pos: 0.0000, 0.0000, 0.0000
      │     rot: 0.0000, 0.0000, 0.0000
      │     scl: 1.0000, 1.0000, 1.0000
      └─*no-name* [Scene]
        │   pos: 0.0000, 0.0000, 0.0000
        │   rot: 0.0000, 0.0000, 0.0000
        │   scl: 1.0000, 1.0000, 1.0000
        ├─Camera_(2) [PerspectiveCamera]
        │     pos: -12.5626, 8.9117, -9.9300
        │     rot: -0.1273, -0.2253, -0.0286
        │     scl: 1.0000, 1.0000, 1.0000
        ├─Texture_Group [Object3D]
        │     pos: 0.0000, 0.0000, 0.0000
        │     rot: 0.0000, 0.0000, 0.0000
        │     scl: 1.0000, 1.0000, 1.0000
        ├─b [Mesh]
        │     pos: 2.6200, 3.9200, -19.2000
        │     rot: 0.0000, 0.0000, 0.0000
        │     scl: 1.0000, 1.0000, 1.0000
        ├─z [Mesh]
        │     pos: 0.0000, 0.0000, 0.0000
        │     rot: 0.0000, 0.0000, 0.0000
        │     scl: 1.0000, 1.0000, 1.0000
        └─bg [Mesh]
              pos: 0.0000, 0.0000, 0.0000
              rot: 0.0000, 0.0000, 0.0000
              scl: 1.0000, 1.0000, 1.0000
    

    如评论部分所述,所有功能都可以使用简单的

    为了检测鼠标何时悬停在场景中的一组对象上,可以在包含子网格的
    THREE.group
    THREE.Object3D
    中将它们组合在一起

    mousemove
    事件绑定到更新包含视口鼠标位置的全局变量
    mouse
    的函数

    function onMouseMove( event ) {
    
      event.preventDefault();
    
      mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
      mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
    
    }
    
    在动画循环中,根据当前鼠标位置执行光线投射,并检查是否与所述组包含的任何对象相交。然后,您需要更新这些对象的颜色,或者在不相交时恢复这些对象的颜色

    function raycast() {
    
      raycaster.setFromCamera( mouse, camera );
    
      var intersects = raycaster.intersectObjects( group.children );
    
      if ( intersects.length > 0 ) {
    
        if ( INTERSECTED != intersects[ 0 ].object ) {
    
          if ( INTERSECTED ) INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
    
          INTERSECTED = intersects[ 0 ].object;
          INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
          INTERSECTED.material.color.setHex( 0xd4d4d4 );
    
        }
    
      } else {
    
        if ( INTERSECTED ) INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
    
        INTERSECTED = null;
    
      }
    
    }
    
    最后,如果对象当前与光线相交,则将
    单击
    事件绑定到另一个执行所需功能的函数

    function onMouseClick( event ) {
    
      if ( INTERSECTED !== null ) clickFunction( INTERSECTED ); // perform object operation
    
    }
    
    这表明这些功能正在发挥作用

    如果有不清楚的地方,请告诉我



    如果
    TEXTURE\u组中的对象没有整齐地组织在网格中,我建议您首先将每个字母打包到一个网格中,在遍历时,将它们添加到
    Object3D
    GROUP
    中(如果它们还没有在一个网格中)。

    第二个目标对我来说不清楚,您能提供更多信息吗?关于第一部分,您可以使用。这实现了您在第一个目标中描述的内容。@ScieCode感谢您回答我的第一个问题。今天我将尝试实现它,我已编辑了问题的第二部分,并提供了更多信息。希望它能让我更容易理解我想做的事情,谢谢你的澄清。我不知道在您的示例中,
    Texture\u组
    是如何组织的,但是如果每个字母都由
    Object3D
    的单个网格子级组成,您可以简单地使用Object3D子级作为光线投射的目标,并根据相交的字母,对该特定字母执行回调响应。如果它太难理解,我可以写一个完整的答案和例子来证明这一点。感谢你的回答,但如果它不是一个麻烦,请写一个例子,它会使它更清楚,这是一个未经探索的领域me@ScieCode是的,谢谢你的帮助,花了我一点时间,直到我遍历了我的gltf文件,但现在它就像一个符咒!