/*
* $Id$
*
* Copyright (C) 2013 Christophe BOHRHAUER.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* This program is free software; you can redistribute it and/or modify
*/
package org.arakhne.afc.math.geometry.d3.continuous;
import org.arakhne.afc.math.MathUtil;
import org.arakhne.afc.math.Unefficient;
import org.arakhne.afc.math.geometry.d3.FunctionalVector3D;
import org.arakhne.afc.math.geometry.d3.Point3D;
import org.arakhne.afc.math.geometry.d3.Vector3D;
import org.eclipse.xtext.xbase.lib.Pure;
/** This class represents a 3D plane.
*
* @param <PT> is the type of the plane.
* @author $Author: cbohrhauer$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
public abstract class AbstractPlane3D<PT extends AbstractPlane3D<? super PT>> implements Plane3D<PT> {
private static final long serialVersionUID = -8226008101273829655L;
/** Replies a clone of this plane.
*
* @return a clone.
*/
@SuppressWarnings("unchecked")
@Pure
@Override
public PT clone() {
try {
return (PT)super.clone();
}
catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
/**
* {@inheritDoc}
*
* @return {@inheritDoc}
*/
@Pure
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append('[');
buf.append(getEquationComponentA());
buf.append(".x ");
double b = getEquationComponentB();
if (b>=0) buf.append('+');
buf.append(b);
buf.append(".y ");
double c = getEquationComponentC();
if (c>=0) buf.append('+');
buf.append(c);
buf.append(".z ");
double d = getEquationComponentD();
if (d>=0) buf.append('+');
buf.append(d);
buf.append("=0]");
return buf.toString();
}
@Pure
@Override
public double distanceTo(Point3D v) {
return distanceTo(v.getX(), v.getY(), v.getZ());
}
@Pure
@Override
public double distanceTo(Plane3D<?> p) {
// Compute the normales
Vector3D oNormal = p.getNormal();
oNormal.normalize();
Vector3D mNormal = getNormal();
mNormal.normalize();
double dotProduct = oNormal.dot(mNormal);
if (MathUtil.isEpsilonEqual(Math.abs(dotProduct),1)) {
// Planes are colinear.
// The problem could be restricted to a 1D problem.
// Compute the coordinate of this pane
// assuming the origin is (0,0,0)
double c1 = -distanceTo(0,0,0);
// Compute the coordinate of the other pane
// assuming the origin is (0,0,0)
double c2 = -p.distanceTo(new Point3f(0,0,0));
if (dotProduct==-1) {
// The planes have not the same orientation.
// Reverse one coordinate.
c2 = -c2;
}
return c2 - c1;
}
return Double.NaN;
}
@Pure
@Override
public AbstractSegment3F getIntersection(Plane3D<?> plane) {
Vector3D n1 = getNormal();
Vector3D n2 = plane.getNormal();
Vector3f u = new Vector3f();
u.cross(n1, n2);
double ulength = u.length();
if (MathUtil.isEpsilonZero(ulength)) {
// planes are parallel
return null;
}
// u is both perpendicular to the two normals,
// so it is parallel to both planes
// ((d2 n1 - d1 n2) x (n1 x n2))
// -----------------------------
// | n1 x n2 | ^2
//
// same as:
//
// ((d2 n1 - d1 n2) x u)
// ---------------------
// ulength ^2
n1.scale(plane.getEquationComponentD());
n2.scale(getEquationComponentD());
n1.sub(n2);
n2.cross(n1, u);
n2.scale(1.f/(ulength*ulength)); // n2 contains intersection point
u.normalize();
return new Segment3f(new Point3f(n2), u);
}
@Pure
@Override
public Point3f getIntersection(AbstractSegment3F line) {
Vector3D n = getNormal();
Vector3D u = line.getDirection();
double s = n.dot(u);
if (MathUtil.isEpsilonZero(s)) {
// line and plane are parallel
return null;
}
// Assuming L: P0 + si * u
//
// -(a x0 + b y0 + c z0 + d)
// si = -------------------------
// n.u
Point3f p0 = (Point3f) line.getP1();
double si = -(
getEquationComponentA()*p0.getX()
+
getEquationComponentB()*p0.getY()
+
getEquationComponentC()*p0.getZ()
+
getEquationComponentD()
)/s;
u.scale(si);
p0.add(u);
return p0;
}
@Pure
@Override
public Point3f getProjection(Point3D point) {
return new Point3f(getProjection(point.getX(), point.getY(), point.getZ()));
}
@Pure
@Override
public boolean intersects(double x, double y, double z) {
double distance = distanceTo(x,y,z);
return MathUtil.isEpsilonZero(distance);
}
@Pure
@Override
public boolean intersects(Point3D vec) {
return intersects(vec.getX(), vec.getY(), vec.getZ());
}
@Pure
@Override
public boolean intersects(Plane3D<?> otherPlane) {
double distance = distanceTo(otherPlane);
if (Double.isNaN(distance)) return true;
// The distance could not be computed
// the planes intersect.
// Planes intersect also when the distance
// is null
return MathUtil.isEpsilonZero(distance);
}
@Pure
@Override
public PlaneClassification classifies(Plane3D<?> otherPlane) {
double distance = distanceTo(otherPlane);
// The distance could not be computed
// the planes intersect.
// Planes intersect also when the distance
// is null
if ((Double.isNaN(distance)) ||
(MathUtil.isEpsilonZero(distance)))
return PlaneClassification.COINCIDENT;
if (distance>0) return PlaneClassification.IN_FRONT_OF;
return PlaneClassification.BEHIND;
}
@Pure
@Override
public PlaneClassification classifies(Point3D vec) {
return classifies(vec.getX(), vec.getY(), vec.getZ());
}
@Pure
@Override
public PlaneClassification classifies(double x, double y, double z) {
PlaneClassification c;
double distance = distanceTo(x, y, z);
if (MathUtil.isEpsilonZero(distance))
c = PlaneClassification.BEHIND;
else if (distance>0)
c = PlaneClassification.IN_FRONT_OF;
else
c = PlaneClassification.COINCIDENT;
return c;
}
@Pure
@Override
public PlaneClassification classifies(AbstractSphere3F sphere) {
double distance = distanceTo(sphere.getX(), sphere.getY(), sphere.getZ());
if (Math.abs(distance) >= sphere.getRadius()) {
if (distance<0) return PlaneClassification.BEHIND;
return PlaneClassification.IN_FRONT_OF;
}
return PlaneClassification.COINCIDENT;
}
@Pure
@Override
public PlaneClassification classifies(AbstractBoxedShape3F<?> box) {
Vector3D normal = getNormal();
double minx, miny, minz, maxx, maxy, maxz;
// X axis
if(normal.getX()>=0.) {
minx = box.getMinX();
maxx = box.getMaxX();
}
else {
minx = box.getMaxX();
maxx = box.getMinX();
}
// Y axis
if(normal.getY()>=0.) {
miny = box.getMinY();
maxy = box.getMaxY();
}
else {
miny = box.getMaxY();
maxy = box.getMinY();
}
// Z axis
if(normal.getZ()>=0.) {
minz = box.getMinZ();
maxz = box.getMaxZ();
}
else {
minz = box.getMaxZ();
maxz = box.getMinZ();
}
double d = getEquationComponentD();
if ((FunctionalVector3D.dotProduct(normal.getX(), normal.getY(), normal.getZ(), minx, miny, minz)+d)>0.)
return PlaneClassification.IN_FRONT_OF;
if ((FunctionalVector3D.dotProduct(normal.getX(), normal.getY(), normal.getZ(), maxx, maxy, maxz)+d)>=0.)
return PlaneClassification.COINCIDENT;
return PlaneClassification.BEHIND;
}
@Pure
@Override
public boolean intersects(AbstractSphere3F sphere) {
double distance = distanceTo(sphere.getX(), sphere.getY(), sphere.getZ());
return MathUtil.isEpsilonZero(distance);
}
@Pure
@Override
@Unefficient
public boolean intersects(AbstractBoxedShape3F<?> box) {
return classifies(box) == PlaneClassification.COINCIDENT;
}
@Pure
@Override
public boolean intersects(AbstractOrientedBox3F box) {
// Compute the effective radius of the obb and
// compare it with the distance between the obb center
// and the plane; source MGPCG pp.235
Vector3f n = (Vector3f) getNormal();
double dist = Math.abs(distanceTo(box.getCenter()));
double effectiveRadius;
effectiveRadius = Math.abs(
FunctionalVector3D.dotProduct(
box.getFirstAxisX() * box.getFirstAxisExtent(),
box.getFirstAxisY() * box.getFirstAxisExtent(),
box.getFirstAxisZ() * box.getFirstAxisExtent(),
n.getX(), n.getY(), n.getZ()));
effectiveRadius += Math.abs(
FunctionalVector3D.dotProduct(
box.getSecondAxisX() * box.getSecondAxisExtent(),
box.getSecondAxisY() * box.getSecondAxisExtent(),
box.getSecondAxisZ() * box.getSecondAxisExtent(),
n.getX(), n.getY(), n.getZ()));
effectiveRadius += Math.abs(
FunctionalVector3D.dotProduct(
box.getThirdAxisX() * box.getThirdAxisExtent(),
box.getThirdAxisY() * box.getThirdAxisExtent(),
box.getThirdAxisZ() * box.getThirdAxisExtent(),
n.getX(), n.getY(), n.getZ()));
return MathUtil.compareEpsilon(dist, effectiveRadius) <= 0;
}
@Pure
@Override
public boolean intersects(AbstractSegment3F segment) {
Vector3D n = getNormal();
Vector3D u = segment.getDirection();
double s = n.dot(u);
if (MathUtil.isEpsilonZero(s)) {
// The plane and the segment are parallel or coplanar.
// Test the coplanarity.
double d = distanceTo(segment.getX1(), segment.getY1(), segment.getZ1());
return (MathUtil.isEpsilonZero(d));
}
double si = -(
getEquationComponentA()*segment.getX1()
+
getEquationComponentB()*segment.getY1()
+
getEquationComponentC()*segment.getZ1()
+
getEquationComponentD()
) / s;
return (si >= 0. && si <= 1.);
}
@Override
public final void setPivot(Point3D point) {
setPivot(point.getX(), point.getY(), point.getZ());
}
}