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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bb743eb7a0d935839d671a33b58a30ea8eb65b945ee20e6016690fd0c6864f8
4
- data.tar.gz: cd5b26777efcf931ed81a84ec63fee3dcaed5dfa3b878dc39350e6eb206f937e
3
+ metadata.gz: 4e7efc4e2333e43b5e4d034e7fa3a3dddbdc9c6fd392e9d2ae1a409995122eb5
4
+ data.tar.gz: 07f098098f6e42d6d71bdab33b44d3250c05c79ee48b2304b60784fba7de3f75
5
5
  SHA512:
6
- metadata.gz: 7adf8c29ae005e8f22c15e460dcf99befd28acef67a5387d950bef1d94781be6fce9cb25b725092892da83a6821b5b59bc699cf5c581af11133d90b4946ed29b
7
- data.tar.gz: 334ef034a6f1010f49ad2fa2edc9dfccae994d0f8524427104100b6f4ccc83a63b276f731d483c494625111ad5a668ac05300c9b589ae57b801645d393130c38
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, distance: 0)`
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, distance: 0)`
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, distance: 0)` (add a distance tolerance fuzz factor option)
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.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), polyline, polyquad, polycubic, and [paths](#perfectshapepath) containing [lines](#perfectshapeline), quadratic bézier curves, 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.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.1.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, 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 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: nil)`: constructs a polygon with `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
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, 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 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: nil)`: constructs a polygon with `points` as `Array` of `Array`s of `[x,y]` pairs or flattened `Array` of alternating x and y values
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: nil, 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), or `PerfectShape::Line`. 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`.
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
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