TrianglePickResults and scene graph, for terrain following

hi all,



I need to do triangle picking for terrain following, and i have a scene graph node with multiple child nodes, for example a “terrain” and a “road” that sits on top of the terrain. i’m not so concerned if i get a few triangles around the actual pick point, i understand that TrianglePickResults is a little off, but the bigger problem is that it returns both the “terrain” mesh and the “road” mesh that sits on top of it, in no particular order that i can ascertain. the road mesh is clearly positioned above the terrain, but usually the terrain is the first mesh in the array returned by the function.



is there a way to determine from the results, which is the first triangle hit by the ray?



thanks!



j

Because of the way the picking works, there’s no way to determine the first triangle hit by the ray. If you wanted that, you could iterate through all the triangles on the list, find the point of plane/ray intersection, and return the smallest distance.

I think that we should add s and t collisions to the to-do list.

thanks again for the quick reply, you guys rock.


...iterate through all the triangles on the list, find the point of plane/ray intersection, and return the smallest distance.


i get the bit about iterating, but how do you find the point of plane/ray intersection? for each triangle?

btw thanks also for replies to my previous post re 3rdperson controllers, the code in code review topic worked like a charm.

best,

j

Hi,



here my implementation. I hope this help

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Logger;

import com.jme.input.MouseInput;
import com.jme.intersection.PickResults;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.system.DisplaySystem;

public class PositionPickManager {
   
   private HashSet<ObjectPickListener> listeners = new HashSet<ObjectPickListener>();
   
    private DisplaySystem display;
   private Camera        cam;
   private Spatial       findPickOn;
   private boolean        onlyFirstResult = true;
   private Vector2f      screenPos = null;
   
   /**
     * @param findPickOn
     * @param mouseButton
     */
   public PositionPickManager(Spatial findPickOn) {
      this.findPickOn = findPickOn;
      reInit();
   }
   
   public void reInit() {
      this.display = DisplaySystem.getDisplaySystem();
      this.cam = display.getRenderer().getCamera();
   }
   
   @SuppressWarnings("unchecked")
   public void addListener(ObjectPickListener listener) {
      this.listeners.add(listener);
   }
   
   public void removeListener(ObjectPickListener listener) {
      this.listeners.remove(listener);
   }
   
   public void update(float tpf) {
      
      screenPos = new Vector2f();
      screenPos.set(MouseInput.get().getXAbsolute(), MouseInput.get().getYAbsolute());
      
      if(findPickOn!=null) {
         Vector3f worldCoords = display.getWorldCoordinates(screenPos, 0.0f);
         final Ray mouseRay = new Ray(cam.getLocation(), worldCoords.subtractLocal(cam.getLocation()));
         results.clear();
         if (findPickOn!=null)
            findPickOn.calculatePick(mouseRay, results);
        }
         
   }
   
   protected void fireObjectPickEvent(TriMesh mesh, Vector3f where) {
      for (Iterator<ObjectPickListener> iter = listeners.iterator(); iter.hasNext();) {
         iter.next().onPositionChange( new PickPositionEvent(mesh, where));
      }
   }
   
   private PickResults results = new TrianglePickResults() {
      
      MathUtil it = new MathUtil();
      
      public void processPick() {
         
         for (int i = 0; i < getNumber(); i++) {               
            Vector3f[] vertices = new Vector3f[3];
            Ray lineOfSight = getPickData(i).getRay();
            
            ArrayList al = getPickData(i).getTargetTris();
            TriMesh mesh = (TriMesh)getPickData(i).getTargetMesh().getParentGeom();
            
            for (int j = 0; j < al.size(); j++) {
               int triIndex = ((Integer) al.get(j)).intValue();
               mesh.getTriangle(triIndex, vertices);
               
               it.rayIntersectsTriangle(
                     lineOfSight.origin.subtract(mesh.getWorldTranslation()),
                     lineOfSight.direction,
                     vertices[0],
                     vertices[1],
                     vertices[2]);
               
               
               try {
                  Vector3f sceneTarget = it.getLastIntersectionVector().add(mesh.getWorldTranslation());
                  
                  fireObjectPickEvent(mesh, sceneTarget);
               } catch (RuntimeException ex) {
                  Logger.getLogger("engine").warning("no object data found");
               }
               
            }
         }
      }
   };
   
