1 package org.opentrafficsim.base.geometry;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.List;
6
7 import org.djutils.draw.line.Polygon2d;
8 import org.djutils.draw.point.Point2d;
9 import org.djutils.exceptions.Throw;
10
11
12
13
14
15
16
17
18
19 public class RoundedRectangleShape implements OtsShape
20 {
21
22
23 private final double dx;
24
25
26 private final double dy;
27
28
29 private final double r;
30
31
32 private final double maxX;
33
34
35 private final double maxY;
36
37
38 private final int polygonSegments;
39
40
41 private Polygon2d polygon;
42
43
44
45
46
47
48
49
50 public RoundedRectangleShape(final double dx, final double dy, final double r)
51 {
52 this(dx, dy, r, DEFAULT_POLYGON_SEGMENTS);
53 }
54
55
56
57
58
59
60
61
62
63 public RoundedRectangleShape(final double dx, final double dy, final double r, final int polygonSegments)
64 {
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 this.dx = Math.abs(dx) / 2.0;
81 this.dy = Math.abs(dy) / 2.0;
82 Throw.when(r >= this.dx + this.dy + Math.sqrt(2.0 * this.dx * this.dy), IllegalArgumentException.class,
83 "Radius makes rounded rectangle non-existent.");
84 Throw.when(r < 0.0, IllegalArgumentException.class, "Radius must be positive.");
85 this.r = r;
86 this.maxX = this.dx - signedDistance(new Point2d(this.dx, 0.0));
87 this.maxY = this.dy - signedDistance(new Point2d(0.0, this.dy));
88 this.polygonSegments = polygonSegments;
89 }
90
91 @Override
92 public double getMinX()
93 {
94 return -this.maxX;
95 }
96
97 @Override
98 public double getMaxX()
99 {
100 return this.maxX;
101 }
102
103 @Override
104 public double getMinY()
105 {
106 return -this.maxY;
107 }
108
109 @Override
110 public double getMaxY()
111 {
112 return this.maxY;
113 }
114
115 @Override
116 public boolean contains(final Point2d point) throws NullPointerException
117 {
118 return signedDistance(point) < 0.0;
119 }
120
121 @Override
122 public boolean contains(final double x, final double y) throws NullPointerException
123 {
124 return contains(new Point2d(x, y));
125 }
126
127
128
129
130
131 @Override
132 public double signedDistance(final Point2d point)
133 {
134 double qx = Math.abs(point.x) - this.dx + this.r;
135 double qy = Math.abs(point.y) - this.dy + this.r;
136 return Math.hypot(Math.max(qx, 0.0), Math.max(qy, 0.0)) + Math.min(Math.max(qx, qy), 0.0) - this.r;
137 }
138
139 @Override
140 public Polygon2d asPolygon()
141 {
142 if (this.polygon == null)
143 {
144
145 int n = this.polygonSegments / 4;
146 List<Point2d> pq = new ArrayList<>();
147 for (int i = 0; i <= n; i++)
148 {
149 double ang = (0.5 * Math.PI * i) / n;
150 double x = this.dx + Math.cos(ang) * this.r - this.r;
151 double y = this.dy + Math.sin(ang) * this.r - this.r;
152 if (x >= 0.0 && y >= 0.0)
153 {
154 pq.add(new Point2d(x, y));
155 }
156 }
157
158 List<Point2d> pqReversed = new ArrayList<>(pq);
159 Collections.reverse(pqReversed);
160 List<Point2d> points = new ArrayList<>(pq);
161 pqReversed.forEach((p) -> points.add(new Point2d(-p.x, p.y)));
162 pq.forEach((p) -> points.add(p.neg()));
163 pqReversed.forEach((p) -> points.add(new Point2d(p.x, -p.y)));
164 this.polygon = new Polygon2d(true, points);
165 }
166 return this.polygon;
167 }
168
169 @Override
170 public String toString()
171 {
172 return "RoundedRectangleShape [dx=" + this.dx + ", dy=" + this.dy + ", r=" + this.r + "]";
173 }
174
175 }