diff --git a/src/main/java/pythagoras/d/AffineTransform.java b/src/main/java/pythagoras/d/AffineTransform.java index c2d29ad..9f7f213 100644 --- a/src/main/java/pythagoras/d/AffineTransform.java +++ b/src/main/java/pythagoras/d/AffineTransform.java @@ -309,6 +309,12 @@ public class AffineTransform extends AbstractTransform (y * m00 - x * m01) * rdet); } + @Override // from Transform + public Vector transformPoint (IVector v, Vector into) { + double x = v.x(), y = v.y(); + return into.set(m00*x + m10*y + tx, m01*x + m11*y + ty); + } + @Override // from Transform public Vector transform (IVector v, Vector into) { double x = v.x(), y = v.y(); diff --git a/src/main/java/pythagoras/d/Box.java b/src/main/java/pythagoras/d/Box.java index b34a5bc..02da7f5 100644 --- a/src/main/java/pythagoras/d/Box.java +++ b/src/main/java/pythagoras/d/Box.java @@ -366,54 +366,45 @@ public class Box implements IBox, Serializable _maxExtent.z >= omin.z() && _minExtent.z <= omax.z(); } - // /** - // * Determines whether the specified ray intersects this box. - // */ - // public boolean intersects (Ray3D ray) { - // Vector3 dir = ray.direction(); - // return - // Math.abs(dir.x) > MathUtil.EPSILON && - // (intersectsX(ray, _minExtent.x) || intersectsX(ray, _maxExtent.x)) || - // Math.abs(dir.y) > MathUtil.EPSILON && - // (intersectsY(ray, _minExtent.y) || intersectsY(ray, _maxExtent.y)) || - // Math.abs(dir.z) > MathUtil.EPSILON && - // (intersectsZ(ray, _minExtent.z) || intersectsZ(ray, _maxExtent.z)); - // } + @Override // from IBox + public boolean intersects (IRay3 ray) { + IVector3 dir = ray.direction(); + return + Math.abs(dir.x()) > MathUtil.EPSILON && + (intersectsX(ray, _minExtent.x) || intersectsX(ray, _maxExtent.x)) || + Math.abs(dir.y()) > MathUtil.EPSILON && + (intersectsY(ray, _minExtent.y) || intersectsY(ray, _maxExtent.y)) || + Math.abs(dir.z()) > MathUtil.EPSILON && + (intersectsZ(ray, _minExtent.z) || intersectsZ(ray, _maxExtent.z)); + } - // /** - // * Finds the location of the (first) intersection between the specified ray and this box. - // * This will be the ray origin if the ray starts inside the box. - // * - // * @param result a vector to hold the location of the intersection. - // * @return true if the ray intersects the box (in which case the result vector will be - // * populated with the location of the intersection), false if not. - // */ - // public boolean intersection (Ray3D ray, Vector3 result) { - // Vector3 origin = ray.origin(); - // if (contains(origin)) { - // result.set(origin); - // return true; - // } - // Vector3 dir = ray.direction(); - // double t = Float.MAX_VALUE; - // if (Math.abs(dir.x) > MathUtil.EPSILON) { - // t = Math.min(t, intersectionX(ray, _minExtent.x)); - // t = Math.min(t, intersectionX(ray, _maxExtent.x)); - // } - // if (Math.abs(dir.y) > MathUtil.EPSILON) { - // t = Math.min(t, intersectionY(ray, _minExtent.y)); - // t = Math.min(t, intersectionY(ray, _maxExtent.y)); - // } - // if (Math.abs(dir.z) > MathUtil.EPSILON) { - // t = Math.min(t, intersectionZ(ray, _minExtent.z)); - // t = Math.min(t, intersectionZ(ray, _maxExtent.z)); - // } - // if (t == Float.MAX_VALUE) { - // return false; - // } - // origin.addScaled(dir, t, result); - // return true; - // } + @Override // from IBox + public boolean intersection (IRay3 ray, Vector3 result) { + IVector3 origin = ray.origin(); + if (contains(origin)) { + result.set(origin); + return true; + } + IVector3 dir = ray.direction(); + double t = Float.MAX_VALUE; + if (Math.abs(dir.x()) > MathUtil.EPSILON) { + t = Math.min(t, intersectionX(ray, _minExtent.x)); + t = Math.min(t, intersectionX(ray, _maxExtent.x)); + } + if (Math.abs(dir.y()) > MathUtil.EPSILON) { + t = Math.min(t, intersectionY(ray, _minExtent.y)); + t = Math.min(t, intersectionY(ray, _maxExtent.y)); + } + if (Math.abs(dir.z()) > MathUtil.EPSILON) { + t = Math.min(t, intersectionZ(ray, _minExtent.z)); + t = Math.min(t, intersectionZ(ray, _maxExtent.z)); + } + if (t == Float.MAX_VALUE) { + return false; + } + origin.addScaled(dir, t, result); + return true; + } @Override // documentation inherited public String toString () { @@ -434,98 +425,98 @@ public class Box implements IBox, Serializable return _minExtent.equals(obox._minExtent) && _maxExtent.equals(obox._maxExtent); } - // /** - // * Helper method for {@link #intersects(Ray3D)}. Determines whether the ray intersects the box - // * at the plane where x equals the value specified. - // */ - // protected boolean intersectsX (Ray3D ray, double x) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (x - origin.x) / dir.x; - // if (t < 0f) { - // return false; - // } - // double iy = origin.y + t*dir.y, iz = origin.z + t*dir.z; - // return iy >= _minExtent.y && iy <= _maxExtent.y && - // iz >= _minExtent.z && iz <= _maxExtent.z; - // } + /** + * Helper method for {@link #intersects(Ray3)}. Determines whether the ray intersects the box + * at the plane where x equals the value specified. + */ + protected boolean intersectsX (IRay3 ray, double x) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (x - origin.x()) / dir.x(); + if (t < 0f) { + return false; + } + double iy = origin.y() + t*dir.y(), iz = origin.z() + t*dir.z(); + return iy >= _minExtent.y && iy <= _maxExtent.y && + iz >= _minExtent.z && iz <= _maxExtent.z; + } - // /** - // * Helper method for {@link #intersects(Ray3D)}. Determines whether the ray intersects the box - // * at the plane where y equals the value specified. - // */ - // protected boolean intersectsY (Ray3D ray, double y) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (y - origin.y) / dir.y; - // if (t < 0f) { - // return false; - // } - // double ix = origin.x + t*dir.x, iz = origin.z + t*dir.z; - // return ix >= _minExtent.x && ix <= _maxExtent.x && - // iz >= _minExtent.z && iz <= _maxExtent.z; - // } + /** + * Helper method for {@link #intersects(Ray3)}. Determines whether the ray intersects the box + * at the plane where y equals the value specified. + */ + protected boolean intersectsY (IRay3 ray, double y) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (y - origin.y()) / dir.y(); + if (t < 0f) { + return false; + } + double ix = origin.x() + t*dir.x(), iz = origin.z() + t*dir.z(); + return ix >= _minExtent.x && ix <= _maxExtent.x && + iz >= _minExtent.z && iz <= _maxExtent.z; + } - // /** - // * Helper method for {@link #intersects(Ray3D)}. Determines whether the ray intersects the box - // * at the plane where z equals the value specified. - // */ - // protected boolean intersectsZ (Ray3D ray, double z) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (z - origin.z) / dir.z; - // if (t < 0f) { - // return false; - // } - // double ix = origin.x + t*dir.x, iy = origin.y + t*dir.y; - // return ix >= _minExtent.x && ix <= _maxExtent.x && - // iy >= _minExtent.y && iy <= _maxExtent.y; - // } + /** + * Helper method for {@link #intersects(Ray3)}. Determines whether the ray intersects the box + * at the plane where z equals the value specified. + */ + protected boolean intersectsZ (IRay3 ray, double z) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (z - origin.z()) / dir.z(); + if (t < 0f) { + return false; + } + double ix = origin.x() + t*dir.x(), iy = origin.y() + t*dir.y(); + return ix >= _minExtent.x && ix <= _maxExtent.x && + iy >= _minExtent.y && iy <= _maxExtent.y; + } - // /** - // * Helper method for {@link #intersection}. Finds the t value where the ray - // * intersects the box at the plane where x equals the value specified, or returns - // * {@link Float#MAX_VALUE} if there is no such intersection. - // */ - // protected double intersectionX (Ray3D ray, double x) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (x - origin.x) / dir.x; - // if (t < 0f) { - // return Float.MAX_VALUE; - // } - // double iy = origin.y + t*dir.y, iz = origin.z + t*dir.z; - // return (iy >= _minExtent.y && iy <= _maxExtent.y && - // iz >= _minExtent.z && iz <= _maxExtent.z) ? t : Float.MAX_VALUE; - // } + /** + * Helper method for {@link #intersection}. Finds the t value where the ray + * intersects the box at the plane where x equals the value specified, or returns + * {@link Float#MAX_VALUE} if there is no such intersection. + */ + protected double intersectionX (IRay3 ray, double x) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (x - origin.x()) / dir.x(); + if (t < 0f) { + return Float.MAX_VALUE; + } + double iy = origin.y() + t*dir.y(), iz = origin.z() + t*dir.z(); + return (iy >= _minExtent.y && iy <= _maxExtent.y && + iz >= _minExtent.z && iz <= _maxExtent.z) ? t : Float.MAX_VALUE; + } - // /** - // * Helper method for {@link #intersection}. Finds the t value where the ray - // * intersects the box at the plane where y equals the value specified, or returns - // * {@link Float#MAX_VALUE} if there is no such intersection. - // */ - // protected double intersectionY (Ray3D ray, double y) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (y - origin.y) / dir.y; - // if (t < 0f) { - // return Float.MAX_VALUE; - // } - // double ix = origin.x + t*dir.x, iz = origin.z + t*dir.z; - // return (ix >= _minExtent.x && ix <= _maxExtent.x && - // iz >= _minExtent.z && iz <= _maxExtent.z) ? t : Float.MAX_VALUE; - // } + /** + * Helper method for {@link #intersection}. Finds the t value where the ray + * intersects the box at the plane where y equals the value specified, or returns + * {@link Float#MAX_VALUE} if there is no such intersection. + */ + protected double intersectionY (IRay3 ray, double y) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (y - origin.y()) / dir.y(); + if (t < 0f) { + return Float.MAX_VALUE; + } + double ix = origin.x() + t*dir.x(), iz = origin.z() + t*dir.z(); + return (ix >= _minExtent.x && ix <= _maxExtent.x && + iz >= _minExtent.z && iz <= _maxExtent.z) ? t : Float.MAX_VALUE; + } - // /** - // * Helper method for {@link #intersection}. Finds the t value where the ray - // * intersects the box at the plane where z equals the value specified, or returns - // * {@link Float#MAX_VALUE} if there is no such intersection. - // */ - // protected double intersectionZ (Ray3D ray, double z) { - // Vector3 origin = ray.origin(), dir = ray.direction(); - // double t = (z - origin.z) / dir.z; - // if (t < 0f) { - // return Float.MAX_VALUE; - // } - // double ix = origin.x + t*dir.x, iy = origin.y + t*dir.y; - // return (ix >= _minExtent.x && ix <= _maxExtent.x && - // iy >= _minExtent.y && iy <= _maxExtent.y) ? t : Float.MAX_VALUE; - // } + /** + * Helper method for {@link #intersection}. Finds the t value where the ray + * intersects the box at the plane where z equals the value specified, or returns + * {@link Float#MAX_VALUE} if there is no such intersection. + */ + protected double intersectionZ (IRay3 ray, double z) { + IVector3 origin = ray.origin(), dir = ray.direction(); + double t = (z - origin.z()) / dir.z(); + if (t < 0f) { + return Float.MAX_VALUE; + } + double ix = origin.x() + t*dir.x(), iy = origin.y() + t*dir.y(); + return (ix >= _minExtent.x && ix <= _maxExtent.x && + iy >= _minExtent.y && iy <= _maxExtent.y) ? t : Float.MAX_VALUE; + } /** The box's minimum extent. */ protected final Vector3 _minExtent = new Vector3(); diff --git a/src/main/java/pythagoras/d/IBox.java b/src/main/java/pythagoras/d/IBox.java index 02ba7ff..c1b9fc0 100644 --- a/src/main/java/pythagoras/d/IBox.java +++ b/src/main/java/pythagoras/d/IBox.java @@ -156,8 +156,18 @@ public interface IBox */ Box expand (double x, double y, double z, Box result); - // /** - // * Determines whether the specified ray intersects this box. - // */ - // boolean intersects (Ray3D ray); + /** + * Determines whether the specified ray intersects this box. + */ + boolean intersects (IRay3 ray); + + /** + * Finds the location of the (first) intersection between the specified ray and this box. This + * will be the ray origin if the ray starts inside the box. + * + * @param result a vector to hold the location of the intersection. + * @return true if the ray intersects the box (in which case the result vector will be + * populated with the location of the intersection), false if not. + */ + boolean intersection (IRay3 ray, Vector3 result); } diff --git a/src/main/java/pythagoras/d/IPlane.java b/src/main/java/pythagoras/d/IPlane.java index 3829f9a..c9e0354 100644 --- a/src/main/java/pythagoras/d/IPlane.java +++ b/src/main/java/pythagoras/d/IPlane.java @@ -32,7 +32,7 @@ public interface IPlane // * // * @return a new plane containing the result. // */ - // Plane transform (Transform transform); + // Plane transform (Transform3D transform); // /** // * Transforms this plane by the specified transformation, placing the result in the object @@ -40,7 +40,7 @@ public interface IPlane // * // * @return a reference to the result plane, for chaining. // */ - // Plane transform (Transform transform, Plane result); + // Plane transform (Transform3D transform, Plane result); /** * Negates this plane. @@ -56,19 +56,19 @@ public interface IPlane */ Plane negate (Plane result); - // /** - // * Computes the intersection of the supplied ray with this plane, placing the result - // * in the given vector (if the ray intersects). - // * - // * @return true if the ray intersects the plane (in which case the result will contain - // * the point of intersection), false if not. - // */ - // boolean intersection (Ray3D ray, Vector3 result); + /** + * Computes the intersection of the supplied ray with this plane, placing the result + * in the given vector (if the ray intersects). + * + * @return true if the ray intersects the plane (in which case the result will contain + * the point of intersection), false if not. + */ + boolean intersection (IRay3 ray, Vector3 result); - // /** - // * Computes the signed distance to this plane along the specified ray. - // * - // * @return the signed distance, or {@link Float#NaN} if the ray runs parallel to the plane. - // */ - // double distance (Ray3D ray); + /** + * Computes the signed distance to this plane along the specified ray. + * + * @return the signed distance, or {@link Float#NaN} if the ray runs parallel to the plane. + */ + double distance (IRay3 ray); } diff --git a/src/main/java/pythagoras/d/IRay2.java b/src/main/java/pythagoras/d/IRay2.java new file mode 100644 index 0000000..dc78f30 --- /dev/null +++ b/src/main/java/pythagoras/d/IRay2.java @@ -0,0 +1,72 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Ray2}. + */ +public interface IRay2 +{ + /** + * Returns a reference to the ray's point of origin. + */ + IVector origin (); + + /** + * Returns a reference to the ray's unit direction vector. + */ + IVector direction (); + + /** + * Transforms this ray. + * + * @return a new ray containing the result. + */ + Ray2 transform (Transform transform); + + /** + * Transforms this ray, placing the result in the object provided. + * + * @return a reference to the result ray, for chaining. + */ + Ray2 transform (Transform transform, Ray2 result); + + /** + * Determines whether the ray intersects the specified point. + */ + boolean intersects (IVector pt); + + /** + * Finds the intersection between the ray and a line segment with the given start and end + * points. + * + * @return true if the ray intersected the segment (in which case the result will contain the + * point of intersection), false otherwise. + */ + boolean getIntersection (IVector start, IVector end, Vector result); + + /** + * Finds the intersection between the ray and a capsule with the given start point, end point, + * and radius. + * + * @return true if the ray intersected the circle (in which case the result will contain the + * point of intersection), false otherwise. + */ + boolean getIntersection (IVector start, IVector end, double radius, Vector result); + + /** + * Finds the intersection between the ray and a circle with the given center and radius. + * + * @return true if the ray intersected the circle (in which case the result will contain the + * point of intersection), false otherwise. + */ + boolean getIntersection (IVector center, double radius, Vector result); + + /** + * Computes the nearest point on the Ray to the supplied point. + * @return {@code result} for chaining. + */ + Vector getNearestPoint (IVector point, Vector result); +} diff --git a/src/main/java/pythagoras/d/IRay3.java b/src/main/java/pythagoras/d/IRay3.java new file mode 100644 index 0000000..72f280a --- /dev/null +++ b/src/main/java/pythagoras/d/IRay3.java @@ -0,0 +1,35 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * Provides read-only access to a {@link Ray3}. + */ +public interface IRay3 +{ + /** + * Returns a reference to the ray's point of origin. + */ + IVector3 origin (); + + /** + * Returns a reference to the ray's unit direction vector. + */ + IVector3 direction (); + + // /** + // * Transforms this ray. + // * + // * @return a new ray containing the result. + // */ + // Ray3 transform (Transform3D transform); + + // /** + // * Transforms this ray, placing the result in the object provided. + // * + // * @return a reference to the result ray, for chaining. + // */ + // Ray3 transform (Transform3D transform, Ray3 result); +} diff --git a/src/main/java/pythagoras/d/IdentityTransform.java b/src/main/java/pythagoras/d/IdentityTransform.java index 267e388..daa88f4 100644 --- a/src/main/java/pythagoras/d/IdentityTransform.java +++ b/src/main/java/pythagoras/d/IdentityTransform.java @@ -86,6 +86,11 @@ public class IdentityTransform extends AbstractTransform return into.set(p); } + @Override // from Transform + public Vector transformPoint (IVector v, Vector into) { + return into.set(v); + } + @Override // from Transform public Vector transform (IVector v, Vector into) { return into.set(v); diff --git a/src/main/java/pythagoras/d/NonUniformTransform.java b/src/main/java/pythagoras/d/NonUniformTransform.java index 8777c39..3a4fd91 100644 --- a/src/main/java/pythagoras/d/NonUniformTransform.java +++ b/src/main/java/pythagoras/d/NonUniformTransform.java @@ -231,6 +231,11 @@ public class NonUniformTransform extends AbstractTransform return Points.inverseTransform(p.x(), p.y(), scaleX, scaleY, rotation, tx, ty, into); } + @Override // from Transform + public Vector transformPoint (IVector v, Vector into) { + return Vectors.transform(v.x(), v.y(), scaleX, scaleY, rotation, tx, ty, into); + } + @Override // from Transform public Vector transform (IVector v, Vector into) { return Vectors.transform(v.x(), v.y(), scaleX, scaleY, rotation, into); diff --git a/src/main/java/pythagoras/d/Plane.java b/src/main/java/pythagoras/d/Plane.java index 8f05548..ab7d8c7 100644 --- a/src/main/java/pythagoras/d/Plane.java +++ b/src/main/java/pythagoras/d/Plane.java @@ -129,7 +129,7 @@ public class Plane implements IPlane, Serializable // * // * @return a reference to this plane, for chaining. // */ - // public Plane transformLocal (Transform transform) { + // public Plane transformLocal (Transform3D transform) { // return transform(transform, this); // } @@ -163,12 +163,12 @@ public class Plane implements IPlane, Serializable } // @Override // from IPlane - // public Plane transform (Transform transform) { + // public Plane transform (Transform3D transform) { // return transform(transform, new Plane()); // } // @Override // from IPlane - // public Plane transform (Transform transform, Plane result) { + // public Plane transform (Transform3D transform, Plane result) { // transform.transformPointLocal(_normal.mult(-constant, _v1)); // transform.transformVector(_normal, _v2).normalizeLocal(); // return result.fromPointNormal(_v1, _v2); @@ -186,29 +186,29 @@ public class Plane implements IPlane, Serializable return result; } - // @Override // from IPlane - // public boolean intersection (Ray3D ray, Vector3 result) { - // double distance = distance(ray); - // if (Double.isNaN(distance) || distance < 0f) { - // return false; - // } else { - // ray.origin().addScaled(ray.direction(), distance, result); - // return true; - // } - // } + @Override // from IPlane + public boolean intersection (IRay3 ray, Vector3 result) { + double distance = distance(ray); + if (Float.isNaN(distance) || distance < 0f) { + return false; + } else { + ray.origin().addScaled(ray.direction(), distance, result); + return true; + } + } - // @Override // from IPlane - // public double distance (Ray3D ray) { - // double dividend = -distance(ray.origin()); - // double divisor = _normal.dot(ray.direction()); - // if (Math.abs(dividend) < MathUtil.EPSILON) { - // return 0f; // origin is on plane - // } else if (Math.abs(divisor) < MathUtil.EPSILON) { - // return Float.NaN; // ray is parallel to plane - // } else { - // return dividend / divisor; - // } - // } + @Override // from IPlane + public double distance (IRay3 ray) { + double dividend = -distance(ray.origin()); + double divisor = _normal.dot(ray.direction()); + if (Math.abs(dividend) < MathUtil.EPSILON) { + return 0f; // origin is on plane + } else if (Math.abs(divisor) < MathUtil.EPSILON) { + return Float.NaN; // ray is parallel to plane + } else { + return dividend / divisor; + } + } @Override public int hashCode () { diff --git a/src/main/java/pythagoras/d/Quaternion.java b/src/main/java/pythagoras/d/Quaternion.java index bc00eab..2e68c44 100644 --- a/src/main/java/pythagoras/d/Quaternion.java +++ b/src/main/java/pythagoras/d/Quaternion.java @@ -308,7 +308,7 @@ public class Quaternion implements IQuaternion, Serializable @Override // from IQuaternion public boolean hasNaN () { - return Double.isNaN(x) || Double.isNaN(y) || Double.isNaN(z) || Double.isNaN(w); + return Float.isNaN(x) || Float.isNaN(y) || Float.isNaN(z) || Float.isNaN(w); } @Override // from IQuaternion diff --git a/src/main/java/pythagoras/d/Ray2.java b/src/main/java/pythagoras/d/Ray2.java new file mode 100644 index 0000000..256756f --- /dev/null +++ b/src/main/java/pythagoras/d/Ray2.java @@ -0,0 +1,234 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * A ray consisting of an origin point and a unit direction vector. + */ +public class Ray2 implements IRay2 +{ + /** The ray's point of origin. */ + public final Vector origin = new Vector(); + + /** The ray's unit direction vector. */ + public final Vector direction = new Vector(); + + /** + * Creates a ray with the values contained in the supplied origin point and unit direction + * vector. + */ + public Ray2 (Vector origin, Vector direction) { + set(origin, direction); + } + + /** + * Copy constructor. + */ + public Ray2 (Ray2 other) { + set(other); + } + + /** + * Creates an empty (invalid) ray. + */ + public Ray2 () { + } + + /** + * Copies the parameters of another ray. + * + * @return a reference to this ray, for chaining. + */ + public Ray2 set (IRay2 other) { + return set(other.origin(), other.direction()); + } + + /** + * Sets the ray parameters to the values contained in the supplied vectors. + * + * @return a reference to this ray, for chaining. + */ + public Ray2 set (IVector origin, IVector direction) { + this.origin.set(origin); + this.direction.set(direction); + return this; + } + + /** + * Transforms this ray in-place. + * + * @return a reference to this ray, for chaining. + */ + public Ray2 transformLocal (Transform transform) { + return transform(transform, this); + } + + @Override // from IRay2 + public IVector origin () { + return origin; + } + + @Override // from IRay2 + public IVector direction () { + return direction; + } + + @Override // from IRay2 + public Ray2 transform (Transform transform) { + return transform(transform, new Ray2()); + } + + @Override // from IRay2 + public Ray2 transform (Transform transform, Ray2 result) { + transform.transformPoint(origin, result.origin); + transform.transform(direction, result.direction).normalizeLocal(); + return result; + } + + @Override // from IRay2 + public boolean intersects (IVector pt) { + if (Math.abs(direction.x) > Math.abs(direction.y)) { + double t = (pt.x() - origin.x) / direction.x; + return t >= 0f && origin.y + t*direction.y == pt.y(); + } else { + double t = (pt.y() - origin.y) / direction.y; + return t >= 0f && origin.x + t*direction.x == pt.x(); + } + } + + @Override // from IRay2 + public boolean getIntersection (IVector start, IVector end, Vector result) { + // ray is a + t*b, segment is c + s*d + double ax = origin.x, ay = origin.y; + double bx = direction.x, by = direction.y; + double cx = start.x(), cy = start.y(); + double dx = end.x() - start.x(), dy = end.y() - start.y(); + + double divisor = bx*dy - by*dx; + if (Math.abs(divisor) < Math.EPSILON) { + // the lines are parallel (or the segment is zero-length) + double t = Math.min(getIntersection(start), getIntersection(end)); + boolean isect = (t != Float.MAX_VALUE); + if (isect) { + origin.addScaled(direction, t, result); + } + return isect; + } + double cxax = cx - ax, cyay = cy - ay; + double s = (by*cxax - bx*cyay) / divisor; + if (s < 0f || s > 1f) { + return false; + } + double t = (dy*cxax - dx*cyay) / divisor; + boolean isect = (t >= 0f); + if (isect) { + origin.addScaled(direction, t, result); + } + return isect; + } + + @Override // from IRay2 + public boolean getIntersection (IVector start, IVector end, double radius, Vector result) { + double startx = start.x(), starty = start.y(); + // compute the segment's line parameters + double a = starty - end.y(), b = end.x() - startx; + double len = Math.hypot(a, b); + if (len < Math.EPSILON) { // start equals end; check as circle + return getIntersection(start, radius, result); + } + double rlen = 1f / len; + a *= rlen; + b *= rlen; + double c = -a*startx - b*starty; + + // find out where the origin lies with respect to the top and bottom + double dist = a*origin.x + b*origin.y + c; + boolean above = (dist > +radius), below = (dist < -radius); + double x, y; + if (above || below) { // check the intersection with the top/bottom boundary + double divisor = a*direction.x + b*direction.y; + if (Math.abs(divisor) < Math.EPSILON) { // lines are parallel + return false; + } + c += (above ? -radius : +radius); + double t = (-a*origin.x - b*origin.y - c) / divisor; + if (t < 0f) { // wrong direction + return false; + } + x = origin.x + t*direction.x; + y = origin.y + t*direction.y; + + } else { // middle; check the origin + x = origin.x; + y = origin.y; + } + // see where the test point lies with respect to the start and end boundaries + double tmp = a; + a = b; + b = -tmp; + c = -a*startx - b*starty; + dist = a*x + b*y + c; + if (dist < 0f) { // before start + return getIntersection(start, radius, result); + } else if (dist > len) { // after end + return getIntersection(end, radius, result); + } else { // middle + result.set(x, y); + return true; + } + } + + @Override // from IRay2 + public boolean getIntersection (IVector center, double radius, Vector result) { + // see if we start inside the circle + if (origin.distanceSq(center) <= radius*radius) { + result.set(origin); + return true; + } + // then if we intersect the circle + double ax = origin.x - center.x(), ay = origin.y - center.y(); + double b = 2f*(direction.x*ax + direction.y*ay); + double c = ax*ax + ay*ay - radius*radius; + double radicand = b*b - 4f*c; + if (radicand < 0f) { + return false; + } + double t = (-b - Math.sqrt(radicand)) * 0.5f; + boolean isect = (t >= 0f); + if (isect) { + origin.addScaled(direction, t, result); + } + return isect; + } + + @Override // from IRay2 + public Vector getNearestPoint (IVector point, Vector result) { + if (result == null) { + result = new Vector(); + } + double r = point.subtract(origin).dot(direction); + result.set(origin.add(direction.scale(r))); + return result; + } + + @Override + public String toString () { + return "[origin=" + origin + ", direction=" + direction + "]"; + } + + /** + * Returns the parameter of the ray when it intersects the supplied point, or + * {@link Float#MAX_VALUE} if there is no such intersection. + */ + protected double getIntersection (IVector pt) { + if (Math.abs(direction.x) > Math.abs(direction.y)) { + double t = (pt.x() - origin.x) / direction.x; + return (t >= 0f && origin.y + t*direction.y == pt.y()) ? t : Float.MAX_VALUE; + } else { + double t = (pt.y() - origin.y) / direction.y; + return (t >= 0f && origin.x + t*direction.x == pt.x()) ? t : Float.MAX_VALUE; + } + } +} diff --git a/src/main/java/pythagoras/d/Ray3.java b/src/main/java/pythagoras/d/Ray3.java new file mode 100644 index 0000000..5218f1b --- /dev/null +++ b/src/main/java/pythagoras/d/Ray3.java @@ -0,0 +1,94 @@ +// +// Pythagoras - a collection of geometry classes +// http://github.com/samskivert/pythagoras + +package pythagoras.d; + +/** + * A ray consisting of an origin point and a unit direction vector. + */ +public class Ray3 implements IRay3 +{ + /** The ray's point of origin. */ + public final Vector3 origin = new Vector3(); + + /** The ray's unit direction vector. */ + public final Vector3 direction = new Vector3(); + + /** + * Creates a ray with the values contained in the supplied origin point and unit direction + * vector. + */ + public Ray3 (Vector3 origin, Vector3 direction) { + set(origin, direction); + } + + /** + * Copy constructor. + */ + public Ray3 (Ray3 other) { + set(other); + } + + /** + * Creates an empty (invalid) ray. + */ + public Ray3 () { + } + + /** + * Copies the parameters of another ray. + * + * @return a reference to this ray, for chaining. + */ + public Ray3 set (Ray3 other) { + return set(other.origin(), other.direction()); + } + + /** + * Sets the ray parameters to the values contained in the supplied vectors. + * + * @return a reference to this ray, for chaining. + */ + public Ray3 set (Vector3 origin, Vector3 direction) { + this.origin.set(origin); + this.direction.set(direction); + return this; + } + + // /** + // * Transforms this ray in-place. + // * + // * @return a reference to this ray, for chaining. + // */ + // public Ray3 transformLocal (Transform3D transform) { + // return transform(transform, this); + // } + + @Override // from IRay3 + public Vector3 origin () { + return origin; + } + + @Override // from IRay3 + public Vector3 direction () { + return direction; + } + + // @Override // from IRay3 + // public Ray3 transform (Transform3D transform) { + // return transform(transform, new Ray3()); + // } + + // @Override // from IRay3 + // public Ray3 transform (Transform3D transform, Ray3 result) { + // transform.transformPoint(origin, result.origin); + // transform.transformVector(direction, result.direction).normalizeLocal(); + // return result; + // } + + @Override + public String toString () { + return "[origin=" + origin + ", direction=" + direction + "]"; + } +} diff --git a/src/main/java/pythagoras/d/RigidTransform.java b/src/main/java/pythagoras/d/RigidTransform.java index e05b6da..ea657ad 100644 --- a/src/main/java/pythagoras/d/RigidTransform.java +++ b/src/main/java/pythagoras/d/RigidTransform.java @@ -145,7 +145,7 @@ public class RigidTransform extends AbstractTransform } @Override // from Transform - public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) { + public void transform (IPoint[] src, int srcOff, Point[] dst, int dstOff, int count) { double sina = Math.sin(rotation), cosa = Math.cos(rotation); for (int ii = 0; ii < count; ii++) { IPoint s = src[srcOff++]; @@ -169,6 +169,11 @@ public class RigidTransform extends AbstractTransform return Points.inverseTransform(p.x(), p.y(), 1, 1, rotation, tx, ty, into); } + @Override // from Transform + public Vector transformPoint (IVector v, Vector into) { + return Vectors.transform(v.x(), v.y(), 1, 1, rotation, tx, ty, into); + } + @Override // from Transform public Vector transform (IVector v, Vector into) { return v.rotate(rotation, into); diff --git a/src/main/java/pythagoras/d/Transform.java b/src/main/java/pythagoras/d/Transform.java index 2e642d6..bb97705 100644 --- a/src/main/java/pythagoras/d/Transform.java +++ b/src/main/java/pythagoras/d/Transform.java @@ -176,6 +176,12 @@ public interface Transform * @throws NoninvertibleTransformException if the transform is not invertible. */ Point inverseTransform (IPoint p, Point into); + /** Transforms the supplied vector as a point (accounting for translation), writing the result + * into {@code into}. + * @param into a vector into which to store the result, may be the same object as {@code v}. + * @return {@code into}, for chaining. */ + Vector transformPoint (IVector v, Vector into); + /** Transforms the supplied vector, writing the result into {@code into}. * @param into a vector into which to store the result, may be the same object as {@code v}. * @return {@code into}, for chaining. */ diff --git a/src/main/java/pythagoras/d/UniformTransform.java b/src/main/java/pythagoras/d/UniformTransform.java index 193e691..93dc962 100644 --- a/src/main/java/pythagoras/d/UniformTransform.java +++ b/src/main/java/pythagoras/d/UniformTransform.java @@ -197,6 +197,11 @@ public class UniformTransform extends AbstractTransform return Points.inverseTransform(p.x(), p.y(), scale, scale, rotation, tx, ty, into); } + @Override // from Transform + public Vector transformPoint (IVector p, Vector into) { + return Vectors.transform(p.x(), p.y(), scale, scale, rotation, tx, ty, into); + } + @Override // from Transform public Vector transform (IVector v, Vector into) { return Vectors.transform(v.x(), v.y(), scale, scale, rotation, into); diff --git a/src/main/java/pythagoras/d/Vectors.java b/src/main/java/pythagoras/d/Vectors.java index 0a3d262..514f9a9 100644 --- a/src/main/java/pythagoras/d/Vectors.java +++ b/src/main/java/pythagoras/d/Vectors.java @@ -85,12 +85,21 @@ public class Vectors return Math.abs(v1.x() - v2.x()) <= epsilon && Math.abs(v1.y() - v2.y()) <= epsilon; } + /** Transforms a vector as specified (as a point, accounting for translation), storing the + * result in the vector provided. + * @return a reference to the result vector, for chaining. */ + public static Vector transform (double x, double y, double sx, double sy, double rotation, + double tx, double ty, Vector result) { + return transform(x, y, sx, sy, Math.sin(rotation), Math.cos(rotation), tx, ty, + result); + } + /** - * Transforms a point as specified, storing the result in the point provided. + * Transforms a vector as specified, storing the result in the vector provided. * @return a reference to the result vector, for chaining. */ public static Vector transform (double x, double y, double sx, double sy, double rotation, - Vector result) { + Vector result) { return transform(x, y, sx, sy, Math.sin(rotation), Math.cos(rotation), result); } @@ -103,8 +112,16 @@ public class Vectors return result.set((x*cosa - y*sina) * sx, (x*sina + y*cosa) * sy); } + /** Transforms a vector as specified (as a point, accounting for translation), storing the + * result in the vector provided. + * @return a reference to the result vector, for chaining. */ + public static Vector transform (double x, double y, double sx, double sy, double sina, double cosa, + double tx, double ty, Vector result) { + return result.set((x*cosa - y*sina) * sx + tx, (x*sina + y*cosa) * sy + ty); + } + /** - * Inverse transforms a point as specified, storing the result in the point provided. + * Inverse transforms a vector as specified, storing the result in the vector provided. * @return a reference to the result vector, for chaining. */ public static Vector inverseTransform (double x, double y, double sx, double sy, double rotation,