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-2024 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 <a href="https://github.com/wjschakel">Wouter Schakel</a>
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(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(toAbsoluteTransform(locatable.getLocation()).transform(locatable.getRelativeBounds().iterator()));
146 }
147
148 /**
149 * Transform the line by location, which may also be an {@code OrientedPoint} for rotation.
150 * @param line line
151 * @param location location, which may also be an {@code OrientedPoint} for rotation
152 * @return transformed line
153 */
154 static PolyLine2d transformLine(final PolyLine2d line, final Point2d location)
155 {
156 return new PolyLine2d(toRelativeTransform(location).transform(line.iterator()));
157 }
158
159 /**
160 * Returns a transformation by which absolute coordinates can be translated and rotated to the frame of the possibly
161 * oriented location around which bounds are defined.
162 * @param location location (can be an {@code Oriented}).
163 * @return transformation.
164 */
165 static Transform2d toRelativeTransform(final Point2d location)
166 {
167 Transform2d transformation = new Transform2d();
168 if (location instanceof Directed dir)
169 {
170 transformation.rotation(-dir.getDirZ());
171 }
172 transformation.translate(-location.getX(), -location.getY());
173 return transformation;
174 }
175
176 /**
177 * Returns a transformation by which relative coordinates can be translated and rotated to the frame of the possibly
178 * oriented location around which bounds are defined.
179 * @param location location (can be an {@code Oriented}).
180 * @return transformation.
181 */
182 static Transform2d toAbsoluteTransform(final Point2d location)
183 {
184 Transform2d transformation = new Transform2d();
185 transformation.translate(location.getX(), location.getY());
186 if (location instanceof Directed dir)
187 {
188 transformation.rotation(dir.getDirZ());
189 }
190 return transformation;
191 }
192
193 }