   public boolean isOnlyFirstResult() {
      return onlyFirstResult;
   }
   
   public void setOnlyFirstResult(boolean onlyFirstResult) {
      this.onlyFirstResult = onlyFirstResult;
   }
   



the listenerInterface, for connection

public interface ObjectPickListener {
    void onPositionChange(PickPositionEvent event);
   
}



and the event-class

import com.jme.math.Vector3f;
import com.jme.scene.TriMesh;

public class PickPositionEvent {

    private TriMesh mesh;
    private Vector3f where;
   
    public PickPositionEvent(TriMesh target, Vector3f position) {
        this.mesh  = target;
        this.where = position;
    }
   
    public TriMesh getTarget() {
        return mesh;
    }
   
    public Vector3f getWhere() {
        return where;
    }
   
}



the util-class found here, (a little bit changed)

import com.jme.math.Vector3f;

public class MathUtil {

    final float EPSILON = 0.000001f;
    private Vector3f edge1 = new Vector3f();
    private Vector3f edge2 = new Vector3f();
    private Vector3f tvec = new Vector3f();
    private Vector3f pvec = new Vector3f();
    private Vector3f qvec = new Vector3f();
    private Vector3f vertex0 = new Vector3f();
    private Vector3f intersection;

    /*
     * return value is not really a Point3f with x,y,z !
     * x is the distance, y,z are really u,v in the plane of the triangle
     */
    public Vector3f rayIntersectsTriangle(Vector3f origin, Vector3f direction, Vector3f triangle0, Vector3f triangle1, Vector3f triangle2)
    {
            intersection = new Vector3f();
            float det, inv_det;
            float t, u, v;

            // find two edges sharing vertex 0
            edge1.set(triangle1.subtract(triangle0));
            edge2.set(triangle2.subtract(triangle0));

            // begin calculating determinant - also used to calculate u
            direction.cross(edge2, pvec);

            // if determinant is near zero, ray lies in plane of triangle
            det = edge1.dot(pvec);

            if (det > -EPSILON && det < EPSILON)
                    return null;
            inv_det = 1.0f / det;
            // calculate distance from vertex 0 to ray origin
            tvec.set(origin.subtract(triangle0));

            // calculate u and test bounds
            u = tvec.dot(pvec) * inv_det;
            if (u < 0.0f || u > 1.0f)
                    return null;

            // prepare to test v
            qvec=tvec.cross(edge1);

            // calculate v and test bounds
            v = direction.dot(qvec) * inv_det;
            if (v < 0.0f || (u + v) > 1.0f)
                    return null;

            // calculate t, scale parameters, ray intersects triangle
            t = edge2.dot(qvec) * inv_det;

            intersection.set(t, u, v);
            vertex0.set(triangle0);
            return intersection;
    }

    public Vector3f getLastIntersectionVector()
    {
       Vector3f res = vertex0.add(edge1.mult(intersection.y)).add(edge2.mult(intersection.z));
       return res;
    }
   
    public static float getUForPixel(int xPixel, int textureWidth) {
        return (float) xPixel / textureWidth;
    }

    public static float getVForPixel(int yPixel, int textureHeight) {
        return 1f - (float) yPixel / textureHeight;
    }
   
}



usage of the manager.

create an Instance of PositionPickManager  and add a ObjectPickListener to this.

private PositionPickManager  groundSelecter;

protected void simpleInitGame() {

     ...

        groundSelecter = new PositionPickManager( ... a TerrainPage node ... );
        {
            groundSelecter.addListener(this);
        }

     ...
}

public void onPositionChange(PickPositionEvent event) {
     ....

     Vector3f pickPosition = event.getWhere();

     if ( MouseInput.get().isButtonDown(0) ) {

          //do anything, if mousebutton is pressed
     }
     ....
}




Ok, thats it...

this code was immensely helpful (have researched into the subject of how to get the point of intersection for some days now). could it be checked into the normal distro – please ?