/* * 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(); } } }