/*******************************************************************************
* Breakout Cave Survey Visualizer
*
* Copyright (C) 2014 James Edwards
*
* jedwards8 at fastmail dot fm
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program 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 General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*******************************************************************************/
package org.andork.vecmath;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
/**
* Performs plane intersections. Right now, it is used to project line segments
* onto triangles and line segments onto other line segments.
*
* @author andy.edwards
*/
public class PlanePlaneIntersection {
public static enum ResultType {
ERROR, NONE,
/**
* Indicates the intersection is a single point.
*/
POINT,
/**
* Indicates the intersection is a finite line segment.
*/
LINE_SEGMENT,
/**
* Indicates the intersection is an infinite line.
*/
LINE,
/**
* Indicates the intersection is a triangle.
*/
TRIANGLE,
/**
* Indicates the intersection is an infinite plane.
*/
PLANE;
}
public static void main(String[] args) {
PlanePlaneIntersection ppx = new PlanePlaneIntersection();
// ppx.setUpPlane( 0 , new Point3d( 0 , 0 , 0 ) , new Point3d( 0 , 1 , 1
// ) , new Point3d( 1 , 0 , 1 ) );
// ppx.setUpProjectedLine( new Point3d( 0.5 , 0.5 , 2 ) , new Point3d( 2
// , 0 , 5 ) , new Vector3d( 0 , 0 , -1 ) );
ppx.setUpPlane(0, new Point3d(-1, -1, -1), new Point3d(-.6, -.6, 2), new Point3d(0, -.75, 0));
ppx.setUpProjectedLine(new Point3d(-10, -10, 100), new Point3d(-.7, -.7, 100), new Vector3d(0, 0, 1));
ppx.doSegmentTriangleProjection();
System.out.println(ppx);
ppx.setUpLineLineProjection(new Point3d(0, 0, 0), new Point3d(1, 0, 1), new Point3d(0, 0, 1),
new Point3d(1, 0, 1), new Vector3d(1, 0, -1));
ppx.doSegmentSegmentProjection();
System.out.println(ppx);
}
public final Point3d po = new Point3d();
public final Vector3d pu = new Vector3d();
public final Vector3d pv = new Vector3d();
public final Vector3d pn = new Vector3d();
public final Point3d qo = new Point3d();
public final Vector3d qu = new Vector3d();
public final Vector3d qv = new Vector3d();
public final Vector3d qn = new Vector3d();
/**
* Weights for vectors (pu, pv, qu, qv) at first intersection point. Will be
* set such that
* <code>x0 = po + params0[0] * pu + params0[1] * pv = qo + params0[2] * qu + params0[3] * qv</code>
* (if there is a valid intersection point).
*/
public double[] params0 = new double[4];
/**
* Weights for vectors (pu, pv, qu, qv) at second intersection point. Will
* be set such that
* <code>x1 = po + params1[0] * pu + params1[1] * pv = qo + params1[2] * qu + params1[3] * qv</code>
* (if there is a valid second intersection point).
*/
public double[] params1 = new double[4];
/**
* First intersection point. Only valid if {@link #resultType} is
* <code>POINT, LINE_SEGMENT, or LINE</code>.
*/
public final Point3d x0 = new Point3d();
/**
* Second intersection point. Only valid if {@link #resultType} is
* <code>LINE_SEGMENT or LINE</code>.
*/
public final Point3d x1 = new Point3d();
/**
* Type of intersection found.
*/
public ResultType resultType;
final LinePlaneIntersection lpx = new LinePlaneIntersection();
private void addSegmentSegmentProjResult(double up, double vp, double uq, Point3d x) {
if (resultType == ResultType.NONE) {
resultType = ResultType.POINT;
params0[0] = up;
params0[1] = vp;
params0[2] = uq;
params0[3] = 0;
x0.set(x);
} else if (resultType == ResultType.POINT) {
if (!x.equals(x0)) {
if (up < params0[0]) {
resultType = ResultType.LINE_SEGMENT;
System.arraycopy(params0, 0, params1, 0, 4);
x1.set(x0);
params0[0] = up;
params0[1] = vp;
params0[2] = uq;
params0[3] = 0;
x0.set(x);
} else if (up > params0[0]) {
resultType = ResultType.LINE_SEGMENT;
params1[0] = up;
params1[1] = vp;
params1[2] = uq;
params1[3] = 0;
x1.set(x);
}
}
}
}
private void addSegmentTriangleProjResult(double up, double vp, double uq, double vq, Point3d x) {
if (resultType == ResultType.NONE) {
resultType = ResultType.POINT;
params0[0] = up;
params0[1] = vp;
params0[2] = uq;
params0[3] = vq;
x0.set(x);
} else if (resultType == ResultType.POINT) {
if (!x.equals(x0)) {
if (uq < params0[2]) {
resultType = ResultType.LINE_SEGMENT;
System.arraycopy(params0, 0, params1, 0, 4);
x1.set(x0);
params0[0] = up;
params0[1] = vp;
params0[2] = uq;
params0[3] = vq;
x0.set(x);
} else if (uq > params0[2]) {
resultType = ResultType.LINE_SEGMENT;
params1[0] = up;
params1[1] = vp;
params1[2] = uq;
params1[3] = vq;
x1.set(x);
}
}
}
}
/**
* Projects the first line segment onto the second line segment in the
* instance variables (set up with
* {@link #setUpLineLineProjection(Point3d, Point3d, Point3d, Point3d, Vector3d)}
* ).
*
* May set {@link #resultType}, {@link #x0}, {@link #x1}, {@link #params0},
* and {@link #params1} with the result values.
*/
public void doSegmentSegmentProjection() {
resultType = ResultType.NONE;
lpx.setUpLine(qo, qu);
lpx.setUpPlane(po, pu, pv);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.isOnLine() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentSegmentProjResult(lpx.u, lpx.v, lpx.t, lpx.result);
} else if (lpx.isInPlane()) {
int hits = 0;
qv.add(qo, pn);
lpx.setUpPlane(qo, qu, qv);
lpx.setUpLine(po, pv);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentSegmentProjResult(0, lpx.t, lpx.u, lpx.result);
if (++hits == 2) {
return;
}
}
lpx.lo.add(po, pu);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentSegmentProjResult(1, lpx.t, lpx.u, lpx.result);
if (++hits == 2) {
return;
}
}
lpx.setUpPlane(po, pu, pn);
lpx.lo.set(qo);
lpx.lt.negate(pv);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentSegmentProjResult(lpx.u, lpx.t, 0, qo);
if (++hits == 2) {
return;
}
}
lpx.lo.add(qo, qu);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentSegmentProjResult(lpx.u, lpx.t, 1, lpx.lo);
if (++hits == 2) {
return;
}
}
}
}
/**
* Projects the line segment onto the triangle in the instance variables
* (set up with
* {@link #setUpLinePlaneProjection(Point3d, Point3d, Point3d, Point3d, Point3d, Vector3d)}
* ).
*
* May set {@link #resultType}, {@link #x0}, {@link #x1}, {@link #params0},
* and {@link #params1} with the result values.
*/
public void doSegmentTriangleProjection() {
resultType = ResultType.NONE;
int hits = 0;
lpx.setUpPlane(qo, qu, qv);
// project first line endpoint onto triangle
lpx.setUpLine(po, pv);
lpx.findIntersection();
if (lpx.isInTriangle()) {
addSegmentTriangleProjResult(0, lpx.t, lpx.u, lpx.v, lpx.result);
if (++hits == 2) {
return;
}
}
// project second line endpoint onto triangle
lpx.lo.add(po, pu);
lpx.findIntersection();
if (lpx.isInTriangle()) {
addSegmentTriangleProjResult(1, lpx.t, lpx.u, lpx.v, lpx.result);
if (++hits == 2) {
return;
}
}
// lpx.setUpPlane( q0.x , q0.y , q0.z , q1.x , q1.y , q1.z , q2.x , q2.y
// , q2.z );
lpx.setUpPlane(po, pu, pv);
// intersect first triangle segment with projection strip
lpx.setUpLine(qo, qu);
lpx.findIntersection();
if (lpx.isOnLine() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentTriangleProjResult(lpx.u, lpx.v, lpx.t, 0, lpx.result);
if (++hits == 2) {
return;
}
}
// intersect second triangle segment with projection strip
lpx.lo.add(qo, qu);
lpx.lt.sub(qv, qu);
lpx.findIntersection();
if (lpx.isOnLine() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentTriangleProjResult(lpx.u, lpx.v, 1 - lpx.t, lpx.t, lpx.result);
if (++hits == 2) {
return;
}
}
// intersect third triangle segment with projection strip
lpx.setUpLine(qo, qv);
lpx.findIntersection();
if (lpx.isOnLine() && lpx.u >= 0 && lpx.u <= 1) {
addSegmentTriangleProjResult(lpx.u, lpx.v, 0, lpx.t, lpx.result);
if (++hits == 2) {
return;
}
}
}
/**
* Finds the intersection of the triangle and plane in the instance
* variables (set up using
* {@link #setUpTrianglePlaneIntersection(Point3d, Point3d, Point3d, Point3d, Vector3d, Vector3d)}
* ).
*
* May set {@link #resultType}, {@link #x0}, {@link #x1}, {@link #params0},
* and {@link #params1} with the result values.
*/
public void doTrianglePlaneIntersection() {
resultType = ResultType.NONE;
lpx.setUpPlane(qo, qu, qv);
lpx.setUpLine(po, pu);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.isOnLine()) {
addSegmentTriangleProjResult(lpx.t, 0, lpx.u, lpx.v, lpx.result);
if (resultType == ResultType.LINE_SEGMENT) {
return;
}
}
lpx.setUpLine(po, pv);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.isOnLine()) {
addSegmentTriangleProjResult(0, lpx.t, lpx.u, lpx.v, lpx.result);
if (resultType == ResultType.LINE_SEGMENT) {
return;
}
}
lpx.lo.add(po, pu);
lpx.lt.sub(pv, pu);
lpx.findIntersection();
if (lpx.isPointIntersection() && lpx.isOnLine()) {
addSegmentTriangleProjResult(1 - lpx.t, lpx.t, lpx.u, lpx.v, lpx.result);
if (resultType == ResultType.LINE_SEGMENT) {
return;
}
}
}
/**
* @return <code>true</code> if
* <code>resultType == ResultType.LINE_SEGMENT</code>.
*/
public boolean isLineSegmentIntersection() {
return resultType == ResultType.LINE_SEGMENT;
}
/**
* @return <code>true</code> if <code>resultType == ResultType.POINT</code>.
*/
public boolean isPointIntersection() {
return resultType == ResultType.POINT;
}
/**
* Sets up a line onto line projection. In this context, po is the origin of
* the first line, pu is the direction of the first line, pv is the
* direction of projection, qo is the origin of the second line, and qu is
* the direction of the second line.
*
* @throws {@link
* IllegalArgumentException} l0 equals l1 or if projDir is
* parallel to the line.
*/
public void setUpLineLineProjection(Point3d l0, Point3d l1, Point3d m0, Point3d m1, Vector3d projDir) {
po.set(l0);
pu.sub(l1, l0);
if (pu.x == 0 && pu.y == 0 && pu.z == 0) {
throw new IllegalArgumentException("l0 must not equal l1");
}
pv.set(projDir);
pn.cross(pu, pv);
if (pn.x == 0 && pn.y == 0 && pn.z == 0) {
throw new IllegalArgumentException("projDir must not be parallel to the line through l0 and l1");
}
qo.set(m0);
qu.sub(m1, m0);
}
/**
* Sets up a line onto plane projection. In this context, po is the line
* origin, pu is the line direction, pv is the direction of projection, qo
* is the first triangle point, and qu, qv are the vectors from there to the
* second and third triangle points.
*
* @throws IllegalArgumentException
* if tri0, tri1, and tri2 are colinear, or if projDir is
* parallel to the line.
*/
public void setUpLinePlaneProjection(Point3d line0, Point3d line1, Point3d tri0, Point3d tri1, Point3d tri2,
Vector3d projDir) {
setUpPlane(1, tri0, tri1, tri2);
setUpProjectedLine(line0, line1, projDir);
}
/**
* Sets one of the planes to be parallel to a triangle of points. The given
* points must not be colinear.
*
* @param planeNumber
* 0 for plane a, 1 for plane b.
*/
public void setUpPlane(int planeNumber, Point3d p0, Point3d p1, Point3d p2) {
if (planeNumber == 0) {
po.set(p0);
pu.sub(p1, p0);
pv.sub(p2, p0);
pn.cross(pu, pv);
if (pn.x == 0 && pn.y == 0 && pn.z == 0) {
throw new IllegalArgumentException("p0, p1, and p2 must not be coplanar");
}
} else if (planeNumber == 1) {
qo.set(p0);
qu.sub(p1, p0);
qv.sub(p2, p0);
qn.cross(qu, qv);
if (qn.x == 0 && qn.y == 0 && qn.z == 0) {
throw new IllegalArgumentException("p0, p1, and p2 must not be coplanar");
}
}
}
/**
* Sets one of the planes to be parallel to a triangle of points. The given
* points must not be colinear.
*
* @param planeNumber
* 0 for plane a, 1 for plane b.
*
* @throws IllegalArgumentException
* if pu and pv are linearly dependent.
*/
public void setUpPlane(int planeNumber, Point3d p0, Vector3d pu, Vector3d pv) {
if (planeNumber == 0) {
po.set(p0);
this.pu.set(pu);
this.pv.set(pv);
pn.cross(pu, pv);
if (pn.x == 0 && pn.y == 0 && pn.z == 0) {
throw new IllegalArgumentException("pu and pv must be linearly independent");
}
} else if (planeNumber == 1) {
qo.set(p0);
qu.set(pu);
qv.set(pv);
qn.cross(qu, qv);
if (qn.x == 0 && qn.y == 0 && qn.z == 0) {
throw new IllegalArgumentException("pu and pv must be linearly independent");
}
}
}
/**
* Sets {@link #po} to l0, {@link #pu} to the vector from l0 to l1, and
* {@link #pv} to projDir.
*
* @param l0
* First line point.
* @param l1
* Second line point.
* @param projDir
* Direction of projection.
*
* @throws IllegalArgumentException
* if l0 equals l1 or projDir is parallel to the line through l0
* and l1.
*/
public void setUpProjectedLine(Point3d l0, Point3d l1, Vector3d projDir) {
po.set(l0);
pu.sub(l1, l0);
if (pu.x == 0 && pu.y == 0 && pu.z == 0) {
throw new IllegalArgumentException("l0 must not equal l1");
}
pv.set(projDir);
pn.cross(pu, pv);
if (pn.x == 0 && pn.y == 0 && pn.z == 0) {
throw new IllegalArgumentException("line must not be parallel to projDir");
}
}
/**
* Sets up a triangle / plane intersection. In this context, {@link #po} is
* t0, {@link #pu} is the vector from t0 to t1, {@link #pv} is the vector
* from t0 to t2, and {@link #qo}, {@link #qu}, and {@link #qv} are set as
* given.
*
* @param t0
* Triangle point 0.
* @param t1
* Triangle point 1.
* @param t2
* Triangle point 2.
* @param qo
* Plane origin.
* @param qu
* Plane u-vector (parallel to plane).
* @param qv
* Plane v-vector (parallel to plane).
*
* @throws IllegalArgumentException
* if t0, t1, and t2 are colinear, or if qu and qv are linearly
* dependent.
*/
public void setUpTrianglePlaneIntersection(Point3d t0, Point3d t1, Point3d t2, Point3d qo, Vector3d qu,
Vector3d qv) {
setUpPlane(0, t0, t1, t2);
setUpPlane(1, qo, qu, qv);
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("PlanePlaneIntersection[\n");
buffer.append("\tPlane p: " + po + ", " + pu + ", " + pv + "\n");
buffer.append("\tPlane q: " + qo + ", " + qu + ", " + qv + "\n");
buffer.append("\tresultType: " + resultType + '\n');
buffer.append("\tx0: " + x0 + ", up: " + params0[0] + ", vp: " + params0[1] + ", uq: " + params0[2] + ", vq: "
+ params0[3] + "\n");
buffer.append("\tx1: " + x1 + ", up: " + params1[0] + ", vp: " + params1[1] + ", uq: " + params1[2] + ", vq: "
+ params1[3] + "\n");
return buffer.toString();
}
}