title: Using raycaster to select specific elements of gltf & add event listeners
body: <p>I'm building an interactive web app. I've loaded a glb file to a scene and now I want to add 2 functionalities:</p>
  
  <ol>
  <li><code>onMouseover</code> on one of the elements from the object I would like to change the color of that element. </li>
  <li>The 3D object consists of a Texture_Group[Object3D] as shown in the console log below. The texture group are essentially 3D letters(objects) that spell out HAJDUK, but I've imported them as a grouped .glb file, I want to add an event listener so when clicked on the individual letter(object) a different link is loaded for each individual object.</li>
  </ol>
  
  <p>My confusion is if I can do this somehow just by using raycaster or do I need to further traverse the Texture_Group and develop a sort of if else logic for that, and how to do any of those?<br>
   I'm guessing I need to use the raycaster and traverse the scene but I'm kind of stuck. Thank you for any of your help and if I need to provide anything else let me now, it's my first question. </p>
  
  <p>I've traversed my scene in the console.log with the help of some code I've found online and adjusted it a bit to my case.</p>
  
  <p>This is my app.js code:</p>
  
  <pre><code>/*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) =&gt; {
      const isLast = ndx === lastNdx;
      dumpObject(child, lines, isLast, newPrefix);
    });
  
    return lines;
  }
  </code></pre>
  
  <p>console.log:</p>
  
  <pre><code>*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
  </code></pre>