FPS370 - Changes from Alpha4 to Alpha5
Bouncing Ball - other object collision detection

This is an interim change. I wanted to show the simple version of the bouncing ball's collision detection with the wall. In this case the ball just bounces in a vertical direction up and down.

As is the same for checking with the camera's collision with the wall, we could test for positional values that are greater that the size of our map, but it is much easier to use the pick method of collision detection and just check the movement to decide if the next position will be outside the limits of the map.

The main change was with the tick() method. I removed the random location changes to the sphere (you may not have seen it unless you ran an older version) and added a tickSphere method that will adjust the location of the sphere based on a velocity vector. Each tick, the velocity increases in the negative y direction. This simulates gravity. The -0.05 amount of gravity is completely arbitrary. The first number I tried -0.00005 was much too slow and I kept increasing (or decreasing depending if your brain works like mine) the value until it 'felt' right.

Here is the complete code listing for the tickSphere method:

  public void tickSphere()
  { 
    // add some gravity to the sphere
    vSphereVel.y = vSphereVel.y - 0.05f;


    // we are sharing the pick tool
    if (pickTool==null)
    {
      // create the picktool to always use the map
      pickTool = new PickTool(bgMap);
    }
    // check for collision with a wall
    pickTool.setShapeRay(new Point3d(vSpherePos.x,vSpherePos.y,vSpherePos.z), 
                       new Vector3d(vSphereVel.x,vSphereVel.y,vSphereVel.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();
      }
    
      // bounce if we are going to move beyond the wall
      float magVel = vSphereVel.length();
      //System.out.println("closestIntersect = "+closestIntersect+" velLen="+magVel);    
      if (closestIntersect<magVel)
      {
        //System.out.println("sphere hit dist="+closestIntersect+" node="+res.getObject());

        // need the surface normal for the object it just hit
        Node nd = res.getObject();
        
        // we have it as a node, make sure it is a shape 3d
        if (nd instanceof Shape3D)
        {
          Shape3D sh = (Shape3D) nd;
          Geometry geo = sh.getGeometry();
          
          // we have the geometry, make sure it is a GeometryArray we can get a surface normal from
          if (geo instanceof GeometryArray)
          {
            // finally!, after all that, we can get the surface normal
            // just use the normal from the first vertex.
            ((GeometryArray)geo).getNormal(0,v3fTmp);
            
            // get a unit vector from the velocity
            //v3fTmp3.normalize(vSphereVel);
            // I had to double normalize here because a 180 degree vector with value
            // = 0.99999994 caused the normal to not be unit length - very odd
            v3fTmp3.set(vSphereVel);
            v3fTmp3.normalize();
            v3fTmp3.normalize();

            // get the dot product between the two
            float f = v3fTmp.dot(v3fTmp3);
            System.out.println("dot ="+f);

            // -1 for the dot product means the vectors are parallel and 180 in direction
            if (f==-1.0f)
            {
              // direct hit, the vectors face each other exactly, just reverse the velocity
              System.out.println("negate the velocity");
              vSphereVel.negate();
              
              // subtract a little friction to make up for the fact that the
              // sphere didn't actually touch the wall
              vSphereVel.scale(0.90f);
              
              // set the sphere to that intersection point
              // WWH - exercise for later
              
            }
            else
            {
              /* add else case later
               * for now the ball just bounces in a vertical direction
              v3fTmp2.cross(v3fTmp,v3fTmp3);            
              System.out.println("Normal = "+v3fTmp+"  vel="+vSphereVel+" normvel="+v3fTmp3+
                                  " dot="+v3fTmp.dot(v3fTmp3)+" cross="+v3fTmp2);
              
              // second normalize
              v3fTmp3.normalize();
              System.out.println("second normalized ="+v3fTmp3);
              
              f = v3fTmp3.length();
              v3fTmp3.x = v3fTmp3.x / f;
              v3fTmp3.y = v3fTmp3.y / f;
              v3fTmp3.z = v3fTmp3.z / f;
              System.out.println("normalized by hand="+v3fTmp3);
              //v3fTmp3.normalize(vSphereVel);
              */

            }
          }
          //System.out.println("is a shape3d "+((Shape3D)nd).getGeometry());
        }
      } // closest intersect
     
    } // check pick result
    
    // add the current velocity to the sphere
    vSpherePos.add(vSphereVel);

    // create a location translation transform for the sphere
    t3dTmp.setTranslation(vSpherePos);
        
    // set the changed transform
    tgSphere.setTransform(t3dTmp);    
 }

