I wrote a pretty simple ellipsoid collision detection and response system that is easily pluggable into any application. It is based on the concepts and code found at http://www.peroxide.dk/papers/collision/collision.pdf. It includes triangle level collisions, sliding, gravity and makes use of the existing JME nodes and collision data (OOBB's).
To use it, create an instance of the MovingEllipseCollision class provided below using the world-dimensions of the ellipse to use as your collision body. On each update, modify the vector's returned by getEllipseCenter() and getEllipseVelocity() to represent the world coordinate center and velocity of your collision body. Then call moveEllipseWithCollisions passing the Node which contains all the collideable objects, the interpolation to use for the velocity, and booleans indicating whether or not to use sliding and/or gravity. The end-point location will now be found in getEllipseCenter(). I have tested this a bit, but I'm sure there are bugs or improvements which can be made. Because of the message size here, I have split the one class into multiple posts, they should be combined into one file for use. Please feel free to use this if needed, or include it in this or other projects.
import com.jme.bounding.BoundingSphere;
import com.jme.bounding.BoundingVolume;
import com.jme.bounding.OBBTree;
import com.jme.math.FastMath;
import com.jme.math.Plane;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
/**
* Class for applying collision detection, response and gravity to a scene based on an ellipsoid representation.
*
* Implementation of algorithm found at: http://www.peroxide.dk/papers/collision/collision.pdf
*/
public class MovingEllipseCollision {
private Vector3f ellipseDimensions = new Vector3f();
private Vector3f ellipseSpaceScale = new Vector3f();
private Vector3f ellipseCenter = new Vector3f();
private Vector3f ellipseVelocity = new Vector3f();
private float veryCloseDistance = 0.001f;
private Vector3f gravityVector = new Vector3f(0, -40, 0);
private int recursionDepth = 5;
//collision internal variables
private Vector3f ellipseSpaceCenter = new Vector3f();
private Vector3f ellipseSpaceVelocity = new Vector3f();
private Vector3f ellipseSpaceVelocityNormalized = new Vector3f();
private Vector3f ellipseSpaceVelocityBuffer = new Vector3f();
private BoundingSphere worldSpaceBounds = new BoundingSphere();
private float resultNearestCollisionDistance = 100000.0f;
private Vector3f resultCollisionPoint = new Vector3f();
private Vector3f resultCollisionNormal = new Vector3f();
private boolean hasCollision = false;
/**
* Constructor for creating the collision system. Takes the dimensions of the ellipsoid to test against the scene.
*
* @param dimensions The dimensions of the ellipsoid (in world space) to test against the scene.
*/
public MovingEllipseCollision(Vector3f dimensions) {
setEllipseDimensions(dimensions);
}
/**
* Sets the sliding recursion depth to use for collisions.
*
* @param depth The new sliding recursion depth.
*/
public void setRecursionDepth(int depth) {
recursionDepth = depth;
}
/**
* Sets the dimensions of the collision ellipse
*
* @param dims dimensions of the collision ellipse
*/
public void setEllipseDimensions(Vector3f dims) {
ellipseDimensions.set(dims);
ellipseSpaceScale.set(1 / dims.x, 1 / dims.y, 1 / dims.z);
}
/**
* Returns the vector used as the base gravity vector. This will be multiplied by the interpolation to determine the actual per-frame gravity.
*
* @return A Vector3f representing the gravity.
*/
public Vector3f getGravityVector() {
return gravityVector;
}
/**
* This distance which should be used to determine when we are "close" to an object. If this is set incorrectly, we may appear to pass through
* certain objects. The default value is 0.001f
*
* @param dist The distance to set as the veryCloseDistance
*/
public void setVeryCloseDistance(float dist) {
veryCloseDistance = 0.001f;
}
/**
* The center of the ellipse in world coordinates. Set the center here before calling the moveEllipseWithCollisions method. Retreive the
* resulting position from this vector after the collisions occur.
*
* @return The Vector which represents the current ellipse center.
*/
public Vector3f getEllipseCenter() {
return ellipseCenter;
}
/**
* Returns the velocity of the ellipse, in world coordinates.
*
* @return The Vector which represents the current ellipse velocity.
*/
public Vector3f getEllipseVelocity() {
return ellipseVelocity;
}
/**
* Reset the collision result members
*/
private void resetResults() {
resultNearestCollisionDistance = 100000.0f;
hasCollision = false;
}
/**
* Check for collisions against the given node.
*
* @param collisionNode
*/
private void checkEllipseCollision(Node collisionNode) {
//reset our results
resetResults();
//create a bounding volume for the start/end points of our ellipse for quick culling against the OBBTree
float minX, maxX, minY, maxY, minZ, maxZ;
if (ellipseVelocity.x > 0) {
minX = ellipseCenter.x - ellipseDimensions.x;
maxX = ellipseCenter.x + ellipseVelocity.x + ellipseDimensions.x;
} else {
maxX = ellipseCenter.x + ellipseDimensions.x;
minX = ellipseCenter.x + ellipseVelocity.x - ellipseDimensions.x;
}
if (ellipseVelocity.y > 0) {
minY = ellipseCenter.y - ellipseDimensions.y;
maxY = ellipseCenter.y + ellipseVelocity.y + ellipseDimensions.y;
} else {
maxY = ellipseCenter.y + ellipseDimensions.y;
minY = ellipseCenter.y + ellipseVelocity.y - ellipseDimensions.y;
}
if (ellipseVelocity.z > 0) {
minZ = ellipseCenter.z - ellipseDimensions.z;
maxZ = ellipseCenter.z + ellipseVelocity.z + ellipseDimensions.z;
} else {
maxZ = ellipseCenter.z + ellipseDimensions.z;
minZ = ellipseCenter.z + ellipseVelocity.z - ellipseDimensions.z;
}
float xRad = (maxX - minX) / 2;
float yRad = (maxY - minY) / 2;
float zRad = (maxZ - minZ) / 2;
worldSpaceBounds.radius = Math.max(Math.max(xRad, yRad), zRad);
worldSpaceBounds.getCenter().set(minX + xRad, minY + yRad, minZ + zRad);
checkEllipseSpaceCollision(collisionNode, ellipseSpaceCenter, ellipseSpaceVelocity,
ellipseSpaceScale, worldSpaceBounds);
}
private final static Vector3f cescA = new Vector3f(), cescB = new Vector3f(), cescC = new Vector3f(), cescD = new Vector3f(), cescE = new Vector3f();
/**
* Check the ellipse collision against an OBBTree
*
* @param parentMesh The mesh which owns the OBBTree
* @param collisionTree The OBBTree to traverse
* @param ellipseSpaceCenter The ellipse space center for the ellipse
* @param ellipseSpaceVelocity The ellipse space velocity for the ellipse
* @param ellipseSpaceScale The scale of ellipse space
* @param worldSpaceBounds The bounds of the movement area in world space
*/
private void checkEllipseSpaceCollision(TriMesh parentMesh, OBBTree collisionTree, Vector3f ellipseSpaceCenter, Vector3f ellipseSpaceVelocity, Vector3f ellipseSpaceScale, BoundingVolume worldSpaceBounds) {
collisionTree.bounds.transform(
parentMesh.getWorldRotation(),
parentMesh.getWorldTranslation(),
parentMesh.getWorldScale(),
collisionTree.worldBounds);
if (!collisionTree.worldBounds.intersects(worldSpaceBounds)) {
return;
}
OBBTree left = collisionTree.getLeftTree();
OBBTree right = collisionTree.getRightTree();
if (left != null) {
checkEllipseSpaceCollision(parentMesh, left, ellipseSpaceCenter, ellipseSpaceVelocity,
ellipseSpaceScale, worldSpaceBounds);
checkEllipseSpaceCollision(parentMesh, right, ellipseSpaceCenter, ellipseSpaceVelocity,
ellipseSpaceScale, worldSpaceBounds);
} else {
//this is a leaf, do the actual test
Quaternion worldRot = parentMesh.getWorldRotation();
Vector3f worldScale = parentMesh.getWorldScale();
Vector3f worldTrans = parentMesh.getWorldTranslation();
for (int i = 0; i < collisionTree.getTriangleCount(); i++) {
Triangle tri = collisionTree.getTriangle(i);
worldRot.mult(tri.get(0), cescA).multLocal(worldScale).addLocal(
worldTrans);
worldRot.mult(tri.get(1), cescB).multLocal(worldScale).addLocal(
worldTrans);
worldRot.mult(tri.get(2), cescC).multLocal(worldScale).addLocal(
worldTrans);
//we now have a triangle in tempVa, tempVb, tempVc which is in world coordinates
//move the triangle into collision space
cescA.multLocal(ellipseSpaceScale);
cescB.multLocal(ellipseSpaceScale);
cescC.multLocal(ellipseSpaceScale);
float intersectionDistance = movingUnitSphereTriIntersection(ellipseSpaceCenter, ellipseSpaceVelocity, cescA, cescB, cescC, cescD, cescE);
//if we found a collision better than our current best, store it instead
if (intersectionDistance < resultNearestCollisionDistance) {
resultNearestCollisionDistance = intersectionDistance;
resultCollisionPoint.set(cescD);
resultCollisionNormal.set(cescE);
hasCollision = true;
}
}
}
}
/**
* Attemps to collide the moving ellipes against a spacial. Only TriMesh spacials are checked.
*
* @param collisionSpatial The spacial to check for collisions
* @param ellipseSpaceCenter The ellipse space center for the ellipse
* @param ellipseSpaceVelocity The ellipse space velocity for the ellipse
* @param ellipseSpaceScale The scale of ellipse space
* @param worldSpaceBounds The bounds of the movement area in world space
*/
private void checkEllipseSpaceCollision(Spatial collisionSpatial, Vector3f ellipseSpaceCenter, Vector3f ellipseSpaceVelocity, Vector3f ellipseSpaceScale, BoundingVolume worldSpaceBounds) {
if ((collisionSpatial.getType() & Spatial.TRIMESH) != 0) {
TriMesh triMesh = (TriMesh)collisionSpatial;
OBBTree collisionTree = triMesh.getCollisionTree();
if (collisionTree == null)
return;
collisionTree.bounds.transform(triMesh.getWorldRotation(), triMesh.getWorldTranslation(),
triMesh.getWorldScale(), collisionTree.worldBounds);
checkEllipseSpaceCollision(triMesh, collisionTree, ellipseSpaceCenter, ellipseSpaceVelocity,
ellipseSpaceScale, worldSpaceBounds);
}
}
/**
* Attemps to collide the moving ellipes against a node's children.
*
* @param collisionNode The node to check for collisions
* @param ellipseSpaceCenter The ellipse space center for the ellipse
* @param ellipseSpaceVelocity The ellipse space velocity for the ellipse
* @param ellipseSpaceScale The scale of ellipse space
* @param worldSpaceBounds The bounds of the movement area in world space
*/
private void checkEllipseSpaceCollision(Node collisionNode, Vector3f ellipseSpaceCenter, Vector3f ellipseSpaceVelocity, Vector3f ellipseSpaceScale, BoundingVolume worldSpaceBounds) {
if (collisionNode.getWorldBound() != null) {
if (collisionNode.getWorldBound().intersects(worldSpaceBounds)) {
for (int i = 0; i < collisionNode.getQuantity(); i++) {
checkEllipseSpaceCollision(collisionNode.getChild(i), ellipseSpaceCenter, ellipseSpaceVelocity, ellipseSpaceScale, worldSpaceBounds);
}
}
}
}
private final static Vector3f v = new Vector3f(), newSourcePoint = new Vector3f(), destinationPoint = new Vector3f(), slidePlaneNormal = new Vector3f();
private final static Vector3f displacementVector = new Vector3f(), newDestinationPoint = new Vector3f(), newVelocityVector = new Vector3f();
private final static Plane slidingPlane = new Plane();
/**
* Recursive method which finds the collisions for a moving ellipse against a collisionNode. If the slide flag is set, it will also attempt
* to slide the ellipse along the collision triangle, and continue recursively.
*
* @param depth The current sliding depth
* @param collisionNode The node to check collisions against
* @param slide Flag specifying whether or not a slide should be attempted
*/
private void collideAndSlideEllipse(int depth, Node collisionNode, boolean slide) {
if (depth > recursionDepth || ellipseSpaceVelocity.length() < veryCloseDistance * 2 )
return;
ellipseSpaceVelocityNormalized.set(ellipseSpaceVelocity);
ellipseSpaceVelocityNormalized.normalizeLocal();
//extend the size of the ellipse space vector by just a bit, to give us some collision buffer
ellipseSpaceVelocityBuffer.set(ellipseSpaceVelocityNormalized);
ellipseSpaceVelocityBuffer.multLocal(veryCloseDistance);
ellipseSpaceVelocity.addLocal(ellipseSpaceVelocityBuffer);
checkEllipseCollision(collisionNode);
if (!hasCollision) {
ellipseSpaceCenter.addLocal(ellipseSpaceVelocity).subtractLocal(ellipseSpaceVelocityBuffer);
return;
}
destinationPoint.set(ellipseSpaceCenter).addLocal(ellipseSpaceVelocity);
v.set(ellipseSpaceVelocity);
if (resultNearestCollisionDistance == 0.0f)
resultNearestCollisionDistance = -veryCloseDistance;
float factorx = resultNearestCollisionDistance - veryCloseDistance;
v.multLocal(factorx);
newSourcePoint.set(ellipseSpaceCenter).addLocal(v);
if (!slide) {
ellipseSpaceCenter.set(newSourcePoint);
return;
}
//slidePlaneNormal.set(newSourcePoint).subtractLocal(collisionResults.getCollisionPoint()).normalizeLocal();
slidePlaneNormal.set(resultCollisionNormal);
v.normalizeLocal().multLocal(veryCloseDistance);
float factor = v.dot(slidePlaneNormal);
if (factor != 0.0f)
factor = veryCloseDistance / factor;
displacementVector.set(v).multLocal(factor);
newSourcePoint.addLocal(displacementVector);
resultCollisionPoint.addLocal(displacementVector);
slidingPlane.setNormal(slidePlaneNormal);
slidingPlane.setConstant(slidePlaneNormal.dot(resultCollisionPoint));
float dist = slidingPlane.pseudoDistance(destinationPoint);
newDestinationPoint.scaleAdd(-dist, slidePlaneNormal, destinationPoint);
newVelocityVector.set(newDestinationPoint).subtractLocal(resultCollisionPoint);
if (newVelocityVector.length() <= veryCloseDistance) {
ellipseSpaceCenter.set(newSourcePoint);
return;
}
ellipseSpaceCenter.set(newSourcePoint);
ellipseSpaceVelocity.set(newVelocityVector);
collideAndSlideEllipse(depth + 1, collisionNode, slide);
}
--Ruab