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