Collision and Intersection

The term collision can be used to refer to physical interactions (where physical objects collide and bump off one another), and also to non-physical intersections. This article is about non-physical (mathematical) collisions. Non-physical collisions are interesting because they take less resources than physical ones. You cn optimize your game if you find a way to simulate a certain effect in a non-physical way, using mathematical techniques such as ray casting. One example for an optimization is a physical vehicle's wheels. You could make the wheels fully physical disks, and have jME calculate every tiny force – sounds very accurate, but is total overkill. An more performant solution is to cast four rays down from the vehicle and calculate the intersections with the floor and other obstacles. These non-physical wheels require (in the simplest case) only four calculations to achieve an effect that players can hardly distinguish from the real thing.

Bounding Volumes

A com.jme3.bounding.BoundingVolume is an interface for dealing with containment of a collection of points. All BoundingVolumes are Collidable and are used as optimization to calculate non-physical collisions more quickly: It's faster to calculate an intersection between simple shapes like spheres and boxes than between complex shapes. In cases where precision is not relevant, you wrap a complex model in a simpler shape to improve collision detection performance.

Note: Physical objects have their own "bounding volumes" called CollisionShapes.

Collisions

The interface com.jme3.collision.Collidable declares one method that returns how many collisions were found between two Collidables: collideWith(Collidable other, CollisionResults results). A com.jme3.collision.CollisionResults object is an ArrayList of comparable com.jme3.collision.CollisionResult objects. You can iterate over the CollisionResults to identify the other parties involved in the collision. Note that jME counts all collisions, this means a ray intersecting a box will be counted as two hits, one on the front where the ray enters, and one on the back where the ray exits.

CollisionResults MethodUsage
size()Returns the number of CollisionResult objects.
getClosestCollision()Returns the CollisionResult with the lowest distance.
getFarthestCollision()Returns the CollisionResult with the farthest distance.
getCollision(i)Returns the CollisionResult at index i.

A CollisionResult object contains information about the second party of the collision event.

CollisionResult MethodUsage
getContactPoint()Returns the contact point coordinate on the second party, as Vector3f.
getContactNormal()Returns the Normal vector at the contact point, as Vector3f.
getDistance()Returns the distance between the Collidable and the second party, as float.
getGeometry()Returns the Geometry of the second party.
getTriangle(t)Binds t to the triangle t on the second party's mesh that was hit.
getTriangleIndex()Returns the index of the triangle on the second party's mesh that was hit. (?)

Assume you have two collidables a and b and want to detect collisions between them. The collision parties can be Geometries, Nodes with Geometries attached (including the rootNode), Planes, Quads, Lines, or Rays.
The following code snippet can be triggered by listeners (e.g. after an input action such as a click), or timed in the update loop.

  // Calculate Results
  CollisionResults results = new CollisionResults();
  a.collideWith(b, results);
  System.out.println("Number of Collisions between" + a.getName()+ " and "
   + b.getName() " : " + results.size());
  // Use the results
  if (results.size() > 0) {
    CollisionResult closest  = results.getClosestCollision();
    System.out.println("What was hit? " + closest.getGeometry().getName() );
    System.out.println("Where was it hit? " + closest.getContactPoint() );
    System.out.println("Distance? " + closest.getDistance() );
  } else {
    // how to react when no collision occured
  }
}

You can also loop over all results and trigger different reactions depending on what was hit and where it was hit. In this example, we simply print info about them.

  // Calculate Results
  CollisionResults results = new CollisionResults();
  a.collideWith(b, results);
  System.out.println("Number of Collisions between" + a.getName()+ " and "
   + b.getName() " : " + results.size());
  // Use the results
  for (int i = 0; i < results.size(); i++) {
    // For each hit, we know distance, impact point, name of geometry.
    float     dist = results.getCollision(i).getDistance();
    Vector3f    pt = results.getCollision(i).getContactPoint();
    String   party = results.getCollision(i).getGeometry().getName();
    int        tri = results.getCollision(i).getTriangleIndex();
    Vector3f  norm = results.getCollision(i).getTriangle(new Triangle()).getNormal();
    System.out.println("Details of Collision #" + i + ":");
    System.out.println("  Party " + party + " was hit at " + pt + ", " + dist + " wu away.");
    System.out.println("  The hit triangle #" + tri + " has a normal vector of " + norm);
  }

Knowing the distance of the collisions is useful for example when you intersect Lines and Rays with other objects.

