perfect-shape 0.1.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a4eb7bc277a02c0795966623e05ee57514483d982bc0d80f264a525f6b3d5afa
4
- data.tar.gz: f070a8292b835f6d2c68ff4ccddd3073b7a83d36e882d4e5c34c6e91a85d88ee
3
+ metadata.gz: d9e16e850ee86ff6e5953dd10f2c1c753109906b671a85115ea1913386a3a736
4
+ data.tar.gz: a5b1fef3214f92dd3185c1ee1c6e3f0522ff278cda0a0f384341c9e4a5bad5f7
5
5
  SHA512:
6
- metadata.gz: 1add8519bdd16f38f8fb8431d8342c77df9f5d8d76f0dc6f1481bba5d9c0a15b653c56a9a0e372082f1590b55f5897ebac3f6a4d4505acd6ba017f257261e80d
7
- data.tar.gz: fc3e2fce45f0a46ad7869fd5fec07d0dcf826e3bd36676439144eb3fd0198b8224d816a26db54858b43d0bb5332ff6b4108950efca3e71c1d5e42cebde4de63f
6
+ metadata.gz: 19c2f3261ef17fa8e048c8e826e56a3f1d07b2ff39a3a8d18832a0b1e2113b63af8ce09aa8a978f6c81a931ffcb8154e0fdc77ed53b0f0937f7d8d6529250fd5
7
+ data.tar.gz: 681654b7cac1242f872225a0726f8f2fa5813a8c61d999342466fa06516ccaeb981be68f5fad003eeb0d86a4b58c698f264e94a356a4ee934361482f6a072726
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.3.1
4
+
5
+ - Check point containment in arc outline with distance tolerance (new method signature: `PerfectShape::Arc#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
6
+ - Check point containment in ellipse outline with distance tolerance (new method signature: `PerfectShape::Ellipse#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
7
+ - Check point containment in circle outline with distance tolerance (new method signature: `PerfectShape::Circle#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
8
+
9
+ ## 0.3.0
10
+
11
+ - Refactoring: rename `distance` option for `#contain?` on `Point`/`Line` into `distance_tolerance`
12
+ - Check point containment in rectangle outline with distance tolerance (new method signature: `PerfectShape::Rectangle#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
13
+ - Check point containment in square outline with distance tolerance (new method signature: `PerfectShape::Square#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
14
+ - Check point containment in polygon outline with distance tolerance (new method signature: `PerfectShape::Polygon#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
15
+
16
+ ## 0.2.0
17
+
18
+ - `PerfectShape::CompositeShape`: aggregate of multiple shapes
19
+ - `PerfectShape::CompositeShape#contain?(x_or_point, y=nil)`
20
+ - `PerfectShape::CompositeShape#==`
21
+
22
+ ## 0.1.2
23
+
24
+ - `PerfectShape::CubicBezierCurve` (two end points and two control points)
25
+ - `PerfectShape::CubicBezierCurve#contain?(x_or_point, y=nil)`
26
+ - `PerfectShape::CubicBezierCurve#==`
27
+ - `PerfectShape::Path` having cubic bezier curves in addition to points, lines, and quadratic bezier curves
28
+
3
29
  ## 0.1.1
4
30
 
5
31
  - `PerfectShape::QuadraticBezierCurve` (two end points and one control point)
@@ -10,7 +36,7 @@
10
36
  ## 0.1.0
11
37
 
12
38
  - `PerfectShape::Path` (having points or lines)
13
- - `PerfectShape::Path#contain?(x_or_point, y=nil, distance: 0)`
39
+ - `PerfectShape::Path#contain?(x_or_point, y=nil, distance_tolerance: 0)`
14
40
  - `PerfectShape::Path#point_crossings(x_or_point, y=nil)`
15
41
  - `PerfectShape::Path#==`
16
42
 
@@ -24,12 +50,12 @@
24
50
 
25
51
  - `PerfectShape::Point`
26
52
  - `PerfectShape::Point#point_distance`
27
- - `PerfectShape::Point#contain?(x_or_point, y=nil, distance: 0)`
53
+ - `PerfectShape::Point#contain?(x_or_point, y=nil, distance_tolerance: 0)`
28
54
  - Refactor `PerfectShape::Point`,`PerfectShape::RectangularShape` to include shared `PerfectShape::PointLocation`
29
55
 
30
56
  ## 0.0.9
31
57
 
32
- - `PerfectShape::Line#contain?(x_or_point, y=nil, distance: 0)` (add a distance tolerance fuzz factor option)
58
+ - `PerfectShape::Line#contain?(x_or_point, y=nil, distance_tolerance: 0)` (add a distance tolerance fuzz factor option)
33
59
 
34
60
  ## 0.0.8
35
61
 
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 0.1.1
1
+ # Perfect Shape 0.3.1
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), and [paths](#perfectshapepath) containing [lines](#perfectshapeline), [quadratic bézier curves](#perfectshapequadraticbeziercurve), and cubic bézier curves (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)).
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.1.1
17
+ gem install perfect-shape -v 0.3.1
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 0.1.1'
23
+ gem 'perfect-shape', '~> 0.3.1'
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,14 +111,23 @@ 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, distance: 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
+ - `#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 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
 
117
118
  Example:
118
119
 
119
120
  ```ruby
121
+ require 'perfect-shape'
122
+
120
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
121
131
  ```
122
132
 
123
133
  ### `PerfectShape::Line`
@@ -133,7 +143,7 @@ Includes `PerfectShape::MultiPoint`
133
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)”.
134
144
  - `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
135
145
  - `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
136
- - `::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 values
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
137
147
  - `#min_x`: min x
138
148
  - `#min_y`: min y
139
149
  - `#max_x`: max x
@@ -143,7 +153,7 @@ Includes `PerfectShape::MultiPoint`
143
153
  - `#center_x`: center x
144
154
  - `#center_y`: center y
145
155
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
146
- - `#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.
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 more successfully.
147
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)”.
148
158
  - `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
149
159
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
@@ -151,7 +161,16 @@ Includes `PerfectShape::MultiPoint`
151
161
  Example:
152
162
 
153
163
  ```ruby
154
- shape = PerfectShape::Line.new(points: [[200, 150], [270, 220]]) # start point and end point
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
155
174
  ```
156
175
 
157
176
  ### `PerfectShape::QuadraticBezierCurve`
@@ -164,7 +183,8 @@ Includes `PerfectShape::MultiPoint`
164
183
 
165
184
  ![quadratic_bezier_curve](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/quadratic_bezier_curve.png)
166
185
 
167
- - `::new(points: [])`: constructs a quadratic bézier curve with three `points` (two end points and one control point) as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y values
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)
168
188
  - `#min_x`: min x
169
189
  - `#min_y`: min y
170
190
  - `#max_x`: max x
@@ -180,7 +200,47 @@ Includes `PerfectShape::MultiPoint`
180
200
  Example:
181
201
 
182
202
  ```ruby
183
- shape = PerfectShape::QuadraticBezierCurve.new(points: [[200, 150], [270, 220], [180, 170]]) # start point, control point, and end point
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
184
244
  ```
185
245
 
186
246
  ### `PerfectShape::Rectangle`
@@ -205,13 +265,26 @@ Includes `PerfectShape::RectangularShape`
205
265
  - `#max_x`: max x
206
266
  - `#max_y`: max y
207
267
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
208
- - `#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 GUI users mouse-click-select a rectangle shape from its outline more successfully
209
269
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
210
270
 
211
271
  Example:
212
272
 
213
273
  ```ruby
274
+ require 'perfect-shape'
275
+
214
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
215
288
  ```
216
289
 
217
290
  ### `PerfectShape::Square`
@@ -235,13 +308,26 @@ Extends `PerfectShape::Rectangle`
235
308
  - `#max_x`: max x
236
309
  - `#max_y`: max y
237
310
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
238
- - `#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 GUI users mouse-click-select a square shape from its outline more successfully
239
312
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
240
313
 
241
314
  Example:
242
315
 
243
316
  ```ruby
317
+ require 'perfect-shape'
318
+
244
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
245
331
  ```
246
332
 
247
333
  ### `PerfectShape::Arc`
@@ -275,14 +361,101 @@ Open Arc | Chord Arc | Pie Arc
275
361
  - `#max_x`: max x
276
362
  - `#max_y`: max y
277
363
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
278
- - `#contain?(x_or_point, y=nil)`: checks if point is inside
364
+ - `#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 GUI users mouse-click-select an arc shape from its outline more successfully
279
365
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
280
366
 
281
367
  Example:
282
368
 
283
369
  ```ruby
284
- shape = PerfectShape::Arc.new(type: :chord, x: 2, y: 3, width: 50, height: 60, start: 30, extent: 90)
285
- shape2 = PerfectShape::Arc.new(type: :chord, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 30, extent: 90)
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: 45, extent: 270)
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
+ shape.contain?(39.5, 33.0, outline: true) # => false
380
+ shape.contain?([39.5, 33.0], outline: true) # => false
381
+ shape2.contain?(39.5, 33.0, outline: true) # => false
382
+ shape2.contain?([39.5, 33.0], outline: true) # => false
383
+ shape.contain?(2.0, 33.0, outline: true) # => true
384
+ shape.contain?([2.0, 33.0], outline: true) # => true
385
+ shape2.contain?(2.0, 33.0, outline: true) # => true
386
+ shape2.contain?([2.0, 33.0], outline: true) # => true
387
+ shape.contain?(3.0, 33.0, outline: true) # => false
388
+ shape.contain?([3.0, 33.0], outline: true) # => false
389
+ shape2.contain?(3.0, 33.0, outline: true) # => false
390
+ shape2.contain?([3.0, 33.0], outline: true) # => false
391
+ shape.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
392
+ shape.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
393
+ shape2.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
394
+ shape2.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
395
+ shape.contain?(shape.center_x, shape.center_y, outline: true) # => false
396
+ shape.contain?([shape.center_x, shape.center_y], outline: true) # => false
397
+ shape2.contain?(shape2.center_x, shape2.center_y, outline: true) # => false
398
+ shape2.contain?([shape2.center_x, shape2.center_y], outline: true) # => false
399
+
400
+ shape3 = PerfectShape::Arc.new(type: :chord, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
401
+ shape4 = PerfectShape::Arc.new(type: :chord, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 45, extent: 270)
402
+
403
+ shape3.contain?(39.5, 33.0) # => true
404
+ shape3.contain?([39.5, 33.0]) # => true
405
+ shape4.contain?(39.5, 33.0) # => true
406
+ shape4.contain?([39.5, 33.0]) # => true
407
+ shape3.contain?(39.5, 33.0, outline: true) # => false
408
+ shape3.contain?([39.5, 33.0], outline: true) # => false
409
+ shape4.contain?(39.5, 33.0, outline: true) # => false
410
+ shape4.contain?([39.5, 33.0], outline: true) # => false
411
+ shape3.contain?(2.0, 33.0, outline: true) # => true
412
+ shape3.contain?([2.0, 33.0], outline: true) # => true
413
+ shape4.contain?(2.0, 33.0, outline: true) # => true
414
+ shape4.contain?([2.0, 33.0], outline: true) # => true
415
+ shape3.contain?(3.0, 33.0, outline: true) # => false
416
+ shape3.contain?([3.0, 33.0], outline: true) # => false
417
+ shape4.contain?(3.0, 33.0, outline: true) # => false
418
+ shape4.contain?([3.0, 33.0], outline: true) # => false
419
+ shape3.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
420
+ shape3.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
421
+ shape4.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
422
+ shape4.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
423
+ shape3.contain?(shape3.center_x, shape3.center_y, outline: true) # => false
424
+ shape3.contain?([shape3.center_x, shape3.center_y], outline: true) # => false
425
+ shape4.contain?(shape4.center_x, shape4.center_y, outline: true) # => false
426
+ shape4.contain?([shape4.center_x, shape4.center_y], outline: true) # => false
427
+
428
+ shape5 = PerfectShape::Arc.new(type: :pie, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
429
+ shape6 = PerfectShape::Arc.new(type: :pie, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 45, extent: 270)
430
+
431
+ shape5.contain?(39.5, 33.0) # => false
432
+ shape5.contain?([39.5, 33.0]) # => false
433
+ shape6.contain?(39.5, 33.0) # => false
434
+ shape6.contain?([39.5, 33.0]) # => false
435
+ shape5.contain?(9.5, 33.0) # => true
436
+ shape5.contain?([9.5, 33.0]) # => true
437
+ shape6.contain?(9.5, 33.0) # => true
438
+ shape6.contain?([9.5, 33.0]) # => true
439
+ shape5.contain?(39.5, 33.0, outline: true) # => false
440
+ shape5.contain?([39.5, 33.0], outline: true) # => false
441
+ shape6.contain?(39.5, 33.0, outline: true) # => false
442
+ shape6.contain?([39.5, 33.0], outline: true) # => false
443
+ shape5.contain?(2.0, 33.0, outline: true) # => true
444
+ shape5.contain?([2.0, 33.0], outline: true) # => true
445
+ shape6.contain?(2.0, 33.0, outline: true) # => true
446
+ shape6.contain?([2.0, 33.0], outline: true) # => true
447
+ shape5.contain?(3.0, 33.0, outline: true) # => false
448
+ shape5.contain?([3.0, 33.0], outline: true) # => false
449
+ shape6.contain?(3.0, 33.0, outline: true) # => false
450
+ shape6.contain?([3.0, 33.0], outline: true) # => false
451
+ shape5.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
452
+ shape5.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
453
+ shape6.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
454
+ shape6.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
455
+ shape5.contain?(shape5.center_x, shape5.center_y, outline: true) # => true
456
+ shape5.contain?([shape5.center_x, shape5.center_y], outline: true) # => true
457
+ shape6.contain?(shape6.center_x, shape6.center_y, outline: true) # => true
458
+ shape6.contain?([shape6.center_x, shape6.center_y], outline: true) # => true
286
459
  ```
287
460
 
288
461
  ### `PerfectShape::Ellipse`
@@ -310,14 +483,37 @@ Extends `PerfectShape::Arc`
310
483
  - `#max_x`: max x
311
484
  - `#max_y`: max y
312
485
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
313
- - `#contain?(x_or_point, y=nil)`: checks if point is inside
486
+ - `#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 GUI users mouse-click-select an ellipse shape from its outline more successfully
314
487
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
315
488
 
316
489
  Example:
317
490
 
318
491
  ```ruby
492
+ require 'perfect-shape'
493
+
319
494
  shape = PerfectShape::Ellipse.new(x: 2, y: 3, width: 50, height: 60)
320
495
  shape2 = PerfectShape::Ellipse.new(center_x: 27, center_y: 33, radius_x: 25, radius_y: 30)
496
+
497
+ shape.contain?(27, 33) # => true
498
+ shape.contain?([27, 33]) # => true
499
+ shape2.contain?(27, 33) # => true
500
+ shape2.contain?([27, 33]) # => true
501
+ shape.contain?(27, 33, outline: true) # => false
502
+ shape.contain?([27, 33], outline: true) # => false
503
+ shape2.contain?(27, 33, outline: true) # => false
504
+ shape2.contain?([27, 33], outline: true) # => false
505
+ shape.contain?(2, 33, outline: true) # => true
506
+ shape.contain?([2, 33], outline: true) # => true
507
+ shape2.contain?(2, 33, outline: true) # => true
508
+ shape2.contain?([2, 33], outline: true) # => true
509
+ shape.contain?(1, 33, outline: true) # => false
510
+ shape.contain?([1, 33], outline: true) # => false
511
+ shape2.contain?(1, 33, outline: true) # => false
512
+ shape2.contain?([1, 33], outline: true) # => false
513
+ shape.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
514
+ shape.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
515
+ shape2.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
516
+ shape2.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
321
517
  ```
322
518
 
323
519
  ### `PerfectShape::Circle`
@@ -347,14 +543,37 @@ Extends `PerfectShape::Ellipse`
347
543
  - `#max_x`: max x
348
544
  - `#max_y`: max y
349
545
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
350
- - `#contain?(x_or_point, y=nil)`: checks if point is inside
546
+ - `#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 GUI users mouse-click-select a circle shape from its outline more successfully
351
547
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
352
548
 
353
549
  Example:
354
550
 
355
551
  ```ruby
552
+ require 'perfect-shape'
553
+
356
554
  shape = PerfectShape::Circle.new(x: 2, y: 3, diameter: 60)
357
555
  shape2 = PerfectShape::Circle.new(center_x: 2 + 30, center_y: 3 + 30, radius: 30)
556
+
557
+ shape.contain?(32, 33) # => true
558
+ shape.contain?([32, 33]) # => true
559
+ shape2.contain?(32, 33) # => true
560
+ shape2.contain?([32, 33]) # => true
561
+ shape.contain?(32, 33, outline: true) # => false
562
+ shape.contain?([32, 33], outline: true) # => false
563
+ shape2.contain?(32, 33, outline: true) # => false
564
+ shape2.contain?([32, 33], outline: true) # => false
565
+ shape.contain?(2, 33, outline: true) # => true
566
+ shape.contain?([2, 33], outline: true) # => true
567
+ shape2.contain?(2, 33, outline: true) # => true
568
+ shape2.contain?([2, 33], outline: true) # => true
569
+ shape.contain?(1, 33, outline: true) # => false
570
+ shape.contain?([1, 33], outline: true) # => false
571
+ shape2.contain?(1, 33, outline: true) # => false
572
+ shape2.contain?([1, 33], outline: true) # => false
573
+ shape.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
574
+ shape.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
575
+ shape2.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
576
+ shape2.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
358
577
  ```
359
578
 
360
579
  ### `PerfectShape::Polygon`
@@ -369,7 +588,7 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
369
588
 
370
589
  ![polygon](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/polygon.png)
371
590
 
372
- - `::new(points: [])`: constructs a polygon with `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y values
591
+ - `::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
373
592
  - `#min_x`: min x
374
593
  - `#min_y`: min y
375
594
  - `#max_x`: max x
@@ -379,13 +598,26 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
379
598
  - `#center_x`: center x
380
599
  - `#center_y`: center y
381
600
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
382
- - `#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))
601
+ - `#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 GUI users mouse-click-select a polygon shape from its outline more successfully
383
602
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
384
603
 
385
604
  Example:
386
605
 
387
606
  ```ruby
607
+ require 'perfect-shape'
608
+
388
609
  shape = PerfectShape::Polygon.new(points: [[200, 150], [270, 170], [250, 220], [220, 190], [200, 200], [180, 170]])
610
+
611
+ shape.contain?(225, 185) # => true
612
+ shape.contain?([225, 185]) # => true
613
+ shape.contain?(225, 185, outline: true) # => false
614
+ shape.contain?([225, 185], outline: true) # => false
615
+ shape.contain?(200, 150, outline: true) # => true
616
+ shape.contain?([200, 150], outline: true) # => true
617
+ shape.contain?(200, 151, outline: true) # => false
618
+ shape.contain?([200, 151], outline: true) # => false
619
+ shape.contain?(200, 151, outline: true, distance_tolerance: 1) # => true
620
+ shape.contain?([200, 151], outline: true, distance_tolerance: 1) # => true
389
621
  ```
390
622
 
391
623
  ### `PerfectShape::Path`
@@ -398,7 +630,7 @@ Includes `PerfectShape::MultiPoint`
398
630
 
399
631
  ![path](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/path.png)
400
632
 
401
- - `::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`, or `PerfectShape::QuadraticBezierCurve`. 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`.
633
+ - `::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`.
402
634
  - `#shapes`: the shapes that the path is composed of (must always start with `PerfectShape::Point` or Array of [x,y] coordinates representing start point)
403
635
  - `#closed?`: returns `true` if closed and `false` otherwise
404
636
  - `#winding_rule`: returns winding rule (`:wind_non_zero` or `:wind_even_odd`)
@@ -419,12 +651,59 @@ Includes `PerfectShape::MultiPoint`
419
651
  Example:
420
652
 
421
653
  ```ruby
654
+ require 'perfect-shape'
655
+
422
656
  path_shapes = []
423
657
  path_shapes << PerfectShape::Point.new(x: 200, y: 150)
424
658
  path_shapes << PerfectShape::Line.new(points: [250, 170]) # no need for start point, just end point
425
659
  path_shapes << PerfectShape::QuadraticBezierCurve.new(points: [[300, 185], [350, 150]]) # no need for start point, just control point and end point
660
+ path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220], [480, 170]]) # no need for start point, just two control points and end point
426
661
 
427
662
  shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
663
+
664
+ shape.contain?(225, 160) # => true
665
+ shape.contain?([225, 160]) # => true
666
+ ```
667
+
668
+ ### `PerfectShape::CompositeShape`
669
+
670
+ Class
671
+
672
+ Extends `PerfectShape::Shape`
673
+
674
+ A composite shape is simply an aggregate of multiple shapes (e.g. square and polygon)
675
+
676
+ ![composite shape](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/composite-shape.png)
677
+
678
+ - `::new(shapes: [])`: constructs a composite shape with `shapes` as `Array` of `PerfectShape::Shape` objects
679
+ - `#shapes`: the shapes that the composite shape is composed of
680
+ - `#min_x`: min x
681
+ - `#min_y`: min y
682
+ - `#max_x`: max x
683
+ - `#max_y`: max y
684
+ - `#width`: width (from min x to max x)
685
+ - `#height`: height (from min y to max y)
686
+ - `#center_x`: center x
687
+ - `#center_y`: center y
688
+ - `#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)
689
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside any of the shapes owned by the composite shape
690
+ - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
691
+
692
+ Example:
693
+
694
+ ```ruby
695
+ require 'perfect-shape'
696
+
697
+ shapes = []
698
+ shapes << PerfectShape::Square.new(x: 120, y: 215, length: 100)
699
+ shapes << PerfectShape::Polygon.new(points: [[120, 215], [170, 165], [220, 215]])
700
+
701
+ shape = PerfectShape::CompositeShape.new(shapes: shapes)
702
+
703
+ shape.contain?(170, 265) # => true
704
+ shape.contain?([170, 265]) # => true
705
+ shape.contain?(170, 190) # => true
706
+ shape.contain?([170, 190]) # => true
428
707
  ```
429
708
 
430
709
  ## Process
@@ -434,7 +713,7 @@ shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule:
434
713
  ## Resources
435
714
 
436
715
  - Rubydoc: https://www.rubydoc.info/gems/perfect-shape
437
- - AWT Geom JavaDoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
716
+ - AWT Geom Javadoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
438
717
 
439
718
  ## TODO
440
719
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.3.1