View Javadoc
1   package nl.tudelft.simulation.language.d2;
2   
3   import java.awt.geom.Line2D;
4   import java.awt.geom.Point2D;
5   
6   /**
7    * A directional line with normal vector. Based on the BSPLine-example from the book Developing games in Java from David
8    * Brackeen.
9    * <p>
10   * Copyright (c) 2003-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
11   * for project information <a href="https://simulation.tudelft.nl/dsol/manual/" target="_blank">DSOL Manual</a>. The DSOL
12   * project is distributed under a three-clause BSD-style license, which can be found at
13   * <a href="https://simulation.tudelft.nl/dsol/docs/latest/license.html" target="_blank">DSOL License</a>.
14   * </p>
15   * @author <a href="mailto:royc@tbm.tudelft.nl">Roy Chin </a>
16   */
17  public class DirectionalLine extends Line2D.Double
18  {
19      /** the default serialVersionUID. */
20      private static final long serialVersionUID = 1L;
21  
22      /** different values for the side a point can be at w.r.t. the line. */
23      public enum Side
24      {
25          /** point at the back of the line. */
26          BACKSIDE(-1),
27  
28          /** point collinear with the line. */
29          COLLINEAR(0),
30  
31          /** point in front of the line. */
32          FRONTSIDE(1),
33  
34          /** other line is spanning this line. */
35          SPANNING(2);
36  
37          /** the value from DSOL-1 before enum was introduced. */
38          private final int value;
39  
40          /**
41           * Create a side; store the value from DSOL-1 as well.
42           * @param value the value from DSOL-1 before enum was introduced
43           */
44          Side(final int value)
45          {
46              this.value = value;
47          }
48  
49          /**
50           * Returns the value from DSOL-1 before enum was introduced.
51           * @return the value from DSOL-1 before enum was introduced
52           */
53          public int getValue()
54          {
55              return this.value;
56          }
57      }
58  
59      /** the thickness of the line. */
60      private double lineThickness = 1;
61  
62      /** x coordinate of the line normal. */
63      private double normalX;
64  
65      /** y coordinate of the line normal. */
66      private double normalY;
67  
68      /**
69       * Creates a new DirectionalLine based on the specified coordinates.
70       * @param x1 Coordinate x1
71       * @param y1 Coordinate y1
72       * @param x2 Coordinate x2
73       * @param y2 Coordinate y2
74       */
75      public DirectionalLine(final double x1, final double y1, final double x2, final double y2)
76      {
77          this.setLine(x1, y1, x2, y2);
78      }
79  
80      /**
81       * Creates a new DirectionalLine based on the specified (float) coordinates.
82       * @param x1 Coordinate x1
83       * @param y1 Coordinate y1
84       * @param x2 Coordinate x2
85       * @param y2 Coordinate y2
86       */
87      public DirectionalLine(final float x1, final float y1, final float x2, final float y2)
88      {
89          this.setLine(x1, y1, x2, y2);
90      }
91  
92      /**
93       * Calculates the normal to this line. The normal of (a, b) is (-b, a).
94       */
95      public void calcNormal()
96      {
97          this.normalX = this.y1 - this.y2;
98          this.normalY = this.x2 - this.x1;
99      }
100 
101     /**
102      * Normalizes the normal of this line (make the normal's length 1).
103      */
104     public void normalize()
105     {
106         double length = Math.sqrt(this.normalX * this.normalX + this.normalY * this.normalY);
107         this.normalX /= length;
108         this.normalY /= length;
109     }
110 
111     /**
112      * Set the line using floats.
113      * @param x1 x1 coordinate
114      * @param y1 y1 coordinate
115      * @param x2 x2 coordinate
116      * @param y2 y2 coordinate
117      */
118     public void setLine(final float x1, final float y1, final float x2, final float y2)
119     {
120         super.setLine(x1, y1, x2, y2);
121         this.calcNormal();
122     }
123 
124     @Override
125     public void setLine(final double x1, final double y1, final double x2, final double y2)
126     {
127         super.setLine(x1, y1, x2, y2);
128         this.calcNormal();
129     }
130 
131     /**
132      * Flips this line so that the end points are reversed (in other words, (x1,y1) becomes (x2,y2) and vice versa) and the
133      * normal is changed to point the opposite direction.
134      */
135     public void flip()
136     {
137         double tx = this.x1;
138         double ty = this.y1;
139         this.x1 = this.x2;
140         this.y1 = this.y2;
141         this.x2 = tx;
142         this.y2 = ty;
143         this.normalX = -this.normalX;
144         this.normalY = -this.normalY;
145     }
146 
147     /**
148      * Returns true if the endpoints of this line match the endpoints of the specified line. Ignores normal and height values.
149      * @param line another line
150      * @return true if this line's coordinates are equal to the other line's coordinates
151      */
152     public boolean equalsCoordinates(final DirectionalLine line)
153     {
154         return (this.x1 == line.x1 && this.x2 == line.x2 && this.y1 == line.y1 && this.y2 == line.y2);
155     }
156 
157     /**
158      * Returns true if the endpoints of this line match the endpoints of the specified line, ignoring endpoint order (if the
159      * first point of this line is equal to the second point of the specified line, and vice versa, returns true). Ignores
160      * normal and height values.
161      * @param line another line
162      * @return true if coordinates match independent of the order
163      */
164     public boolean equalsCoordinatesIgnoreOrder(final DirectionalLine line)
165     {
166         return equalsCoordinates(line)
167                 || ((this.x1 == line.x2 && this.x2 == line.x1 && this.y1 == line.y2 && this.y2 == line.y1));
168     }
169 
170     @Override
171     public String toString()
172     {
173         return "(" + this.x1 + "," + this.y1 + ")->(" + this.x2 + "," + this.y2 + ")";
174     }
175 
176     /**
177      * Gets the side of this line the specified point is on. This method treats the line as 1-unit thick, so points within this
178      * 1-unit border are considered collinear. For this to work correctly, the normal of this line must be normalized, either by
179      * setting this line to a polygon or by calling normalize(). Returns either FRONTSIDE, BACKSIDE, or COLLINEAR.
180      * @param x coordinate x
181      * @param y coordinate y
182      * @return the side
183      */
184     public Side getSideThick(final double x, final double y)
185     {
186         double normalX2 = this.normalX * this.lineThickness;
187         double normalY2 = this.normalY * this.lineThickness;
188 
189         Side frontSide = getSideThin(x - normalX2 / 2, y - normalY2 / 2);
190         if (frontSide.equals(Side.FRONTSIDE))
191         {
192             return Side.FRONTSIDE;
193         }
194         else if (frontSide.equals(Side.BACKSIDE))
195         {
196             Side backSide = getSideThin(x + normalX2 / 2, y + normalY2 / 2);
197             if (backSide.equals(Side.BACKSIDE))
198             {
199                 return Side.BACKSIDE;
200             }
201         }
202         return Side.COLLINEAR;
203     }
204 
205     /**
206      * Gets the side of this line the specified point is on. Because of doubling point inaccuracy, a collinear line will be
207      * rare. For this to work correctly, the normal of this line must be normalized, either by setting this line to a polygon or
208      * by calling normalize(). Returns either FRONTSIDE, BACKSIDE, or COLLINEAR.
209      * @param x coordinate x
210      * @param y coordinate y
211      * @return the side
212      */
213     public Side getSideThin(final double x, final double y)
214     {
215         // dot product between vector to the point and the normal
216         double side = (x - this.x1) * this.normalX + (y - this.y1) * this.normalY;
217         if (side < 0)
218         {
219             return Side.BACKSIDE;
220         }
221         else if (side > 0)
222         {
223             return Side.FRONTSIDE;
224         }
225         else
226         {
227             return Side.COLLINEAR;
228         }
229     }
230 
231     /**
232      * Gets the side of this line that the specified line segment is on. Returns either FRONT, BACK, COLINEAR, or SPANNING.
233      * @param line Line2D.Double; line segment
234      * @return the side
235      */
236     public Side getSide(final Line2D.Double line)
237     {
238         if (this.x1 == line.x1 && this.x2 == line.x2 && this.y1 == line.y1 && this.y2 == line.y2)
239         {
240             return Side.COLLINEAR;
241         }
242         Side p1Side = getSideThick(line.x1, line.y1);
243         Side p2Side = getSideThick(line.x2, line.y2);
244         if (p1Side == p2Side)
245         {
246             return p1Side;
247         }
248         else if (p1Side == Side.COLLINEAR)
249         {
250             return p2Side;
251         }
252         else if (p2Side == Side.COLLINEAR)
253         {
254             return p1Side;
255         }
256         else
257         {
258             return Side.SPANNING;
259         }
260     }
261 
262     /**
263      * Returns the fraction of intersection along this line. Returns a value from 0 to 1 if the segments intersect. For example,
264      * a return value of 0 means the intersection occurs at point (x1, y1), 1 means the intersection occurs at point (x2, y2),
265      * and .5 mean the intersection occurs halfway between the two endpoints of this line. Returns -1 if the lines are parallel.
266      * @param line Line2D.Double; a line
267      * @return the intersection
268      */
269     public double getIntersection(final Line2D.Double line)
270     {
271         // The intersection point I, of two vectors, A1->A2 and
272         // B1->B2, is:
273         // I = A1 + Ua * (A2 - A1)
274         // I = B1 + Ub * (B2 - B1)
275         //
276         // Solving for Ua gives us the following formula.
277         // Ua is returned.
278         double denominator = (line.y2 - line.y1) * (this.x2 - this.x1) - (line.x2 - line.x1) * (this.y2 - this.y1);
279 
280         // check if the two lines are parallel
281         if (denominator == 0)
282         {
283             return -1;
284         }
285 
286         double numerator = (line.x2 - line.x1) * (this.y1 - line.y1) - (line.y2 - line.y1) * (this.x1 - line.x1);
287 
288         return numerator / denominator;
289     }
290 
291     /**
292      * Returns the intersection point of this line with the specified line.
293      * @param line Line2D.Double; a line
294      * @return intersection point
295      */
296     public Point2D.Double getIntersectionPoint(final Line2D.Double line)
297     {
298         double fraction = getIntersection(line);
299         Point2D.Double intersection = new Point2D.Double();
300         intersection.setLocation(this.x1 + fraction * (this.x2 - this.x1), this.y1 + fraction * (this.y2 - this.y1));
301         return intersection;
302     }
303 
304     /**
305      * Gets the thickness of the line.
306      * @return returns the lineThickness
307      */
308     public double getLineThickness()
309     {
310         return this.lineThickness;
311     }
312 
313     /**
314      * Sets the thickness of the line.
315      * @param lineThickness the lineThickness to set
316      */
317     public void setLineThickness(final double lineThickness)
318     {
319         this.lineThickness = lineThickness;
320     }
321 
322     /**
323      * @return returns the normalX
324      */
325     public double getNormalx()
326     {
327         return this.normalX;
328     }
329 
330     /**
331      * @return returns the normalY
332      */
333     public double getNormaly()
334     {
335         return this.normalY;
336     }
337 }