/*
* __ .__ .__ ._____.
* _/ |_ _______ __|__| ____ | | |__\_ |__ ______
* \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/
* | | ( <_> > <| \ \___| |_| || \_\ \\___ \
* |__| \____/__/\_ \__|\___ >____/__||___ /____ >
* \/ \/ \/ \/
*
* Copyright (c) 2006-2011 Karsten Schmidt
*
* 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.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
package spimedb.util.geom;
import spimedb.util.geom.Line3D.LineIntersection.Type;
import spimedb.util.math.MathUtils;
import java.util.ArrayList;
import java.util.List;
public class Line3D {
public static class LineIntersection {
public enum Type {
NON_INTERSECTING,
INTERSECTING
}
private final Type type;
private final Line3D line;
private final float[] coeff;
private LineIntersection(Type type) {
this(type, null, 0, 0);
}
private LineIntersection(Type type, Line3D line, float mua, float mub) {
this.type = type;
this.line = line;
this.coeff = new float[] {
mua, mub
};
}
public float[] getCoefficients() {
return coeff;
}
public float getLength() {
return line.getLength();
}
/**
* @return the pos
*/
public Line3D getLine() {
return line.copy();
}
/**
* @return the type
*/
public Type getType() {
return type;
}
public boolean isIntersectionInside() {
return type == Type.INTERSECTING && coeff[0] >= 0 && coeff[0] <= 1
&& coeff[1] >= 0 && coeff[1] <= 1;
}
public String toString() {
return "type: " + type + " line: " + line;
}
}
/**
* Splits the line between A and B into segments of the given length,
* starting at point A. The tweened points are added to the given result
* list. The last point added is B itself and hence it is likely that the
* last segment has a shorter length than the step length requested. The
* first point (A) can be omitted and not be added to the list if so
* desired.
*
* @param a
* start point
* @param b
* end point (always added to results)
* @param stepLength
* desired distance between points
* @param segments
* existing array list for results (or a new list, if null)
* @param addFirst
* false, if A is NOT to be added to results
* @return list of result vectors
*/
public static List<XYZ> splitIntoSegments(Vec3D a, Vec3D b,
float stepLength, List<XYZ> segments, boolean addFirst) {
if (segments == null) {
segments = new ArrayList();
}
if (addFirst) {
segments.add(a.copy());
}
float dist = a.distanceTo(b);
if (dist > stepLength) {
Vec3D pos = a.copy();
Vec3D step = b.sub(a).limit(stepLength);
while (dist > stepLength) {
pos.addSelf(step);
segments.add(pos.copy());
dist -= stepLength;
}
}
segments.add(b.copy());
return segments;
}
//@XmlElement
public Vec3D a, b;
public Line3D(float x1, float y1, float z1, float x2, float y2, float z2) {
this.a = new Vec3D(x1, y1, z1);
this.b = new Vec3D(x2, y2, z2);
}
public Line3D(XYZ a, XYZ b) {
this.a = new Vec3D(a);
this.b = new Vec3D(b);
}
public Line3D(Vec3D a, Vec3D b) {
this.a = a;
this.b = b;
}
/**
* Calculates the line segment that is the shortest route between this line
* and the given one. Also calculates the coefficients where the end points
* of this new line lie on the existing ones. If these coefficients are
* within the 0.0 .. 1.0 interval the endpoints of the intersection line are
* within the given line segments, if not then the intersection line is
* outside.
*
* <p>
* Code based on original by Paul Bourke:<br/>
* http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/
* </p>
*/
public LineIntersection closestLineTo(Line3D l) {
Vec3D p43 = l.a.sub(l.b);
if (p43.isZeroVector()) {
return new LineIntersection(Type.NON_INTERSECTING);
}
Vec3D p21 = b.sub(a);
if (p21.isZeroVector()) {
return new LineIntersection(Type.NON_INTERSECTING);
}
Vec3D p13 = a.sub(l.a);
double d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z;
double d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z;
double d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z;
double d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z;
double d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z;
double denom = d2121 * d4343 - d4321 * d4321;
if (Math.abs(denom) < MathUtils.EPS) {
return new LineIntersection(Type.NON_INTERSECTING);
}
double numer = d1343 * d4321 - d1321 * d4343;
float mua = (float) (numer / denom);
float mub = (float) ((d1343 + d4321 * mua) / d4343);
Vec3D pa = a.plus(p21.scaleSelf(mua));
Vec3D pb = l.a.plus(p43.scaleSelf(mub));
return new LineIntersection(Type.INTERSECTING, new Line3D(pa, pb), mua,
mub);
}
/**
* Computes the closest point on this line to the given one.
*
* @param p
* point to check against
* @return closest point on the line
*/
public Vec3D closestPointTo(roVec3D p) {
final Vec3D v = b.sub(a);
final float t = p.sub(a).dot(v) / v.magSquared();
// Check to see if t is beyond the extents of the line segment
if (t < 0.0f) {
return a.copy();
} else if (t > 1.0f) {
return b.copy();
}
// Return the point between 'a' and 'b'
return a.plus(v.scaleSelf(t));
}
public Line3D copy() {
return new Line3D(a.copy(), b.copy());
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (!(obj instanceof Line3D)) {
return false;
}
Line3D l = (Line3D) obj;
return (a.equals(l.a) || a.equals(l.b))
&& (b.equals(l.b) || b.equals(l.a));
}
/**
* Returns the line's axis-aligned bounding box.
*
* @return aabb
* @see AABB
*/
public BB getBounds() {
return BB.fromMinMax(a, b);
}
public Vec3D getDirection() {
return b.sub(a).normalize();
}
public float getLength() {
return a.distanceTo(b);
}
public float getLengthSquared() {
return a.distanceToSquared(b);
}
public Vec3D getMidPoint() {
return a.plus(b).scaleSelf(0.5f);
}
public XYZ getNormal() {
return b.cross(a);
}
public boolean hasEndPoint(Vec3D p) {
return a.equals(p) || b.equals(p);
}
/**
* Computes a hash code ignoring the directionality of the line.
*
* @return hash code
*
* @see Object#hashCode()
* @see #hashCodeWithDirection()
*/
public int hashCode() {
return a.hashCode() + b.hashCode();
}
/**
* Computes the hash code for this instance taking directionality into
* account. A->B will produce a different hash code than B->A. If
* directionality is not required or desired use the default
* {@link #hashCode()} method.
*
* @return hash code
*
* @see #hashCode()
*/
public int hashCodeWithDirection() {
long bits = 1L;
bits = 31L * bits + a.hashCode();
bits = 31L * bits + b.hashCode();
return (int) (bits ^ (bits >> 32));
}
public Line3D offsetAndGrowBy(float offset, float scale, Vec3D ref) {
Vec3D m = getMidPoint();
Vec3D d = getDirection();
Vec3D n = a.cross(d).normalize();
if (ref != null && m.sub(ref).dot(n) < 0) {
n.invert();
}
n.normalizeTo(offset);
a.addSelf(n);
b.addSelf(n);
d.scaleSelf(scale);
a.subSelf(d);
b.addSelf(d);
return this;
}
public Line3D scaleLength(float scale) {
float delta = (1 - scale) * 0.5f;
Vec3D newA = a.interpolateTo(b, delta);
b.interpolateToSelf(a, delta);
a.set(newA);
return this;
}
public Line3D set(roVec3D a, roVec3D b) {
this.a = a.copy();
this.b = b.copy();
return this;
}
public Line3D set(Vec3D a, Vec3D b) {
this.a = a;
this.b = b;
return this;
}
public List<XYZ> splitIntoSegments(List<XYZ> segments,
float stepLength, boolean addFirst) {
return splitIntoSegments(a, b, stepLength, segments, addFirst);
}
public Ray3D toRay3D() {
return new Ray3D(a.copy(), getDirection());
}
public String toString() {
return a.toString() + " -> " + b.toString();
}
}