1 package org.opentrafficsim.base.geometry;
2
3 import org.djunits.value.vdouble.scalar.Duration;
4 import org.djutils.draw.Directed;
5 import org.djutils.draw.Transform2d;
6 import org.djutils.draw.bounds.Bounds2d;
7 import org.djutils.draw.line.PolyLine2d;
8 import org.djutils.draw.line.Polygon2d;
9 import org.djutils.draw.point.DirectedPoint2d;
10 import org.djutils.draw.point.Point2d;
11
12 import nl.tudelft.simulation.dsol.animation.Locatable;
13
14 /**
15 * Locatable that provides absolute and relative contours and bounds, both statically and dynamically.
16 * <p>
17 * Copyright (c) 2024-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
19 * </p>
20 * @author Wouter Schakel
21 */
22 public interface OtsShape extends Locatable
23 {
24
25 /** Minimum world margin to click on object line or point objects. */
26 double WORLD_MARGIN_LINE = 1.0;
27
28 /** The default intended number of polygon segments to represent continuous shapes. */
29 int DEFAULT_POLYGON_SEGMENTS = 128;
30
31 @Override
32 DirectedPoint2d getLocation();
33
34 /**
35 * Returns the contour of the object in world coordinates.
36 * @return the contour of the object in world coordinates
37 */
38 default Polygon2d getAbsoluteContour()
39 {
40 Transform2d transform = toAbsoluteTransform(getLocation());
41 return new Polygon2d(0.0, transform.transform(getRelativeContour().iterator()));
42 }
43
44 /**
45 * Return the contour of a dynamic object at time 'time' in world coordinates. The default implementation returns the static
46 * contour.
47 * @param time simulation time for which we want the shape
48 * @return the shape of the object at time 'time'
49 */
50 default Polygon2d getAbsoluteContour(final Duration time)
51 {
52 return getAbsoluteContour();
53 }
54
55 /**
56 * Returns the contour of the object in relative coordinates.
57 * @return the contour of the object in relative coordinates
58 */
59 Polygon2d getRelativeContour();
60
61 /**
62 * Returns the bounds relative to the location. The default implementation returns the bounds of the contour.
63 * @return bounds relative to the location.
64 */
65 @Override
66 default Bounds2d getRelativeBounds()
67 {
68 return getRelativeContour().getAbsoluteBounds();
69 }
70
71 /**
72 * Returns whether the point is contained within the shape. The default implementation calculates this based on the contour.
73 * @param point point
74 * @return whether the point is contained within the shape
75 */
76 default boolean contains(final Point2d point)
77 {
78 return signedDistance(point) < 0.0;
79 }
80
81 /**
82 * Returns whether the point is contained within the shape. The default implementation calculates this based on the contour.
83 * @param x x coordinate of the point
84 * @param y y coordinate of the point
85 * @return whether the point is contained within the shape
86 */
87 default boolean contains(final double x, final double y)
88 {
89 return contains(new Point2d(x, y));
90 }
91
92 /**
93 * Returns the bounds in world coordinates.
94 * @return bounds in world coordinates.
95 */
96 default Bounds2d getAbsoluteBounds()
97 {
98 return getAbsoluteContour().getAbsoluteBounds();
99 }
100
101 @Override
102 default double getDirZ()
103 {
104 return getLocation().dirZ;
105 }
106
107 /**
108 * Signed distance function. The point must be relative. Negative distances returned are inside the bounds, with the
109 * absolute value of the distance towards the edge. The default implementation is based on the polygon representation, which
110 * is expensive.
111 * @param point point for which distance is returned.
112 * @return distance from point to these bounds.
113 */
114 default double signedDistance(final Point2d point)
115 {
116 double dist = getRelativeContour().closestPointOnPolyLine(point).distance(point);
117 return getRelativeContour().contains(point) ? -dist : dist;
118 }
119
120 /**
121 * Signed distance function. The point must be relative. Negative distances returned are inside the bounds, with the
122 * absolute value of the distance towards the edge. The default implementation is based on the polygon representation, which
123 * is expensive.
124 * @param x x coordinate of the point
125 * @param y y coordinate of the point
126 * @return distance from point to these bounds.
127 */
128 default double signedDistance(final double x, final double y)
129 {
130 return signedDistance(new Point2d(x, y));
131 }
132
133 /*
134 * The following methods that take an OtsLocatable as input are defined not as default but as static, as these methods
135 * should not be exposed as functions of an OtsLocatable.
136 */
137
138 /**
139 * Generates a polygon as contour based on bounds and location of the locatable.
140 * @param locatable locatable
141 * @return contour
142 */
143 static Polygon2d boundsAsAbsoluteContour(final OtsShape locatable)
144 {
145 return new Polygon2d(0.0,
146 toAbsoluteTransform(locatable.getLocation()).transform(locatable.getRelativeBounds().iterator()));
147 }
148
149 /**
150 * Transform the line by location, which may also be an {@code OrientedPoint} for rotation.
151 * @param line line
152 * @param location location, which may also be an {@code OrientedPoint} for rotation
153 * @return transformed line
154 */
155 static PolyLine2d transformLine(final PolyLine2d line, final Point2d location)
156 {
157 return new PolyLine2d(0.0, toRelativeTransform(location).transform(line.iterator()));
158 }
159
160 /**
161 * Returns a transformation by which absolute coordinates can be translated and rotated to the frame of the possibly
162 * oriented location around which bounds are defined.
163 * @param location location (can be an {@code Oriented}).
164 * @return transformation.
165 */
166 static Transform2d toRelativeTransform(final Point2d location)
167 {
168 Transform2d transformation = new Transform2d();
169 if (location instanceof Directed dir)
170 {
171 transformation.rotation(-dir.getDirZ());
172 }
173 transformation.translate(-location.getX(), -location.getY());
174 return transformation;
175 }
176
177 /**
178 * Returns a transformation by which relative coordinates can be translated and rotated to the frame of the possibly
179 * oriented location around which bounds are defined.
180 * @param location location (can be an {@code Oriented}).
181 * @return transformation.
182 */
183 static Transform2d toAbsoluteTransform(final Point2d location)
184 {
185 Transform2d transformation = new Transform2d();
186 transformation.translate(location.getX(), location.getY());
187 if (location instanceof Directed dir)
188 {
189 transformation.rotation(dir.getDirZ());
190 }
191 return transformation;
192 }
193
194 }