/* * __ .__ .__ ._____. * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ * |__| \____/__/\_ \__|\___ >____/__||___ /____ > * \/ \/ \/ \/ * * 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 toxi.geom; import java.util.ArrayList; import java.util.List; import toxi.geom.Line3D.LineIntersection.Type; import toxi.math.MathUtils; /** * * @author tux */ public class Line3D { /** * */ public static class LineIntersection { /** * */ public static 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 }; } /** * * @return */ public float[] getCoefficients() { return coeff; } /** * * @return */ public float getLength() { return line.getLength(); } /** * @return the pos */ public Line3D getLine() { return line.copy(); } /** * @return the type */ public Type getType() { return type; } /** * * @return */ public boolean isIntersectionInside() { return type == Type.INTERSECTING && coeff[0] >= 0 && coeff[0] <= 1 && coeff[1] >= 0 && coeff[1] <= 1; } /** * * @return */ @Override 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 final List splitIntoSegments(Vec3D a, Vec3D b, float stepLength, List 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; } public Vec3D a, /** * */ /** * */ b; /** * * @param x1 * @param y1 * @param z1 * @param x2 * @param y2 * @param z2 */ 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); } /** * * @param a * @param b */ public Line3D(ReadonlyVec3D a, ReadonlyVec3D b) { this.a = a.copy(); this.b = b.copy(); } /** * * @param a * @param 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. * *

* Code based on original by Paul Bourke:
* http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline3d/ *

* * @param l * @return */ 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 (MathUtils.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.add(p21.scaleSelf(mua)); Vec3D pb = l.a.add(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(ReadonlyVec3D 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.add(v.scaleSelf(t)); } /** * * @return */ public Line3D copy() { return new Line3D(a.copy(), b.copy()); } /** * * @param obj * @return */ @Override public boolean equals(Object obj) { if (obj instanceof Line3D) { Line3D l = (Line3D) obj; return (a.equals(l.a) || a.equals(l.b)) && (b.equals(l.b) || b.equals(l.a)); } return false; } /** * * @return */ @Override public int hashCode() { return a.hashCode() + b.hashCode(); } /** * Returns the line's axis-aligned bounding box. * * @return aabb * @see toxi.geom.AABB */ public AABB getBounds() { return AABB.fromMinMax(a, b); } /** * * @return */ public Vec3D getDirection() { return b.sub(a).normalize(); } /** * * @return */ public float getLength() { return a.distanceTo(b); } /** * * @return */ public float getLengthSquared() { return a.distanceToSquared(b); } /** * * @return */ public Vec3D getMidPoint() { return a.add(b).scaleSelf(0.5f); } /** * * @return */ public Vec3D getNormal() { return b.cross(a); } /** * * @param p * @return */ public boolean hasEndPoint(Vec3D p) { return a.equals(p) || b.equals(p); } /** * 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() { int hash = 7; hash = 53 * hash + a.hashCode(); hash = 53 * hash + b.hashCode(); return hash; } /** * * @param offset * @param scale * @param ref * @return */ 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; } /** * * @param scale * @return */ 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; } /** * * @param a * @param b * @return */ public Line3D set(ReadonlyVec3D a, ReadonlyVec3D b) { this.a = a.copy(); this.b = b.copy(); return this; } /** * * @param a * @param b * @return */ public Line3D set(Vec3D a, Vec3D b) { this.a = a; this.b = b; return this; } /** * * @param segments * @param stepLength * @param addFirst * @return */ public List splitIntoSegments(List segments, float stepLength, boolean addFirst) { return splitIntoSegments(a, b, stepLength, segments, addFirst); } /** * * @return */ public Ray3D toRay3D() { return new Ray3D(a.copy(), getDirection()); } /** * * @return */ @Override public String toString() { return a.toString() + " -> " + b.toString(); } }