/*
* Copyright (c) 2009-2010 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.aionemu.gameserver.geoEngine.math;
import com.aionemu.gameserver.geoEngine.bounding.BoundingVolume;
import com.aionemu.gameserver.geoEngine.collision.Collidable;
import com.aionemu.gameserver.geoEngine.collision.CollisionResults;
import com.aionemu.gameserver.geoEngine.collision.UnsupportedCollisionException;
import com.aionemu.gameserver.geoEngine.utils.TempVars;
/**
* <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 Cloneable, Collidable {
/** The ray's begining point. */
public Vector3f origin;
/** The direction of the ray. */
public Vector3f direction;
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,0).
*/
public Ray() {
origin = new Vector3f();
direction = new Vector3f();
}
/**
* 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) {
this.origin = origin;
this.direction = direction;
}
/**
* <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>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);
}
@Override
public int collideWith(Collidable other, CollisionResults results) {
if (other instanceof BoundingVolume) {
BoundingVolume bv = (BoundingVolume) other;
return bv.collideWith(this, results);
} 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);
}
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 or the ray, aka the length. If the limit is not infinity, then this ray is
* a line with length <code>
* limit</code>.
*
* @return
*/
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) {
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());
}
@Override
public String toString() {
return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]";
}
@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();
}
}
}