perfect-shape 0.2.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -3
- data/LICENSE.txt +1 -1
- data/README.md +178 -31
- data/VERSION +1 -1
- data/lib/perfect-shape.rb +1 -1
- data/lib/perfect_shape/arc.rb +50 -35
- data/lib/perfect_shape/circle.rb +1 -1
- data/lib/perfect_shape/composite_shape.rb +1 -1
- data/lib/perfect_shape/cubic_bezier_curve.rb +106 -16
- data/lib/perfect_shape/ellipse.rb +13 -9
- data/lib/perfect_shape/line.rb +5 -5
- data/lib/perfect_shape/math.rb +21 -0
- data/lib/perfect_shape/multi_point.rb +1 -1
- data/lib/perfect_shape/path.rb +1 -1
- data/lib/perfect_shape/point.rb +5 -5
- data/lib/perfect_shape/point_location.rb +1 -1
- data/lib/perfect_shape/polygon.rb +74 -64
- data/lib/perfect_shape/quadratic_bezier_curve.rb +172 -86
- data/lib/perfect_shape/rectangle.rb +17 -3
- data/lib/perfect_shape/rectangular_shape.rb +1 -1
- data/lib/perfect_shape/shape.rb +5 -1
- data/lib/perfect_shape/square.rb +1 -1
- data/perfect-shape.gemspec +4 -4
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 42bff3742b2697349882d865b7c5da9c06ce4a259850996bb92f291e8a74130e
         | 
| 4 | 
            +
              data.tar.gz: 5a2f007129f1e0f483a48ea607a7ee3e44cbdf01b400a656f406b412dc8e9524
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9bf87bebe682dfbab8a4b082caa321ac0b7051510d86e946e3f214337e818a18da7cee51bc12432510567deaa68f65a69cb3c7da10b7c9153045803e1d25f0b7
         | 
| 7 | 
            +
              data.tar.gz: fa32aa79d500055b81b572e17ded38429503a3757430bba1c0fea021853e4377d7e025071cf52ed8c2f965c8bc8ecd35601276f2a1bcda847202c6f49c8422a0
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,36 @@ | |
| 1 1 | 
             
            # Change Log
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 0.3.3
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Check point containment in quadratic bezier curve outline with distance tolerance (new method signature: `PerfectShape::QuadraticBezierCurve#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
         | 
| 6 | 
            +
            - `PerfectShape::QuadraticBezierCurve#curve_center_point`, `PerfectShape::QuadraticBezierCurve#curve_center_x`, `PerfectShape::QuadraticBezierCurve#curve_center_y`
         | 
| 7 | 
            +
            - `PerfectShape::QuadraticBezierCurve#subdivisions(level=1)`
         | 
| 8 | 
            +
            - `PerfectShape::QuadraticBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
         | 
| 9 | 
            +
            - `PerfectShape::Polygon#edges` returns edges of polygon as `PerfectShape::Line` objects
         | 
| 10 | 
            +
            - `PerfectShape::Rectangle#edges` returns edges of rectangle as `PerfectShape::Line` objects
         | 
| 11 | 
            +
            - `PerfectShape::Square#edges` returns edges of square as `PerfectShape::Line` objects
         | 
| 12 | 
            +
            - Rename `number` arg to `level` in `CubicBezierCurve#subdivisions(level=1)`, making it signify the level of subdivision recursion to perform.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            ## 0.3.2
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            - Check point containment in cubic bezier curve outline with distance tolerance (new method signature: `PerfectShape::CubicBezierCurve#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
         | 
| 17 | 
            +
            - `PerfectShape::CubicBezierCurve#curve_center_point`, `PerfectShape::CubicBezierCurve#curve_center_x`, `PerfectShape::CubicBezierCurve#curve_center_y`
         | 
| 18 | 
            +
            - `PerfectShape::CubicBezierCurve#subdivisions(level=1)`
         | 
| 19 | 
            +
            - `PerfectShape::CubicBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## 0.3.1
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            - 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)`)
         | 
| 24 | 
            +
            - 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)`)
         | 
| 25 | 
            +
            - 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)`)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## 0.3.0
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            - Refactoring: rename `distance` option for `#contain?` on `Point`/`Line` into `distance_tolerance`
         | 
| 30 | 
            +
            - 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)`)
         | 
| 31 | 
            +
            - 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)`)
         | 
| 32 | 
            +
            - 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)`)
         | 
| 33 | 
            +
             | 
| 3 34 | 
             
            ## 0.2.0
         | 
| 4 35 |  | 
| 5 36 | 
             
            - `PerfectShape::CompositeShape`: aggregate of multiple shapes
         | 
| @@ -23,7 +54,7 @@ | |
| 23 54 | 
             
            ## 0.1.0
         | 
| 24 55 |  | 
| 25 56 | 
             
            - `PerfectShape::Path` (having points or lines)
         | 
| 26 | 
            -
            - `PerfectShape::Path#contain?(x_or_point, y=nil,  | 
| 57 | 
            +
            - `PerfectShape::Path#contain?(x_or_point, y=nil, distance_tolerance: 0)`
         | 
| 27 58 | 
             
            - `PerfectShape::Path#point_crossings(x_or_point, y=nil)`
         | 
| 28 59 | 
             
            - `PerfectShape::Path#==`
         | 
| 29 60 |  | 
| @@ -37,12 +68,12 @@ | |
| 37 68 |  | 
| 38 69 | 
             
            - `PerfectShape::Point`
         | 
| 39 70 | 
             
            - `PerfectShape::Point#point_distance`
         | 
| 40 | 
            -
            - `PerfectShape::Point#contain?(x_or_point, y=nil,  | 
| 71 | 
            +
            - `PerfectShape::Point#contain?(x_or_point, y=nil, distance_tolerance: 0)`
         | 
