CollisionUtils - Helper class for finding uv-coordinates of a click

Hi guys. I finally managed to obtain the uv-coordinates of a click. I learned a lot on the way but now I want to know whether it was the right way. How it works:

  1. Obtain the triangle of the mesh which intersects with the ray.
  2. Convert the triangle coordinates to world coordinates via the world transform of the geometry.
  3. Intersect the ray again with the new triangle to get the st-coordinates in triangle space.
    Or get the st-coordinates of the contact point.
  4. Obtain the texture coordinates triangle.
  5. Use the texture coordinates triangle and the st-coordinates to get the uv-coordinates.

Especially point 2 looks cumbersome to me. Is there another way to obtain the world coordinates of the triangle?

But now lets come to the fun part: How to use it?

You can find the class there: CollisionUtils. A little test case is here: CollisionUtilsTest

I could add the getWorldTriangle and getUV methods to CollisionResult and make a PR if you want it.
I would really appreciate some feedback or suggestions. Also, I haven’t seen this anywhere. If you did, please inform me.

For those of you who don’t believe what they can’t see:

1 Like

I think you can maybe avoid a lot of the stuff you do by calculating the barycentric coordinates in 3D instead of in 2D.

Then it goes like:
-convert world space collision to local space (Spatial.worldToLocal())
-calculate the barycentric coordinates from that
-look up the texture coordinates and calculate the uv for that barycentric coordinate.

I have some utility code I’ve written somewhere… I’ll see if I can find it.

1 Like

Here is one of my TriangleUtils class (since what you’ve done is really triangle stuff and not really collision stuff, stylistically speaking):

import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;


/**
 *  Various utility methods for easily extracting different vertex
 *  attributes for triangle corners and then performing barycentric
 *  based interpolation to find the value for some intersection point.
 *
 *  See also: https://en.wikipedia.org/wiki/Barycentric_coordinate_system
 *
 *  @author    Paul Speed
 */
public class TriangleUtils {

    public static Vector4f getBarycentricInterpolation( Vector2f coord, Vector4f t0, Vector4f t1, Vector4f t2 ) {    
        Vector4f tv1 = t1.subtract(t0);
        Vector4f tv2 = t2.subtract(t0);
        Vector4f result = t0.add(tv1.mult(coord.x)).add(tv2.mult(coord.y));
        return result;
    }

    public static Vector4f getBarycentricInterpolation( Vector2f coord, Mesh mesh, Type type, int index ) {
        Vector4f t0 = new Vector4f();
        Vector4f t1 = new Vector4f();
        Vector4f t2 = new Vector4f();
        getTriangle(mesh, type, index, t0, t1, t2);
        return getBarycentricInterpolation(coord, t0, t1, t2); 
    }    

    public static Vector2f getBarycentricCoordinates( Vector3f point, Vector3f tri0, Vector3f tri1, Vector3f tri2 ) {
 
        // Get the triangle edges and the vector to p       
        Vector3f v0 = tri1.subtract(tri0);
        Vector3f v1 = tri2.subtract(tri0);
        Vector3f v2 = point.subtract(tri0);

        float dot00 = v0.dot(v0);
        float dot01 = v0.dot(v1);
        float dot02 = v0.dot(v2);
        float dot11 = v1.dot(v1);
        float dot12 = v1.dot(v2);

        float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
        float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
        float v = (dot00 * dot12 - dot01 * dot02) * invDenom;

        return new Vector2f(u, v);
    }
    
    public static Vector2f getBarycentricCoordinates( Vector3f p, Mesh mesh, int index ) {
        
        Vector3f tri0 = new Vector3f();               
        Vector3f tri1 = new Vector3f();               
        Vector3f tri2 = new Vector3f();
              
        getTriangle(mesh, Type.Position, index, tri0, tri1, tri2);
        
        return getBarycentricCoordinates(p, tri0, tri1, tri2);
    }        

    public static void getTriangle( Mesh mesh, Type type, int index, Vector3f v1, Vector3f v2, Vector3f v3 ){
        VertexBuffer vb = mesh.getBuffer(type);   
        IndexBuffer ib = mesh.getIndicesAsList();
        if( vb != null && vb.getFormat() == Format.Float && vb.getNumComponents() == 3 ) {
            FloatBuffer fpb = (FloatBuffer)vb.getData();
    
            // aquire triangle's vertex indices
            int vertIndex = index * 3;
            int vert1 = ib.get(vertIndex);
            int vert2 = ib.get(vertIndex+1);
            int vert3 = ib.get(vertIndex+2);

            BufferUtils.populateFromBuffer(v1, fpb, vert1);
            BufferUtils.populateFromBuffer(v2, fpb, vert2);
            BufferUtils.populateFromBuffer(v3, fpb, vert3);
        } else {
            throw new UnsupportedOperationException("Buffer not set for:" + type + " or "
                                                    + " has incompatible format: Float * 3");
        }
    }

