three.jsbuffer-geometry

How To Get Clicked Geometry’s Name Attribute From BufferGeometry in ThreeJS?


I’m developing a VueJs application and added 1000 box model to the scene. Although I’ve set the every geometry’s name attribute when I clicked the box model I couldn’t get the box geometry’s name. I think this is caused by applying merged buffer geometry. Is it possible to get name attribute by clicking in some way ?

Here is my code;

addCubeToScene() {
  this.oDracoLoader = new DRACOLoader();
  this.oDracoLoader.setDecoderPath("./draco/");
  this.oGltfLoader = new GLTFLoader();
  this.oGltfLoader.setDRACOLoader(this.oDracoLoader);
  this.oDracoLoader.preload();
  this.oGltfLoader.load(BoxModel, function(oObject) {
    oObject.scene.traverse(function(oObject) {
      if (oObject.isMesh) {
        var oObjectGeometry = oObject.geometry;
        for (var i = 0; i < 1000; i++) {
          var oGeometry = oObjectGeometry.clone();
          oGeometry.applyMatrix4(
            new THREE.Matrix4().makeTranslation(
              Math.random() * 3000,
              Math.random() * 5000,
              Math.random() * 1000
            )
          );
          oGeometry.name = "Box-" + i;
          oCubes.push(oGeometry);
        }
        var oGeometriesCubes = BufferGeometryUtils.mergeBufferGeometries(
          oCubes
        );
        oGeometriesCubes.computeBoundingSphere();
        const oTexture = new THREE.TextureLoader().load(BoxTexture);
        oTexture.magFilter = THREE.NearestFilter;
        var oMesh = new THREE.Mesh(
          oGeometriesCubes,
          new THREE.MeshLambertMaterial({
            map: oTexture,
            side: THREE.DoubleSide,
          })
          // new THREE.MeshNormalMaterial()
        );
        oMesh.scale.set(0.5, 0.5, 0.5);
        oThis.scene.add(oMesh);
        oThis.renderScene();
      }
    });
  });
}

onMouseClick(oEvent) {
  oEvent.preventDefault();
  oEvent.stopPropagation();
  oMouse.x =
    (oEvent.offsetX / this.renderer.domElement.clientWidth) * 2 - 1;
  oMouse.y =
    -(oEvent.offsetY / this.renderer.domElement.clientHeight) * 2 + 1;
  oRaycaster.setFromCamera(oMouse, this.camera);
  var oIntersects = oRaycaster.intersectObjects(this.scene.children, true);
  if (oIntersects[0] && oIntersects[0].object) {
    var oObject = oIntersects[0].object;
    console.log(oObject); // I would like to get clicked geometry's name from this object
  }
}

Here is the image representing box model’s view;

enter image description here

Here is the result of box geometry datas

enter image description here

When I merged all geometries I can't see any name attribute


Solution

  • I assume you're trying to merge the geometry to increase render performance. While you did manage to reduce the number of draw calls the GPU had to make, you also removed all uniqueness of the different BufferGeometry objects. Your geometry is now represented by one big buffer (debug oGeometriesCubes.attributes.position.array to see this).

    In the interest of performance, we can not only save you GPU draw calls, but also memory, and we can get your box picking to work as well. Let's start from scratch. It will seem like we've taken a step backward, but then it will all fall together...

    Your geometry definitions

    First, you do not need to uniquely identify each BufferGeometry. Mesh objects can share geometry! AND materials! This equates to memory savings.

    Having unique Mesh objects also means you can put the name and translation information at the Mesh level. Translating at the geometry level 1000 times is expensive, even if you do it up-front like you do. Trust that the GPU can do these translations very quickly from the Mesh level. It's literally the GPU's job.

    Your mesh definitions

    Now, we're still looking at 1000 meshes, which means 1000 draw calls, right? That's where InstancedMesh comes into play.

    Instancing--in very simple terms--is a way for the GPU to not only re-use the geometry buffer information, but also to perform the drawing of all 1000 meshes in a single swipe, rather than one at a time.

    To sum it up:

    Ready? Let's do it.

    The code

    let geometry = yourGeometryFromGLTF;
    // load your texture here...
    let material = new new THREE.MeshLambertMaterial({
      map: oTexture,
      side: THREE.DoubleSide,
    })
    
    let iMesh = new THREE.InstancedMesh( geometry, material, 1000 );
    
    let translateMatrix = new THREE.Matrix4();
    let scaleMatrix = new THREE.Matrix4().makeScale( 0.5, 0.5, 0.5 );
    let finalMatrix = new THREE.Matrix4();
    
    for( let i = 0; i < 1000; ++i ){ 
     ​
      ​translateMatrix.makeTranslation(
        ​Math.random() * 1500,
        ​Math.random() * 2500,
        ​Math.random() * 500
     ​ );
    
      finalMatrix.multiplyMatrices( translateMatrix, scaleMatrix );
     ​ 
      iMesh.setMatrixAt( i, finalMatrix );
    
    }
    
    imesh.instanceMatrix.needsUpdate = true; // IMPORTANT
    scene.add( iMesh );
    

    Now your Raycaster should return the instances, but it will take one more step to get the "ID" of which box you selected.

    let intersects = raycaster.intersectObjects(scene);
    if(intersects.length > 0){
      let boxName = `Box-${ intersects[0].instanceId }`;
    }
    

    The instanceId will be the index of the box instance in your InstancedMesh. You can even use it to get more information about that instance, like its transformation matrix:

    let iMatrix = new THREE.Matrix4();
    iMesh.getMatrixAt( intersects.instanceId, iMatrix );