All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ardor3d.math.Ray3 Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2008-2012 Ardor Labs, Inc.
 *
 * This file is part of Ardor3D.
 *
 * Ardor3D is free software: you can redistribute it and/or modify it 
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or at .
 */

package com.ardor3d.math;

import com.ardor3d.math.type.ReadOnlyPlane;
import com.ardor3d.math.type.ReadOnlyRay3;
import com.ardor3d.math.type.ReadOnlyVector3;

public class Ray3 extends Line3Base implements ReadOnlyRay3, Poolable {

    private static final long serialVersionUID = 1L;

    private static final ObjectPool RAY_POOL = ObjectPool.create(Ray3.class, MathConstants.maxMathPoolSize);

    /**
     * Constructs a new ray with an origin at (0,0,0) and a direction of (0,0,1).
     */
    public Ray3() {
        super(Vector3.ZERO, Vector3.UNIT_Z);
    }

    /**
     * Copy constructor.
     * 
     * @param source
     *            the ray to copy from.
     */
    public Ray3(final ReadOnlyRay3 source) {
        this(source.getOrigin(), source.getDirection());
    }

    /**
     * Constructs a new ray using the supplied origin point and unit length direction vector
     * 
     * @param origin
     * @param direction
     *            - unit length
     */
    public Ray3(final ReadOnlyVector3 origin, final ReadOnlyVector3 direction) {
        super(origin, direction);
    }

    /**
     * Copies the values of the given source ray into this ray.
     * 
     * @param source
     * @return this ray for chaining
     * @throws NullPointerException
     *             if source is null.
     */
    public Ray3 set(final ReadOnlyRay3 source) {
        _origin.set(source.getOrigin());
        _direction.set(source.getDirection());
        return this;
    }

    /**
     * @param worldVertices
     *            an array of vectors describing a polygon
     * @return the distance from our origin to the primitive or POSITIVE_INFINITY if we do not intersect.
     */
    @Override
    public double getDistanceToPrimitive(final Vector3[] worldVertices) {
        // Intersection test
        final Vector3 intersect = Vector3.fetchTempInstance();
        try {
            if (intersects(worldVertices, intersect)) {
                return getOrigin().distance(intersect);
            }
        } finally {
            Vector3.releaseTempInstance(intersect);
        }
        return Double.POSITIVE_INFINITY;
    }

    /**
     * @param polygonVertices
     * @param locationStore
     * @return true if this ray intersects a polygon described by the given vertices.
     */
    @Override
    public boolean intersects(final Vector3[] polygonVertices, final Vector3 locationStore) {
        if (polygonVertices.length == 3) {
            // TRIANGLE
            return intersectsTriangle(polygonVertices[0], polygonVertices[1], polygonVertices[2], locationStore);
        } else if (polygonVertices.length == 4) {
            // QUAD
            return intersectsQuad(polygonVertices[0], polygonVertices[1], polygonVertices[2], polygonVertices[3],
                    locationStore);
        }
        // TODO: Add support for line and point
        return false;
    }

    /**
     * @param pointA
     * @param pointB
     * @param pointC
     * @param locationStore
     *            if not null, and this ray intersects, the point of intersection is calculated and stored in this
     *            Vector3
     * @return true if this ray intersects a triangle formed by the given three points.
     * @throws NullPointerException
     *             if any of the points are null.
     */
    @Override
    public boolean intersectsTriangle(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB,
            final ReadOnlyVector3 pointC, final Vector3 locationStore) {
        return intersects(pointA, pointB, pointC, locationStore, false);
    }

    /**
     * @param pointA
     * @param pointB
     * @param pointC
     * @param locationStore
     *            if not null, and this ray intersects, the point of intersection is calculated and stored in this
     *            Vector3 as (t, u, v) where t is the distance from the _origin to the point of intersection and (u, v)
     *            is the intersection point on the triangle plane.
     * @return true if this ray intersects a triangle formed by the given three points.
     * @throws NullPointerException
     *             if any of the points are null.
     */
    @Override
    public boolean intersectsTrianglePlanar(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB,
            final ReadOnlyVector3 pointC, final Vector3 locationStore) {
        return intersects(pointA, pointB, pointC, locationStore, true);
    }

