safanth
safanth

Reputation: 11

LIBGDX 3D: unable to accurately determine which mesh triangle is hit by a camera ray

I am learning to use LIBGDX to create a minecraft alike world, created out of textured blocks (cubes). I have the 3D world rendered and the player movement is working flawlessly (the result and movement experience thru the 3D world feels as expected).

I am now aiming to detect which side/face of a block is pointed at by the camera from a firstperson perspective. Using the Intersector.intersectRayBounds I managed to determine which block is pointed at. I draw a boundingbox around the found block for visual result.

The next step is to visually mark the exact side/face of the block (cube). I am trying to achieve this by using the Intersector.intersectRayTriangles after iterating thru the list of meshparts of the found block and retrieve the indices/vertices of the mesh.

The current result is that I am able to mark the side/face of the cube which is pointed at, however, it is inaccurate. Only when "pointing at" the block in a specific corner it will result in a positive intersection - as if I am only able to test a subset of triangles rather than all.

Code example to determine which block(cube) is pointed at:

Ray ray = camera.getPickRay(1280/2, 720/2+BLOCK_SIZE);//, 0, 0, 1280, 720);
float distance = -1f;
float distance2;
Vector3 position = new Vector3();
Vector3 intersection = new Vector3();
BoundingBox bb = new BoundingBox();
for (BlockBase3D block:mBlocksArray) {
  bb.set(block.getBoundingBox());
  bb.mul(block.getModelInstance().transform);
  position.set(block.getCenterPosition());
  distance2 = ray.origin.dst2(position);
  if (distance >= 0f && distance2 > distance) continue;
  if (Intersector.intersectRayBounds(ray, bb, intersection)) {
    mBlockPointed = block;
    distance = distance2;
  }
}
mBlockPointed.setIsShowingBoundingBox(true);

This part of the code results in a found block mBlockPointed.

With the next code part I try to determine the exact side/face of the block pointed at:

if (mBlockPointed != null){        
  NodePart np = null;
  MeshPart mp = null;
  float[] meshPartVertices = {};
  short[] meshPartIndices = {};

  Mesh mesh;
  int vertexSize;
  ray = camera.getPickRay(1280/2, 720/2+BLOCK_SIZE);

  for (int j=0; j<mBlockPointed.getModelInstance().nodes.size; j++){
    for (int i = 0; i < mBlockPointed.getModelInstance().nodes.get(j).parts.size; i++) {
      np = mBlockPointed.getModelInstance().nodes.get(j).parts.get(i);
      mp = np.meshPart;
      mesh = mp.mesh.copy(false);
      mesh.transform(mBlockPointed.getModelInstance().transform);
      meshPartIndices = new short[mp.size];
      mesh.getIndices(mp.offset, mp.size, meshPartIndices, 0);
      vertexSize = mesh.getVertexSize() / 4;
      meshPartVertices = new float[mesh.getNumVertices() * vertexSize];
      mesh.getVertices(meshPartVertices);

      if (Intersector.intersectRayTriangles(ray, meshPartVertices, meshPartIndices, vertexSize, null)) {
        np.material.set(mSelectionMaterial);
        //break;
      }                        
    }
  }

My thought behind this codepart are:

The result is that when I move the (perspective-) camera around I get a positive result only when the camera points to one of the corner areas of the blockside. I am unable to think of the cause of this, which if I was able to, could set me off in the direction to solve this.

EDIT - working code after using ModelInstance.Renderer.worldTransform in stead of ModelInstance.transform

Also updated my code with some inline comments and optimization.

public void detectObjectPointed(int screenX, int screenY, PerspectiveCamera camera){
        Ray ray = camera.getPickRay(1280/2, 720/2+BLOCK_SIZE);
        float distance = -1f;
        float distance2;

        // if previous block found, restore original materials
        if (mBlockPointed != null){
            for (int i = 0; i < mBlockPointed.getModelInstance().nodes.get(0).parts.size; i++) {
                mBlockPointed.getModelInstance().nodes.get(0).parts.get(i).material.set(mOriginalMaterials.get(i));
            }
            mBlockPointed.setIsShowingBoundingBox(false);
            mBlockPointed = null;
        }

        // attempt to find block pointing at by camera ray
        for (BlockBase3D block:mBlocksArray) {
            mBlockPointedBoundingBox.set(block.getBoundingBox());
            mBlockPointedBoundingBox.mul(block.getModelInstance().transform);
            mBlockPointedPosition.set(block.getCenterPosition().cpy());
            distance2 = ray.origin.dst2(mBlockPointedPosition);
            if (distance >= 0f && distance2 > distance) continue;
            if (Intersector.intersectRayBounds(ray, mBlockPointedBoundingBox, mBlockPointedIntersection)) {
                mBlockPointed = block;
                distance = distance2;
            }
        }

        // if block pointed at is found
        if (mBlockPointed != null){
            // draw the boundingbox (wireframe) to visually mark the block pointed at
            mBlockPointed.setIsShowingBoundingBox(true);

            // get the mesh of the block pointed at and populate the vertices array; assumption made we have 1 mesh only in the model
            float[] meshPartVertices = {};
            short[] meshPartIndices = {};
            int vertexSize;
            // get the worldtransform matrix of the renderable from the ModelInstance
            Matrix4 m4 = mBlockPointed.getModelInstance().getRenderable(new Renderable()).worldTransform;
            mBlockPointedMesh = mBlockPointed.getModel().meshes.get(0).copy(false);
            // transform the vertices of the mesh to match world position/rotation/scaling
            mBlockPointedMesh.transform(m4);
            vertexSize = mBlockPointedMesh.getVertexSize() / 4; // a float is 4 bytes, divide by four to get the number of floats
            meshPartVertices = new float[mBlockPointedMesh.getNumVertices() * vertexSize];
            mBlockPointedMesh.getVertices(meshPartVertices);

            // clear the backup of original materials
            mOriginalMaterials.clear();
            // assume we have one model node only and loop over the nodeparts (expected is 6 nodeparts, 1 for each block/cube side)
            for (int i = 0; i < mBlockPointed.getModelInstance().nodes.get(0).parts.size; i++) {
                // get a nodepart and populate the indices array
                mBlockPointedNodePart = mBlockPointed.getModelInstance().nodes.get(0).parts.get(i);
                meshPartIndices = new short[mBlockPointedNodePart.meshPart.size];
                mBlockPointedMesh.getIndices(mBlockPointedNodePart.meshPart.offset, mBlockPointedNodePart.meshPart.size, meshPartIndices, 0);

                // backup the original material for this nodepart
                mOriginalMaterials.add(i, mBlockPointedNodePart.material.copy());

                // check if the ray intersects with one or more of the triangles for this nodepart
                if (Intersector.intersectRayTriangles(ray, meshPartVertices, meshPartIndices, vertexSize, null)) {
                    // intersection detected, visually mark the nodepart by setting a selection material
                    mBlockPointedNodePart.material.set(mSelectionMaterial);
                }
            }
        }

Upvotes: 1

Views: 345

Answers (0)

Related Questions