The early parts of the pickTool code should look completely familiar from the previous example. We create a shapeRay, pick the closest node in the direction of that ray and test that the distance is less than our movement distance. Simple. That is where it gets interesting.

       Node nd = res.getObject();
        
        // we have it as a node, make sure it is a shape 3d
        if (nd instanceof Shape3D)
        {
          Shape3D sh = (Shape3D) nd;
          Geometry geo = sh.getGeometry();
          
          // we have the geometry, make sure it is a GeometryArray we can get a surface normal from
          if (geo instanceof GeometryArray)
          {
            // finally!, after all that, we can get the surface normal
            // just use the normal from the first vertex.
            ((GeometryArray)geo).getNormal(0,v3fTmp);

Once we know that we are too close we have to somehow change the velocity. In this case, we have to get a surface normal from the object we are going to hit and change our velocity based on that surface normal. The code above shows everything it takes just to get the surface normal! First we get the Node from the object we are going to hit. We make sure it is a Shape3D so we can convert it to a Shape3D and get the Geometry from it. Once we have the Geometry we make sure it is a GeometryArray so we can get the normal from it.

Please don't think I'm a brainiac for knowing how to do this. I did this in iterations each time printing out the object which showed me it's type. Then I would spend a bunch of time in the Java3D javadoc to see how I was going to get the surface normal from it.

The last line get's the surface normal from the first vertex in the GeometryArray. I store that value in v3fTmp.

There was an exception on this line in the development and I need to be clear that this was a pain to find.

Exception occurred during Behavior execution:
javax.media.j3d.CapabilityNotSetException: GeometryArray: no capability to read normal
        at javax.media.j3d.GeometryArray.getNormal(Unknown Source)
        at fps370.Fps370Panel.tickSphere(Unknown Source)

This says that I was not allowed to get the surface normal from the geometry. Remember, Java3D does optimizations if you don't tell it you need to read or write certain values. In this case, I was not telling it I wanted the surface normal from that Shape3D so it couldn't give me the values.

I added the code to all the read of this value back where I loaded the model

 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);
        
        // need to also get normals for collision detection
        ((Shape3D)o).getGeometry().setCapability(GeometryArray.ALLOW_NORMAL_READ);
      }
    }
  }

For a long time I was doing this instead and still getting the exception.

((Shape3D)o).setCapability(GeometryArray.ALLOW_NORMAL_READ);

This will set the capability into the Shape3D, but not into the Geometry and the Geometry is where we need to read the surface normal from.

            // get a unit vector from the velocity

            v3fTmp3.set(vSphereVel);
            v3fTmp3.normalize();
            v3fTmp3.normalize();


            // get the dot product between the two
            float f = v3fTmp.dot(v3fTmp3);


            // -1 for teh dot product means the vectors are parallel and 180 in direction
            if (f==-1.0f)
            {
              // direct hit, the vectors face each other exactly, just reverse the velocity
              System.out.println("negate the velocity");
              vSphereVel.negate();
              
              // subtract a little friction to make up for the fact that the
              // sphere didn't actually touch the wall
              vSphereVel.scale(0.90f);
            }

Once I have the surface normal, I need to calculate the dot product between the velocity and that surface normal. For this sample I know the dot product will always be -1.0 because the sphere is always falling straight down. You have to normalize the veolcity vector so you have a unit vector (length 1) because the dot product will not return the correct values for perpendicular vectors unless they are both unit vectors. In development I found a case where the normalize() method did not return a unit vector. Seems like a bug to me, but after a lot of testing, either doing the normalization myself (or calling it twice seemed to fix the problem). I left it that way because it works. Don't ask me why it was broken, I have better things to do, and that's teach you how to build an FPS game...

Once I have the dot product, I check it of -1.0 which means it was a square hit and we just reverse the velocity vector. I also added a little friction to work around the fact that the sphere didn't actually hit the wall, and it will continue to get higher and higher. We could calculate the exact location with a lot of math, but that just slows things down and we already have all that extra work in calculating the normalization twice.

Next up we'll do a bunch of math to calculate the direction change based on hitting the wall at an odd angle.

 

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