/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.math;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.collision.UnsupportedCollisionException;
import com.jme3.export.*;
import com.jme3.util.TempVars;
import java.io.IOException;
/**
* <code>Ray</code> defines a line segment which has an origin and a direction.
* That is, a point and an infinite ray is cast from this point. The ray is
* defined by the following equation: R(t) = origin + t*direction for t >= 0.
*
* @author Mark Powell
* @author Joshua Slack
*/
public final class Ray implements Savable, Cloneable, Collidable, java.io.Serializable {
static final long serialVersionUID = 1;
/**
* The ray's begining point.
*/
public Vector3f origin = new Vector3f();
/**
* The direction of the ray.
*/
public Vector3f direction = new Vector3f(0, 0, 1);
public float limit = Float.POSITIVE_INFINITY;
/**
* Constructor instantiates a new <code>Ray</code> object. As default, the
* origin is (0,0,0) and the direction is (0,0,1).
*
*/
public Ray() {
}
/**
* Constructor instantiates a new <code>Ray</code> object. The origin and
* direction are given.
* @param origin the origin of the ray.
* @param direction the direction the ray travels in.
*/
public Ray(Vector3f origin, Vector3f direction) {
setOrigin(origin);
setDirection(direction);
}
/**
* <code>intersect</code> determines if the Ray intersects a triangle.
* @param t the Triangle to test against.
* @return true if the ray collides.
*/
// public boolean intersect(Triangle t) {
// return intersect(t.get(0), t.get(1), t.get(2));
// }
/**
* <code>intersect</code> determines if the Ray intersects a triangle
* defined by the specified points.
*
* @param v0
* first point of the triangle.
* @param v1
* second point of the triangle.
* @param v2
* third point of the triangle.
* @return true if the ray collides.
*/
// public boolean intersect(Vector3f v0,Vector3f v1,Vector3f v2){
// return intersectWhere(v0, v1, v2, null);
// }
/**
* <code>intersectWhere</code> determines if the Ray intersects a triangle. It then
* stores the point of intersection in the given loc vector
* @param t the Triangle to test against.
* @param loc
* storage vector to save the collision point in (if the ray
* collides)
* @return true if the ray collides.
*/
public boolean intersectWhere(Triangle t, Vector3f loc) {
return intersectWhere(t.get(0), t.get(1), t.get(2), loc);
}
/**
* <code>intersectWhere</code> determines if the Ray intersects a triangle
* defined by the specified points and if so it stores the point of
* intersection in the given loc vector.
*
* @param v0
* first point of the triangle.
* @param v1
* second point of the triangle.
* @param v2
* third point of the triangle.
* @param loc
* storage vector to save the collision point in (if the ray
* collides) if null, only boolean is calculated.
* @return true if the ray collides.
*/
public boolean intersectWhere(Vector3f v0, Vector3f v1, Vector3f v2,
Vector3f loc) {
return intersects(v0, v1, v2, loc, false, false);
}
/**
* <code>intersectWherePlanar</code> determines if the Ray intersects a
* triangle and if so it stores the point of
* intersection in the given loc vector as t, u, v where t is the distance
* from the origin to the point of intersection and u,v is the intersection
* point in terms of the triangle plane.
*
* @param t the Triangle to test against.
* @param loc
* storage vector to save the collision point in (if the ray
* collides) as t, u, v
* @return true if the ray collides.
*/
public boolean intersectWherePlanar(Triangle t, Vector3f loc) {
return intersectWherePlanar(t.get(0), t.get(1), t.get(2), loc);
}
/**
* <code>intersectWherePlanar</code> determines if the Ray intersects a
* triangle defined by the specified points and if so it stores the point of
* intersection in the given loc vector as t, u, v where t is the distance
* from the origin to the point of intersection and u,v is the intersection
* point in terms of the triangle plane.
*
* @param v0
* first point of the triangle.
* @param v1
* second point of the triangle.
* @param v2
* third point of the triangle.
* @param loc
* storage vector to save the collision point in (if the ray
* collides) as t, u, v
* @return true if the ray collides.
*/
public boolean intersectWherePlanar(Vector3f v0, Vector3f v1, Vector3f v2,
Vector3f loc) {
return intersects(v0, v1, v2, loc, true, false);
}
/**
* <code>intersects</code> does the actual intersection work.
*
* @param v0
* first point of the triangle.
* @param v1
* second point of the triangle.
* @param v2
* third point of the triangle.
* @param store
* storage vector - if null, no intersection is calc'd
* @param doPlanar
* true if we are calcing planar results.
* @param quad
* @return true if ray intersects triangle
*/
private boolean intersects(Vector3f v0, Vector3f v1, Vector3f v2,
Vector3f store, boolean doPlanar, boolean quad) {
TempVars vars = TempVars.get();
Vector3f tempVa = vars.vect1,
tempVb = vars.vect2,
tempVc = vars.vect3,
tempVd = vars.vect4;
Vector3f diff = origin.subtract(v0, tempVa);
Vector3f edge1 = v1.subtract(v0, tempVb);
Vector3f edge2 = v2.subtract(v0, tempVc);
Vector3f norm = edge1.cross(edge2, tempVd);
float dirDotNorm = direction.dot(norm);
float sign;
if (dirDotNorm > FastMath.FLT_EPSILON) {
sign = 1;
} else if (dirDotNorm < -FastMath.FLT_EPSILON) {
sign = -1f;
dirDotNorm = -dirDotNorm;
} else {
// ray and triangle/quad are parallel
vars.release();
return false;
}
float dirDotDiffxEdge2 = sign * direction.dot(diff.cross(edge2, edge2));
if (dirDotDiffxEdge2 >= 0.0f) {
float dirDotEdge1xDiff = sign
* direction.dot(edge1.crossLocal(diff));
if (dirDotEdge1xDiff >= 0.0f) {
if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) {
float diffDotNorm = -sign * diff.dot(norm);
if (diffDotNorm >= 0.0f) {
// this method always returns
vars.release();
// ray intersects triangle
// if storage vector is null, just return true,
if (store == null) {
return true;
}
// else fill in.
float inv = 1f / dirDotNorm;
float t = diffDotNorm * inv;
if (!doPlanar) {
store.set(origin).addLocal(direction.x * t,
direction.y * t, direction.z * t);
} else {
// these weights can be used to determine
// interpolated values, such as texture coord.
// eg. texcoord s,t at intersection point:
// s = w0*s0 + w1*s1 + w2*s2;
// t = w0*t0 + w1*t1 + w2*t2;
float w1 = dirDotDiffxEdge2 * inv;
float w2 = dirDotEdge1xDiff * inv;
//float w0 = 1.0f - w1 - w2;
store.set(t, w1, w2);
}
return true;
}
}
}
}
vars.release();
return false;
}
public float intersects(Vector3f v0, Vector3f v1, Vector3f v2) {
float edge1X = v1.x - v0.x;
float edge1Y = v1.y - v0.y;
float edge1Z = v1.z - v0.z;
float edge2X = v2.x - v0.x;
float edge2Y = v2.y - v0.y;
float edge2Z = v2.z - v0.z;
float normX = ((edge1Y * edge2Z) - (edge1Z * edge2Y));
float normY = ((edge1Z * edge2X) - (edge1X * edge2Z));
float normZ = ((edge1X * edge2Y) - (edge1Y * edge2X));
float dirDotNorm = direction.x * normX + direction.y * normY + direction.z * normZ;
float diffX = origin.x - v0.x;
float diffY = origin.y - v0.y;
float diffZ = origin.z - v0.z;
float sign;
if (dirDotNorm > FastMath.FLT_EPSILON) {
sign = 1;
} else if (dirDotNorm < -FastMath.FLT_EPSILON) {
sign = -1f;
dirDotNorm = -dirDotNorm;
} else {
// ray and triangle/quad are parallel
return Float.POSITIVE_INFINITY;
}
float diffEdge2X = ((diffY * edge2Z) - (diffZ * edge2Y));
float diffEdge2Y = ((diffZ * edge2X) - (diffX * edge2Z));
float diffEdge2Z = ((diffX * edge2Y) - (diffY * edge2X));
float dirDotDiffxEdge2 = sign * (direction.x * diffEdge2X
+ direction.y * diffEdge2Y
+ direction.z * diffEdge2Z);
if (dirDotDiffxEdge2 >= 0.0f) {
diffEdge2X = ((edge1Y * diffZ) - (edge1Z * diffY));
diffEdge2Y = ((edge1Z * diffX) - (edge1X * diffZ));
diffEdge2Z = ((edge1X * diffY) - (edge1Y * diffX));
float dirDotEdge1xDiff = sign * (direction.x * diffEdge2X
+ direction.y * diffEdge2Y
+ direction.z * diffEdge2Z);
if (dirDotEdge1xDiff >= 0.0f) {
if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) {
float diffDotNorm = -sign * (diffX * normX + diffY * normY + diffZ * normZ);
if (diffDotNorm >= 0.0f) {
// ray intersects triangle
// fill in.
float inv = 1f / dirDotNorm;
float t = diffDotNorm * inv;
return t;
}
}
}
}
return Float.POSITIVE_INFINITY;
}
/**
* <code>intersectWherePlanar</code> determines if the Ray intersects a
* quad defined by the specified points and if so it stores the point of
* intersection in the given loc vector as t, u, v where t is the distance
* from the origin to the point of intersection and u,v is the intersection
* point in terms of the quad plane.
* One edge of the quad is [v0,v1], another one [v0,v2]. The behaviour thus is like
* {@link #intersectWherePlanar(Vector3f, Vector3f, Vector3f, Vector3f)} except for
* the extended area, which is equivalent to the union of the triangles [v0,v1,v2]
* and [-v0+v1+v2,v1,v2].
*
* @param v0
* top left point of the quad.
* @param v1
* top right point of the quad.
* @param v2
* bottom left point of the quad.
* @param loc
* storage vector to save the collision point in (if the ray
* collides) as t, u, v
* @return true if the ray collides with the quad.
*/
public boolean intersectWherePlanarQuad(Vector3f v0, Vector3f v1, Vector3f v2,
Vector3f loc) {
return intersects(v0, v1, v2, loc, true, true);
}
/**
*
* @param p
* @param loc
* @return true if the ray collides with the given Plane
*/
public boolean intersectsWherePlane(Plane p, Vector3f loc) {
float denominator = p.getNormal().dot(direction);
if (denominator > -FastMath.FLT_EPSILON && denominator < FastMath.FLT_EPSILON) {
return false; // coplanar
}
float numerator = -(p.getNormal().dot(origin) - p.getConstant());
float ratio = numerator / denominator;
if (ratio < FastMath.FLT_EPSILON) {
return false; // intersects behind origin
}
loc.set(direction).multLocal(ratio).addLocal(origin);
return true;
}
public int collideWith(Collidable other, CollisionResults results) {
if (other instanceof BoundingVolume) {
BoundingVolume bv = (BoundingVolume) other;
return bv.collideWith(this, results);
} else if (other instanceof AbstractTriangle) {
AbstractTriangle tri = (AbstractTriangle) other;
float d = intersects(tri.get1(), tri.get2(), tri.get3());
if (Float.isInfinite(d) || Float.isNaN(d)) {
return 0;
}
Vector3f point = new Vector3f(direction).multLocal(d).addLocal(origin);
results.addCollision(new CollisionResult(point, d));
return 1;
} else {
throw new UnsupportedCollisionException();
}
}
public float distanceSquared(Vector3f point) {
TempVars vars = TempVars.get();
Vector3f tempVa = vars.vect1,
tempVb = vars.vect2;
point.subtract(origin, tempVa);
float rayParam = direction.dot(tempVa);
if (rayParam > 0) {
origin.add(direction.mult(rayParam, tempVb), tempVb);
} else {
tempVb.set(origin);
rayParam = 0.0f;
}
tempVb.subtract(point, tempVa);
float len = tempVa.lengthSquared();
vars.release();
return len;
}
/**
*
* <code>getOrigin</code> retrieves the origin point of the ray.
*
* @return the origin of the ray.
*/
public Vector3f getOrigin() {
return origin;
}
/**
*
* <code>setOrigin</code> sets the origin of the ray.
* @param origin the origin of the ray.
*/
public void setOrigin(Vector3f origin) {
this.origin.set(origin);
}
/**
* <code>getLimit</code> returns the limit of the ray, aka the length.
* If the limit is not infinity, then this ray is a line with length <code>
* limit</code>.
*
* @return the limit of the ray, aka the length.
*/
public float getLimit() {
return limit;
}
/**
* <code>setLimit</code> sets the limit of the ray.
* @param limit the limit of the ray.
* @see Ray#getLimit()
*/
public void setLimit(float limit) {
this.limit = limit;
}
/**
*
* <code>getDirection</code> retrieves the direction vector of the ray.
* @return the direction of the ray.
*/
public Vector3f getDirection() {
return direction;
}
/**
*
* <code>setDirection</code> sets the direction vector of the ray.
* @param direction the direction of the ray.
*/
public void setDirection(Vector3f direction) {
assert direction.isUnitVector();
this.direction.set(direction);
}
/**
* Copies information from a source ray into this ray.
*
* @param source
* the ray to copy information from
*/
public void set(Ray source) {
origin.set(source.getOrigin());
direction.set(source.getDirection());
}
public String toString() {
return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]";
}
public void write(JmeExporter e) throws IOException {
OutputCapsule capsule = e.getCapsule(this);
capsule.write(origin, "origin", Vector3f.ZERO);
capsule.write(direction, "direction", Vector3f.ZERO);
}
public void read(JmeImporter e) throws IOException {
InputCapsule capsule = e.getCapsule(this);
origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());
direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());
}
@Override
public Ray clone() {
try {
Ray r = (Ray) super.clone();
r.direction = direction.clone();
r.origin = origin.clone();
return r;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}