/* * __ .__ .__ ._____. * _/ |_ _______ __|__| ____ | | |__\_ |__ ______ * \ __\/ _ \ \/ / |/ ___\| | | || __ \ / ___/ * | | ( <_> > <| \ \___| |_| || \_\ \\___ \ * |__| \____/__/\_ \__|\___ >____/__||___ /____ > * \/ \/ \/ \/ * * 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.Line2D.LineIntersection.Type; import java.util.ArrayList; import java.util.List; public class Line2D { public static class LineIntersection { public enum Type { COINCIDENT, COINCIDENT_NO_INTERSECT, PARALLEL, NON_INTERSECTING, INTERSECTING } private final Type type; private final ReadonlyVec2D pos; private final float[] coeff; public LineIntersection(Type type, ReadonlyVec2D pos) { this(type, pos, 0, 0); } public LineIntersection(Type type, ReadonlyVec2D pos, float ua, float ub) { this.type = type; this.pos = pos; this.coeff = new float[] { ua, ub }; } public float[] getCoefficients() { return coeff; } /** * Returns copy of intersection point. * * @return point */ public Vec2D getPos() { return pos != null ? pos.copy() : null; } /** * Returns intersection type enum. * * @return type */ public Type getType() { return type; } public String toString() { return "type: " + type + " pos: " + pos; } } /** * 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<Vec2D> splitIntoSegments(Vec2D a, Vec2D b, float stepLength, List<Vec2D> segments, boolean addFirst) { if (segments == null) { segments = new ArrayList<>(); } if (addFirst) { segments.add(a.copy()); } float dist = a.distanceTo(b); if (dist > stepLength) { Vec2D pos = a.copy(); Vec2D 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 Vec2D a, b; public Line2D(float x1, float y1, float x2, float y2) { this.a = new Vec2D(x1, y1); this.b = new Vec2D(x2, y2); } public Line2D(ReadonlyVec2D a, ReadonlyVec2D b) { this.a = a.copy(); this.b = b.copy(); } public Line2D(Vec2D a, Vec2D b) { this.a = a; this.b = b; } /** * Computes the dot product of these 2 vectors: line start -> point and the * perpendicular line direction. If the result is negative * * @param p * @return classifier float */ public float classifyPoint(ReadonlyVec2D p) { Vec2D normal = b.sub(a).perpendicular(); float d = p.sub(a).dot(normal); return Math.signum(d); } /** * Computes the closest point on this line to the point given. * * @param p * point to check against * @return closest point on the line */ public Vec2D closestPointTo(ReadonlyVec2D p) { final Vec2D 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)); } public Line2D copy() { return new Line2D(a.copy(), b.copy()); } public float distanceToPoint(ReadonlyVec2D p) { return closestPointTo(p).distanceTo(p); } public float distanceToPointSquared(ReadonlyVec2D p) { return closestPointTo(p).distanceToSquared(p); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (!(obj instanceof Line2D)) { return false; } Line2D l = (Line2D) obj; return (a.equals(l.a) || a.equals(l.b)) && (b.equals(l.b) || b.equals(l.a)); } public Circle getBoundingCircle() { return Circle.from2Points(a, b); } /** * Returns the line's bounding rect. * * @return bounding rect */ public Rect getBounds() { return new Rect(a, b); } public Vec2D getDirection() { return b.sub(a).normalize(); } public float getHeading() { return b.sub(a).heading(); } public float getLength() { return a.distanceTo(b); } public float getLengthSquared() { return a.distanceToSquared(b); } public Vec2D getMidPoint() { return a.add(b).scaleSelf(0.5f); } public Vec2D getNormal() { return b.sub(a).perpendicular(); } public boolean hasEndPoint(Vec2D 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)); } /** * Computes intersection between this and the given line. The returned value * is a {@link LineIntersection} instance and contains both the type of * intersection as well as the intersection points (if existing). The * intersection points are ALWAYS computed for the types INTERSECTING and * NON_INTERSECTING. In the latter case the points will lie outside the two * given line segments and constitute the intersections of the infinite * versions of each line. * * Based on: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ * * @param l * line to intersect with * @return intersection result */ public LineIntersection intersectLine(Line2D l) { LineIntersection isec = null; float denom = (l.b.y - l.a.y) * (b.x - a.x) - (l.b.x - l.a.x) * (b.y - a.y); float na = (l.b.x - l.a.x) * (a.y - l.a.y) - (l.b.y - l.a.y) * (a.x - l.a.x); float nb = (b.x - a.x) * (a.y - l.a.y) - (b.y - a.y) * (a.x - l.a.x); if (denom != 0.0) { float ua = na / denom; float ub = nb / denom; final Vec2D i = a.interpolateTo(b, ua); if (ua >= 0.0f && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) { isec = new LineIntersection(Type.INTERSECTING, i, ua, ub); } else { isec = new LineIntersection(Type.NON_INTERSECTING, i, ua, ub); } } else { if (na == 0.0 && nb == 0.0) { if (distanceToPoint(l.a) == 0.0) { isec = new LineIntersection(Type.COINCIDENT, null); } else { isec = new LineIntersection(Type.COINCIDENT_NO_INTERSECT, null); } } else { isec = new LineIntersection(Type.PARALLEL, null); } } return isec; } public Line2D offsetAndGrowBy(float offset, float scale, Vec2D ref) { Vec2D m = getMidPoint(); Vec2D d = getDirection(); Vec2D n = d.getPerpendicular(); 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 Line2D scaleLength(float scale) { float delta = (1 - scale) * 0.5f; Vec2D newA = a.interpolateTo(b, delta); b.interpolateToSelf(a, delta); a.set(newA); return this; } public Line2D set(Vec2D a, Vec2D b) { this.a = a; this.b = b; return this; } public List<Vec2D> splitIntoSegments(List<Vec2D> segments, float stepLength, boolean addFirst) { return splitIntoSegments(a, b, stepLength, segments, addFirst); } public Ray2D toRay2D() { return new Ray2D(a.copy(), getDirection()); } public String toString() { return a.toString() + " -> " + b.toString(); } }