/* * Copyright (C) 2011 United States Government as represented by the Administrator of the * National Aeronautics and Space Administration. * All Rights Reserved. */ package gov.nasa.worldwind.geom; /** * Represents a <code>Plane</code> in Cartesian coordinates, defined by a normal vector to the plane and a signed scalar * value proportional to the distance of the plane from the origin. The sign of the value is relative to the direction * of the plane normal. * * @author Tom Gaskins * @version $Id$ */ public final class Plane { private final Vec4 n; // the plane normal and proportional distance. The vector is not necessarily a unit vector. /** * Constructs a plane from a 4-D vector giving the plane normal vector and distance. * * @param vec a 4-D vector indicating the plane's normal vector and distance. The normal need not be unit length. * * @throws IllegalArgumentException if the vector is null. */ public Plane(Vec4 vec) { if (vec == null) { throw new IllegalArgumentException("Vector Is Null"); } if (vec.getLengthSquared3() == 0.0) { throw new IllegalArgumentException("Vector Is Zero"); } this.n = vec; } /** * Constructs a plane from four values giving the plane normal vector and distance. * * @param nx the X component of the plane normal vector. * @param ny the Y component of the plane normal vector. * @param nz the Z component of the plane normal vector. * @param d the plane distance. * * @throws IllegalArgumentException if the normal vector components define the zero vector (all values are zero). */ public Plane(double nx, double ny, double nz, double d) { if (nx == 0.0 && ny == 0.0 && nz == 0.0) { throw new IllegalArgumentException("Vector Is Zero"); } this.n = new Vec4(nx, ny, nz, d); } /** * Returns the plane that passes through the specified three points. The plane's normal is the cross product of the * two vectors from <code>pb</code> to </code>pa</code> and <code>pc</code> to </code>pa</code>, respectively. The * returned plane is undefined if any of the specified points are colinear. * * @param pa the first point. * @param pb the second point. * @param pc the third point. * * @return a <code>Plane</code> passing through the specified points. * * @throws IllegalArgumentException if <code>pa</code>, <code>pb</code>, or <code>pc</code> is <code>null</code>. */ public static Plane fromPoints(Vec4 pa, Vec4 pb, Vec4 pc) { if (pa == null || pb == null || pc == null) { throw new IllegalArgumentException("Vec4 Is Null"); } Vec4 vab = pb.subtract3(pa); Vec4 vac = pc.subtract3(pa); Vec4 n = vab.cross3(vac); double d = -n.dot3(pa); return new Plane(n.x, n.y, n.z, d); } /** * Returns the plane's normal vector. * * @return the plane's normal vector. */ public final Vec4 getNormal() { return this.n;//new Vec4(this.n.x, this.n.y, this.n.z); } /** * Returs the plane distance. * * @return the plane distance. */ public final double getDistance() { return this.n.w; } /** * Returns a 4-D vector holding the plane's normal and distance. * * @return a 4-D vector indicating the plane's normal vector and distance. */ public final Vec4 getVector() { return this.n; } /** * Returns a normalized version of this plane. The normalized plane's normal vector is unit length and its distance * is D/|N| where |N| is the length of this plane's normal vector. * * @return a normalized copy of this Plane. */ public final Plane normalize() { double length = this.n.getLength3(); if (length == 0) // should not happen, but check to be sure. return this; return new Plane(new Vec4( this.n.x / length, this.n.y / length, this.n.z / length, this.n.w / length)); } /** * Calculates the 4-D dot product of this plane with a vector. * * @param p the vector. * * @return the dot product of the plane and the vector. * * @throws IllegalArgumentException if the vector is null. */ public final double dot(Vec4 p) { if (p == null) { throw new IllegalArgumentException("Point Is Null"); } return this.n.x * p.x + this.n.y * p.y + this.n.z * p.z + this.n.w * p.w; } /** * Determine the intersection point of a line with this plane. * * @param line the line to intersect. * * @return the intersection point if the line intersects the plane, otherwise null. * * @throws IllegalArgumentException if the line is null. */ public Vec4 intersect(Line line) { if (line == null) { throw new IllegalArgumentException("Line Is Null"); } double t = this.intersectDistance(line); if (Double.isNaN(t)) return null; if (Double.isInfinite(t)) return line.getOrigin(); return line.getPointAt(t); } /** * Determine the parametric point of intersection of a line with this plane. * * @param line the line to test * * @return The parametric value of the point on the line at which it intersects the plane. {@link Double#NaN} is * returned if the line does not intersect the plane. {@link Double#POSITIVE_INFINITY} is returned if the * line is coincident with the plane. * * @throws IllegalArgumentException if the line is null. */ public double intersectDistance(Line line) { if (line == null) { throw new IllegalArgumentException("Line Is Null"); } double ldotv = this.n.dot3(line.getDirection()); if (ldotv == 0) // are line and plane parallel { double ldots = this.n.dot4(line.getOrigin()); if (ldots == 0) return Double.POSITIVE_INFINITY; // line is coincident with the plane else return Double.NaN; // line is not coincident with the plane } return -this.n.dot4(line.getOrigin()) / ldotv; // ldots / ldotv } /** * Test a line segment for intersection with this plane. If it intersects, return the point of intersection. * * @param pa the first point of the line segment. * @param pb the second point of the line segment. * * @return The point of intersection with the plane. Null is returned if the segment does not instersect this plane. * {@link gov.nasa.worldwind.geom.Vec4#INFINITY} coincident with the plane. * * @throws IllegalArgumentException if either input point is null. */ public Vec4 intersect(Vec4 pa, Vec4 pb) { if (pa == null || pb == null) { throw new IllegalArgumentException("Point Is Null"); } try { // Test if line segment is in fact a point if (pa.equals(pb)) { double d = this.distanceTo(pa); if (d == 0) return pa; else return null; } Line l = Line.fromSegment(pa, pb); double t = this.intersectDistance(l); if (Double.isInfinite(t)) return Vec4.INFINITY; if (Double.isNaN(t) || t < 0 || t > 1) return null; return l.getPointAt(t); } catch (IllegalArgumentException e) { return null; } } /** * Clip a line segment to this plane. * * @param pa the first point of the segment. * @param pb the second point of the segment. * * @return An array of two points both on the positive side of the plane. If the direction of the line formed by the * two points is positive with respect to this plane's normal vector, the first point in the array will be * the intersection point on the plane, and the second point will be the original segment end point. If the * direction of the line is negative with respect to this plane's normal vector, the first point in the * array will be the original segment's begin point, and the second point will be the intersection point on * the plane. If the segment does not intersect the plane, null is returned. If the segment is coincident * with the plane, the input points are returned, in their input order. * * @throws IllegalArgumentException if either input point is null. */ public Vec4[] clip(Vec4 pa, Vec4 pb) { if (pa == null || pb == null) { throw new IllegalArgumentException("Point Is Null"); } if (pa.equals(pb)) return null; // Get the projection of the segment onto the plane. Line line = Line.fromSegment(pa, pb); double ldotv = this.n.dot3(line.getDirection()); // Are the line and plane parallel? if (ldotv == 0) // line and plane are parallel and maybe coincident { double ldots = this.n.dot4(line.getOrigin()); if (ldots == 0) return new Vec4[] {pa, pb}; // line is coincident with the plane else return null; // line is not coincident with the plane } // Not parallel so the line intersects. But does the segment intersect? double t = -this.n.dot4(line.getOrigin()) / ldotv; // ldots / ldotv if (t < 0 || t > 1) // segment does not intersect return null; Vec4 p = line.getPointAt(t); if (ldotv > 0) return new Vec4[] {p, pb}; else return new Vec4[] {pa, p}; } public double distanceTo(Vec4 p) { return this.n.dot4(p); } /** * Determines whether two points are on the same side of a plane. * * @param pa the first point. * @param pb the second point. * * @return true if the points are on the same side of the plane, otherwise false. * * @throws IllegalArgumentException if either point is null. */ public int onSameSide(Vec4 pa, Vec4 pb) { if (pa == null || pb == null) { throw new IllegalArgumentException("Point Is Null"); } double da = this.distanceTo(pa); double db = this.distanceTo(pb); if (da < 0 && db < 0) return -1; if (da > 0 && db > 0) return 1; return 0; } /** * Determines whether multiple points are on the same side of a plane. * * @param pts the array of points. * * @return true if the points are on the same side of the plane, otherwise false. * * @throws IllegalArgumentException if the points array is null or any point within it is null. */ public int onSameSide(Vec4[] pts) { if (pts == null) { throw new IllegalArgumentException("Points Array Is Null"); } double d = this.distanceTo(pts[0]); int side = d < 0 ? -1 : d > 0 ? 1 : 0; if (side == 0) return 0; for (int i = 1; i < pts.length; i++) { if (pts[i] == null) { throw new IllegalArgumentException("Point Is Null"); } d = this.distanceTo(pts[i]); if ((side == -1 && d < 0) || (side == 1 && d > 0)) continue; return 0; // point is not on same side as the others } return side; } /** * Compute the intersection of three planes. * * @param pa the first plane. * @param pb the second plane. * @param pc the third plane. * * @return the Cartesian coordinates of the intersection, or null if the three planes to not intersect at a point. * * @throws IllegalArgumentException if any of the planes are null. */ public static Vec4 intersect(Plane pa, Plane pb, Plane pc) { if (pa == null || pb == null || pc == null) { throw new IllegalArgumentException("Plane Is Null"); } Vec4 na = pa.getNormal(); Vec4 nb = pb.getNormal(); Vec4 nc = pc.getNormal(); Matrix m = new Matrix( na.x, na.y, na.z, 0, nb.x, nb.y, nb.z, 0, nc.x, nc.y, nc.z, 0, 0, 0, 0, 1, true ); Matrix mInverse = m.getInverse(); Vec4 D = new Vec4(-pa.getDistance(), -pb.getDistance(), -pc.getDistance()); return D.transformBy3(mInverse); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Plane)) return false; Plane plane = (Plane) o; return !(n != null ? !n.equals(plane.n) : plane.n != null); } @Override public int hashCode() { return n != null ? n.hashCode() : 0; } @Override public final String toString() { return this.n.toString(); } }