perfect-shape 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -3
- data/README.md +294 -14
- data/VERSION +1 -1
- data/lib/perfect_shape/composite_shape.rb +72 -0
- data/lib/perfect_shape/cubic_bezier_curve.rb +120 -0
- data/lib/perfect_shape/line.rb +4 -4
- data/lib/perfect_shape/multi_point.rb +2 -2
- data/lib/perfect_shape/path.rb +84 -63
- data/lib/perfect_shape/point.rb +4 -4
- data/lib/perfect_shape/polygon.rb +68 -62
- data/lib/perfect_shape/quadratic_bezier_curve.rb +190 -0
- data/lib/perfect_shape/rectangle.rb +10 -2
- data/perfect-shape.gemspec +8 -5
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e7efc4e2333e43b5e4d034e7fa3a3dddbdc9c6fd392e9d2ae1a409995122eb5
|
4
|
+
data.tar.gz: 07f098098f6e42d6d71bdab33b44d3250c05c79ee48b2304b60784fba7de3f75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a69ec76140e777f303724c2db8d7dd904b2f679b11ce6380629c27ae0f3feb1cc0be0d615b79c085cd276e9a96b9a5842fbf8eb4d810398ae1329a3598b37592
|
7
|
+
data.tar.gz: d87a97b80e155939e380024653b01fe7a230660c0b58957d96f6d3d9392b113996f051bf55dbbc3b935c7d44a763cd686716dc9cb568d7ff85580aa1ca955c9d
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,36 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
- Refactoring: rename `distance` option for `#contain?` on `Point`/`Line` into `distance_tolerance`
|
6
|
+
- Check point containment in rectangle outline with distance tolerance: `PerfectShape::Rectangle#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`
|
7
|
+
- Check point containment in square outline with distance tolerance: `PerfectShape::Square#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`
|
8
|
+
- Check point containment in polygon outline with distance tolerance: `PerfectShape::Polygon#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`
|
9
|
+
|
10
|
+
## 0.2.0
|
11
|
+
|
12
|
+
- `PerfectShape::CompositeShape`: aggregate of multiple shapes
|
13
|
+
- `PerfectShape::CompositeShape#contain?(x_or_point, y=nil)`
|
14
|
+
- `PerfectShape::CompositeShape#==`
|
15
|
+
|
16
|
+
## 0.1.2
|
17
|
+
|
18
|
+
- `PerfectShape::CubicBezierCurve` (two end points and two control points)
|
19
|
+
- `PerfectShape::CubicBezierCurve#contain?(x_or_point, y=nil)`
|
20
|
+
- `PerfectShape::CubicBezierCurve#==`
|
21
|
+
- `PerfectShape::Path` having cubic bezier curves in addition to points, lines, and quadratic bezier curves
|
22
|
+
|
23
|
+
## 0.1.1
|
24
|
+
|
25
|
+
- `PerfectShape::QuadraticBezierCurve` (two end points and one control point)
|
26
|
+
- `PerfectShape::QuadraticBezierCurve#contain?(x_or_point, y=nil)`
|
27
|
+
- `PerfectShape::QuadraticBezierCurve#==`
|
28
|
+
- `PerfectShape::Path` having quadratic bezier curves in addition to points and lines
|
29
|
+
|
3
30
|
## 0.1.0
|
4
31
|
|
5
32
|
- `PerfectShape::Path` (having points or lines)
|
6
|
-
- `PerfectShape::Path#contain?(x_or_point, y=nil,
|
33
|
+
- `PerfectShape::Path#contain?(x_or_point, y=nil, distance_tolerance: 0)`
|
7
34
|
- `PerfectShape::Path#point_crossings(x_or_point, y=nil)`
|
8
35
|
- `PerfectShape::Path#==`
|
9
36
|
|
@@ -17,12 +44,12 @@
|
|
17
44
|
|
18
45
|
- `PerfectShape::Point`
|
19
46
|
- `PerfectShape::Point#point_distance`
|
20
|
-
- `PerfectShape::Point#contain?(x_or_point, y=nil,
|
47
|
+
- `PerfectShape::Point#contain?(x_or_point, y=nil, distance_tolerance: 0)`
|
21
48
|
- Refactor `PerfectShape::Point`,`PerfectShape::RectangularShape` to include shared `PerfectShape::PointLocation`
|
22
49
|
|
23
50
|
## 0.0.9
|
24
51
|
|
25
|
-
- `PerfectShape::Line#contain?(x_or_point, y=nil,
|
52
|
+
- `PerfectShape::Line#contain?(x_or_point, y=nil, distance_tolerance: 0)` (add a distance tolerance fuzz factor option)
|
26
53
|
|
27
54
|
## 0.0.8
|
28
55
|
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# Perfect Shape 0.
|
1
|
+
# Perfect Shape 0.3.0
|
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
|
[![Test](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml/badge.svg)](https://github.com/AndyObtiva/perfect-shape/actions/workflows/ruby.yml)
|
5
5
|
|
6
|
-
[`PerfectShape`](https://rubygems.org/gems/perfect-shape) 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](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon),
|
6
|
+
[`PerfectShape`](https://rubygems.org/gems/perfect-shape) 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](#perfectshapepoint) in popular geometry shapes such as [rectangle](#perfectshaperectangle), [square](#perfectshapesquare), [arc](#perfectshapearc) (open, chord, and pie), [ellipse](#perfectshapeellipse), [circle](#perfectshapecircle), [polygon](#perfectshapepolygon), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and [cubic bezier curves](#perfectshapecubicbeziercurve) (including both [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm), aka [Even-odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule), and [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm), aka [Nonzero Rule](https://en.wikipedia.org/wiki/Nonzero-rule)).
|
7
7
|
|
8
8
|
Additionally, [`PerfectShape::Math`](#perfectshapemath) contains some purely mathematical algorithms, like [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985).
|
9
9
|
|
@@ -14,13 +14,13 @@ To ensure high accuracy, this library does all its mathematical operations with
|
|
14
14
|
Run:
|
15
15
|
|
16
16
|
```
|
17
|
-
gem install perfect-shape -v 0.
|
17
|
+
gem install perfect-shape -v 0.3.0
|
18
18
|
```
|
19
19
|
|
20
20
|
Or include in Bundler `Gemfile`:
|
21
21
|
|
22
22
|
```ruby
|
23
|
-
gem 'perfect-shape', '~> 0.
|
23
|
+
gem 'perfect-shape', '~> 0.3.0'
|
24
24
|
```
|
25
25
|
|
26
26
|
And, run:
|
@@ -56,6 +56,7 @@ This is a base class for all shapes. It is not meant to be used directly. Subcla
|
|
56
56
|
- `#center_y`: center y
|
57
57
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height just as those of shape
|
58
58
|
- `#normalize_point(x_or_point, y = nil)`: normalizes point into an `Array` of `[x,y]` coordinates
|
59
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
59
60
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
60
61
|
|
61
62
|
### `PerfectShape::PointLocation`
|
@@ -110,10 +111,25 @@ Points are simply represented by an `Array` of `[x,y]` coordinates when used wit
|
|
110
111
|
- `#center_x`: center x (always x)
|
111
112
|
- `#center_y`: center y (always y)
|
112
113
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
113
|
-
- `#contain?(x_or_point, y=nil,
|
114
|
+
- `#contain?(x_or_point, y=nil, distance_tolerance: 0)`: checks if point matches self, with a distance tolerance (0 by default). Distance tolerance provides a fuzz factor that for example enables GUI users to mouse-click-select a point shape in a GUI more successfully.
|
114
115
|
- `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
|
115
116
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
116
117
|
|
118
|
+
Example:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
require 'perfect-shape'
|
122
|
+
|
123
|
+
shape = PerfectShape::Point.new(x: 200, y: 150)
|
124
|
+
|
125
|
+
shape.contain?(200, 150) # => true
|
126
|
+
shape.contain?([200, 150]) # => true
|
127
|
+
shape.contain?(200, 151) # => false
|
128
|
+
shape.contain?([200, 151]) # => false
|
129
|
+
shape.contain?(200, 151, distance_tolerance: 5) # => true
|
130
|
+
shape.contain?([200, 151], distance_tolerance: 5) # => true
|
131
|
+
```
|
132
|
+
|
117
133
|
### `PerfectShape::Line`
|
118
134
|
|
119
135
|
Class
|
@@ -127,7 +143,7 @@ Includes `PerfectShape::MultiPoint`
|
|
127
143
|
- `::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)”.
|
128
144
|
- `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
|
129
145
|
- `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
|
130
|
-
- `::new(points:
|
146
|
+
- `::new(points: [])`: constructs a line with two `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
131
147
|
- `#min_x`: min x
|
132
148
|
- `#min_y`: min y
|
133
149
|
- `#max_x`: max x
|
@@ -137,11 +153,96 @@ Includes `PerfectShape::MultiPoint`
|
|
137
153
|
- `#center_x`: center x
|
138
154
|
- `#center_y`: center y
|
139
155
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
140
|
-
- `#contain?(x_or_point, y=nil,
|
156
|
+
- `#contain?(x_or_point, y=nil, distance_tolerance: 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.
|
141
157
|
- `#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)”.
|
142
158
|
- `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
|
143
159
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
144
160
|
|
161
|
+
Example:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
require 'perfect-shape'
|
165
|
+
|
166
|
+
shape = PerfectShape::Line.new(points: [[0, 0], [100, 100]]) # start point and end point
|
167
|
+
|
168
|
+
shape.contain?(50, 50) # => true
|
169
|
+
shape.contain?([50, 50]) # => true
|
170
|
+
shape.contain?(50, 51) # => false
|
171
|
+
shape.contain?([50, 51]) # => false
|
172
|
+
shape.contain?(50, 51, distance_tolerance: 5) # => true
|
173
|
+
shape.contain?([50, 51], distance_tolerance: 5) # => true
|
174
|
+
```
|
175
|
+
|
176
|
+
### `PerfectShape::QuadraticBezierCurve`
|
177
|
+
|
178
|
+
Class
|
179
|
+
|
180
|
+
Extends `PerfectShape::Shape`
|
181
|
+
|
182
|
+
Includes `PerfectShape::MultiPoint`
|
183
|
+
|
184
|
+
![quadratic_bezier_curve](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/quadratic_bezier_curve.png)
|
185
|
+
|
186
|
+
- `::new(points: [])`: constructs a quadratic bézier curve with three `points` (start point, control point, and end point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
187
|
+
- `#points`: points (start point, control point, and end point)
|
188
|
+
- `#min_x`: min x
|
189
|
+
- `#min_y`: min y
|
190
|
+
- `#max_x`: max x
|
191
|
+
- `#max_y`: max y
|
192
|
+
- `#width`: width (from min x to max x)
|
193
|
+
- `#height`: height (from min y to max y)
|
194
|
+
- `#center_x`: center x
|
195
|
+
- `#center_y`: center y
|
196
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape (bounding box only guarantees that the shape is within it, but it might be bigger than the shape)
|
197
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
198
|
+
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
199
|
+
|
200
|
+
Example:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
require 'perfect-shape'
|
204
|
+
|
205
|
+
shape = PerfectShape::QuadraticBezierCurve.new(points: [[200, 150], [270, 320], [380, 150]]) # start point, control point, and end point
|
206
|
+
|
207
|
+
shape.contain?(270, 220) # => true
|
208
|
+
shape.contain?([270, 220]) # => true
|
209
|
+
```
|
210
|
+
|
211
|
+
### `PerfectShape::CubicBezierCurve`
|
212
|
+
|
213
|
+
Class
|
214
|
+
|
215
|
+
Extends `PerfectShape::Shape`
|
216
|
+
|
217
|
+
Includes `PerfectShape::MultiPoint`
|
218
|
+
|
219
|
+
![cubic_bezier_curve](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/cubic_bezier_curve.png)
|
220
|
+
|
221
|
+
- `::new(points: [])`: constructs a cubic bézier curve with four `points` (start point, two control points, and end point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
222
|
+
- `#points`: points (start point, two control points, and end point)
|
223
|
+
- `#min_x`: min x
|
224
|
+
- `#min_y`: min y
|
225
|
+
- `#max_x`: max x
|
226
|
+
- `#max_y`: max y
|
227
|
+
- `#width`: width (from min x to max x)
|
228
|
+
- `#height`: height (from min y to max y)
|
229
|
+
- `#center_x`: center x
|
230
|
+
- `#center_y`: center y
|
231
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape (bounding box only guarantees that the shape is within it, but it might be bigger than the shape)
|
232
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
233
|
+
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
234
|
+
|
235
|
+
Example:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
require 'perfect-shape'
|
239
|
+
|
240
|
+
shape = PerfectShape::CubicBezierCurve.new(points: [[200, 150], [235, 235], [270, 320], [380, 150]]) # start point, two control points, and end point
|
241
|
+
|
242
|
+
shape.contain?(270, 220) # => true
|
243
|
+
shape.contain?([270, 220]) # => true
|
244
|
+
```
|
245
|
+
|
145
246
|
### `PerfectShape::Rectangle`
|
146
247
|
|
147
248
|
Class
|
@@ -164,9 +265,28 @@ Includes `PerfectShape::RectangularShape`
|
|
164
265
|
- `#max_x`: max x
|
165
266
|
- `#max_y`: max y
|
166
267
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
167
|
-
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
268
|
+
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help users mouse-click-select a rectangle shape from its edges in a GUI more successfully
|
168
269
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
169
270
|
|
271
|
+
Example:
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
require 'perfect-shape'
|
275
|
+
|
276
|
+
shape = PerfectShape::Rectangle.new(x: 15, y: 30, width: 200, height: 100)
|
277
|
+
|
278
|
+
shape.contain?(115, 80) # => true
|
279
|
+
shape.contain?([115, 80]) # => true
|
280
|
+
shape.contain?(115, 80, outline: true) # => false
|
281
|
+
shape.contain?([115, 80], outline: true) # => false
|
282
|
+
shape.contain?(115, 30, outline: true) # => true
|
283
|
+
shape.contain?([115, 30], outline: true) # => true
|
284
|
+
shape.contain?(115, 31, outline: true) # => false
|
285
|
+
shape.contain?([115, 31], outline: true) # => false
|
286
|
+
shape.contain?(115, 31, outline: true, distance_tolerance: 1) # => true
|
287
|
+
shape.contain?([115, 31], outline: true, distance_tolerance: 1) # => true
|
288
|
+
```
|
289
|
+
|
170
290
|
### `PerfectShape::Square`
|
171
291
|
|
172
292
|
Class
|
@@ -188,9 +308,28 @@ Extends `PerfectShape::Rectangle`
|
|
188
308
|
- `#max_x`: max x
|
189
309
|
- `#max_y`: max y
|
190
310
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
191
|
-
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
311
|
+
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help users mouse-click-select a square shape from its edges in a GUI more successfully
|
192
312
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
193
313
|
|
314
|
+
Example:
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
require 'perfect-shape'
|
318
|
+
|
319
|
+
shape = PerfectShape::Square.new(x: 15, y: 30, length: 200)
|
320
|
+
|
321
|
+
shape.contain?(115, 130) # => true
|
322
|
+
shape.contain?([115, 130]) # => true
|
323
|
+
shape.contain?(115, 130, outline: true) # => false
|
324
|
+
shape.contain?([115, 130], outline: true) # => false
|
325
|
+
shape.contain?(115, 30, outline: true) # => true
|
326
|
+
shape.contain?([115, 30], outline: true) # => true
|
327
|
+
shape.contain?(115, 31, outline: true) # => false
|
328
|
+
shape.contain?([115, 31], outline: true) # => false
|
329
|
+
shape.contain?(115, 31, outline: true, distance_tolerance: 1) # => true
|
330
|
+
shape.contain?([115, 31], outline: true, distance_tolerance: 1) # => true
|
331
|
+
```
|
332
|
+
|
194
333
|
### `PerfectShape::Arc`
|
195
334
|
|
196
335
|
Class
|
@@ -225,6 +364,40 @@ Open Arc | Chord Arc | Pie Arc
|
|
225
364
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
226
365
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
227
366
|
|
367
|
+
Example:
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
require 'perfect-shape'
|
371
|
+
|
372
|
+
shape = PerfectShape::Arc.new(type: :open, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
|
373
|
+
shape2 = PerfectShape::Arc.new(type: :open, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 30, extent: 90)
|
374
|
+
|
375
|
+
shape.contain?(39.5, 33.0) # => true
|
376
|
+
shape.contain?([39.5, 33.0]) # => true
|
377
|
+
shape2.contain?(39.5, 33.0) # => true
|
378
|
+
shape2.contain?([39.5, 33.0]) # => true
|
379
|
+
|
380
|
+
shape3 = PerfectShape::Arc.new(type: :chord, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
|
381
|
+
shape4 = PerfectShape::Arc.new(type: :chord, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 30, extent: 90)
|
382
|
+
|
383
|
+
shape3.contain?(39.5, 33.0) # => true
|
384
|
+
shape3.contain?([39.5, 33.0]) # => true
|
385
|
+
shape4.contain?(39.5, 33.0) # => true
|
386
|
+
shape4.contain?([39.5, 33.0]) # => true
|
387
|
+
|
388
|
+
shape5 = PerfectShape::Arc.new(type: :pie, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
|
389
|
+
shape6 = PerfectShape::Arc.new(type: :pie, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 30, extent: 90)
|
390
|
+
|
391
|
+
shape5.contain?(39.5, 33.0) # => false
|
392
|
+
shape5.contain?([39.5, 33.0]) # => false
|
393
|
+
shape6.contain?(39.5, 33.0) # => false
|
394
|
+
shape6.contain?([39.5, 33.0]) # => false
|
395
|
+
shape5.contain?(9.5, 33.0) # => true
|
396
|
+
shape5.contain?([9.5, 33.0]) # => true
|
397
|
+
shape6.contain?(9.5, 33.0) # => true
|
398
|
+
shape6.contain?([9.5, 33.0]) # => true
|
399
|
+
```
|
400
|
+
|
228
401
|
### `PerfectShape::Ellipse`
|
229
402
|
|
230
403
|
Class
|
@@ -253,6 +426,20 @@ Extends `PerfectShape::Arc`
|
|
253
426
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
254
427
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
255
428
|
|
429
|
+
Example:
|
430
|
+
|
431
|
+
```ruby
|
432
|
+
require 'perfect-shape'
|
433
|
+
|
434
|
+
shape = PerfectShape::Ellipse.new(x: 2, y: 3, width: 50, height: 60)
|
435
|
+
shape2 = PerfectShape::Ellipse.new(center_x: 27, center_y: 33, radius_x: 25, radius_y: 30)
|
436
|
+
|
437
|
+
shape.contain?(27, 33) # => true
|
438
|
+
shape.contain?([27, 33]) # => true
|
439
|
+
shape2.contain?(27, 33) # => true
|
440
|
+
shape2.contain?([27, 33]) # => true
|
441
|
+
```
|
442
|
+
|
256
443
|
### `PerfectShape::Circle`
|
257
444
|
|
258
445
|
Class
|
@@ -283,6 +470,20 @@ Extends `PerfectShape::Ellipse`
|
|
283
470
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside
|
284
471
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
285
472
|
|
473
|
+
Example:
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
require 'perfect-shape'
|
477
|
+
|
478
|
+
shape = PerfectShape::Circle.new(x: 2, y: 3, diameter: 60)
|
479
|
+
shape2 = PerfectShape::Circle.new(center_x: 2 + 30, center_y: 3 + 30, radius: 30)
|
480
|
+
|
481
|
+
shape.contain?(32, 33) # => true
|
482
|
+
shape.contain?([32, 33]) # => true
|
483
|
+
shape2.contain?(32, 33) # => true
|
484
|
+
shape2.contain?([32, 33]) # => true
|
485
|
+
```
|
486
|
+
|
286
487
|
### `PerfectShape::Polygon`
|
287
488
|
|
288
489
|
Class
|
@@ -291,9 +492,11 @@ Extends `PerfectShape::Shape`
|
|
291
492
|
|
292
493
|
Includes `PerfectShape::MultiPoint`
|
293
494
|
|
495
|
+
A polygon can be thought of as a special case of [path](#perfectshapepath) that is closed, has the [Even-Odd](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule) winding rule, and consists of lines only.
|
496
|
+
|
294
497
|
![polygon](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/polygon.png)
|
295
498
|
|
296
|
-
- `::new(points:
|
499
|
+
- `::new(points: [])`: constructs a polygon with `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y coordinates
|
297
500
|
- `#min_x`: min x
|
298
501
|
- `#min_y`: min y
|
299
502
|
- `#max_x`: max x
|
@@ -303,9 +506,28 @@ Includes `PerfectShape::MultiPoint`
|
|
303
506
|
- `#center_x`: center x
|
304
507
|
- `#center_y`: center y
|
305
508
|
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
306
|
-
- `#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))
|
509
|
+
- `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it 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)). Otherwise, when `outline` is `true`, it checks if point is on the outline. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help users mouse-click-select a polygon shape from its edges in a GUI more successfully
|
307
510
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
308
511
|
|
512
|
+
Example:
|
513
|
+
|
514
|
+
```ruby
|
515
|
+
require 'perfect-shape'
|
516
|
+
|
517
|
+
shape = PerfectShape::Polygon.new(points: [[200, 150], [270, 170], [250, 220], [220, 190], [200, 200], [180, 170]])
|
518
|
+
|
519
|
+
shape.contain?(225, 185) # => true
|
520
|
+
shape.contain?([225, 185]) # => true
|
521
|
+
shape.contain?(225, 185, outline: true) # => false
|
522
|
+
shape.contain?([225, 185], outline: true) # => false
|
523
|
+
shape.contain?(200, 150, outline: true) # => true
|
524
|
+
shape.contain?([200, 150], outline: true) # => true
|
525
|
+
shape.contain?(200, 151, outline: true) # => false
|
526
|
+
shape.contain?([200, 151], outline: true) # => false
|
527
|
+
shape.contain?(200, 151, outline: true, distance_tolerance: 1) # => true
|
528
|
+
shape.contain?([200, 151], outline: true, distance_tolerance: 1) # => true
|
529
|
+
```
|
530
|
+
|
309
531
|
### `PerfectShape::Path`
|
310
532
|
|
311
533
|
Class
|
@@ -316,8 +538,8 @@ Includes `PerfectShape::MultiPoint`
|
|
316
538
|
|
317
539
|
![path](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/path.png)
|
318
540
|
|
319
|
-
- `::new(shapes:
|
320
|
-
- `#shapes`: the shapes that the path is composed of
|
541
|
+
- `::new(shapes: [], closed: false, winding_rule: :wind_non_zero)`: constructs a path with `shapes` as `Array` of shape objects, which can be `PerfectShape::Point` (or `Array` of `[x, y]` coordinates), `PerfectShape::Line`, `PerfectShape::QuadraticBezierCurve`, or `PerfectShape::CubicBezierCurve`. If a path is closed, its last point is automatically connected to its first point with a line segment. The winding rule can be `:wind_non_zero` (default) or `:wind_even_odd`.
|
542
|
+
- `#shapes`: the shapes that the path is composed of (must always start with `PerfectShape::Point` or Array of [x,y] coordinates representing start point)
|
321
543
|
- `#closed?`: returns `true` if closed and `false` otherwise
|
322
544
|
- `#winding_rule`: returns winding rule (`:wind_non_zero` or `:wind_even_odd`)
|
323
545
|
- `#points`: path points calculated (derived) from shapes
|
@@ -329,11 +551,69 @@ Includes `PerfectShape::MultiPoint`
|
|
329
551
|
- `#height`: height (from min y to max y)
|
330
552
|
- `#center_x`: center x
|
331
553
|
- `#center_y`: center y
|
332
|
-
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
|
554
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape (bounding box only guarantees that the shape is within it, but it might be bigger than the shape)
|
333
555
|
- `#contain?(x_or_point, y=nil)`: checks if point is inside path utilizing the configured winding rule, which can be the [Nonzero-Rule](https://en.wikipedia.org/wiki/Nonzero-rule) (aka [Winding Number Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm)) or the [Even-Odd Rule](https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule) (aka [Ray Casting Algorithm](https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm))
|
334
556
|
- `#point_crossings(x_or_point, y=nil)`: calculates the number of times the given path crosses the ray extending to the right from (x,y)
|
335
557
|
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
336
558
|
|
559
|
+
Example:
|
560
|
+
|
561
|
+
```ruby
|
562
|
+
require 'perfect-shape'
|
563
|
+
|
564
|
+
path_shapes = []
|
565
|
+
path_shapes << PerfectShape::Point.new(x: 200, y: 150)
|
566
|
+
path_shapes << PerfectShape::Line.new(points: [250, 170]) # no need for start point, just end point
|
567
|
+
path_shapes << PerfectShape::QuadraticBezierCurve.new(points: [[300, 185], [350, 150]]) # no need for start point, just control point and end point
|
568
|
+
path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220], [480, 170]]) # no need for start point, just two control points and end point
|
569
|
+
|
570
|
+
shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
|
571
|
+
|
572
|
+
shape.contain?(225, 160) # => true
|
573
|
+
shape.contain?([225, 160]) # => true
|
574
|
+
```
|
575
|
+
|
576
|
+
### `PerfectShape::CompositeShape`
|
577
|
+
|
578
|
+
Class
|
579
|
+
|
580
|
+
Extends `PerfectShape::Shape`
|
581
|
+
|
582
|
+
A composite shape is simply an aggregate of multiple shapes (e.g. square and polygon)
|
583
|
+
|
584
|
+
![composite shape](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/composite-shape.png)
|
585
|
+
|
586
|
+
- `::new(shapes: [])`: constructs a composite shape with `shapes` as `Array` of `PerfectShape::Shape` objects
|
587
|
+
- `#shapes`: the shapes that the composite shape is composed of
|
588
|
+
- `#min_x`: min x
|
589
|
+
- `#min_y`: min y
|
590
|
+
- `#max_x`: max x
|
591
|
+
- `#max_y`: max y
|
592
|
+
- `#width`: width (from min x to max x)
|
593
|
+
- `#height`: height (from min y to max y)
|
594
|
+
- `#center_x`: center x
|
595
|
+
- `#center_y`: center y
|
596
|
+
- `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape (bounding box only guarantees that the shape is within it, but it might be bigger than the shape)
|
597
|
+
- `#contain?(x_or_point, y=nil)`: checks if point is inside any of the shapes owned by the composite shape
|
598
|
+
- `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
|
599
|
+
|
600
|
+
Example:
|
601
|
+
|
602
|
+
```ruby
|
603
|
+
require 'perfect-shape'
|
604
|
+
|
605
|
+
shapes = []
|
606
|
+
shapes << PerfectShape::Square.new(x: 120, y: 215, length: 100)
|
607
|
+
shapes << PerfectShape::Polygon.new(points: [[120, 215], [170, 165], [220, 215]])
|
608
|
+
|
609
|
+
shape = PerfectShape::CompositeShape.new(shapes: shapes)
|
610
|
+
|
611
|
+
shape.contain?(170, 265) # => true
|
612
|
+
shape.contain?([170, 265]) # => true
|
613
|
+
shape.contain?(170, 190) # => true
|
614
|
+
shape.contain?([170, 190]) # => true
|
615
|
+
```
|
616
|
+
|
337
617
|
## Process
|
338
618
|
|
339
619
|
[Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
@@ -0,0 +1,72 @@
|
|
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/point'
|
24
|
+
require 'perfect_shape/line'
|
25
|
+
require 'perfect_shape/quadratic_bezier_curve'
|
26
|
+
require 'perfect_shape/cubic_bezier_curve'
|
27
|
+
require 'perfect_shape/multi_point'
|
28
|
+
|
29
|
+
module PerfectShape
|
30
|
+
# A composite of multiple shapes
|
31
|
+
class CompositeShape < Shape
|
32
|
+
include Equalizer.new(:shapes)
|
33
|
+
|
34
|
+
attr_accessor :shapes
|
35
|
+
|
36
|
+
# Constructs from multiple shapes
|
37
|
+
def initialize(shapes: [])
|
38
|
+
self.shapes = shapes
|
39
|
+
end
|
40
|
+
|
41
|
+
def min_x
|
42
|
+
shapes.map(&:min_x).min
|
43
|
+
end
|
44
|
+
|
45
|
+
def min_y
|
46
|
+
shapes.map(&:min_y).min
|
47
|
+
end
|
48
|
+
|
49
|
+
def max_x
|
50
|
+
shapes.map(&:max_x).max
|
51
|
+
end
|
52
|
+
|
53
|
+
def max_y
|
54
|
+
shapes.map(&:max_y).max
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks if composite shape contains point (two-number Array or x, y args)
|
58
|
+
# by comparing against all shapes it consists of
|
59
|
+
#
|
60
|
+
# @param x The X coordinate of the point to test.
|
61
|
+
# @param y The Y coordinate of the point to test.
|
62
|
+
#
|
63
|
+
# @return true if the point lies within the bound of
|
64
|
+
# the composite shape or false if the point lies outside of the
|
65
|
+
# path's bounds.
|
66
|
+
def contain?(x_or_point, y = nil)
|
67
|
+
x, y = normalize_point(x_or_point, y)
|
68
|
+
return unless x && y
|
69
|
+
shapes.any? {|shape| shape.contain?(x, y) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|