    /**
     * @param pointA
     * @param pointB
     * @param pointC
     * @param pointD
     * @param locationStore
     *            if not null, and this ray intersects, the point of intersection is calculated and stored in this
     *            Vector3
     * @return true if this ray intersects a triangle formed by the given three points. The points are assumed to be
     *         coplanar.
     * @throws NullPointerException
     *             if any of the points are null.
     */
    @Override
    public boolean intersectsQuad(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB,
            final ReadOnlyVector3 pointC, final ReadOnlyVector3 pointD, final Vector3 locationStore) {
        return intersects(pointA, pointB, pointC, locationStore, false)
                || intersects(pointA, pointC, pointD, locationStore, false);
    }

    /**
     * @param pointA
     * @param pointB
     * @param pointC
     * @param pointD
     * @param locationStore
     *            if not null, and this ray intersects, the point of intersection is calculated and stored in this
     *            Vector3 as (t, u, v) where t is the distance from the _origin to the point of intersection and (u, v)
     *            is the intersection point on the triangle plane.
     * @return true if this ray intersects a quad formed by the given four points. The points are assumed to be
     *         coplanar.
     * @throws NullPointerException
     *             if any of the points are null.
     */
    @Override
    public boolean intersectsQuadPlanar(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB,
            final ReadOnlyVector3 pointC, final ReadOnlyVector3 pointD, final Vector3 locationStore) {
        return intersects(pointA, pointB, pointC, locationStore, true)
                || intersects(pointA, pointC, pointD, locationStore, true);
    }

    /**
     * Ray vs triangle implementation.
     * 
     * @param pointA
     * @param pointB
     * @param pointC
     * @param locationStore
     * @param doPlanar
     * @return true if this ray intersects a triangle formed by the given three points.
     * @throws NullPointerException
     *             if any of the points are null.
     */
    protected boolean intersects(final ReadOnlyVector3 pointA, final ReadOnlyVector3 pointB,
            final ReadOnlyVector3 pointC, final Vector3 locationStore, final boolean doPlanar) {
        final Vector3 diff = Vector3.fetchTempInstance().set(_origin).subtractLocal(pointA);
        final Vector3 edge1 = Vector3.fetchTempInstance().set(pointB).subtractLocal(pointA);
        final Vector3 edge2 = Vector3.fetchTempInstance().set(pointC).subtractLocal(pointA);
        final Vector3 norm = Vector3.fetchTempInstance().set(edge1).crossLocal(edge2);

        double dirDotNorm = _direction.dot(norm);
        double sign;
        if (dirDotNorm > MathUtils.EPSILON) {
            sign = 1.0;
        } else if (dirDotNorm < -MathUtils.EPSILON) {
            sign = -1.0;
            dirDotNorm = -dirDotNorm;
        } else {
            // ray and triangle/quad are parallel
            return false;
        }

        final double dirDotDiffxEdge2 = sign * _direction.dot(diff.cross(edge2, edge2));
        boolean result = false;
        if (dirDotDiffxEdge2 >= 0.0) {
            final double dirDotEdge1xDiff = sign * _direction.dot(edge1.crossLocal(diff));
            if (dirDotEdge1xDiff >= 0.0) {
                if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) {
                    final double diffDotNorm = -sign * diff.dot(norm);
                    if (diffDotNorm >= 0.0) {
                        // ray intersects triangle
                        // if storage vector is null, just return true,
                        if (locationStore == null) {
                            return true;
                        }
                        // else fill in.
                        final double inv = 1f / dirDotNorm;
                        final double t = diffDotNorm * inv;
                        if (!doPlanar) {
                            locationStore.set(_origin).addLocal(_direction.getX() * t, _direction.getY() * t,
                                    _direction.getZ() * 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;
                            final double w1 = dirDotDiffxEdge2 * inv;
                            final double w2 = dirDotEdge1xDiff * inv;
                            // double w0 = 1.0 - w1 - w2;
                            locationStore.set(t, w1, w2);
                        }
                        result = true;
                    }
                }
            }
        }
        Vector3.releaseTempInstance(diff);
        Vector3.releaseTempInstance(edge1);
        Vector3.releaseTempInstance(edge2);
        Vector3.releaseTempInstance(norm);
        return result;
    }

