All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dyn4j.geometry.Segment Maven / Gradle / Ivy

/*
 * Copyright (c) 2010-2015 William Bittle  http://www.dyn4j.org/
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted 
 * provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this list of conditions 
 *     and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions 
 *     and the following disclaimer in the documentation and/or other materials provided with the 
 *     distribution.
 *   * Neither the name of dyn4j nor the names of its contributors may be used to endorse or 
 *     promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.dyn4j.geometry;

import java.util.Iterator;

import org.dyn4j.DataContainer;
import org.dyn4j.Epsilon;
import org.dyn4j.resources.Messages;

/**
 * Implementation of a Line Segment {@link Convex} {@link Shape}.
 * 

* This class represents a line segment that is infinitely thin. * @author William Bittle * @version 3.2.0 * @since 1.0.0 */ public class Segment extends AbstractShape implements Convex, Wound, Shape, Transformable, DataContainer { /** The segment vertices */ final Vector2[] vertices; /** The segment normals */ final Vector2[] normals; /** The segment length */ final double length; /** * Validated constructor. *

* Creates a new segment using the given points. The center will be the * average of the points. * @param valid always true or this constructor would not be called * @param point1 the first point * @param point2 the second point */ private Segment(boolean valid, Vector2[] vertices, Vector2 segment, double length) { super(Geometry.getAverageCenter(vertices), length * 0.5); // assign the verices this.vertices = vertices; // create the normals this.normals = new Vector2[2]; this.normals[0] = segment.right(); this.normals[0].normalize(); this.normals[1] = segment.getNegative(); this.normals[1].normalize(); // compute the length this.length = length; } /** * Full constructor. *

* Creates a new segment using the given points. The center will be the * average of the points. *

* A segment's points cannot be null or the same point. * @param point1 the first point * @param point2 the second point * @throws NullPointerException if point1 or point2 is null * @throws IllegalArgumentException if point1 == point2 */ public Segment(Vector2 point1, Vector2 point2) { this(validate(point1, point2), new Vector2[] { point1, point2 }, point1.to(point2), point1.distance(point2)); } /** * Validates the constructor input returning true if valid or throwing an exception if invalid. * @param point1 the first point * @param point2 the second point * @return boolean true * @throws NullPointerException if point1 or point2 is null * @throws IllegalArgumentException if point1 == point2 */ private static final boolean validate(Vector2 point1, Vector2 point2) { // make sure either point is not null if (point1 == null) throw new NullPointerException(Messages.getString("geometry.segment.nullPoint1")); if (point2 == null) throw new NullPointerException(Messages.getString("geometry.segment.nullPoint2")); // make sure the two points are not coincident if (point1.equals(point2)) { throw new IllegalArgumentException(Messages.getString("geometry.segment.samePoint")); } return true; } /* (non-Javadoc) * @see org.dyn4j.geometry.Wound#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Segment[").append(super.toString()) .append("|Length=").append(this.length) .append("]"); return sb.toString(); } /* (non-Javadoc) * @see org.dyn4j.geometry.Wound#getVertices() */ @Override public Vector2[] getVertices() { return this.vertices; } /* (non-Javadoc) * @see org.dyn4j.geometry.Wound#getNormals() */ @Override public Vector2[] getNormals() { return this.normals; } /* (non-Javadoc) * @see org.dyn4j.geometry.Wound#getVertexIterator() */ @Override public Iterator getVertexIterator() { return new WoundIterator(this.vertices); } /* (non-Javadoc) * @see org.dyn4j.geometry.Wound#getNormalIterator() */ @Override public Iterator getNormalIterator() { return new WoundIterator(this.normals); } /* (non-Javadoc) * @see org.dyn4j.geometry.Shape#getRadius(org.dyn4j.geometry.Vector2) */ @Override public double getRadius(Vector2 center) { return Geometry.getRotationRadius(center, this.vertices); } /** * Returns point1 in local coordinates. * @return {@link Vector2} */ public Vector2 getPoint1() { return this.vertices[0]; } /** * Returns point2 in local coordinates. * @return {@link Vector2} */ public Vector2 getPoint2() { return this.vertices[1]; } /** * Returns the length of the line {@link Segment}. * @return double */ public double getLength() { return this.length; } /** * Determines where the point is relative to the given line. *

Set L = linePoint2 - linePoint1 * Set P = point - linePoint1 * location = L.cross(P)

* Returns 0 if the point lies on the line created from the line segment.
* Assuming a right handed coordinate system:
* Returns < 0 if the point lies on the right side of the line
* Returns > 0 if the point lies on the left side of the line *

* Assumes all points are in world space. * @param point the point * @param linePoint1 the first point of the line * @param linePoint2 the second point of the line * @throws NullPointerException if point, linePoint1, or linePoint2 is null * @return double */ public static double getLocation(Vector2 point, Vector2 linePoint1, Vector2 linePoint2) { return (linePoint2.x - linePoint1.x) * (point.y - linePoint1.y) - (point.x - linePoint1.x) * (linePoint2.y - linePoint1.y); } /** * Returns the point on the given line closest to the given point. *

* Project the point onto the line: *

Vline = P1 - P0 * Vpoint = P0 - P * Pclosest = Vpoint.project(Vline)

* Assumes all points are in world space. * @see Vector2#project(Vector2) * @param point the point * @param linePoint1 the first point of the line * @param linePoint2 the second point of the line * @throws NullPointerException if point, linePoint1, or linePoint2 is null * @return {@link Vector2} */ public static Vector2 getPointOnLineClosestToPoint(Vector2 point, Vector2 linePoint1, Vector2 linePoint2) { // create a vector from the point to the first line point Vector2 p1ToP = point.difference(linePoint1); // create a vector representing the line Vector2 line = linePoint2.difference(linePoint1); // get the length squared of the line double ab2 = line.dot(line); // check ab2 for zero (linePoint1 == linePoint2) if (ab2 <= Epsilon.E) return linePoint1.copy(); // get the projection of AP on AB double ap_ab = p1ToP.dot(line); // get the position from the first line point to the projection double t = ap_ab / ab2; // create the point on the line return line.multiply(t).add(linePoint1); } /** * Returns the point on the line that this {@link Segment} * defines closest to the given point. *

* This method works in this {@link Segment}'s local space. * @param point the local space point * @return {@link Vector2} * @throws NullPointerException if the given point is null * @since 3.1.5 * @see #getPointOnLineClosestToPoint(Vector2, Vector2, Vector2) */ public Vector2 getPointOnLineClosestToPoint(Vector2 point) { return Segment.getPointOnLineClosestToPoint(point, this.vertices[0], this.vertices[1]); } /** * Returns the point on the given line segment closest to the given point. *

* If the point closest to the given point is on the line created by the * given line segment, but is not on the line segment then either of the segments * end points will be returned. *

* Assumes all points are in world space. * @see Segment#getPointOnLineClosestToPoint(Vector2, Vector2, Vector2) * @param point the point * @param linePoint1 the first point of the line * @param linePoint2 the second point of the line * @return {@link Vector2} * @throws NullPointerException if point, linePoint1, or linePoint2 is null */ public static Vector2 getPointOnSegmentClosestToPoint(Vector2 point, Vector2 linePoint1, Vector2 linePoint2) { // create a vector from the point to the first line point Vector2 p1ToP = point.difference(linePoint1); // create a vector representing the line Vector2 line = linePoint2.difference(linePoint1); // get the length squared of the line double ab2 = line.dot(line); // get the projection of AP on AB double ap_ab = p1ToP.dot(line); // check ab2 for zero (linePoint1 == linePoint2) if (ab2 <= Epsilon.E) return linePoint1.copy(); // get the position from the first line point to the projection double t = ap_ab / ab2; // make sure t is in between 0.0 and 1.0 t = Interval.clamp(t, 0.0, 1.0); // create the point on the line return line.multiply(t).add(linePoint1); } /** * Returns the point on this {@link Segment} closest to the given point. *

* This method works in this {@link Segment}'s local space. * @param point the local space point * @return {@link Vector2} * @throws NullPointerException if the given point is null * @since 3.1.5 * @see #getPointOnSegmentClosestToPoint(Vector2, Vector2, Vector2) */ public Vector2 getPointOnSegmentClosestToPoint(Vector2 point) { return Segment.getPointOnSegmentClosestToPoint(point, this.vertices[0], this.vertices[1]); } /** * Returns the intersection point of the two lines or null if they are parallel or coincident. *

* If we let: *

A = Ap2 - Ap1 * B = Bp2 - Bp1

* we can create two parametric equations: *

Q = Ap1 + taA * Q = Bp1 + tbB

* Where Q is the intersection point: *

Ap1 + taA = Bp1 + tbB

* We can solve for tb by applying the cross product with A on both sides: *

(Ap1 + taA) x A = (Bp1 + tbB) x A * Ap1 x A = Bp1 x A + tbB x A * (Ap1 - Bp1) x A = tbB x A * tb = ((Ap1 - Bp1) x A) / (B x A)

* If B x A == 0 then the lines are parallel. If both the top and bottom are zero * then the lines are coincident. *

* If the lines are parallel or coincident, null is returned. * @param ap1 the first point of the first line * @param ap2 the second point of the first line * @param bp1 the first point of the second line * @param bp2 the second point of the second line * @return Vector2 the intersection point; null if the lines are parallel or coincident * @see #getSegmentIntersection(Vector2, Vector2, Vector2, Vector2) * @throws NullPointerException if ap1, ap2, bp1 or bp2 is null * @since 3.1.1 */ public static Vector2 getLineIntersection(Vector2 ap1, Vector2 ap2, Vector2 bp1, Vector2 bp2) { Vector2 A = ap1.to(ap2); Vector2 B = bp1.to(bp2); // compute the bottom double BxA = B.cross(A); if (Math.abs(BxA) <= Epsilon.E) { // the lines are parallel and don't intersect return null; } // compute the top double ambxA = ap1.difference(bp1).cross(A); if (Math.abs(ambxA) <= Epsilon.E) { // the lines are coincident return null; } // compute tb double tb = ambxA / BxA; // compute the intersection point return B.product(tb).add(bp1); } /** * Returns the line intersection of the given {@link Segment} and this {@link Segment}. *

* This method treats this segment and the given segment as defining lines rather than segments. *

* This method assumes that both this and the given segment are in the same space (either * local or world space). *

* If the lines are parallel or coincident, null is returned. * @param segment the other segment * @return {@link Vector2} * @throws NullPointerException if the given segment is null * @since 3.1.5 * @see #getLineIntersection(Vector2, Vector2, Vector2, Vector2) */ public Vector2 getLineIntersection(Segment segment) { return Segment.getLineIntersection(this.vertices[0], this.vertices[1], segment.vertices[0], segment.vertices[1]); } /** * Returns the intersection point of the two line segments or null if they are parallel, coincident * or don't intersect. *

* If we let: *

A = Ap2 - Ap1 * B = Bp2 - Bp1

* we can create two parametric equations: *

Q = Ap1 + taA * Q = Bp1 + tbB

* Where Q is the intersection point: *

Ap1 + taA = Bp1 + tbB

* We can solve for tb by applying the cross product with A on both sides: *

(Ap1 + taA) x A = (Bp1 + tbB) x A * Ap1 x A = Bp1 x A + tbB x A * (Ap1 - Bp1) x A = tbB x A * tb = ((Ap1 - Bp1) x A) / (B x A)

* If B x A == 0 then the segments are parallel. If the top == 0 then they don't intersect. If both the * top and bottom are zero then the segments are coincident. *

* If tb or ta less than zero or greater than 1 then the segments do not intersect. *

* If the segments do not intersect, are parallel, or are coincident, null is returned. * @param ap1 the first point of the first line segment * @param ap2 the second point of the first line segment * @param bp1 the first point of the second line segment * @param bp2 the second point of the second line segment * @return Vector2 the intersection point; null if the line segments don't intersect, are parallel, or are coincident * @see #getLineIntersection(Vector2, Vector2, Vector2, Vector2) * @throws NullPointerException if ap1, ap2, bp1, or bp2 is null * @since 3.1.1 */ public static Vector2 getSegmentIntersection(Vector2 ap1, Vector2 ap2, Vector2 bp1, Vector2 bp2) { Vector2 A = ap1.to(ap2); Vector2 B = bp1.to(bp2); // compute the bottom double BxA = B.cross(A); if (Math.abs(BxA) <= Epsilon.E) { // the line segments are parallel and don't intersect return null; } // compute the top double ambxA = ap1.difference(bp1).cross(A); if (Math.abs(ambxA) <= Epsilon.E) { // the line segments are coincident return null; } // compute tb double tb = ambxA / BxA; if (tb < 0.0 || tb > 1.0) { // no intersection return null; } // compute the intersection point Vector2 ip = B.product(tb).add(bp1); // since both are segments we need to verify that // ta is also valid. // compute ta double ta = ip.difference(ap1).dot(A) / A.dot(A); if (ta < 0.0 || ta > 1.0) { // no intersection return null; } return ip; } /** * Returns the intersection of the given {@link Segment} and this {@link Segment}. *

* This method assumes that both this and the given segment are in the same space (either * local or world space). *

* If the segments do not intersect, are parallel, or are coincident, null is returned. * @param segment the other segment * @return {@link Vector2} * @throws NullPointerException if the given segment is null * @since 3.1.5 * @see #getSegmentIntersection(Vector2, Vector2, Vector2, Vector2) */ public Vector2 getSegmentIntersection(Segment segment) { return Segment.getSegmentIntersection(this.vertices[0], this.vertices[1], segment.vertices[0], segment.vertices[1]); } /** * Returns the farthest feature on the given segment. *

* This will always return the segment itself, but must return it with the correct winding * and the correct maximum. * @param v1 the first segment vertex * @param v2 the second segment vertex * @param vector the direction * @param transform the local to world space {@link Transform} of this {@link Convex} {@link Shape} * @return {@link EdgeFeature} * @throws NullPointerException if v1, v2, vector, or transform is null * @since 3.1.5 */ public static final EdgeFeature getFarthestFeature(Vector2 v1, Vector2 v2, Vector2 vector, Transform transform) { // the farthest feature for a line is always the line itself Vector2 max = null; // get the vertices Vector2 p1 = transform.getTransformed(v1); Vector2 p2 = transform.getTransformed(v2); // project them onto the vector double dot1 = vector.dot(p1); double dot2 = vector.dot(p2); // find the greatest projection int index = 0; if (dot1 >= dot2) { max = p1; index = 0; } else { max = p2; index = 1; } // return the points of the segment in the // opposite direction as the other shape PointFeature vp1 = new PointFeature(p1, 0); PointFeature vp2 = new PointFeature(p2, 1); PointFeature vm = new PointFeature(max, index); // make sure the edge is the right winding if (p1.to(p2).right().dot(vector) > 0) { return new EdgeFeature(vp2, vp1, vm, p2.to(p1), 0); } else { return new EdgeFeature(vp1, vp2, vm, p1.to(p2), 0); } } /** * Returns the farthest point on the given segment. * @param v1 the first point of the segment * @param v2 the second point of the segment * @param vector the direction * @param transform the local to world space {@link Transform} of this {@link Convex} {@link Shape} * @return {@link Vector2} * @throws NullPointerException if v1, v2, vector, or transform is null * @since 3.1.5 */ public static final Vector2 getFarthestPoint(Vector2 v1, Vector2 v2, Vector2 vector, Transform transform) { // get the vertices and the center Vector2 p1 = transform.getTransformed(v1); Vector2 p2 = transform.getTransformed(v2); // project them onto the vector double dot1 = vector.dot(p1); double dot2 = vector.dot(p2); // find the greatest projection if (dot1 >= dot2) { return p1; } else { return p2; } } /* (non-Javadoc) * @see org.dyn4j.geometry.Convex#getAxes(java.util.List, org.dyn4j.geometry.Transform) */ @Override public Vector2[] getAxes(Vector2[] foci, Transform transform) { // get the number of foci int size = foci != null ? foci.length : 0; // create an array to hold the axes Vector2[] axes = new Vector2[2 + size]; int n = 0; // get the vertices Vector2 p1 = transform.getTransformed(this.vertices[0]); Vector2 p2 = transform.getTransformed(this.vertices[1]); // use both the edge and its normal axes[n++] = transform.getTransformedR(this.normals[1]); axes[n++] = transform.getTransformedR(this.normals[0].getLeftHandOrthogonalVector()); Vector2 axis; // add the voronoi region axes if point is supplied for (int i = 0; i < size; i++) { // get the focal point Vector2 f = foci[i]; // find the closest point if (p1.distanceSquared(f) < p2.distanceSquared(f)) { axis = p1.to(f); } else { axis = p2.to(f); } // normalize the axis axis.normalize(); // add the axis to the array axes[n++] = axis; } // return all the axes return axes; } /** * {@inheritDoc} *

* Not applicable to this shape. Always returns null. * @return null */ @Override public Vector2[] getFoci(Transform transform) { return null; } /** * {@inheritDoc} *

* Should almost always return false since this shape represents an infinitely * thin line segment. Use the {@link #contains(Vector2, Transform, double)} * method instead for better, though technically inaccurate, results. */ @Override public boolean contains(Vector2 point, Transform transform) { // put the point in local coordinates Vector2 p = transform.getInverseTransformed(point); // create a reference to the end points Vector2 p1 = this.vertices[0]; Vector2 p2 = this.vertices[1]; // get the location of the given point relative to this segment double value = Segment.getLocation(p, p1, p2); // see if the point is on the line created by this line segment if (Math.abs(value) <= Epsilon.E) { double distSqrd = p1.distanceSquared(p2); if (p.distanceSquared(p1) <= distSqrd && p.distanceSquared(p2) <= distSqrd) { // if the distance to the point from both points is less than or equal // to the length of the segment squared then we know its on the line segment return true; } // if the point is further away from either point than the length of the // segment then its not on the segment return false; } return false; } /** * Returns true if the given point is inside this {@link Shape}. *

* If the given point lies on an edge the point is considered * to be inside the {@link Shape}. *

* The given point is assumed to be in world space. *

* If the radius is greater than zero then the point is tested to be * within the shape expanded radially by the radius. * @param point world space point * @param transform {@link Transform} for this {@link Shape} * @param radius the expansion radius; in the range [0, ∞] * @return boolean */ public boolean contains(Vector2 point, Transform transform, double radius) { // if the radius is zero or less then perform the normal procedure if (radius <= 0) { return contains(point, transform); } else { // put the point in local coordinates Vector2 p = transform.getInverseTransformed(point); // otherwise act like the segment is two circles and a rectangle if (this.vertices[0].distanceSquared(p) <= radius * radius) { return true; } else if (this.vertices[1].distanceSquared(p) <= radius * radius) { return true; } else { // see if the point is in the rectangle portion Vector2 l = this.vertices[0].to(this.vertices[1]); Vector2 p1 = this.vertices[0].to(p); Vector2 p2 = this.vertices[1].to(p); if (l.dot(p1) > 0 && -l.dot(p2) > 0) { double dist = p1.project(l.getRightHandOrthogonalVector()).getMagnitudeSquared(); if (dist <= radius * radius) { return true; } } } } return false; } /* (non-Javadoc) * @see org.dyn4j.geometry.Shape#project(org.dyn4j.geometry.Vector2, org.dyn4j.geometry.Transform) */ @Override public Interval project(Vector2 vector, Transform transform) { double v = 0.0; // get the vertices Vector2 p1 = transform.getTransformed(this.vertices[0]); Vector2 p2 = transform.getTransformed(this.vertices[1]); // project the first double min = vector.dot(p1); double max = min; // project the second v = vector.dot(p2); if (v < min) { min = v; } else if (v > max) { max = v; } return new Interval(min, max); } /* (non-Javadoc) * @see org.dyn4j.geometry.Convex#getFurthestPoint(org.dyn4j.geometry.Vector, org.dyn4j.geometry.Transform) */ @Override public Vector2 getFarthestPoint(Vector2 vector, Transform transform) { return Segment.getFarthestPoint(this.vertices[0], this.vertices[1], vector, transform); } /** * Returns the feature farthest in the direction of n. *

* For a {@link Segment} it's always the {@link Segment} itself. * @param vector the direction * @param transform the local to world space {@link Transform} of this {@link Convex} {@link Shape} * @return {@link EdgeFeature} */ @Override public EdgeFeature getFarthestFeature(Vector2 vector, Transform transform) { return Segment.getFarthestFeature(this.vertices[0], this.vertices[1], vector, transform); } /* (non-Javadoc) * @see org.dyn4j.geometry.AbstractShape#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { super.rotate(theta, x, y); this.vertices[0].rotate(theta, x, y); this.vertices[1].rotate(theta, x, y); this.normals[0].rotate(theta, x, y); this.normals[1].rotate(theta, x, y); } /* (non-Javadoc) * @see org.dyn4j.geometry.AbstractShape#translate(double, double) */ @Override public void translate(double x, double y) { super.translate(x, y); this.vertices[0].add(x, y); this.vertices[1].add(x, y); } /** * Creates a {@link Mass} object using the geometric properties of * this {@link Segment} and the given density. *

m = d * length * I = l2 * m / 12

* @param density the density in kg/m2 * @return {@link Mass} the {@link Mass} of this {@link Segment} */ @Override public Mass createMass(double density) { double length = this.length; // compute the mass double mass = density * length; // compute the inertia tensor double inertia = length * length * mass / 12.0; // since we know that a line segment has only two points we can // feel safe using the averaging method for the centroid return new Mass(this.center, mass, inertia); } /** * {@inheritDoc} *

* Be aware that this method could produce an infinitely thin * AABB if this segment is aligned to either the x or y-axis. */ @Override public AABB createAABB(Transform transform) { double vx = 0.0; double vy = 0.0; // get the first point Vector2 p = transform.getTransformed(this.vertices[0]); // project the point onto the vector double minX = Vector2.X_AXIS.dot(p); double maxX = minX; double minY = Vector2.Y_AXIS.dot(p); double maxY = minY; // get the other point p = transform.getTransformed(this.vertices[1]); // project it onto the vector vx = Vector2.X_AXIS.dot(p); vy = Vector2.Y_AXIS.dot(p); // compare the x values minX = Math.min(minX, vx); maxX = Math.max(maxX, vx); minY = Math.min(minY, vy); maxY = Math.max(maxY, vy); // create the aabb return new AABB(minX, minY, maxX, maxY); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy