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 }