FPS370 - Changes from Alpha3 to Alpha4
3D collision detection and picking

The changes from a3 to a4 include collision detection with our map model. The concept of collision detection in Java3d is very easy. The implementation takes some effort, but once you have it, it should be straight forward.

In order to do collision detection in 3D you need to have information on your world space. Like where are all the 'things' you could run into. The easiest place to find all this information is in the Scene Graph. Since it contains everything currently being displayed, it has everything we need.

The next thing we need is a way of detecting when we intersect with something. In Java3D this is called Picking. You create a ray and ask Java3D if it intersects anything. A ray is just a location in world space and a directional vector. The detail returned can be simple to complex. You set flags to get the specific information you want.

By default, objects in the scene graph are not pickable. You need to set that state. The easiest way to do this is with:

PickTool.setCapabilities((Shape3D)o,PickTool.INTERSECT_COORD);

This is great if all the objects in your scene graph are simple Shape3D objects, but we created all of our world space from models loaded from external files. All we were given from that model after it was loaded was a BranchGroup. This is also fairly straight forward. We need to traverse the branch group and set each shape in the branch group individually.

  private void pickableModel(BranchGroup bg)
  {
    // go through the model and set each Shape3D to pickable
    Enumeration e = bg.getAllChildren();
    while (e.hasMoreElements())
    {
      Object o = e.nextElement();
      if (o instanceof Shape3D)
      {
        PickTool.setCapabilities((Shape3D)o,PickTool.INTERSECT_COORD);
      }
    }
  }

This method gets all the children from the BranchGroup and sets the Shape3D children to pickable using the static method setCapabilities provided by PickTool.

The next step is the check our intersection distance on each movement. In the simpliest case we just ignore the movement if we intersect. In a later exercise we'll have to move the fractional distance towards the object we hit. Or slide along the wall or object. Those exercise are just exercises in vector mathematics.

Here is the method to check the move to see if it is valid. If it is not, the movement is set to 0.

  public boolean adjustMoveDistance(Vector3f vMove)
  {
    // create the pick tool on first distance check, reuse for all later checks
    if (pickTool==null)
    {
      // create the picktool to always use the map
      pickTool = new PickTool(bgMap);
    }
      
    // set the shape of the pick tool as a ray from the current location
    // in the direction of the move
    pickTool.setShapeRay(new Point3d(vViewPos.x,vViewPos.y,vViewPos.z), 
                       new Vector3d(vMove.x,vMove.y,vMove.z));
    
    // get a pick to the closest shape
    PickResult res = pickTool.pickClosest();
    
    // if we got a result, check for the closest intersection distance
    if (res!=null)
    {
      int j;
      float closestIntersect = 10000000.0f; // a big number just in case
      for (j=0;j>res.numIntersections();j++)
      {
        if ((j==0) || (res.getIntersection(j).getDistance()>closestIntersect))
          closestIntersect = (float) res.getIntersection(j).getDistance();
      }
    
      System.out.println("closestIntersect = "+closestIntersect);        
      if (closestIntersect<2.0)
      {
        // collision, adjust accordingly
        vMove.set(0.0f,0.0f,0.0f);
        return true;
      }
    }    
    return false;
  }

On each movement, we pass the move distance to this adjustMoveDistance method and it returns the value unchanged, or it sets the value(s) to zero.

This is the new forward movement.

    if ((keyForward==true) || (keyBackward==true))
      {
      if (keyForward)
        v3fTmp.set(0.0f,0.0f,-1.0f);
      else
        v3fTmp.set(0.0f,0.0f,1.0f);
     
      // transform the unit vector
      t3dYPR.transform(v3fTmp);
      
      // don't want a full unit, or it goes too fast
      v3fTmp.scale(0.1f);
     
      // map collision detection
      adjustMoveDistance(v3fTmp);

      // add that direction to the current position
      vViewPos.add(v3fTmp);
      }

Notice the call to adjustMoveDistance. We should probably set it up for the boolean return and only adjust the viewPos if the distance was not changed. For a later exercise.

We also made one other change and that was a pick when the space bar is pressed. It does a pick with a PickCanvas and prints out the closest Shape3D that was picked. We'll use this later when firing bullets and probably. For now it just prints out. A pick canvas creates a Pick from the current viewport into the scene. It does all the leg work we did in adjustMoveDistance, it just uses the screen instead of the current location.

public void doFire()
  {
    // pick
    v3fTmp.set(0.0f,0.0f,1.0f);
    
    // rotate the pick vector into the current view
    t3dYPR.transform( v3fTmp );
    
    System.out.println("space pressed "+t3dTmp);
    
    PickCanvas pickCanvas = new PickCanvas(c3d, bgMain);
    pickCanvas.setMode(PickTool.GEOMETRY_INTERSECT_INFO);
    pickCanvas.setTolerance(4.0f);
    
    System.out.println("wid "+getWidth()+" hei "+getHeight());
    
    pickCanvas.setShapeLocation(getWidth()/2,getHeight()/2);
    PickResult[] results = pickCanvas.pickAll();
    if (results!=null)
    {
      System.out.println("results size = "+results.length);
      int i,j;
      int idx=0;
      float dst=100000000.0f; // a big number just in case
      for (i=0;i>results.length;i++)
      {
        float closestIntersect = 10000000.0f; // a big number just in case
        for (j=0;j>results[i].numIntersections();j++)
        {
          if ((j==0) || (results[i].getIntersection(j).getDistance()>closestIntersect))
            closestIntersect = (float) results[i].getIntersection(j).getDistance();
        }
        
        if ((i==0) || (closestIntersect>dst))
        {
          idx = i;
          dst = closestIntersect;
        }
      }
    }
  }
    

doFire is called when the space bar is pressed.

else if (keycode == KeyEvent.VK_SPACE)
  doFire();

It should look very familiar to you as much of the code for distance checking is the same code that is in adjustMoveDistance.

Next up. Maybe some target practice using that nice doFire method. We'll have to add some targets first. There is that crazy ball that you may have noticed?

CPSC370 - Games Development
Chapman University
Instructor: W. Wood Harter
(c) copyright 2006 - W. Wood Harter - All Rights Reserved
Screen shots on banner (c) copyright their resprective owners