perfect-shape 0.0.5 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/README.md +151 -15
- data/VERSION +1 -1
- data/lib/perfect-shape.rb +1 -0
- data/lib/perfect_shape/arc.rb +3 -2
- data/lib/perfect_shape/circle.rb +91 -0
- data/lib/perfect_shape/ellipse.rb +1 -0
- data/lib/perfect_shape/line.rb +132 -1
- data/lib/perfect_shape/math.rb +9 -8
- data/lib/perfect_shape/multi_point.rb +67 -0
- data/lib/perfect_shape/polygon.rb +114 -0
- data/lib/perfect_shape/rectangle.rb +2 -1
- data/lib/perfect_shape/rectangular_shape.rb +12 -4
- data/lib/perfect_shape/shape.rb +43 -0
- data/perfect-shape.gemspec +9 -4
- metadata +23 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 934071723ece0f7ebe64e6e67880b5a1f4ec6781517d2ee2a7dea70ceae4a9be
|
4
|
+
data.tar.gz: d436da8745d7f4d971a7df04b5d54701e6e89abb9f8624273f79a6ac1d28ea77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bcea5c4c02f2e056c0e18a857404bbef4b411420c2c3a7e438a2f122df90a30466979c60cc7804c61b5d454ed2879cd81fd1fc750360839393c20a4561aebce6
|
7
|
+
data.tar.gz: c321b5b1697b28e9608c11d4bc5e04038c3e269df311b9c7263e468c6f952c60bec6ecf4244ff044eb8fb33cf68462e9d362326962c0473bbd72150847181e0e
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.0.9
|
4
|
+
|
5
|
+
- `PerfectShape::Line#contain?(x_or_point, y=nil, distance: 0)` (add a distance tolerance fuzz factor option)
|
6
|
+
|
7
|
+
## 0.0.8
|
8
|
+
|
9
|
+
- `PerfectShape::Line`
|
10
|
+
- `PerfectShape::Line#contain?(x_or_point, y=nil)`
|
11
|
+
- `PerfectShape::Line#relative_counterclockwise`
|
12
|
+
- `PerfectShape::Line#point_segment_distance`
|
13
|
+
- Update `PerfectShape::Math::radians_to_degrees`, `PerfectShape::Math::degrees_to_radians`, and `PerfectShape::Math::normalize_degrees` to normalize numbers to `BigDecimal`
|
14
|
+
|
15
|
+
## 0.0.7
|
16
|
+
|
17
|
+
- `PerfectShape::Shape#min_x`/`PerfectShape::Shape#min_y`/`PerfectShape::Shape#max_x`/`PerfectShape::Shape#max_y`/`PerfectShape::Shape#center_x`/`PerfectShape::Shape#center_y`/`PerfectShape::Shape#bounding_box`
|
18
|
+
|
19
|
+
## 0.0.6
|
20
|
+
|
21
|
+
- `PerfectShape::Circle`
|
22
|
+
- `PerfectShape::Circle#contain?(x_or_point, y=nil)`
|
23
|
+
|
3
24
|
## 0.0.5
|
4
25
|
|
5
26
|
- `PerfectShape::Ellipse`
|
6
|
-
- `PerfectShape::Ellipse#contain
|
27
|
+
- `PerfectShape::Ellipse#contain?(x_or_point, y=nil)`
|
7
28
|
|
8
29
|
## 0.0.4
|
9
30
|
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
# Perfect Shape 0.0.
|
1
|
+
# Perfect Shape 0.0.9
|
2
2
|
## Geometric Algorithms
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/perfect-shape.svg)](http://badge.fury.io/rb/perfect-shape)
|
4
4
|
|
5
|
-
`PerfectShape` is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, polyline, polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves.
|
5
|
+
`PerfectShape` is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon (ray casting algorithm/even-odd rule), polyline, polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves.
|
6
6
|
|
7
7
|
Additionally, `PerfectShape::Math` contains some purely mathematical algorithms.
|
8
8
|
|
@@ -13,13 +13,13 @@ To ensure high accuracy, this library does all its mathematical operations with
|
|
13
13
|
Run:
|
14
14
|
|
15
15
|
```
|
16
|
-
gem install perfect-shape -v 0.0.
|
16
|
+
gem install perfect-shape -v 0.0.9
|
17
17
|
```
|
18
18
|
|
19
19
|
Or include in Bundler `Gemfile`:
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
gem 'perfect-shape', '~> 0.0.
|
22
|
+
gem 'perfect-shape', '~> 0.0.9'
|
23
23
|
```
|
24
24
|
|
25
25
|
And, run:
|
@@ -32,17 +32,76 @@ bundle
|
|
32
32
|
|
33
33
|
### `PerfectShape::Math`
|
34
34
|
|
35
|
+
Module
|
36
|
+
|
35
37
|
- `::degrees_to_radians(angle)`: converts degrees to radians
|
36
38
|
- `::radians_to_degrees(angle)`: converts radians to degrees
|
37
39
|
- `::normalize_degrees(angle)`: normalizes the specified angle into the range -180 to 180.
|
38
40
|
- `::ieee_remainder(x, y)` (alias: `ieee754_remainder`): [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985) (different from standard % modulo operator as it operates on floats and could return a negative result)
|
39
41
|
|
42
|
+
### `PerfectShape::Shape`
|
43
|
+
|
44
|
+
Class
|
45
|
+
|
46
|
+
- `#min_x`: min x
|
47
|
+
- `#min_y`: min y
|
48
|
+
- `#max_x`: max x
|
49
|
+
- `#max_y`: max y
|
50
|
+
- `#width`: width
|
51
|
+
- `#height`: height
|
52
|
+
- `#center_x`: center x
|
53
|
+
- `#center_y`: center y
|
54
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height just as those of shape
|
55
|
+
- `#normalize_point(x_or_point, y = nil)`: normalizes point into an `Array` of (x,y) coordinates
|
56
|
+
|
57
|
+
### `PerfectShape::RectangularShape`
|
58
|
+
|
59
|
+
Module
|
60
|
+
|
61
|
+
- `#initialize(x: 0, y: 0, width: 1, height: 1)`: initializes a rectangular shape
|
62
|
+
- `#x`: top-left x
|
63
|
+
- `#y`: top-left y
|
64
|
+
- `#width`: width
|
65
|
+
- `#height`: height
|
66
|
+
- `#min_x`: min x
|
67
|
+
- `#min_y`: min y
|
68
|
+
- `#max_x`: max x
|
69
|
+
- `#max_y`: max y
|
70
|
+
- `#center_x`: center x
|
71
|
+
- `#center_y`: center y
|
72
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
73
|
+
|
40
74
|
### `PerfectShape::Line`
|
41
75
|
|
42
|
-
|
76
|
+
Class
|
77
|
+
|
78
|
+
Extends `PerfectShape::Shape`
|
79
|
+
|
80
|
+
![line](images/line.png)
|
81
|
+
|
82
|
+
- `::relative_counterclockwise(x1, y1, x2, y2, px, py)`: Returns an indicator of where the specified point (px,py) lies with respect to the line segment from (x1,y1) to (x2,y2). The return value can be either 1, -1, or 0 and indicates in which direction the specified line must pivot around its first end point, (x1,y1), in order to point at the specified point (px,py). A return value of 1 indicates that the line segment must turn in the direction that takes the positive X axis towards the negative Y axis. In the default coordinate system used by Java 2D, this direction is counterclockwise. A return value of -1 indicates that the line segment must turn in the direction that takes the positive X axis towards the positive Y axis. In the default coordinate system, this direction is clockwise. A return value of 0 indicates that the point lies exactly on the line segment. Note that an indicator value of 0 is rare and not useful for determining collinearity because of floating point rounding issues. If the point is colinear with the line segment, but not between the end points, then the value will be -1 if the point lies “beyond (x1,y1)” or 1 if the point lies “beyond (x2,y2)”.
|
83
|
+
- `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
|
84
|
+
- `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
|
85
|
+
- `::new(points: nil)`: constructs a polygon with `points` as `Array` of `Array`s of (x,y) pairs or flattened `Array` of alternating x and y values
|
86
|
+
- `#min_x`: min x
|
87
|
+
- `#min_y`: min y
|
88
|
+
- `#max_x`: max x
|
89
|
+
- `#max_y`: max y
|
90
|
+
- `#width`: width (from min x to max x)
|
91
|
+
- `#height`: height (from min y to max y)
|
92
|
+
- `#center_x`: center x
|
93
|
+
- `#center_y`: center y
|
94
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
95
|
+
- `#contain?(x_or_point, y=nil, distance: 0)`: checks if point lies on line, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a line shape in a GUI more successfully.
|
96
|
+
- `#relative_counterclockwise(x_or_point, y=nil)`: Returns an indicator of where the specified point (px,py) lies with respect to the line segment from (x1,y1) to (x2,y2). The return value can be either 1, -1, or 0 and indicates in which direction the specified line must pivot around its first end point, (x1,y1), in order to point at the specified point (px,py). A return value of 1 indicates that the line segment must turn in the direction that takes the positive X axis towards the negative Y axis. In the default coordinate system used by Java 2D, this direction is counterclockwise. A return value of -1 indicates that the line segment must turn in the direction that takes the positive X axis towards the positive Y axis. In the default coordinate system, this direction is clockwise. A return value of 0 indicates that the point lies exactly on the line segment. Note that an indicator value of 0 is rare and not useful for determining collinearity because of floating point rounding issues. If the point is colinear with the line segment, but not between the end points, then the value will be -1 if the point lies “beyond (x1,y1)” or 1 if the point lies “beyond (x2,y2)”.
|
97
|
+
- `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
|
43
98
|
|
44
99
|
### `PerfectShape::Rectangle`
|
45
100
|
|
101
|
+
Class
|
102
|
+
|
103
|
+
Extends `PerfectShape::Shape`
|
104
|
+
|
46
105
|
Includes `PerfectShape::RectangularShape`
|
47
106
|
|
48
107
|
![rectangle](images/rectangle.png)
|
@@ -54,10 +113,17 @@ Includes `PerfectShape::RectangularShape`
|
|
54
113
|
- `#height`: height
|
55
114
|
- `#center_x`: center x
|
56
115
|
- `#center_y`: center y
|
116
|
+
- `#min_x`: min x
|
117
|
+
- `#min_y`: min y
|
118
|
+
- `#max_x`: max x
|
119
|
+
- `#max_y`: max y
|
120
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
57
121
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
58
122
|
|
59
123
|
### `PerfectShape::Square`
|
60
124
|
|
125
|
+
Class
|
126
|
+
|
61
127
|
Extends `PerfectShape::Rectangle`
|
62
128
|
|
63
129
|
![square](images/square.png)
|
@@ -70,10 +136,19 @@ Extends `PerfectShape::Rectangle`
|
|
70
136
|
- `#height`: height (equal to length)
|
71
137
|
- `#center_x`: center x
|
72
138
|
- `#center_y`: center y
|
139
|
+
- `#min_x`: min x
|
140
|
+
- `#min_y`: min y
|
141
|
+
- `#max_x`: max x
|
142
|
+
- `#max_y`: max y
|
143
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
73
144
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
74
145
|
|
75
146
|
### `PerfectShape::Arc`
|
76
147
|
|
148
|
+
Class
|
149
|
+
|
150
|
+
Extends `PerfectShape::Shape`
|
151
|
+
|
77
152
|
Includes `PerfectShape::RectangularShape`
|
78
153
|
|
79
154
|
Arcs can be of type `:open`, `:chord`, or `:pie`
|
@@ -84,29 +159,36 @@ Open Arc | Chord Arc | Pie Arc
|
|
84
159
|
|
85
160
|
- `::new(type: :open, x: 0, y: 0, width: 1, height: 1, start: 0, extent: 360, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)`: constructs an arc of type `:open` (default), `:chord`, or `:pie`
|
86
161
|
- `#type`: `:open`, `:chord`, or `:pie`
|
87
|
-
- `#x`: top-left x
|
88
|
-
- `#y`: top-left y
|
89
|
-
- `#width`: width
|
90
|
-
- `#height`: height
|
162
|
+
- `#x`: top-left x
|
163
|
+
- `#y`: top-left y
|
164
|
+
- `#width`: width
|
165
|
+
- `#height`: height
|
91
166
|
- `#start`: start angle in degrees
|
92
167
|
- `#extent`: extent angle in degrees
|
93
168
|
- `#center_x`: center x
|
94
169
|
- `#center_y`: center y
|
95
170
|
- `#radius_x`: radius along the x-axis
|
96
171
|
- `#radius_y`: radius along the y-axis
|
172
|
+
- `#min_x`: min x
|
173
|
+
- `#min_y`: min y
|
174
|
+
- `#max_x`: max x
|
175
|
+
- `#max_y`: max y
|
176
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
97
177
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
98
178
|
|
99
179
|
### `PerfectShape::Ellipse`
|
100
180
|
|
181
|
+
Class
|
182
|
+
|
101
183
|
Extends `PerfectShape::Arc`
|
102
184
|
|
103
185
|
![ellipse](images/ellipse.png)
|
104
186
|
|
105
187
|
- `::new(x: 0, y: 0, width: 1, height: 1, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)`: constructs an ellipse
|
106
|
-
- `#x`: top-left x
|
107
|
-
- `#y`: top-left y
|
108
|
-
- `#width`: width
|
109
|
-
- `#height`: height
|
188
|
+
- `#x`: top-left x
|
189
|
+
- `#y`: top-left y
|
190
|
+
- `#width`: width
|
191
|
+
- `#height`: height
|
110
192
|
- `#center_x`: center x
|
111
193
|
- `#center_y`: center y
|
112
194
|
- `#radius_x`: radius along the x-axis
|
@@ -114,8 +196,62 @@ Extends `PerfectShape::Arc`
|
|
114
196
|
- `#type`: always `:open`
|
115
197
|
- `#start`: always `0`
|
116
198
|
- `#extent`: always `360`
|
199
|
+
- `#min_x`: min x
|
200
|
+
- `#min_y`: min y
|
201
|
+
- `#max_x`: max x
|
202
|
+
- `#max_y`: max y
|
203
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
204
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
205
|
+
|
206
|
+
### `PerfectShape::Circle`
|
207
|
+
|
208
|
+
Class
|
209
|
+
|
210
|
+
Extends `PerfectShape::Ellipse`
|
211
|
+
|
212
|
+
![circle](images/circle.png)
|
213
|
+
|
214
|
+
- `::new(x: 0, y: 0, diameter: 1, width: 1, height: 1, center_x: nil, center_y: nil, radius: nil, radius_x: nil, radius_y: nil)`: constructs a circle
|
215
|
+
- `#x`: top-left x
|
216
|
+
- `#y`: top-left y
|
217
|
+
- `#diameter`: diameter
|
218
|
+
- `#width`: width (equal to diameter)
|
219
|
+
- `#height`: height (equal to diameter)
|
220
|
+
- `#center_x`: center x
|
221
|
+
- `#center_y`: center y
|
222
|
+
- `#radius`: radius
|
223
|
+
- `#radius_x`: radius along the x-axis (equal to radius)
|
224
|
+
- `#radius_y`: radius along the y-axis (equal to radius)
|
225
|
+
- `#type`: always `:open`
|
226
|
+
- `#start`: always `0`
|
227
|
+
- `#extent`: always `360`
|
228
|
+
- `#min_x`: min x
|
229
|
+
- `#min_y`: min y
|
230
|
+
- `#max_x`: max x
|
231
|
+
- `#max_y`: max y
|
232
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
117
233
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
118
234
|
|
235
|
+
### `PerfectShape::Polygon`
|
236
|
+
|
237
|
+
Class
|
238
|
+
|
239
|
+
Extends `PerfectShape::Shape`
|
240
|
+
|
241
|
+
![polygon](images/polygon.png)
|
242
|
+
|
243
|
+
- `::new(points: nil)`: constructs a polygon with `points` as `Array` of `Array`s of (x,y) pairs or flattened `Array` of alternating x and y values
|
244
|
+
- `#min_x`: min x
|
245
|
+
- `#min_y`: min y
|
246
|
+
- `#max_x`: max x
|
247
|
+
- `#max_y`: max y
|
248
|
+
- `#width`: width (from min x to max x)
|
249
|
+
- `#height`: height (from min y to max y)
|
250
|
+
- `#center_x`: center x
|
251
|
+
- `#center_y`: center y
|
252
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
253
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside using the [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon) (aka [Even-Odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule))
|
254
|
+
|
119
255
|
## Process
|
120
256
|
|
121
257
|
[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
|
@@ -123,7 +259,7 @@ Extends `PerfectShape::Arc`
|
|
123
259
|
## Resources
|
124
260
|
|
125
261
|
- Rubydoc: https://www.rubydoc.info/gems/perfect-shape
|
126
|
-
- AWT Geom JavaDoc: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
|
262
|
+
- AWT Geom JavaDoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
|
127
263
|
|
128
264
|
## TODO
|
129
265
|
|
@@ -133,7 +269,7 @@ Extends `PerfectShape::Arc`
|
|
133
269
|
|
134
270
|
[CHANGELOG.md](CHANGELOG.md)
|
135
271
|
|
136
|
-
## Contributing
|
272
|
+
## Contributing
|
137
273
|
|
138
274
|
- Check out the latest master to make sure the feature hasn't been
|
139
275
|
implemented or the bug hasn't been fixed yet.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.9
|
data/lib/perfect-shape.rb
CHANGED
@@ -22,6 +22,7 @@
|
|
22
22
|
$LOAD_PATH.unshift File.expand_path('.', __dir__)
|
23
23
|
|
24
24
|
require 'bigdecimal'
|
25
|
+
require 'equalizer'
|
25
26
|
|
26
27
|
# Perfect Shape algorithms are mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
|
27
28
|
module PerfectShape
|
data/lib/perfect_shape/arc.rb
CHANGED
@@ -27,6 +27,7 @@ module PerfectShape
|
|
27
27
|
# Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Arc2D.html
|
28
28
|
class Arc < Shape
|
29
29
|
include RectangularShape
|
30
|
+
include Equalizer.new(:type, :x, :y, :width, :height, :start, :extent)
|
30
31
|
|
31
32
|
TYPES = [:open, :chord, :pie]
|
32
33
|
attr_accessor :type
|
@@ -177,8 +178,8 @@ module PerfectShape
|
|
177
178
|
angle += Math.degrees_to_radians(-extent)
|
178
179
|
x2 = Math.cos(angle)
|
179
180
|
y2 = Math.sin(angle)
|
180
|
-
inside = (Line.
|
181
|
-
Line.
|
181
|
+
inside = (Line.relative_counterclockwise(x1, y1, x2, y2, 2*normx, 2*normy) *
|
182
|
+
Line.relative_counterclockwise(x1, y1, x2, y2, 0, 0) >= 0)
|
182
183
|
inarc ? !inside : inside
|
183
184
|
end
|
184
185
|
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright (c) 2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'perfect_shape/ellipse'
|
23
|
+
|
24
|
+
module PerfectShape
|
25
|
+
class Circle < Ellipse
|
26
|
+
MESSAGE_WIDTH_AND_HEIGHT_AND_DIAMETER_NOT_EQUAL = 'Circle width, height, and diameter must all be equal if more than one is specified; or otherwise keep only one of them in arguments!'
|
27
|
+
MESSAGE_RADIUS_X_AND_RADIUS_Y_AND_RADIUS_NOT_EQUAL = 'Circle radius_x, radius_y, and radius must all be equal if more than one is specified; or otherwise keep only one of them in arguments!'
|
28
|
+
|
29
|
+
def initialize(x: 0, y: 0, width: nil, height: nil, diameter: nil, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil, radius: nil)
|
30
|
+
raise MESSAGE_WIDTH_AND_HEIGHT_AND_DIAMETER_NOT_EQUAL if (diameter && width && diameter != width) || (diameter && height && diameter != height) || (width && height && width != height)
|
31
|
+
raise MESSAGE_RADIUS_X_AND_RADIUS_Y_AND_RADIUS_NOT_EQUAL if (radius && radius_x && radius != radius_x) || (radius && radius_y && radius != radius_y) || (radius_x && radius_y && radius_x != radius_y)
|
32
|
+
if center_x && center_y && (radius || radius_x || radius_y)
|
33
|
+
radius ||= radius_x || radius_y
|
34
|
+
self.radius = radius
|
35
|
+
super(center_x: center_x, center_y: center_y, radius_x: self.radius_x, radius_y: self.radius_y)
|
36
|
+
else
|
37
|
+
diameter ||= width || height || 1
|
38
|
+
self.diameter = diameter
|
39
|
+
super(x: x, y: y, width: self.width, height: self.height)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def diameter
|
44
|
+
@radius ? @radius * BigDecimal('2.0') : @diameter
|
45
|
+
end
|
46
|
+
|
47
|
+
def radius
|
48
|
+
@diameter ? @diameter / BigDecimal('2.0') : @radius
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets length, normalizing to BigDecimal
|
52
|
+
def diameter=(value)
|
53
|
+
@diameter = BigDecimal(value.to_s)
|
54
|
+
@radius = nil
|
55
|
+
self.width = value unless width == value
|
56
|
+
self.height = value unless height == value
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets radius, normalizing to BigDecimal
|
60
|
+
def radius=(value)
|
61
|
+
@radius = BigDecimal(value.to_s)
|
62
|
+
@diameter = nil
|
63
|
+
self.radius_x = value unless width == value
|
64
|
+
self.radius_y = value unless height == value
|
65
|
+
end
|
66
|
+
|
67
|
+
def width=(value)
|
68
|
+
super
|
69
|
+
self.diameter = value unless diameter == value
|
70
|
+
self.height = value unless height == value
|
71
|
+
end
|
72
|
+
|
73
|
+
def height=(value)
|
74
|
+
super
|
75
|
+
self.diameter = value unless diameter == value
|
76
|
+
self.width = value unless width == value
|
77
|
+
end
|
78
|
+
|
79
|
+
def radius_x=(value)
|
80
|
+
super
|
81
|
+
self.radius = value unless radius == value
|
82
|
+
self.radius_y = value unless radius_y == value
|
83
|
+
end
|
84
|
+
|
85
|
+
def radius_y=(value)
|
86
|
+
super
|
87
|
+
self.radius = value unless radius == value
|
88
|
+
self.radius_x = value unless radius_x == value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -64,6 +64,7 @@ module PerfectShape
|
|
64
64
|
# the ellipse, {@code false} if the point lies outside of the
|
65
65
|
# ellipse's bounds.
|
66
66
|
def contain?(x_or_point, y = nil)
|
67
|
+
# This is implemented again even though super would have just worked to have an optimized algorithm for Ellipse.
|
67
68
|
x, y = normalize_point(x_or_point, y)
|
68
69
|
return unless x && y
|
69
70
|
ellw = self.width
|
data/lib/perfect_shape/line.rb
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
22
|
require 'perfect_shape/shape'
|
23
|
+
require 'perfect_shape/multi_point'
|
23
24
|
|
24
25
|
module PerfectShape
|
25
26
|
# Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Line2D.html
|
@@ -52,7 +53,7 @@ module PerfectShape
|
|
52
53
|
# @return an integer that indicates the position of the third specified
|
53
54
|
# coordinates with respect to the line segment formed
|
54
55
|
# by the first two specified coordinates.
|
55
|
-
def
|
56
|
+
def relative_counterclockwise(x1, y1, x2, y2, px, py)
|
56
57
|
x2 -= x1;
|
57
58
|
y2 -= y1;
|
58
59
|
px -= x1;
|
@@ -82,6 +83,136 @@ module PerfectShape
|
|
82
83
|
end
|
83
84
|
(ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
|
84
85
|
end
|
86
|
+
|
87
|
+
# Returns the square of the distance from a point to a line segment.
|
88
|
+
# The distance measured is the distance between the specified
|
89
|
+
# point and the closest point between the specified end points.
|
90
|
+
# If the specified point intersects the line segment in between the
|
91
|
+
# end points, this method returns 0.0.
|
92
|
+
#
|
93
|
+
# @param x1 the X coordinate of the start point of the
|
94
|
+
# specified line segment
|
95
|
+
# @param y1 the Y coordinate of the start point of the
|
96
|
+
# specified line segment
|
97
|
+
# @param x2 the X coordinate of the end point of the
|
98
|
+
# specified line segment
|
99
|
+
# @param y2 the Y coordinate of the end point of the
|
100
|
+
# specified line segment
|
101
|
+
# @param px the X coordinate of the specified point being
|
102
|
+
# measured against the specified line segment
|
103
|
+
# @param py the Y coordinate of the specified point being
|
104
|
+
# measured against the specified line segment
|
105
|
+
# @return a double value that is the square of the distance from the
|
106
|
+
# specified point to the specified line segment.
|
107
|
+
def point_segment_distance_square(x1, y1,
|
108
|
+
x2, y2,
|
109
|
+
px, py)
|
110
|
+
x1 = BigDecimal(x1.to_s)
|
111
|
+
y1 = BigDecimal(y1.to_s)
|
112
|
+
x2 = BigDecimal(x2.to_s)
|
113
|
+
y2 = BigDecimal(y2.to_s)
|
114
|
+
px = BigDecimal(px.to_s)
|
115
|
+
py = BigDecimal(py.to_s)
|
116
|
+
# Adjust vectors relative to x1,y1
|
117
|
+
# x2,y2 becomes relative vector from x1,y1 to end of segment
|
118
|
+
x2 -= x1
|
119
|
+
y2 -= y1
|
120
|
+
# px,py becomes relative vector from x1,y1 to test point
|
121
|
+
px -= x1
|
122
|
+
py -= y1
|
123
|
+
dot_product = px * x2 + py * y2;
|
124
|
+
if dot_product <= 0.0
|
125
|
+
# px,py is on the side of x1,y1 away from x2,y2
|
126
|
+
# distance to segment is length of px,py vector
|
127
|
+
# "length of its (clipped) projection" is now 0.0
|
128
|
+
projected_length_square = BigDecimal('0.0');
|
129
|
+
else
|
130
|
+
# switch to backwards vectors relative to x2,y2
|
131
|
+
# x2,y2 are already the negative of x1,y1=>x2,y2
|
132
|
+
# to get px,py to be the negative of px,py=>x2,y2
|
133
|
+
# the dot product of two negated vectors is the same
|
134
|
+
# as the dot product of the two normal vectors
|
135
|
+
px = x2 - px
|
136
|
+
py = y2 - py
|
137
|
+
dot_product = px * x2 + py * y2
|
138
|
+
if dot_product <= 0.0
|
139
|
+
# px,py is on the side of x2,y2 away from x1,y1
|
140
|
+
# distance to segment is length of (backwards) px,py vector
|
141
|
+
# "length of its (clipped) projection" is now 0.0
|
142
|
+
projected_length_square = BigDecimal('0.0')
|
143
|
+
else
|
144
|
+
# px,py is between x1,y1 and x2,y2
|
145
|
+
# dot_product is the length of the px,py vector
|
146
|
+
# projected on the x2,y2=>x1,y1 vector times the
|
147
|
+
# length of the x2,y2=>x1,y1 vector
|
148
|
+
projected_length_square = dot_product * dot_product / (x2 * x2 + y2 * y2)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
# Distance to line is now the length of the relative point
|
152
|
+
# vector minus the length of its projection onto the line
|
153
|
+
# (which is zero if the projection falls outside the range
|
154
|
+
# of the line segment).
|
155
|
+
length_square = px * px + py * py - projected_length_square
|
156
|
+
length_square = BigDecimal('0.0') if length_square < 0
|
157
|
+
length_square
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the distance from a point to a line segment.
|
161
|
+
# The distance measured is the distance between the specified
|
162
|
+
# point and the closest point between the specified end points.
|
163
|
+
# If the specified point intersects the line segment in between the
|
164
|
+
# end points, this method returns 0.0.
|
165
|
+
#
|
166
|
+
# @param x1 the X coordinate of the start point of the
|
167
|
+
# specified line segment
|
168
|
+
# @param y1 the Y coordinate of the start point of the
|
169
|
+
# specified line segment
|
170
|
+
# @param x2 the X coordinate of the end point of the
|
171
|
+
# specified line segment
|
172
|
+
# @param y2 the Y coordinate of the end point of the
|
173
|
+
# specified line segment
|
174
|
+
# @param px the X coordinate of the specified point being
|
175
|
+
# measured against the specified line segment
|
176
|
+
# @param py the Y coordinate of the specified point being
|
177
|
+
# measured against the specified line segment
|
178
|
+
# @return a double value that is the distance from the specified point
|
179
|
+
# to the specified line segment.
|
180
|
+
def point_segment_distance(x1, y1,
|
181
|
+
x2, y2,
|
182
|
+
px, py)
|
183
|
+
BigDecimal(::Math.sqrt(point_segment_distance_square(x1, y1, x2, y2, px, py)).to_s)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
include MultiPoint
|
188
|
+
|
189
|
+
# Checks if polygon contains point denoted by point (two-number Array or x, y args)
|
190
|
+
# using the Ray Casting Algorithm (aka Even-Odd Rule): https://en.wikipedia.org/wiki/Point_in_polygon
|
191
|
+
#
|
192
|
+
# @param x The X coordinate of the point to test.
|
193
|
+
# @param y The Y coordinate of the point to test.
|
194
|
+
#
|
195
|
+
# @return {@code true} if the point lies within the bound of
|
196
|
+
# the polygon, {@code false} if the point lies outside of the
|
197
|
+
# polygon's bounds.
|
198
|
+
def contain?(x_or_point, y = nil, distance: 0)
|
199
|
+
x, y = normalize_point(x_or_point, y)
|
200
|
+
return unless x && y
|
201
|
+
distance = BigDecimal(distance.to_s)
|
202
|
+
# TODO implement contain?(point) with a fuzz factor to enable successfully selecting a line in a GUI application
|
203
|
+
Line.point_segment_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y) <= distance
|
204
|
+
end
|
205
|
+
|
206
|
+
def point_segment_distance(x_or_point, y = nil)
|
207
|
+
x, y = normalize_point(x_or_point, y)
|
208
|
+
return unless x && y
|
209
|
+
Line.point_segment_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
|
210
|
+
end
|
211
|
+
|
212
|
+
def relative_counterclockwise(x_or_point, y = nil)
|
213
|
+
x, y = normalize_point(x_or_point, y)
|
214
|
+
return unless x && y
|
215
|
+
Line.relative_counterclockwise(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
|
85
216
|
end
|
86
217
|
end
|
87
218
|
end
|
data/lib/perfect_shape/math.rb
CHANGED
@@ -6,33 +6,34 @@ module PerfectShape
|
|
6
6
|
# Also includes standard Ruby ::Math utility methods
|
7
7
|
module Math
|
8
8
|
class << self
|
9
|
-
# converts angle from radians to degrees
|
9
|
+
# converts angle from radians to degrees (normalizing to BigDecimal)
|
10
10
|
def radians_to_degrees(radians)
|
11
|
-
(180/Math::PI)*radians
|
11
|
+
(BigDecimal('180')/Math::PI)*BigDecimal(radians.to_s)
|
12
12
|
end
|
13
13
|
|
14
|
-
# converts angle from degrees to radians
|
14
|
+
# converts angle from degrees to radians (normalizing to BigDecimal)
|
15
15
|
def degrees_to_radians(degrees)
|
16
|
-
(Math::PI/180)*degrees
|
16
|
+
(Math::PI/BigDecimal('180'))*BigDecimal(degrees.to_s)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Normalizes the specified angle into the range -180 to 180.
|
20
20
|
def normalize_degrees(angle)
|
21
|
+
angle = BigDecimal(angle.to_s)
|
21
22
|
if angle > 180.0
|
22
23
|
if angle <= (180.0 + 360.0)
|
23
|
-
angle = angle - 360.0
|
24
|
+
angle = angle - BigDecimal('360.0')
|
24
25
|
else
|
25
26
|
angle = Math.ieee_remainder(angle, 360.0)
|
26
27
|
# IEEEremainder can return -180 here for some input values...
|
27
|
-
angle = 180.0 if angle == -180.0
|
28
|
+
angle = BigDecimal('180.0') if angle == -180.0
|
28
29
|
end
|
29
30
|
elsif angle <= -180.0
|
30
31
|
if angle > (-180.0 - 360.0)
|
31
|
-
angle = angle + 360.0
|
32
|
+
angle = angle + BigDecimal('360.0')
|
32
33
|
else
|
33
34
|
angle = Math.ieee_remainder(angle, 360.0)
|
34
35
|
# IEEEremainder can return -180 here for some input values...
|
35
|
-
angle = 180.0 if angle == -180.0
|
36
|
+
angle = BigDecimal('180.0') if angle == -180.0
|
36
37
|
end
|
37
38
|
end
|
38
39
|
angle
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright (c) 2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'perfect_shape/shape'
|
23
|
+
|
24
|
+
module PerfectShape
|
25
|
+
# Represents multi-point shapes like Line, Polygon, and Polyline
|
26
|
+
module MultiPoint
|
27
|
+
attr_reader :points
|
28
|
+
|
29
|
+
def initialize(points: nil)
|
30
|
+
self.points = points || []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets points, normalizing to an Array of Arrays of (x,y) pairs as BigDecimal
|
34
|
+
def points=(the_points)
|
35
|
+
unless the_points.first.is_a?(Array)
|
36
|
+
xs = the_points.each_with_index.select {|n, i| i.even?}.map(&:first)
|
37
|
+
ys = the_points.each_with_index.select {|n, i| i.odd?}.map(&:first)
|
38
|
+
the_points = xs.zip(ys)
|
39
|
+
end
|
40
|
+
@points = the_points.map {|pair| [BigDecimal(pair.first.to_s), BigDecimal(pair.last.to_s)]}
|
41
|
+
end
|
42
|
+
|
43
|
+
def min_x
|
44
|
+
points.map(&:first).min
|
45
|
+
end
|
46
|
+
|
47
|
+
def min_y
|
48
|
+
points.map(&:last).min
|
49
|
+
end
|
50
|
+
|
51
|
+
def max_x
|
52
|
+
points.map(&:first).max
|
53
|
+
end
|
54
|
+
|
55
|
+
def max_y
|
56
|
+
points.map(&:last).max
|
57
|
+
end
|
58
|
+
|
59
|
+
def width
|
60
|
+
max_x - min_x if min_x && max_x
|
61
|
+
end
|
62
|
+
|
63
|
+
def height
|
64
|
+
max_y - min_y if min_y && max_y
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# Copyright (c) 2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'perfect_shape/shape'
|
23
|
+
require 'perfect_shape/multi_point'
|
24
|
+
|
25
|
+
module PerfectShape
|
26
|
+
# Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/Polygon.html
|
27
|
+
class Polygon < Shape
|
28
|
+
include MultiPoint
|
29
|
+
|
30
|
+
# Checks if polygon contains point denoted by point (two-number Array or x, y args)
|
31
|
+
# using the Ray Casting Algorithm (aka Even-Odd Rule): https://en.wikipedia.org/wiki/Point_in_polygon
|
32
|
+
#
|
33
|
+
# @param x The X coordinate of the point to test.
|
34
|
+
# @param y The Y coordinate of the point to test.
|
35
|
+
#
|
36
|
+
# @return {@code true} if the point lies within the bound of
|
37
|
+
# the polygon, {@code false} if the point lies outside of the
|
38
|
+
# polygon's bounds.
|
39
|
+
def contain?(x_or_point, y = nil)
|
40
|
+
x, y = normalize_point(x_or_point, y)
|
41
|
+
return unless x && y
|
42
|
+
npoints = points.count
|
43
|
+
xpoints = points.map(&:first)
|
44
|
+
ypoints = points.map(&:last)
|
45
|
+
return false if npoints <= 2 || !bounding_box.contain?(x, y)
|
46
|
+
hits = 0
|
47
|
+
|
48
|
+
lastx = xpoints[npoints - 1]
|
49
|
+
lasty = ypoints[npoints - 1]
|
50
|
+
|
51
|
+
# Walk the edges of the polygon
|
52
|
+
npoints.times do |i|
|
53
|
+
curx = xpoints[i]
|
54
|
+
cury = ypoints[i]
|
55
|
+
|
56
|
+
if cury == lasty
|
57
|
+
lastx = curx
|
58
|
+
lasty = cury
|
59
|
+
next
|
60
|
+
end
|
61
|
+
|
62
|
+
if curx < lastx
|
63
|
+
if x >= lastx
|
64
|
+
lastx = curx
|
65
|
+
lasty = cury
|
66
|
+
next
|
67
|
+
end
|
68
|
+
leftx = curx
|
69
|
+
else
|
70
|
+
if x >= curx
|
71
|
+
lastx = curx
|
72
|
+
lasty = cury
|
73
|
+
next
|
74
|
+
end
|
75
|
+
leftx = lastx
|
76
|
+
end
|
77
|
+
|
78
|
+
if cury < lasty
|
79
|
+
if y < cury || y >= lasty
|
80
|
+
lastx = curx
|
81
|
+
lasty = cury
|
82
|
+
next
|
83
|
+
end
|
84
|
+
if x < leftx
|
85
|
+
hits += 1
|
86
|
+
lastx = curx
|
87
|
+
lasty = cury
|
88
|
+
next
|
89
|
+
end
|
90
|
+
test1 = x - curx
|
91
|
+
test2 = y - cury
|
92
|
+
else
|
93
|
+
if y < lasty || y >= cury
|
94
|
+
lastx = curx
|
95
|
+
lasty = cury
|
96
|
+
next
|
97
|
+
end
|
98
|
+
if x < leftx
|
99
|
+
hits += 1
|
100
|
+
lastx = curx
|
101
|
+
lasty = cury
|
102
|
+
next
|
103
|
+
end
|
104
|
+
test1 = x - lastx
|
105
|
+
test2 = y - lasty
|
106
|
+
end
|
107
|
+
|
108
|
+
hits += 1 if (test1 < (test2 / (lasty - cury) * (lastx - curx)))
|
109
|
+
end
|
110
|
+
|
111
|
+
(hits & 1) != 0
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -26,7 +26,8 @@ module PerfectShape
|
|
26
26
|
# Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Rectangle2D.html
|
27
27
|
class Rectangle < Shape
|
28
28
|
include RectangularShape
|
29
|
-
|
29
|
+
include Equalizer.new(:x, :y, :width, :height)
|
30
|
+
|
30
31
|
# Checks if rectangle contains point denoted by point (two-number Array or x, y args)
|
31
32
|
#
|
32
33
|
# @param x The X coordinate of the point to test.
|
@@ -54,12 +54,20 @@ module PerfectShape
|
|
54
54
|
@height = BigDecimal(value.to_s)
|
55
55
|
end
|
56
56
|
|
57
|
-
def
|
58
|
-
@x
|
57
|
+
def min_x
|
58
|
+
@x
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
62
|
-
@y
|
61
|
+
def min_y
|
62
|
+
@y
|
63
|
+
end
|
64
|
+
|
65
|
+
def max_x
|
66
|
+
@x + width if @x && width
|
67
|
+
end
|
68
|
+
|
69
|
+
def max_y
|
70
|
+
@y + height if @y && height
|
63
71
|
end
|
64
72
|
end
|
65
73
|
end
|
data/lib/perfect_shape/shape.rb
CHANGED
@@ -20,7 +20,50 @@
|
|
20
20
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
21
|
|
22
22
|
module PerfectShape
|
23
|
+
# Superclass of all shapes
|
23
24
|
class Shape
|
25
|
+
# Subclasses must implement
|
26
|
+
def min_x
|
27
|
+
end
|
28
|
+
|
29
|
+
# Subclasses must implement
|
30
|
+
def min_y
|
31
|
+
end
|
32
|
+
|
33
|
+
# Subclasses must implement
|
34
|
+
def max_x
|
35
|
+
end
|
36
|
+
|
37
|
+
# Subclasses must implement
|
38
|
+
def max_y
|
39
|
+
end
|
40
|
+
|
41
|
+
# Subclasses must implement
|
42
|
+
def width
|
43
|
+
end
|
44
|
+
|
45
|
+
# Subclasses must implement
|
46
|
+
def height
|
47
|
+
end
|
48
|
+
|
49
|
+
# center_x is min_x + width/2.0 by default
|
50
|
+
# Returns nil if min_x or width are nil
|
51
|
+
def center_x
|
52
|
+
min_x + width / BigDecimal('2.0') if min_x && width
|
53
|
+
end
|
54
|
+
|
55
|
+
# center_y is min_y + height/2.0 by default
|
56
|
+
# Returns nil if min_y or height are nil
|
57
|
+
def center_y
|
58
|
+
min_y + height / BigDecimal('2.0') if min_y && height
|
59
|
+
end
|
60
|
+
|
61
|
+
# Rectangle with x = self.min_x, y = self.min_y, width = self.width, height = self.height
|
62
|
+
def bounding_box
|
63
|
+
require 'perfect_shape/rectangle'
|
64
|
+
Rectangle.new(x: min_x, y: min_y, width: width, height: height)
|
65
|
+
end
|
66
|
+
|
24
67
|
# Normalizes point args whether two-number Array or x, y args returning
|
25
68
|
# normalized point array of two BigDecimal's
|
26
69
|
#
|
data/perfect-shape.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: perfect-shape 0.0.
|
5
|
+
# stub: perfect-shape 0.0.9 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "perfect-shape".freeze
|
9
|
-
s.version = "0.0.
|
9
|
+
s.version = "0.0.9"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Andy Maleh".freeze]
|
14
|
-
s.date = "2021-12-
|
15
|
-
s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, polyline, polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves. Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
|
14
|
+
s.date = "2021-12-20"
|
15
|
+
s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon (Ray Casting Algorithm aka Even-Odd Rule), polyline, polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves. Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
|
16
16
|
s.email = "andy.am@gmail.com".freeze
|
17
17
|
s.extra_rdoc_files = [
|
18
18
|
"CHANGELOG.md",
|
@@ -26,9 +26,12 @@ Gem::Specification.new do |s|
|
|
26
26
|
"VERSION",
|
27
27
|
"lib/perfect-shape.rb",
|
28
28
|
"lib/perfect_shape/arc.rb",
|
29
|
+
"lib/perfect_shape/circle.rb",
|
29
30
|
"lib/perfect_shape/ellipse.rb",
|
30
31
|
"lib/perfect_shape/line.rb",
|
31
32
|
"lib/perfect_shape/math.rb",
|
33
|
+
"lib/perfect_shape/multi_point.rb",
|
34
|
+
"lib/perfect_shape/polygon.rb",
|
32
35
|
"lib/perfect_shape/rectangle.rb",
|
33
36
|
"lib/perfect_shape/rectangular_shape.rb",
|
34
37
|
"lib/perfect_shape/shape.rb",
|
@@ -45,12 +48,14 @@ Gem::Specification.new do |s|
|
|
45
48
|
end
|
46
49
|
|
47
50
|
if s.respond_to? :add_runtime_dependency then
|
51
|
+
s.add_runtime_dependency(%q<equalizer>.freeze, ["= 0.0.11"])
|
48
52
|
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
49
53
|
s.add_development_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
50
54
|
s.add_development_dependency(%q<minitest>.freeze, ["~> 5.14.4"])
|
51
55
|
s.add_development_dependency(%q<puts_debuggerer>.freeze, ["~> 0.13.1"])
|
52
56
|
s.add_development_dependency(%q<rake-tui>.freeze, ["> 0"])
|
53
57
|
else
|
58
|
+
s.add_dependency(%q<equalizer>.freeze, ["= 0.0.11"])
|
54
59
|
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
55
60
|
s.add_dependency(%q<juwelier>.freeze, ["~> 2.1.0"])
|
56
61
|
s.add_dependency(%q<minitest>.freeze, ["~> 5.14.4"])
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perfect-shape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Maleh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: equalizer
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.0.11
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.11
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rdoc
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,9 +97,10 @@ dependencies:
|
|
83
97
|
description: Perfect Shape is a collection of pure Ruby geometric algorithms that
|
84
98
|
are mostly useful for GUI manipulation like checking containment of a mouse click
|
85
99
|
point in popular geometry shapes such as rectangle, square, arc (open, chord, and
|
86
|
-
pie), ellipse, circle, polygon
|
87
|
-
lines, bezier curves, and quadratic curves.
|
88
|
-
|
100
|
+
pie), ellipse, circle, polygon (Ray Casting Algorithm aka Even-Odd Rule), polyline,
|
101
|
+
polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves.
|
102
|
+
Additionally, it contains some purely mathematical algorithms like IEEEremainder
|
103
|
+
(also known as IEEE-754 remainder).
|
89
104
|
email: andy.am@gmail.com
|
90
105
|
executables: []
|
91
106
|
extensions: []
|
@@ -100,9 +115,12 @@ files:
|
|
100
115
|
- VERSION
|
101
116
|
- lib/perfect-shape.rb
|
102
117
|
- lib/perfect_shape/arc.rb
|
118
|
+
- lib/perfect_shape/circle.rb
|
103
119
|
- lib/perfect_shape/ellipse.rb
|
104
120
|
- lib/perfect_shape/line.rb
|
105
121
|
- lib/perfect_shape/math.rb
|
122
|
+
- lib/perfect_shape/multi_point.rb
|
123
|
+
- lib/perfect_shape/polygon.rb
|
106
124
|
- lib/perfect_shape/rectangle.rb
|
107
125
|
- lib/perfect_shape/rectangular_shape.rb
|
108
126
|
- lib/perfect_shape/shape.rb
|