    /**
     * @param plane
     * @param locationStore
     *            if not null, and this ray intersects the plane, the world location of the point of intersection is
     *            stored in this vector.
     * @return true if the ray collides with the given Plane
     * @throws NullPointerException
     *             if the plane is null.
     */
    @Override
    public boolean intersectsPlane(final ReadOnlyPlane plane, final Vector3 locationStore) {
        final ReadOnlyVector3 normal = plane.getNormal();
        final double denominator = normal.dot(_direction);

        if (denominator > -MathUtils.EPSILON && denominator < MathUtils.EPSILON) {
            return false; // coplanar
        }

        final double numerator = -normal.dot(_origin) + plane.getConstant();
        final double ratio = numerator / denominator;

        if (ratio < MathUtils.EPSILON) {
            return false; // intersects behind _origin
        }

        if (locationStore != null) {
            locationStore.set(_direction).multiplyLocal(ratio).addLocal(_origin);
        }

        return true;
    }

    /**
     * @param point
     * @param store
     *            if not null, the closest point is stored in this param
     * @return the squared distance from this ray to the given point.
     * @throws NullPointerException
     *             if the point is null.
     */
    @Override
    public double distanceSquared(final ReadOnlyVector3 point, final Vector3 store) {
        final Vector3 vectorA = Vector3.fetchTempInstance();
        vectorA.set(point).subtractLocal(_origin);
        final double t0 = _direction.dot(vectorA);
        if (t0 > 0) {
            // d = |P - (O + t*D)|
            vectorA.set(_direction).multiplyLocal(t0);
            vectorA.addLocal(_origin);
        } else {
            // ray is closest to origin point
            vectorA.set(_origin);
        }

        // Save away the closest point if requested.
        if (store != null) {
            store.set(vectorA);
        }

        point.subtract(vectorA, vectorA);
        final double lSQ = vectorA.lengthSquared();
        Vector3.releaseTempInstance(vectorA);
        return lSQ;
    }

    /**
     * Check a ray... if it is null or the values of its origin or direction are NaN or infinite, return false. Else
     * return true.
     * 
     * @param ray
     *            the ray to check
     * @return true or false as stated above.
     */
    public static boolean isValid(final ReadOnlyRay3 ray) {
        if (ray == null) {
            return false;
        }

        return Vector3.isValid(ray.getDirection()) && Vector3.isValid(ray.getOrigin());
    }

    /**
     * @return the string representation of this ray.
     */
    @Override
    public String toString() {
        return "com.ardor3d.math.Ray [Origin: " + _origin + " - Direction: " + _direction + "]";
    }

    /**
     * @param o
     *            the object to compare for equality
     * @return true if this ray and the provided ray have the same constant and normal values.
     */
    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ReadOnlyRay3)) {
            return false;
        }
        final ReadOnlyRay3 comp = (ReadOnlyRay3) o;
        return _origin.equals(comp.getOrigin()) && _direction.equals(comp.getDirection());
    }

    // /////////////////
    // Method for Cloneable
    // /////////////////

    @Override
    public Ray3 clone() {
        return new Ray3(this);
    }

    // /////////////////
    // Methods for creating temp variables (pooling)
    // /////////////////

    /**
     * @return An instance of Ray that is intended for temporary use in calculations and so forth. Multiple calls to the
     *         method should return instances of this class that are not currently in use.
     */
    public final static Ray3 fetchTempInstance() {
        if (MathConstants.useMathPools) {
            return Ray3.RAY_POOL.fetch();
        } else {
            return new Ray3();
        }
    }

    /**
     * Releases a Ray back to be used by a future call to fetchTempInstance. TAKE CARE: this Ray object should no longer
     * have other classes referencing it or "Bad Things" will happen.
     * 
     * @param ray
     *            the Ray to release.
     */
    public final static void releaseTempInstance(final Ray3 ray) {
        if (MathConstants.useMathPools) {
            Ray3.RAY_POOL.release(ray);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy