forked from golang/geo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rect.go
255 lines (212 loc) · 8.06 KB
/
rect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package r2
import (
"fmt"
"math"
"github.com/MadHive/geo/r1"
)
// Point represents a point in ℝ².
type Point struct {
X, Y float64
}
// Add returns the sum of p and op.
func (p Point) Add(op Point) Point { return Point{p.X + op.X, p.Y + op.Y} }
// Sub returns the difference of p and op.
func (p Point) Sub(op Point) Point { return Point{p.X - op.X, p.Y - op.Y} }
// Mul returns the scalar product of p and m.
func (p Point) Mul(m float64) Point { return Point{m * p.X, m * p.Y} }
// Ortho returns a counterclockwise orthogonal point with the same norm.
func (p Point) Ortho() Point { return Point{-p.Y, p.X} }
// Dot returns the dot product between p and op.
func (p Point) Dot(op Point) float64 { return p.X*op.X + p.Y*op.Y }
// Cross returns the cross product of p and op.
func (p Point) Cross(op Point) float64 { return p.X*op.Y - p.Y*op.X }
// Norm returns the vector's norm.
func (p Point) Norm() float64 { return math.Hypot(p.X, p.Y) }
// Normalize returns a unit point in the same direction as p.
func (p Point) Normalize() Point {
if p.X == 0 && p.Y == 0 {
return p
}
return p.Mul(1 / p.Norm())
}
func (p Point) String() string { return fmt.Sprintf("(%.12f, %.12f)", p.X, p.Y) }
// Rect represents a closed axis-aligned rectangle in the (x,y) plane.
type Rect struct {
X, Y r1.Interval
}
// RectFromPoints constructs a rect that contains the given points.
func RectFromPoints(pts ...Point) Rect {
// Because the default value on interval is 0,0, we need to manually
// define the interval from the first point passed in as our starting
// interval, otherwise we end up with the case of passing in
// Point{0.2, 0.3} and getting the starting Rect of {0, 0.2}, {0, 0.3}
// instead of the Rect {0.2, 0.2}, {0.3, 0.3} which is not correct.
if len(pts) == 0 {
return Rect{}
}
r := Rect{
X: r1.Interval{Lo: pts[0].X, Hi: pts[0].X},
Y: r1.Interval{Lo: pts[0].Y, Hi: pts[0].Y},
}
for _, p := range pts[1:] {
r = r.AddPoint(p)
}
return r
}
// RectFromCenterSize constructs a rectangle with the given center and size.
// Both dimensions of size must be non-negative.
func RectFromCenterSize(center, size Point) Rect {
return Rect{
r1.Interval{Lo: center.X - size.X/2, Hi: center.X + size.X/2},
r1.Interval{Lo: center.Y - size.Y/2, Hi: center.Y + size.Y/2},
}
}
// EmptyRect constructs the canonical empty rectangle. Use IsEmpty() to test
// for empty rectangles, since they have more than one representation. A Rect{}
// is not the same as the EmptyRect.
func EmptyRect() Rect {
return Rect{r1.EmptyInterval(), r1.EmptyInterval()}
}
// IsValid reports whether the rectangle is valid.
// This requires the width to be empty iff the height is empty.
func (r Rect) IsValid() bool {
return r.X.IsEmpty() == r.Y.IsEmpty()
}
// IsEmpty reports whether the rectangle is empty.
func (r Rect) IsEmpty() bool {
return r.X.IsEmpty()
}
// Vertices returns all four vertices of the rectangle. Vertices are returned in
// CCW direction starting with the lower left corner.
func (r Rect) Vertices() [4]Point {
return [4]Point{
{r.X.Lo, r.Y.Lo},
{r.X.Hi, r.Y.Lo},
{r.X.Hi, r.Y.Hi},
{r.X.Lo, r.Y.Hi},
}
}
// VertexIJ returns the vertex in direction i along the X-axis (0=left, 1=right) and
// direction j along the Y-axis (0=down, 1=up).
func (r Rect) VertexIJ(i, j int) Point {
x := r.X.Lo
if i == 1 {
x = r.X.Hi
}
y := r.Y.Lo
if j == 1 {
y = r.Y.Hi
}
return Point{x, y}
}
// Lo returns the low corner of the rect.
func (r Rect) Lo() Point {
return Point{r.X.Lo, r.Y.Lo}
}
// Hi returns the high corner of the rect.
func (r Rect) Hi() Point {
return Point{r.X.Hi, r.Y.Hi}
}
// Center returns the center of the rectangle in (x,y)-space
func (r Rect) Center() Point {
return Point{r.X.Center(), r.Y.Center()}
}
// Size returns the width and height of this rectangle in (x,y)-space. Empty
// rectangles have a negative width and height.
func (r Rect) Size() Point {
return Point{r.X.Length(), r.Y.Length()}
}
// ContainsPoint reports whether the rectangle contains the given point.
// Rectangles are closed regions, i.e. they contain their boundary.
func (r Rect) ContainsPoint(p Point) bool {
return r.X.Contains(p.X) && r.Y.Contains(p.Y)
}
// InteriorContainsPoint returns true iff the given point is contained in the interior
// of the region (i.e. the region excluding its boundary).
func (r Rect) InteriorContainsPoint(p Point) bool {
return r.X.InteriorContains(p.X) && r.Y.InteriorContains(p.Y)
}
// Contains reports whether the rectangle contains the given rectangle.
func (r Rect) Contains(other Rect) bool {
return r.X.ContainsInterval(other.X) && r.Y.ContainsInterval(other.Y)
}
// InteriorContains reports whether the interior of this rectangle contains all of the
// points of the given other rectangle (including its boundary).
func (r Rect) InteriorContains(other Rect) bool {
return r.X.InteriorContainsInterval(other.X) && r.Y.InteriorContainsInterval(other.Y)
}
// Intersects reports whether this rectangle and the other rectangle have any points in common.
func (r Rect) Intersects(other Rect) bool {
return r.X.Intersects(other.X) && r.Y.Intersects(other.Y)
}
// InteriorIntersects reports whether the interior of this rectangle intersects
// any point (including the boundary) of the given other rectangle.
func (r Rect) InteriorIntersects(other Rect) bool {
return r.X.InteriorIntersects(other.X) && r.Y.InteriorIntersects(other.Y)
}
// AddPoint expands the rectangle to include the given point. The rectangle is
// expanded by the minimum amount possible.
func (r Rect) AddPoint(p Point) Rect {
return Rect{r.X.AddPoint(p.X), r.Y.AddPoint(p.Y)}
}
// AddRect expands the rectangle to include the given rectangle. This is the
// same as replacing the rectangle by the union of the two rectangles, but
// is more efficient.
func (r Rect) AddRect(other Rect) Rect {
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}
// ClampPoint returns the closest point in the rectangle to the given point.
// The rectangle must be non-empty.
func (r Rect) ClampPoint(p Point) Point {
return Point{r.X.ClampPoint(p.X), r.Y.ClampPoint(p.Y)}
}
// Expanded returns a rectangle that has been expanded in the x-direction
// by margin.X, and in y-direction by margin.Y. If either margin is empty,
// then shrink the interval on the corresponding sides instead. The resulting
// rectangle may be empty. Any expansion of an empty rectangle remains empty.
func (r Rect) Expanded(margin Point) Rect {
xx := r.X.Expanded(margin.X)
yy := r.Y.Expanded(margin.Y)
if xx.IsEmpty() || yy.IsEmpty() {
return EmptyRect()
}
return Rect{xx, yy}
}
// ExpandedByMargin returns a Rect that has been expanded by the amount on all sides.
func (r Rect) ExpandedByMargin(margin float64) Rect {
return r.Expanded(Point{margin, margin})
}
// Union returns the smallest rectangle containing the union of this rectangle and
// the given rectangle.
func (r Rect) Union(other Rect) Rect {
return Rect{r.X.Union(other.X), r.Y.Union(other.Y)}
}
// Intersection returns the smallest rectangle containing the intersection of this
// rectangle and the given rectangle.
func (r Rect) Intersection(other Rect) Rect {
xx := r.X.Intersection(other.X)
yy := r.Y.Intersection(other.Y)
if xx.IsEmpty() || yy.IsEmpty() {
return EmptyRect()
}
return Rect{xx, yy}
}
// ApproxEqual returns true if the x- and y-intervals of the two rectangles are
// the same up to the given tolerance.
func (r Rect) ApproxEqual(r2 Rect) bool {
return r.X.ApproxEqual(r2.X) && r.Y.ApproxEqual(r2.Y)
}
func (r Rect) String() string { return fmt.Sprintf("[Lo%s, Hi%s]", r.Lo(), r.Hi()) }