| 41 72 | 
             
            - Refactor `PerfectShape::Point`,`PerfectShape::RectangularShape` to include shared `PerfectShape::PointLocation`
         | 
| 42 73 |  | 
| 43 74 | 
             
            ## 0.0.9
         | 
| 44 75 |  | 
| 45 | 
            -
            - `PerfectShape::Line#contain?(x_or_point, y=nil,  | 
| 76 | 
            +
            - `PerfectShape::Line#contain?(x_or_point, y=nil, distance_tolerance: 0)` (add a distance tolerance fuzz factor option)
         | 
| 46 77 |  | 
| 47 78 | 
             
            ## 0.0.8
         | 
| 48 79 |  | 
    
        data/LICENSE.txt
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,9 +1,9 @@ | |
| 1 | 
            -
            # Perfect Shape 0. | 
| 1 | 
            +
            # Perfect Shape 0.3.3
         | 
| 2 2 | 
             
            ## Geometric Algorithms
         | 
| 3 3 | 
             
            [](http://badge.fury.io/rb/perfect-shape)
         | 
| 4 4 | 
             
            [](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  | 
| 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.3
         | 
| 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.3'
         | 
| 24 24 | 
             
            ```
         | 
| 25 25 |  | 
| 26 26 | 
             
            And, run:
         | 
| @@ -38,7 +38,7 @@ Module | |
| 38 38 | 
             
            - `::degrees_to_radians(angle)`: converts degrees to radians
         | 
| 39 39 | 
             
            - `::radians_to_degrees(angle)`: converts radians to degrees
         | 
| 40 40 | 
             
            - `::normalize_degrees(angle)`: normalizes the specified angle into the range -180 to 180.
         | 
| 41 | 
            -
            - `::ieee_remainder(x, y)` (alias: `ieee754_remainder`): [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985) (different from standard  | 
| 41 | 
            +
            - `::ieee_remainder(x, y)` (alias: `ieee754_remainder`): [IEEE 754-1985 Remainder](https://en.wikipedia.org/wiki/IEEE_754-1985) (different from standard `%` modulo operator as it operates on floats and could return a negative result)
         | 
| 42 42 |  | 
| 43 43 | 
             
            ### `PerfectShape::Shape`
         | 
| 44 44 |  | 
| @@ -55,9 +55,9 @@ This is a base class for all shapes. It is not meant to be used directly. Subcla | |
| 55 55 | 
             
            - `#center_x`: center x
         | 
| 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 | 
            -
            - `#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
         | 
| 60 58 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 59 | 
            +
            - `#normalize_point(x_or_point, y = nil)`: normalizes point into an `Array` of `[x,y]` coordinates
         | 
| 60 | 
            +
            - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside if `outline` is `false` or if point is on the outline if `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 shape from its outline more successfully
         | 
| 61 61 |  | 
| 62 62 | 
             
            ### `PerfectShape::PointLocation`
         | 
| 63 63 |  | 
| @@ -111,9 +111,9 @@ Points are simply represented by an `Array` of `[x,y]` coordinates when used wit | |
| 111 111 | 
             
            - `#center_x`: center x (always x)
         | 
| 112 112 | 
             
            - `#center_y`: center y (always y)
         | 
| 113 113 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 114 | 
            -
            - `#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.
         | 
| 115 | 
            -
            - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
         | 
| 116 114 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 115 | 
            +
            - `#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.
         | 
| 116 | 
            +
            - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
         | 
| 117 117 |  | 
| 118 118 | 
             
            Example:
         | 
| 119 119 |  | 
| @@ -126,8 +126,8 @@ shape.contain?(200, 150) # => true | |
| 126 126 | 
             
            shape.contain?([200, 150]) # => true
         | 
| 127 127 | 
             
            shape.contain?(200, 151) # => false
         | 
| 128 128 | 
             
            shape.contain?([200, 151]) # => false
         | 
| 129 | 
            -
            shape.contain?(200, 151,  | 
| 130 | 
            -
            shape.contain?([200, 151],  | 
| 129 | 
            +
            shape.contain?(200, 151, distance_tolerance: 5) # => true
         | 
| 130 | 
            +
            shape.contain?([200, 151], distance_tolerance: 5) # => true
         | 
| 131 131 | 
             
            ```
         | 
| 132 132 |  | 
| 133 133 | 
             
            ### `PerfectShape::Line`
         | 
| @@ -153,10 +153,10 @@ Includes `PerfectShape::MultiPoint` | |
| 153 153 | 
             
            - `#center_x`: center x
         | 
| 154 154 | 
             
            - `#center_y`: center y
         | 
| 155 155 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 156 | 
            -
            -  | 
| 156 | 
            +
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 157 | 
            +
            - `#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.
         | 
| 157 158 | 
             
            - `#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)”.
         | 
| 158 159 | 
             
            - `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
         | 
| 159 | 
            -
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 160 160 |  | 
| 161 161 | 
             
            Example:
         | 
| 162 162 |  | 
| @@ -169,8 +169,8 @@ shape.contain?(50, 50) # => true | |
| 169 169 | 
             
            shape.contain?([50, 50]) # => true
         | 
| 170 170 | 
             
            shape.contain?(50, 51) # => false
         | 
| 171 171 | 
             
            shape.contain?([50, 51]) # => false
         | 
| 172 | 
            -
            shape.contain?(50, 51,  | 
| 173 | 
            -
            shape.contain?([50, 51],  | 
| 172 | 
            +
            shape.contain?(50, 51, distance_tolerance: 5) # => true
         | 
| 173 | 
            +
            shape.contain?([50, 51], distance_tolerance: 5) # => true
         | 
| 174 174 | 
             
            ```
         | 
| 175 175 |  | 
| 176 176 | 
             
            ### `PerfectShape::QuadraticBezierCurve`
         | 
| @@ -194,8 +194,13 @@ Includes `PerfectShape::MultiPoint` | |
| 194 194 | 
             
            - `#center_x`: center x
         | 
| 195 195 | 
             
            - `#center_y`: center y
         | 
| 196 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 197 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 198 | 
            +
            - `#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 quadratic bezier curve shape from its outline more successfully
         | 
| 199 | 
            +
            - `#curve_center_point`: point at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 200 | 
            +
            - `#curve_center_x`: point x coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 201 | 
            +
            - `#curve_center_y`: point y coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 202 | 
            +
            - `#subdivisions(level=1)`: subdivides quadratic bezier curve at its center into into 2 quadratic bezier curves by default, or more if `level` of recursion is specified. The resulting number of subdivisions is `2` to the power of `level`.
         | 
| 203 | 
            +
            - `#point_segment_distance(x_or_point, y=nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`: calculates distance from point to curve segment. It does so by subdividing curve into smaller curves and checking against the curve center points until the distance is less than `minimum_distance_threshold`, to avoid being an overly costly operation.
         | 
| 199 204 |  | 
| 200 205 | 
             
            Example:
         | 
| 201 206 |  | 
| @@ -206,6 +211,14 @@ shape = PerfectShape::QuadraticBezierCurve.new(points: [[200, 150], [270, 320], | |
| 206 211 |  | 
| 207 212 | 
             
            shape.contain?(270, 220) # => true
         | 
| 208 213 | 
             
            shape.contain?([270, 220]) # => true
         | 
| 214 | 
            +
            shape.contain?(270, 220, outline: true) # => false
         | 
| 215 | 
            +
            shape.contain?([270, 220], outline: true) # => false
         | 
| 216 | 
            +
            shape.contain?(280, 235, outline: true) # => true
         | 
| 217 | 
            +
            shape.contain?([280, 235], outline: true) # => true
         | 
| 218 | 
            +
            shape.contain?(281, 235, outline: true) # => false
         | 
| 219 | 
            +
            shape.contain?([281, 235], outline: true) # => false
         | 
| 220 | 
            +
            shape.contain?(281, 235, outline: true, distance_tolerance: 1) # => true
         | 
| 221 | 
            +
            shape.contain?([281, 235], outline: true, distance_tolerance: 1) # => true
         | 
| 209 222 | 
             
            ```
         | 
| 210 223 |  | 
| 211 224 | 
             
            ### `PerfectShape::CubicBezierCurve`
         | 
| @@ -229,8 +242,13 @@ Includes `PerfectShape::MultiPoint` | |
| 229 242 | 
             
            - `#center_x`: center x
         | 
| 230 243 | 
             
            - `#center_y`: center y
         | 
| 231 244 | 
             
            - `#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 245 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 246 | 
            +
            - `#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 cubic bezier curve shape from its outline more successfully
         | 
| 247 | 
            +
            - `#curve_center_point`: point at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 248 | 
            +
            - `#curve_center_x`: point x coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 249 | 
            +
            - `#curve_center_y`: point y coordinate at the center of the curve (not the center of the bounding box area like `center_x` and `center_y`)
         | 
| 250 | 
            +
            - `#subdivisions(level=1)`: subdivides cubic bezier curve at its center into into 2 cubic bezier curves by default, or more if `level` of recursion is specified. The resulting number of subdivisions is `2` to the power of `level`.
         | 
| 251 | 
            +
            - `#point_segment_distance(x_or_point, y=nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`: calculates distance from point to curve segment. It does so by subdividing curve into smaller curves and checking against the curve center points until the distance is less than `minimum_distance_threshold`, to avoid being an overly costly operation.
         | 
| 234 252 |  | 
| 235 253 | 
             
            Example:
         | 
| 236 254 |  | 
| @@ -241,6 +259,14 @@ shape = PerfectShape::CubicBezierCurve.new(points: [[200, 150], [235, 235], [270 | |
| 241 259 |  | 
| 242 260 | 
             
            shape.contain?(270, 220) # => true
         | 
| 243 261 | 
             
            shape.contain?([270, 220]) # => true
         | 
| 262 | 
            +
            shape.contain?(270, 220, outline: true) # => false
         | 
| 263 | 
            +
            shape.contain?([270, 220], outline: true) # => false
         | 
| 264 | 
            +
            shape.contain?(261.875, 245.625, outline: true) # => true
         | 
| 265 | 
            +
            shape.contain?([261.875, 245.625], outline: true) # => true
         | 
| 266 | 
            +
            shape.contain?(261.875, 246.625, outline: true) # => false
         | 
| 267 | 
            +
            shape.contain?([261.875, 246.625], outline: true) # => false
         | 
| 268 | 
            +
            shape.contain?(261.875, 246.625, outline: true, distance_tolerance: 1) # => true
         | 
| 269 | 
            +
            shape.contain?([261.875, 246.625], outline: true, distance_tolerance: 1) # => true
         | 
| 244 270 | 
             
            ```
         | 
| 245 271 |  | 
| 246 272 | 
             
            ### `PerfectShape::Rectangle`
         | 
| @@ -265,8 +291,9 @@ Includes `PerfectShape::RectangularShape` | |
| 265 291 | 
             
            - `#max_x`: max x
         | 
| 266 292 | 
             
            - `#max_y`: max y
         | 
| 267 293 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 268 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside
         | 
| 269 294 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 295 | 
            +
            - `#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
         | 
| 296 | 
            +
            - `#edges`: edges of rectangle as `PerfectShape::Line` objects
         | 
| 270 297 |  | 
| 271 298 | 
             
            Example:
         | 
| 272 299 |  | 
| @@ -277,6 +304,14 @@ shape = PerfectShape::Rectangle.new(x: 15, y: 30, width: 200, height: 100) | |
| 277 304 |  | 
| 278 305 | 
             
            shape.contain?(115, 80) # => true
         | 
| 279 306 | 
             
            shape.contain?([115, 80]) # => true
         | 
| 307 | 
            +
            shape.contain?(115, 80, outline: true) # => false
         | 
| 308 | 
            +
            shape.contain?([115, 80], outline: true) # => false
         | 
| 309 | 
            +
            shape.contain?(115, 30, outline: true) # => true
         | 
| 310 | 
            +
            shape.contain?([115, 30], outline: true) # => true
         | 
| 311 | 
            +
            shape.contain?(115, 31, outline: true) # => false
         | 
| 312 | 
            +
            shape.contain?([115, 31], outline: true) # => false
         | 
| 313 | 
            +
            shape.contain?(115, 31, outline: true, distance_tolerance: 1) # => true
         | 
| 314 | 
            +
            shape.contain?([115, 31], outline: true, distance_tolerance: 1) # => true
         | 
| 280 315 | 
             
            ```
         | 
| 281 316 |  | 
| 282 317 | 
             
            ### `PerfectShape::Square`
         | 
| @@ -300,8 +335,9 @@ Extends `PerfectShape::Rectangle` | |
| 300 335 | 
             
            - `#max_x`: max x
         | 
| 301 336 | 
             
            - `#max_y`: max y
         | 
| 302 337 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 303 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside
         | 
| 304 338 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 339 | 
            +
            - `#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
         | 
| 340 | 
            +
            - `#edges`: edges of square as `PerfectShape::Line` objects
         | 
| 305 341 |  | 
| 306 342 | 
             
            Example:
         | 
| 307 343 |  | 
| @@ -312,6 +348,14 @@ shape = PerfectShape::Square.new(x: 15, y: 30, length: 200) | |
| 312 348 |  | 
| 313 349 | 
             
            shape.contain?(115, 130) # => true
         | 
| 314 350 | 
             
            shape.contain?([115, 130]) # => true
         | 
| 351 | 
            +
            shape.contain?(115, 130, outline: true) # => false
         | 
| 352 | 
            +
            shape.contain?([115, 130], outline: true) # => false
         | 
| 353 | 
            +
            shape.contain?(115, 30, outline: true) # => true
         | 
| 354 | 
            +
            shape.contain?([115, 30], outline: true) # => true
         | 
| 355 | 
            +
            shape.contain?(115, 31, outline: true) # => false
         | 
| 356 | 
            +
            shape.contain?([115, 31], outline: true) # => false
         | 
| 357 | 
            +
            shape.contain?(115, 31, outline: true, distance_tolerance: 1) # => true
         | 
| 358 | 
            +
            shape.contain?([115, 31], outline: true, distance_tolerance: 1) # => true
         | 
| 315 359 | 
             
            ```
         | 
| 316 360 |  | 
| 317 361 | 
             
            ### `PerfectShape::Arc`
         | 
| @@ -345,8 +389,8 @@ Open Arc | Chord Arc | Pie Arc | |
| 345 389 | 
             
            - `#max_x`: max x
         | 
| 346 390 | 
             
            - `#max_y`: max y
         | 
| 347 391 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 348 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside
         | 
| 349 392 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 393 | 
            +
            - `#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
         | 
| 350 394 |  | 
| 351 395 | 
             
            Example:
         | 
| 352 396 |  | 
| @@ -354,23 +398,63 @@ Example: | |
| 354 398 | 
             
            require 'perfect-shape'
         | 
| 355 399 |  | 
| 356 400 | 
             
            shape = PerfectShape::Arc.new(type: :open, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
         | 
| 357 | 
            -
            shape2 = PerfectShape::Arc.new(type: :open, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start:  | 
| 401 | 
            +
            shape2 = PerfectShape::Arc.new(type: :open, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 45, extent: 270)
         | 
| 358 402 |  | 
| 359 403 | 
             
            shape.contain?(39.5, 33.0) # => true
         | 
| 360 404 | 
             
            shape.contain?([39.5, 33.0]) # => true
         | 
| 361 405 | 
             
            shape2.contain?(39.5, 33.0) # => true
         | 
| 362 406 | 
             
            shape2.contain?([39.5, 33.0]) # => true
         | 
| 407 | 
            +
            shape.contain?(39.5, 33.0, outline: true) # => false
         | 
| 408 | 
            +
            shape.contain?([39.5, 33.0], outline: true) # => false
         | 
| 409 | 
            +
            shape2.contain?(39.5, 33.0, outline: true) # => false
         | 
| 410 | 
            +
            shape2.contain?([39.5, 33.0], outline: true) # => false
         | 
| 411 | 
            +
            shape.contain?(2.0, 33.0, outline: true) # => true
         | 
| 412 | 
            +
            shape.contain?([2.0, 33.0], outline: true) # => true
         | 
| 413 | 
            +
            shape2.contain?(2.0, 33.0, outline: true) # => true
         | 
| 414 | 
            +
            shape2.contain?([2.0, 33.0], outline: true) # => true
         | 
| 415 | 
            +
            shape.contain?(3.0, 33.0, outline: true) # => false
         | 
| 416 | 
            +
            shape.contain?([3.0, 33.0], outline: true) # => false
         | 
| 417 | 
            +
            shape2.contain?(3.0, 33.0, outline: true) # => false
         | 
| 418 | 
            +
            shape2.contain?([3.0, 33.0], outline: true) # => false
         | 
| 419 | 
            +
            shape.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 420 | 
            +
            shape.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 421 | 
            +
            shape2.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 422 | 
            +
            shape2.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 423 | 
            +
            shape.contain?(shape.center_x, shape.center_y, outline: true) # => false
         | 
| 424 | 
            +
            shape.contain?([shape.center_x, shape.center_y], outline: true) # => false
         | 
| 425 | 
            +
            shape2.contain?(shape2.center_x, shape2.center_y, outline: true) # => false
         | 
| 426 | 
            +
            shape2.contain?([shape2.center_x, shape2.center_y], outline: true) # => false
         | 
| 363 427 |  | 
| 364 428 | 
             
            shape3 = PerfectShape::Arc.new(type: :chord, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
         | 
| 365 | 
            -
            shape4 = PerfectShape::Arc.new(type: :chord, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start:  | 
| 429 | 
            +
            shape4 = PerfectShape::Arc.new(type: :chord, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 45, extent: 270)
         | 
| 366 430 |  | 
| 367 431 | 
             
            shape3.contain?(39.5, 33.0) # => true
         | 
| 368 432 | 
             
            shape3.contain?([39.5, 33.0]) # => true
         | 
| 369 433 | 
             
            shape4.contain?(39.5, 33.0) # => true
         | 
| 370 434 | 
             
            shape4.contain?([39.5, 33.0]) # => true
         | 
| 435 | 
            +
            shape3.contain?(39.5, 33.0, outline: true) # => false
         | 
| 436 | 
            +
            shape3.contain?([39.5, 33.0], outline: true) # => false
         | 
| 437 | 
            +
            shape4.contain?(39.5, 33.0, outline: true) # => false
         | 
| 438 | 
            +
            shape4.contain?([39.5, 33.0], outline: true) # => false
         | 
| 439 | 
            +
            shape3.contain?(2.0, 33.0, outline: true) # => true
         | 
| 440 | 
            +
            shape3.contain?([2.0, 33.0], outline: true) # => true
         | 
| 441 | 
            +
            shape4.contain?(2.0, 33.0, outline: true) # => true
         | 
| 442 | 
            +
            shape4.contain?([2.0, 33.0], outline: true) # => true
         | 
| 443 | 
            +
            shape3.contain?(3.0, 33.0, outline: true) # => false
         | 
| 444 | 
            +
            shape3.contain?([3.0, 33.0], outline: true) # => false
         | 
| 445 | 
            +
            shape4.contain?(3.0, 33.0, outline: true) # => false
         | 
| 446 | 
            +
            shape4.contain?([3.0, 33.0], outline: true) # => false
         | 
| 447 | 
            +
            shape3.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 448 | 
            +
            shape3.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 449 | 
            +
            shape4.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 450 | 
            +
            shape4.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 451 | 
            +
            shape3.contain?(shape3.center_x, shape3.center_y, outline: true) # => false
         | 
| 452 | 
            +
            shape3.contain?([shape3.center_x, shape3.center_y], outline: true) # => false
         | 
| 453 | 
            +
            shape4.contain?(shape4.center_x, shape4.center_y, outline: true) # => false
         | 
| 454 | 
            +
            shape4.contain?([shape4.center_x, shape4.center_y], outline: true) # => false
         | 
| 371 455 |  | 
| 372 456 | 
             
            shape5 = PerfectShape::Arc.new(type: :pie, x: 2, y: 3, width: 50, height: 60, start: 45, extent: 270)
         | 
| 373 | 
            -
            shape6 = PerfectShape::Arc.new(type: :pie, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start:  | 
| 457 | 
            +
            shape6 = PerfectShape::Arc.new(type: :pie, center_x: 2 + 25, center_y: 3 + 30, radius_x: 25, radius_y: 30, start: 45, extent: 270)
         | 
| 374 458 |  | 
| 375 459 | 
             
            shape5.contain?(39.5, 33.0) # => false
         | 
| 376 460 | 
             
            shape5.contain?([39.5, 33.0]) # => false
         | 
| @@ -380,6 +464,26 @@ shape5.contain?(9.5, 33.0) # => true | |
| 380 464 | 
             
            shape5.contain?([9.5, 33.0]) # => true
         | 
| 381 465 | 
             
            shape6.contain?(9.5, 33.0) # => true
         | 
| 382 466 | 
             
            shape6.contain?([9.5, 33.0]) # => true
         | 
| 467 | 
            +
            shape5.contain?(39.5, 33.0, outline: true) # => false
         | 
| 468 | 
            +
            shape5.contain?([39.5, 33.0], outline: true) # => false
         | 
| 469 | 
            +
            shape6.contain?(39.5, 33.0, outline: true) # => false
         | 
| 470 | 
            +
            shape6.contain?([39.5, 33.0], outline: true) # => false
         | 
| 471 | 
            +
            shape5.contain?(2.0, 33.0, outline: true) # => true
         | 
| 472 | 
            +
            shape5.contain?([2.0, 33.0], outline: true) # => true
         | 
| 473 | 
            +
            shape6.contain?(2.0, 33.0, outline: true) # => true
         | 
| 474 | 
            +
            shape6.contain?([2.0, 33.0], outline: true) # => true
         | 
| 475 | 
            +
            shape5.contain?(3.0, 33.0, outline: true) # => false
         | 
| 476 | 
            +
            shape5.contain?([3.0, 33.0], outline: true) # => false
         | 
| 477 | 
            +
            shape6.contain?(3.0, 33.0, outline: true) # => false
         | 
| 478 | 
            +
            shape6.contain?([3.0, 33.0], outline: true) # => false
         | 
| 479 | 
            +
            shape5.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 480 | 
            +
            shape5.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 481 | 
            +
            shape6.contain?(3.0, 33.0, outline: true, distance_tolerance: 1.0) # => true
         | 
| 482 | 
            +
            shape6.contain?([3.0, 33.0], outline: true, distance_tolerance: 1.0) # => true
         | 
| 483 | 
            +
            shape5.contain?(shape5.center_x, shape5.center_y, outline: true) # => true
         | 
| 484 | 
            +
            shape5.contain?([shape5.center_x, shape5.center_y], outline: true) # => true
         | 
| 485 | 
            +
            shape6.contain?(shape6.center_x, shape6.center_y, outline: true) # => true
         | 
| 486 | 
            +
            shape6.contain?([shape6.center_x, shape6.center_y], outline: true) # => true
         | 
| 383 487 | 
             
            ```
         | 
| 384 488 |  | 
| 385 489 | 
             
            ### `PerfectShape::Ellipse`
         | 
| @@ -407,8 +511,8 @@ Extends `PerfectShape::Arc` | |
| 407 511 | 
             
            - `#max_x`: max x
         | 
| 408 512 | 
             
            - `#max_y`: max y
         | 
| 409 513 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 410 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside
         | 
| 411 514 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 515 | 
            +
            - `#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
         | 
| 412 516 |  | 
| 413 517 | 
             
            Example:
         | 
| 414 518 |  | 
| @@ -422,6 +526,22 @@ shape.contain?(27, 33) # => true | |
| 422 526 | 
             
            shape.contain?([27, 33]) # => true
         | 
| 423 527 | 
             
            shape2.contain?(27, 33) # => true
         | 
| 424 528 | 
             
            shape2.contain?([27, 33]) # => true
         | 
| 529 | 
            +
            shape.contain?(27, 33, outline: true) # => false
         | 
| 530 | 
            +
            shape.contain?([27, 33], outline: true) # => false
         | 
| 531 | 
            +
            shape2.contain?(27, 33, outline: true) # => false
         | 
| 532 | 
            +
            shape2.contain?([27, 33], outline: true) # => false
         | 
| 533 | 
            +
            shape.contain?(2, 33, outline: true) # => true
         | 
| 534 | 
            +
            shape.contain?([2, 33], outline: true) # => true
         | 
| 535 | 
            +
            shape2.contain?(2, 33, outline: true) # => true
         | 
| 536 | 
            +
            shape2.contain?([2, 33], outline: true) # => true
         | 
| 537 | 
            +
            shape.contain?(1, 33, outline: true) # => false
         | 
| 538 | 
            +
            shape.contain?([1, 33], outline: true) # => false
         | 
| 539 | 
            +
            shape2.contain?(1, 33, outline: true) # => false
         | 
| 540 | 
            +
            shape2.contain?([1, 33], outline: true) # => false
         | 
| 541 | 
            +
            shape.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
         | 
| 542 | 
            +
            shape.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
         | 
| 543 | 
            +
            shape2.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
         | 
| 544 | 
            +
            shape2.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
         | 
| 425 545 | 
             
            ```
         | 
| 426 546 |  | 
| 427 547 | 
             
            ### `PerfectShape::Circle`
         | 
| @@ -451,8 +571,8 @@ Extends `PerfectShape::Ellipse` | |
| 451 571 | 
             
            - `#max_x`: max x
         | 
| 452 572 | 
             
            - `#max_y`: max y
         | 
| 453 573 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 454 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside
         | 
| 455 574 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 575 | 
            +
            - `#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
         | 
| 456 576 |  | 
| 457 577 | 
             
            Example:
         | 
| 458 578 |  | 
| @@ -466,6 +586,22 @@ shape.contain?(32, 33) # => true | |
| 466 586 | 
             
            shape.contain?([32, 33]) # => true
         | 
| 467 587 | 
             
            shape2.contain?(32, 33) # => true
         | 
| 468 588 | 
             
            shape2.contain?([32, 33]) # => true
         | 
| 589 | 
            +
            shape.contain?(32, 33, outline: true) # => false
         | 
| 590 | 
            +
            shape.contain?([32, 33], outline: true) # => false
         | 
| 591 | 
            +
            shape2.contain?(32, 33, outline: true) # => false
         | 
| 592 | 
            +
            shape2.contain?([32, 33], outline: true) # => false
         | 
| 593 | 
            +
            shape.contain?(2, 33, outline: true) # => true
         | 
| 594 | 
            +
            shape.contain?([2, 33], outline: true) # => true
         | 
| 595 | 
            +
            shape2.contain?(2, 33, outline: true) # => true
         | 
| 596 | 
            +
            shape2.contain?([2, 33], outline: true) # => true
         | 
| 597 | 
            +
            shape.contain?(1, 33, outline: true) # => false
         | 
| 598 | 
            +
            shape.contain?([1, 33], outline: true) # => false
         | 
| 599 | 
            +
            shape2.contain?(1, 33, outline: true) # => false
         | 
| 600 | 
            +
            shape2.contain?([1, 33], outline: true) # => false
         | 
| 601 | 
            +
            shape.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
         | 
| 602 | 
            +
            shape.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
         | 
| 603 | 
            +
            shape2.contain?(1, 33, outline: true, distance_tolerance: 1) # => true
         | 
| 604 | 
            +
            shape2.contain?([1, 33], outline: true, distance_tolerance: 1) # => true
         | 
| 469 605 | 
             
            ```
         | 
| 470 606 |  | 
| 471 607 | 
             
            ### `PerfectShape::Polygon`
         | 
| @@ -490,8 +626,9 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that | |
| 490 626 | 
             
            - `#center_x`: center x
         | 
| 491 627 | 
             
            - `#center_y`: center y
         | 
| 492 628 | 
             
            - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
         | 
| 493 | 
            -
            - `#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))
         | 
| 494 629 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 630 | 
            +
            - `#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
         | 
| 631 | 
            +
            - `#edges`: edges of polygon as `PerfectShape::Line` objects
         | 
| 495 632 |  | 
| 496 633 | 
             
            Example:
         | 
| 497 634 |  | 
| @@ -502,6 +639,14 @@ shape = PerfectShape::Polygon.new(points: [[200, 150], [270, 170], [250, 220], [ | |
| 502 639 |  | 
| 503 640 | 
             
            shape.contain?(225, 185) # => true
         | 
| 504 641 | 
             
            shape.contain?([225, 185]) # => true
         | 
| 642 | 
            +
            shape.contain?(225, 185, outline: true) # => false
         | 
| 643 | 
            +
            shape.contain?([225, 185], outline: true) # => false
         | 
| 644 | 
            +
            shape.contain?(200, 150, outline: true) # => true
         | 
| 645 | 
            +
            shape.contain?([200, 150], outline: true) # => true
         | 
| 646 | 
            +
            shape.contain?(200, 151, outline: true) # => false
         | 
| 647 | 
            +
            shape.contain?([200, 151], outline: true) # => false
         | 
| 648 | 
            +
            shape.contain?(200, 151, outline: true, distance_tolerance: 1) # => true
         | 
| 649 | 
            +
            shape.contain?([200, 151], outline: true, distance_tolerance: 1) # => true
         | 
| 505 650 | 
             
            ```
         | 
| 506 651 |  | 
| 507 652 | 
             
            ### `PerfectShape::Path`
         | 
| @@ -528,9 +673,9 @@ Includes `PerfectShape::MultiPoint` | |
| 528 673 | 
             
            - `#center_x`: center x
         | 
| 529 674 | 
             
            - `#center_y`: center y
         | 
| 530 675 | 
             
            - `#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)
         | 
| 676 | 
            +
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 531 677 | 
             
            - `#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))
         | 
| 532 678 | 
             
            - `#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)
         | 
| 533 | 
            -
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 534 679 |  | 
| 535 680 | 
             
            Example:
         | 
| 536 681 |  | 
| @@ -555,10 +700,12 @@ Class | |
| 555 700 |  | 
| 556 701 | 
             
            Extends `PerfectShape::Shape`
         | 
| 557 702 |  | 
| 703 | 
            +
            A composite shape is simply an aggregate of multiple shapes (e.g. square and polygon)
         | 
| 704 | 
            +
             | 
| 558 705 | 
             
            
         | 
| 559 706 |  | 
| 560 707 | 
             
            - `::new(shapes: [])`: constructs a composite shape with `shapes` as `Array` of `PerfectShape::Shape` objects
         | 
| 561 | 
            -
            - `#shapes`: the shapes that the  | 
| 708 | 
            +
            - `#shapes`: the shapes that the composite shape is composed of
         | 
| 562 709 | 
             
            - `#min_x`: min x
         | 
| 563 710 | 
             
            - `#min_y`: min y
         | 
| 564 711 | 
             
            - `#max_x`: max x
         | 
| @@ -568,8 +715,8 @@ Extends `PerfectShape::Shape` | |
| 568 715 | 
             
            - `#center_x`: center x
         | 
| 569 716 | 
             
            - `#center_y`: center y
         | 
| 570 717 | 
             
            - `#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)
         | 
| 571 | 
            -
            - `#contain?(x_or_point, y=nil)`: checks if point is inside any of the shapes owned by the composite shape
         | 
| 572 718 | 
             
            - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
         | 
| 719 | 
            +
            - `#contain?(x_or_point, y=nil)`: checks if point is inside any of the shapes owned by the composite shape
         | 
| 573 720 |  | 
| 574 721 | 
             
            Example:
         | 
| 575 722 |  | 
| @@ -595,7 +742,7 @@ shape.contain?([170, 190]) # => true | |
| 595 742 | 
             
            ## Resources
         | 
| 596 743 |  | 
| 597 744 | 
             
            - Rubydoc: https://www.rubydoc.info/gems/perfect-shape
         | 
| 598 | 
            -
            - AWT Geom  | 
| 745 | 
            +
            - AWT Geom Javadoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
         | 
| 599 746 |  | 
| 600 747 | 
             
            ## TODO
         | 
| 601 748 |  | 
| @@ -625,5 +772,5 @@ shape.contain?([170, 190]) # => true | |
| 625 772 |  | 
| 626 773 | 
             
            [MIT](LICENSE.txt)
         | 
| 627 774 |  | 
| 628 | 
            -
            Copyright (c) 2021 Andy Maleh. See
         | 
| 775 | 
            +
            Copyright (c) 2021-2022 Andy Maleh. See
         | 
| 629 776 | 
             
            [LICENSE.txt](LICENSE.txt) for further details.
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.3.3
         | 
    
        data/lib/perfect-shape.rb
    CHANGED
    
    
    
        data/lib/perfect_shape/arc.rb
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            # Copyright (c) 2021 Andy Maleh
         | 
| 1 | 
            +
            # Copyright (c) 2021-2022 Andy Maleh
         | 
| 2 2 | 
             
            #
         | 
| 3 3 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining
         | 
| 4 4 | 
             
            # a copy of this software and associated documentation files (the
         | 
| @@ -30,6 +30,7 @@ module PerfectShape | |
| 30 30 | 
             
                include Equalizer.new(:type, :x, :y, :width, :height, :start, :extent)
         | 
| 31 31 |  | 
| 32 32 | 
             
                TYPES = [:open, :chord, :pie]
         | 
| 33 | 
            +
                DEFAULT_OUTLINE_RADIUS = BigDecimal('0.001')
         | 
| 33 34 | 
             
                attr_accessor :type
         | 
| 34 35 | 
             
                attr_reader :start, :extent
         | 
| 35 36 |  | 
| @@ -143,44 +144,58 @@ module PerfectShape | |
| 143 144 | 
             
                # @return {@code true} if the point lies within the bound of
         | 
| 144 145 | 
             
                # the arc, {@code false} if the point lies outside of the
         | 
| 145 146 | 
             
                # arc's bounds.
         | 
| 146 | 
            -
                def contain?(x_or_point, y = nil)
         | 
| 147 | 
            +
                def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
         | 
| 147 148 | 
             
                  x, y = normalize_point(x_or_point, y)
         | 
| 148 149 | 
             
                  return unless x && y
         | 
| 149 | 
            -
                   | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
                  inarc = contain_angle?(-1*Math.radians_to_degrees(Math.atan2(normy, normx)))
         | 
| 162 | 
            -
                  
         | 
| 163 | 
            -
                  return inarc if type == :pie
         | 
| 164 | 
            -
                  # CHORD and OPEN behave the same way
         | 
| 165 | 
            -
                  if inarc
         | 
| 166 | 
            -
                    return true if ang_ext >= 180.0
         | 
| 167 | 
            -
                    # point must be outside the "pie triangle"
         | 
| 150 | 
            +
                  if outline
         | 
| 151 | 
            +
                    if type == :pie && x == center_x && y == center_y
         | 
| 152 | 
            +
                      true
         | 
| 153 | 
            +
                    else
         | 
| 154 | 
            +
                      distance_tolerance = BigDecimal(distance_tolerance.to_s)
         | 
| 155 | 
            +
                      outside_inside_radius_difference = DEFAULT_OUTLINE_RADIUS + distance_tolerance * 2.0
         | 
| 156 | 
            +
                      outside_radius_difference = inside_radius_difference = outside_inside_radius_difference / 2.0
         | 
| 157 | 
            +
                      outside_shape = Arc.new(type: type, center_x: center_x, center_y: center_y, radius_x: radius_x + outside_radius_difference, radius_y: radius_y + outside_radius_difference, start: start, extent: extent)
         | 
| 158 | 
            +
                      inside_shape = Arc.new(type: type, center_x: center_x, center_y: center_y, radius_x: radius_x - inside_radius_difference, radius_y: radius_y - inside_radius_difference, start: start, extent: extent)
         | 
| 159 | 
            +
                      outside_shape.contain?(x, y, outline: false) and
         | 
| 160 | 
            +
                        !inside_shape.contain?(x, y, outline: false)
         | 
| 161 | 
            +
                    end
         | 
| 168 162 | 
             
                  else
         | 
| 169 | 
            -
                     | 
| 170 | 
            -
                    #  | 
| 163 | 
            +
                    # Normalize the coordinates compared to the ellipse
         | 
| 164 | 
            +
                    # having a center at 0,0 and a radius of 0.5.
         | 
| 165 | 
            +
                    ellw = width
         | 
| 166 | 
            +
                    return false if (ellw <= 0.0)
         | 
| 167 | 
            +
                    normx = (x - self.x) / ellw - 0.5
         | 
| 168 | 
            +
                    ellh = height
         | 
| 169 | 
            +
                    return false if (ellh <= 0.0)
         | 
| 170 | 
            +
                    normy = (y - self.y) / ellh - 0.5
         | 
| 171 | 
            +
                    dist_sq = (normx * normx) + (normy * normy)
         | 
| 172 | 
            +
                    return false if (dist_sq >= 0.25)
         | 
| 173 | 
            +
                    ang_ext = self.extent.abs
         | 
| 174 | 
            +
                    return true if (ang_ext >= 360.0)
         | 
| 175 | 
            +
                    inarc = contain_angle?(-1*Math.radians_to_degrees(Math.atan2(normy, normx)))
         | 
| 176 | 
            +
                    
         | 
| 177 | 
            +
                    return inarc if type == :pie
         | 
| 178 | 
            +
                    # CHORD and OPEN behave the same way
         | 
| 179 | 
            +
                    if inarc
         | 
| 180 | 
            +
                      return true if ang_ext >= 180.0
         | 
| 181 | 
            +
                      # point must be outside the "pie triangle"
         | 
| 182 | 
            +
                    else
         | 
| 183 | 
            +
                      return false if ang_ext <= 180.0
         | 
| 184 | 
            +
                      # point must be inside the "pie triangle"
         | 
| 185 | 
            +
                    end
         | 
| 186 | 
            +
                    
         | 
| 187 | 
            +
                    # The point is inside the pie triangle iff it is on the same
         | 
| 188 | 
            +
                    # side of the line connecting the ends of the arc as the center.
         | 
| 189 | 
            +
                    angle = Math.degrees_to_radians(-start)
         | 
| 190 | 
            +
                    x1 = Math.cos(angle)
         | 
| 191 | 
            +
                    y1 = Math.sin(angle)
         | 
| 192 | 
            +
                    angle += Math.degrees_to_radians(-extent)
         | 
| 193 | 
            +
                    x2 = Math.cos(angle)
         | 
| 194 | 
            +
                    y2 = Math.sin(angle)
         | 
| 195 | 
            +
                    inside = (Line.relative_counterclockwise(x1, y1, x2, y2, 2*normx, 2*normy) *
         | 
| 196 | 
            +
                                      Line.relative_counterclockwise(x1, y1, x2, y2, 0, 0) >= 0)
         | 
| 197 | 
            +
                    inarc ? !inside : inside
         | 
| 171 198 | 
             
                  end
         | 
| 172 | 
            -
                  
         | 
| 173 | 
            -
                  # The point is inside the pie triangle iff it is on the same
         | 
| 174 | 
            -
                  # side of the line connecting the ends of the arc as the center.
         | 
| 175 | 
            -
                  angle = Math.degrees_to_radians(-start)
         | 
| 176 | 
            -
                  x1 = Math.cos(angle)
         | 
| 177 | 
            -
                  y1 = Math.sin(angle)
         | 
| 178 | 
            -
                  angle += Math.degrees_to_radians(-extent)
         | 
| 179 | 
            -
                  x2 = Math.cos(angle)
         | 
| 180 | 
            -
                  y2 = Math.sin(angle)
         | 
| 181 | 
            -
                  inside = (Line.relative_counterclockwise(x1, y1, x2, y2, 2*normx, 2*normy) *
         | 
| 182 | 
            -
                                    Line.relative_counterclockwise(x1, y1, x2, y2, 0, 0) >= 0)
         | 
| 183 | 
            -
                  inarc ? !inside : inside
         | 
| 184 199 | 
             
                end
         | 
| 185 200 |  | 
| 186 201 | 
             
                # Determines whether or not the specified angle is within the
         |