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.bounds.Bounds2d;
8 import org.djutils.draw.line.Polygon2d;
9 import org.djutils.draw.point.Point2d;
10 import org.djutils.exceptions.Throw;
11
12
13
14
15
16
17
18
19
20 public abstract class RoundedRectangleShape implements OtsShape
21 {
22
23
24 private final double dx;
25
26
27 private final double dy;
28
29
30 private final double r;
31
32
33 private final int polygonSegments;
34
35
36 private Polygon2d polygon;
37
38
39 private Bounds2d bounds;
40
41
42
43
44
45
46
47
48 public RoundedRectangleShape(final double dx, final double dy, final double r)
49 {
50 this(dx, dy, r, DEFAULT_POLYGON_SEGMENTS);
51 }
52
53
54
55
56
57
58
59
60
61 public RoundedRectangleShape(final double dx, final double dy, final double r, final int polygonSegments)
62 {
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 this.dx = Math.abs(dx) / 2.0;
79 this.dy = Math.abs(dy) / 2.0;
80 Throw.when(r >= this.dx + this.dy + Math.sqrt(2.0 * this.dx * this.dy), IllegalArgumentException.class,
81 "Radius makes rounded rectangle non-existent.");
82 Throw.when(r < 0.0, IllegalArgumentException.class, "Radius must be positive.");
83 this.r = r;
84 double maxX = this.dx - signedDistance(new Point2d(this.dx, 0.0));
85 double maxY = this.dy - signedDistance(new Point2d(0.0, this.dy));
86 this.polygonSegments = polygonSegments;
87 this.bounds = new Bounds2d(-maxX, maxX, -maxY, maxY);
88 }
89
90 @Override
91 public Bounds2d getRelativeBounds()
92 {
93 return this.bounds;
94 }
95
96
97
98
99
100 @Override
101 public double signedDistance(final Point2d point)
102 {
103 double qx = Math.abs(point.x) - this.dx + this.r;
104 double qy = Math.abs(point.y) - this.dy + this.r;
105 return Math.hypot(Math.max(qx, 0.0), Math.max(qy, 0.0)) + Math.min(Math.max(qx, qy), 0.0) - this.r;
106 }
107
108 @Override
109 public Polygon2d getRelativeContour()
110 {
111 if (this.polygon == null)
112 {
113
114 int n = this.polygonSegments / 4;
115 List<Point2d> pq = new ArrayList<>();
116 for (int i = 0; i <= n; i++)
117 {
118 double ang = (0.5 * Math.PI * i) / n;
119 double x = this.dx + Math.cos(ang) * this.r - this.r;
120 double y = this.dy + Math.sin(ang) * this.r - this.r;
121 if (x >= 0.0 && y >= 0.0)
122 {
123 pq.add(new Point2d(x, y));
124 }
125 }
126
127 List<Point2d> pqReversed = new ArrayList<>(pq);
128 Collections.reverse(pqReversed);
129 List<Point2d> points = new ArrayList<>(pq);
130 pqReversed.forEach((p) -> points.add(new Point2d(-p.x, p.y)));
131 pq.forEach((p) -> points.add(p.neg()));
132 pqReversed.forEach((p) -> points.add(new Point2d(p.x, -p.y)));
133 this.polygon = new Polygon2d(0.0, points);
134 }
135 return this.polygon;
136 }
137
138 @Override
139 public String toString()
140 {
141 return "RoundedRectangleShape [dx=" + this.dx + ", dy=" + this.dy + ", r=" + this.r + "]";
142 }
143
144 }