DirectionalLine.java
package nl.tudelft.simulation.language.d2;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
/**
* A directional line with normal vector. Based on the BSPLine-example from the book Developing games in Java from David
* Brackeen.
* <p>
* Copyright (c) 2003-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
* project is distributed under a three-clause BSD-style license, which can be found at
* <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
* </p>
* @author <a href="mailto:royc@tbm.tudelft.nl">Roy Chin </a>
*/
public class DirectionalLine extends Line2D.Double
{
/** the default serialVersionUId. */
private static final long serialVersionUID = 1L;
/** different values for the side a point can be at w.r.t. the line. */
public enum Side
{
/** point at the back of the line. */
BACKSIDE(-1),
/** point collinear with the line. */
COLLINEAR(0),
/** point in front of the line. */
FRONTSIDE(1),
/** other line is spanning this line. */
SPANNING(2);
/** the value from DSOL-1 before enum was introduced. */
private final int value;
/**
* Create a side; store the value from DSOL-1 as well.
* @param value int; the value from DSOL-1 before enum was introduced
*/
Side(final int value)
{
this.value = value;
}
/**
* Returns the value from DSOL-1 before enum was introduced.
* @return int; the value from DSOL-1 before enum was introduced
*/
public int getValue()
{
return this.value;
}
}
/** the thickness of the line. */
private double lineThickness = 1;
/** x coordinate of the line normal. */
private double normalX;
/** y coordinate of the line normal. */
private double normalY;
/**
* Creates a new DirectionalLine based on the specified coordinates.
* @param x1 double; Coordinate x1
* @param y1 double; Coordinate y1
* @param x2 double; Coordinate x2
* @param y2 double; Coordinate y2
*/
public DirectionalLine(final double x1, final double y1, final double x2, final double y2)
{
this.setLine(x1, y1, x2, y2);
}
/**
* Creates a new DirectionalLine based on the specified (float) coordinates.
* @param x1 float; Coordinate x1
* @param y1 float; Coordinate y1
* @param x2 float; Coordinate x2
* @param y2 float; Coordinate y2
*/
public DirectionalLine(final float x1, final float y1, final float x2, final float y2)
{
this.setLine(x1, y1, x2, y2);
}
/**
* Calculates the normal to this line. The normal of (a, b) is (-b, a).
*/
public void calcNormal()
{
this.normalX = this.y1 - this.y2;
this.normalY = this.x2 - this.x1;
}
/**
* Normalizes the normal of this line (make the normal's length 1).
*/
public void normalize()
{
double length = Math.sqrt(this.normalX * this.normalX + this.normalY * this.normalY);
this.normalX /= length;
this.normalY /= length;
}
/**
* Set the line using floats.
* @param x1 float; x1 coordinate
* @param y1 float; y1 coordinate
* @param x2 float; x2 coordinate
* @param y2 float; y2 coordinate
*/
public void setLine(final float x1, final float y1, final float x2, final float y2)
{
super.setLine(x1, y1, x2, y2);
this.calcNormal();
}
@Override
public void setLine(final double x1, final double y1, final double x2, final double y2)
{
super.setLine(x1, y1, x2, y2);
this.calcNormal();
}
/**
* Flips this line so that the end points are reversed (in other words, (x1,y1) becomes (x2,y2) and vice versa) and the
* normal is changed to point the opposite direction.
*/
public void flip()
{
double tx = this.x1;
double ty = this.y1;
this.x1 = this.x2;
this.y1 = this.y2;
this.x2 = tx;
this.y2 = ty;
this.normalX = -this.normalX;
this.normalY = -this.normalY;
}
/**
* Returns true if the endpoints of this line match the endpoints of the specified line. Ignores normal and height values.
* @param line DirectionalLine; another line
* @return true if this line's coordinates are equal to the other line's coordinates
*/
public boolean equalsCoordinates(final DirectionalLine line)
{
return (this.x1 == line.x1 && this.x2 == line.x2 && this.y1 == line.y1 && this.y2 == line.y2);
}
/**
* Returns true if the endpoints of this line match the endpoints of the specified line, ignoring endpoint order (if the
* first point of this line is equal to the second point of the specified line, and vice versa, returns true). Ignores
* normal and height values.
* @param line DirectionalLine; another line
* @return true if coordinates match independent of the order
*/
public boolean equalsCoordinatesIgnoreOrder(final DirectionalLine line)
{
return equalsCoordinates(line)
|| ((this.x1 == line.x2 && this.x2 == line.x1 && this.y1 == line.y2 && this.y2 == line.y1));
}
@Override
public String toString()
{
return "(" + this.x1 + "," + this.y1 + ")->(" + this.x2 + "," + this.y2 + ")";
}
/**
* Gets the side of this line the specified point is on. This method treats the line as 1-unit thick, so points within this
* 1-unit border are considered collinear. For this to work correctly, the normal of this line must be normalized, either by
* setting this line to a polygon or by calling normalize(). Returns either FRONTSIDE, BACKSIDE, or COLLINEAR.
* @param x double; coordinate x
* @param y double; coordinate y
* @return the side
*/
public Side getSideThick(final double x, final double y)
{
double normalX2 = this.normalX * this.lineThickness;
double normalY2 = this.normalY * this.lineThickness;
Side frontSide = getSideThin(x - normalX2 / 2, y - normalY2 / 2);
if (frontSide.equals(Side.FRONTSIDE))
{
return Side.FRONTSIDE;
}
else if (frontSide.equals(Side.BACKSIDE))
{
Side backSide = getSideThin(x + normalX2 / 2, y + normalY2 / 2);
if (backSide.equals(Side.BACKSIDE))
{
return Side.BACKSIDE;
}
}
return Side.COLLINEAR;
}
/**
* Gets the side of this line the specified point is on. Because of doubling point inaccuracy, a collinear line will be
* rare. For this to work correctly, the normal of this line must be normalized, either by setting this line to a polygon or
* by calling normalize(). Returns either FRONTSIDE, BACKSIDE, or COLLINEAR.
* @param x double; coordinate x
* @param y double; coordinate y
* @return the side
*/
public Side getSideThin(final double x, final double y)
{
// dot product between vector to the point and the normal
double side = (x - this.x1) * this.normalX + (y - this.y1) * this.normalY;
if (side < 0)
{
return Side.BACKSIDE;
}
else if (side > 0)
{
return Side.FRONTSIDE;
}
else
{
return Side.COLLINEAR;
}
}
/**
* Gets the side of this line that the specified line segment is on. Returns either FRONT, BACK, COLINEAR, or SPANNING.
* @param line Line2D.Double; line segment
* @return the side
*/
public Side getSide(final Line2D.Double line)
{
if (this.x1 == line.x1 && this.x2 == line.x2 && this.y1 == line.y1 && this.y2 == line.y2)
{
return Side.COLLINEAR;
}
Side p1Side = getSideThick(line.x1, line.y1);
Side p2Side = getSideThick(line.x2, line.y2);
if (p1Side == p2Side)
{
return p1Side;
}
else if (p1Side == Side.COLLINEAR)
{
return p2Side;
}
else if (p2Side == Side.COLLINEAR)
{
return p1Side;
}
else
{
return Side.SPANNING;
}
}
/**
* Returns the fraction of intersection along this line. Returns a value from 0 to 1 if the segments intersect. For example,
* a return value of 0 means the intersection occurs at point (x1, y1), 1 means the intersection occurs at point (x2, y2),
* and .5 mean the intersection occurs halfway between the two endpoints of this line. Returns -1 if the lines are parallel.
* @param line Line2D.Double; a line
* @return the intersection
*/
public double getIntersection(final Line2D.Double line)
{
// The intersection point I, of two vectors, A1->A2 and
// B1->B2, is:
// I = A1 + Ua * (A2 - A1)
// I = B1 + Ub * (B2 - B1)
//
// Solving for Ua gives us the following formula.
// Ua is returned.
double denominator = (line.y2 - line.y1) * (this.x2 - this.x1) - (line.x2 - line.x1) * (this.y2 - this.y1);
// check if the two lines are parallel
if (denominator == 0)
{
return -1;
}
double numerator = (line.x2 - line.x1) * (this.y1 - line.y1) - (line.y2 - line.y1) * (this.x1 - line.x1);
return numerator / denominator;
}
/**
* Returns the intersection point of this line with the specified line.
* @param line Line2D.Double; a line
* @return intersection point
*/
public Point2D.Double getIntersectionPoint(final Line2D.Double line)
{
double fraction = getIntersection(line);
Point2D.Double intersection = new Point2D.Double();
intersection.setLocation(this.x1 + fraction * (this.x2 - this.x1), this.y1 + fraction * (this.y2 - this.y1));
return intersection;
}
/**
* Gets the thickness of the line.
* @return returns the lineThickness
*/
public double getLineThickness()
{
return this.lineThickness;
}
/**
* Sets the thickness of the line.
* @param lineThickness double; the lineThickness to set
*/
public void setLineThickness(final double lineThickness)
{
this.lineThickness = lineThickness;
}
/**
* @return returns the normalX
*/
public double getNormalx()
{
return this.normalX;
}
/**
* @return returns the normalY
*/
public double getNormaly()
{
return this.normalY;
}
}