    public static void getTriangle( Mesh mesh, Type type, int index, Vector4f v1, Vector4f v2, Vector4f v3 ){
        VertexBuffer vb = mesh.getBuffer(type);   
        IndexBuffer ib = mesh.getIndicesAsList();
        if( vb != null && vb.getFormat() == Format.Float && vb.getNumComponents() == 4 ) {
            FloatBuffer fpb = (FloatBuffer)vb.getData();
    
            // aquire triangle's vertex indices
            int vertIndex = index * 3;
            int vert1 = ib.get(vertIndex);
            int vert2 = ib.get(vertIndex+1);
            int vert3 = ib.get(vertIndex+2);

            BufferUtils.populateFromBuffer(v1, fpb, vert1);
            BufferUtils.populateFromBuffer(v2, fpb, vert2);
            BufferUtils.populateFromBuffer(v3, fpb, vert3);
        } else {
            throw new UnsupportedOperationException("Buffer not set for:" + type + " or "
                                                    + " has incompatible format: Float * 4");
        }
    } 
}

Some day this will go into one of my public utility libraries with some additional methods. For example, it needs some more versions of the getBarycentricInterpolation() method to handle Vector3f, Vector2f, etc…

The general approach:

  1. find the barycentric coordinate for your point, for example using: getBarycentricCoordinates( Vector3f point, Vector3f tri0, Vector3f tri1, Vector3f tri2 ) passing the collision point in model space along with the triangle points.
  2. then use one of the getBarycentricInterpolation() to interpolate a value from some vertex attributes. For example, TexCoord (if I’d written the Vector2f version of those methods… which is mostly a cut+paste op)

These were designed to interpolate any vertex attribute because in my case I have several things I want interpolated.

1 Like

Thanks pspeed. I think I did exactly what you suggested in step 3-5 without knowing that it is called barycentric interpolation. I only converted the triangle to world space before to obtain the barycentric coordinates for the contact point and not the other way round.

Nevertheless here is the new code:

Vector3f localContact = new Vector3f();
geom.worldToLocal(collision.getContactPoint(), localContact);
Triangle triangle = new Triangle();
collision.getTriangle(triangle);
Vector2f bc = TriangleUtils.getBarycentricCoordinates(localContact, triangle.get1(), triangle.get2(), triangle.get3());
Vector2f uv = TriangleUtils.getBarycentricInterpolation2(bc, geom.getMesh(), VertexBuffer.Type.TexCoord, collision.getTriangleIndex());

System.out.println(uv);

And I adapted your methods in this way:

public static Vector2f getBarycentricInterpolation( Vector2f coord, Vector2f t0, Vector2f t1, Vector2f t2 ) {
    Vector2f tv1 = t1.subtract(t0);
    Vector2f tv2 = t2.subtract(t0);
    Vector2f result = t0.add(tv1.mult(coord.x)).add(tv2.mult(coord.y));
    return result;
}

public static Vector2f getBarycentricInterpolation2( Vector2f coord, Mesh mesh, Type type, int index ) {
    Vector2f t0 = new Vector2f();
    Vector2f t1 = new Vector2f();
    Vector2f t2 = new Vector2f();
    getTriangle(mesh, type, index, t0, t1, t2);
    return getBarycentricInterpolation(coord, t0, t1, t2);
}

public static void getTriangle( Mesh mesh, Type type, int index, Vector2f v1, Vector2f v2, Vector2f v3 ){
    VertexBuffer vb = mesh.getBuffer(type);
    IndexBuffer ib = mesh.getIndicesAsList();
    if( vb != null && vb.getFormat() == Format.Float && vb.getNumComponents() == 2 ) {
        FloatBuffer fpb = (FloatBuffer)vb.getData();

        // aquire triangle's vertex indices
        int vertIndex = index * 3;
        int vert1 = ib.get(vertIndex);
        int vert2 = ib.get(vertIndex+1);
        int vert3 = ib.get(vertIndex+2);

        BufferUtils.populateFromBuffer(v1, fpb, vert1);
        BufferUtils.populateFromBuffer(v2, fpb, vert2);
        BufferUtils.populateFromBuffer(v3, fpb, vert3);
    } else {
        throw new UnsupportedOperationException("Buffer not set for:" + type + " or "
                + " has incompatible format: Float * 2");
    }
}

Thank you for showing me how to do it properly!