Intersection

A com.jme3.math.Ray is an infinite line with a beginning, a direction, and no end; whereas a com.jme3.math.Line is an infinite line with only a direction (no beginning, no end). Rays are used to detect where a 3D object is "looking" and whether one object can "see" the other.

These simple ray-surface intersection tests are called Ray Casting. (As opposed to the more advanced Ray Tracing, Ray Casting does not follow a ray's reflection after the first hit, but the ray goes straight on.)

Usecase: Picking a target with crosshairs

This pick target input mapping implements an action that determines what a user clicked (if you map this to a mouse click). It assumes that there are crosshairs in the center of the screen, and the user aims the crosshairs at an object in the scene. We use a ray casting approach to determine the geometry that was picked by the user. You can extend this code to do whatever with the identified target (shoot it, take it, move it, …) Use this together with inputManager.setCursorVisible(false).

  private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float intensity, float tpf) {
        if (name.equals("pick target")) {
         // Reset results list.
         CollisionResults results = new CollisionResults();
         // Aim the ray from camera location in camera direction
         // (assuming crosshairs in center of screen).
         Ray ray = new Ray(cam.getLocation(), cam.getDirection());
         // Collect intersections between ray and all nodes in results list.
         rootNode.collideWith(ray, results);
         // Print the results so we see what is going on
         for (int i = 0; i < results.size(); i++) {
           // For each “hit”, we know distance, impact point, geometry.
           float dist = results.getCollision(i).getDistance();
           Vector3f pt = results.getCollision(i).getContactPoint();
           String target = results.getCollision(i).getGeometry().getName();
           System.out.println("Selection #" + i + ": " + target + " at " + pt + ", " + dist + " WU away.");
         }
         // 5. Use the results -- we rotate the selected geometry.
         if (results.size() > 0) {
           // The closest result is the target that the player picked:
           Geometry target = results.getClosestCollision().getGeometry();
           // Here comes the action:
           if(target.getName().equals("Red Box"))
             target.rotate(0, - intensity, 0);
           else if(target.getName().equals("Blue Box"))
             target.rotate(0, intensity, 0);
         }
        } // else if ...
    }
  };

Usecase: Picking a target with mouse cursor

This pick target input mapping implements an action that determines what a user clicked (if you map this to a mouse click). It assumes that the cursor is visible, and the user aims the cursor at an object in the scene. We use a ray casting approach to determine the geometry that was picked by the user. You can extend this code to do whatever with the identified target (shoot it, take it, move it, …) Use this together with inputManager.setCursorVisible(true).

private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float intensity, float tpf) {
      if (name.equals("pick target")) {
        // Reset results list.
        CollisionResults results = new CollisionResults();
        // Convert screen click to 3d position
        Vector2f click2d = inputManager.getCursorPosition();
        Vector3f click3d = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
        Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d);
        // Aim the ray from the clicked spot forwards.
        Ray ray = new Ray(click3d, dir);
        // Collect intersections between ray and all nodes in results list.
        rootNode.collideWith(ray, results);
        // (Print the results so we see what is going on:)
        for (int i = 0; i < results.size(); i++) {
          // (For each “hit”, we know distance, impact point, geometry.)
          float dist = results.getCollision(i).getDistance();
          Vector3f pt = results.getCollision(i).getContactPoint();
          String target = results.getCollision(i).getGeometry().getName();
          System.out.println("Selection #" + i + ": " + target + " at " + pt + ", " + dist + " WU away.");
        }
        // Use the results -- we rotate the selected geometry.
        if (results.size() > 0) {
          // The closest result is the target that the player picked:
          Geometry target = results.getClosestCollision().getGeometry();
          // Here comes the action:
          if (target.getName().equals("Red Box")) {
            target.rotate(0, -intensity, 0);
          } else if (target.getName().equals("Blue Box")) {
            target.rotate(0, intensity, 0);
          }
        }
      } // else if ...
    }
  };

Bounding Interval Hierarchy

com.jme3.collision.bih.BIHNode com.jme3.scene.CollisionData

SweepSphere

A com.jme3.collision.SweepSphere implements a collidable "stretched" sphere that is shaped like a capsule (an upright cylinder with half a sphere on top and the second half at the bottom). This shape is usually used to simulate simple non-physcial collisions for character entities in games. The sweep sphere can be used to check collision against a triangle or another sweep sphere.

view online version