perfect-shape 0.3.3 → 0.5.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: 42bff3742b2697349882d865b7c5da9c06ce4a259850996bb92f291e8a74130e
4
- data.tar.gz: 5a2f007129f1e0f483a48ea607a7ee3e44cbdf01b400a656f406b412dc8e9524
3
+ metadata.gz: ea11a066d120228aeb5889877909e17d93022e44e9c46899088bf97e52a328a6
4
+ data.tar.gz: 2d9f3031bd0edfa073d20c6c8f8d56541aa473ef92335f6606b88fbe0a9b39f4
5
5
  SHA512:
6
- metadata.gz: 9bf87bebe682dfbab8a4b082caa321ac0b7051510d86e946e3f214337e818a18da7cee51bc12432510567deaa68f65a69cb3c7da10b7c9153045803e1d25f0b7
7
- data.tar.gz: fa32aa79d500055b81b572e17ded38429503a3757430bba1c0fea021853e4377d7e025071cf52ed8c2f965c8bc8ecd35601276f2a1bcda847202c6f49c8422a0
6
+ metadata.gz: 8025f51a536223a337d10b8a3b0bb9cd66aefe37543d24108aa002d719c4eb123a5b19d89b00157a85054bd614f92bdaf944562efbbecf14a93ab6d9c0a6481c
7
+ data.tar.gz: 06701161fbe42ec6afe1f19d9ece0db38d44db679702ea0db420fc260fa19526a2d2b4475c5aaa0d6f0500260ad48ad7267b4da1ec626153749d70175c50e523
data/CHANGELOG.md CHANGED
@@ -1,11 +1,45 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.5.0
4
+
5
+ - `Line#intersect?(rectangle)`
6
+ - `Rectangle#out_state(x_or_point, y = nil)`
7
+
8
+ ## 0.4.0
9
+
10
+ - `PerfectShape::AffineTransform#new`
11
+ - `PerfectShape::AffineTransform#==`
12
+ - `PerfectShape::AffineTransform#transform_point`
13
+ - `PerfectShape::AffineTransform#transform_points`
14
+ - `PerfectShape::AffineTransform#identity!` (alias: `reset!`)
15
+ - `PerfectShape::AffineTransform#invert!`
16
+ - `PerfectShape::AffineTransform#invertible?`
17
+ - `PerfectShape::AffineTransform#multiply!`
18
+ - `PerfectShape::AffineTransform#translate!`
19
+ - `PerfectShape::AffineTransform#scale!`
20
+ - `PerfectShape::AffineTransform#rotate!`
21
+ - `PerfectShape::AffineTransform#shear!` (alias: `skew!`)
22
+ - `PerfectShape::AffineTransform#clone`
23
+ - `PerfectShape::AffineTransform#inverse_transform_point`
24
+ - `PerfectShape::AffineTransform#inverse_transform_points`
25
+
26
+ ## 0.3.5
27
+
28
+ - Check point containment in composite shape outline with distance tolerance (new method signature: `PerfectShape::CompositeShape#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
29
+
30
+ ## 0.3.4
31
+
32
+ - Check point containment in path outline with distance tolerance (new method signature: `PerfectShape::Path#contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)`)
33
+ - `PerfectShape::Path#disconnected_shapes`: Disconnected shapes have their start point filled in so that each shape does not depend on the previous shape to determine its start point.
34
+ - `Shape#center_point` as `[center_x, center_y]`
35
+ - Rename `#point_segment_distance` to `#point_distance` everywhere
36
+
3
37
  ## 0.3.3
4
38
 
5
39
  - 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
40
  - `PerfectShape::QuadraticBezierCurve#curve_center_point`, `PerfectShape::QuadraticBezierCurve#curve_center_x`, `PerfectShape::QuadraticBezierCurve#curve_center_y`
7
41
  - `PerfectShape::QuadraticBezierCurve#subdivisions(level=1)`
8
- - `PerfectShape::QuadraticBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
42
+ - `PerfectShape::QuadraticBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
9
43
  - `PerfectShape::Polygon#edges` returns edges of polygon as `PerfectShape::Line` objects
10
44
  - `PerfectShape::Rectangle#edges` returns edges of rectangle as `PerfectShape::Line` objects
11
45
  - `PerfectShape::Square#edges` returns edges of square as `PerfectShape::Line` objects
@@ -16,7 +50,7 @@
16
50
  - 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
51
  - `PerfectShape::CubicBezierCurve#curve_center_point`, `PerfectShape::CubicBezierCurve#curve_center_x`, `PerfectShape::CubicBezierCurve#curve_center_y`
18
52
  - `PerfectShape::CubicBezierCurve#subdivisions(level=1)`
19
- - `PerfectShape::CubicBezierCurve#point_segment_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
53
+ - `PerfectShape::CubicBezierCurve#point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)`
20
54
 
21
55
  ## 0.3.1
22
56
 
@@ -80,7 +114,7 @@
80
114
  - `PerfectShape::Line`
81
115
  - `PerfectShape::Line#contain?(x_or_point, y=nil)`
82
116
  - `PerfectShape::Line#relative_counterclockwise`
83
- - `PerfectShape::Line#point_segment_distance`
117
+ - `PerfectShape::Line#point_distance`
84
118
  - Update `PerfectShape::Math::radians_to_degrees`, `PerfectShape::Math::degrees_to_radians`, and `PerfectShape::Math::normalize_degrees` to normalize numbers to `BigDecimal`
85
119
 
86
120
  ## 0.0.7
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 0.3.3
1
+ # Perfect Shape 0.5.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), 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)).
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 viewport rectangle intersection or 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), potentially with [affine transforms](#perfectshapeaffinetransform) applied like translation, scale, rotation, shear/skew, and inversion (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.3.3
17
+ gem install perfect-shape -v 0.5.0
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 0.3.3'
23
+ gem 'perfect-shape', '~> 0.5.0'
24
24
  ```
25
25
 
26
26
  And, run:
@@ -52,11 +52,11 @@ This is a base class for all shapes. It is not meant to be used directly. Subcla
52
52
  - `#max_y`: max y
53
53
  - `#width`: width
54
54
  - `#height`: height
55
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
55
56
  - `#center_x`: center x
56
57
  - `#center_y`: center y
57
58
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height just as those of shape
58
59
  - `#==(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
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`
@@ -84,9 +84,71 @@ Includes `PerfectShape::PointLocation`
84
84
  - `#min_y`: min y
85
85
  - `#max_x`: max x
86
86
  - `#max_y`: max y
87
- - `#center_x`: center x
88
- - `#center_y`: center y
89
- - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
87
+
88
+ ### `PerfectShape::AffineTransform`
89
+
90
+ Class
91
+
92
+ Affine transforms have the following matrix:
93
+
94
+ [ xxp xyp xt ]<br>
95
+ [ yxp yyp yt ]
96
+
97
+ The matrix is used to transform (x,y) point coordinates as follows:
98
+
99
+ [ xxp xyp xt ] * [x] = [ xxp * x + xyp * y + xt ]<br>
100
+ [ yxp yyp yt ] * [y] = [ yxp * x + yyp * y + yt ]
101
+
102
+ `xxp` is the x coordinate x product (`m11`)<br>
103
+ `xyp` is the x coordinate y product (`m12`)<br>
104
+ `yxp` is the y coordinate x product (`m21`)<br>
105
+ `yyp` is the y coordinate y product (`m22`)<br>
106
+ `xt` is the x coordinate translation (`m13`)<br>
107
+ `yt` is the y coordinate translation (`m23`)
108
+
109
+ Affine transform mutation operations ending with `!` can be chained as they all return `self`.
110
+
111
+ - `::new(xxp_element = nil, xyp_element = nil, yxp_element = nil, yyp_element = nil, xt_element = nil, yt_element = nil,
112
+ xxp: nil, xyp: nil, yxp: nil, yyp: nil, xt: nil, yt: nil,
113
+ m11: nil, m12: nil, m21: nil, m22: nil, m13: nil, m23: nil)`:
114
+ The constructor accepts either the (x,y)-operation related argument/kwarg names or traditional matrix element kwarg names. If no arguments are supplied, it constructs an identity matrix (i.e. like calling `::new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: 0, yt: 0)`).
115
+ - `#matrix_3d`: Returns Ruby `Matrix` object representing affine transform in 3D (used internally for performing multiplication)
116
+ - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
117
+ - `#identity!` (alias: `reset!`): Resets to identity matrix (i.e. like calling `::new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: 0, yt: 0)`)
118
+ - `#invertible?` Returns `true` if matrix is invertible and `false` otherwise
119
+ - `#invert!`: Inverts affine transform matrix if invertible or raises an error otherwise
120
+ - `#multiply!(other)`: Multiplies affine transform with another affine transform, storing resulting changes in matrix elements
121
+ - `#translate!(x_or_point, y=nil)`: Translates affine transform with (x, y) translation values
122
+ - `#scale!(x_or_point, y=nil)`: Scales affine transform with (x, y) scale values
123
+ - `#rotate!(degrees)`: Rotates by angle degrees counter-clockwise if angle value is positive or clockwise if angle value is negative. Note that it returns very close approximate results for rotations that are 90/180/270 degrees (good enough for inverse-transform GUI point containment checks needed when checking if mouse-click-point is inside a transformed shape).
124
+ - `#shear!(x_or_point, y=nil)`: Shears by x and y factors
125
+ - `#clone`: Returns a new AffineTransform with the same matrix elements
126
+ - `#transform_point(x_or_point, y=nil)`: returns `[xxp * x + xyp * y + xt, yxp * x + yyp * y + yt]`. Note that result is a close approximation, but should be good enough for GUI mouse-click-point containment checks.
127
+ - `#transform_points(*xy_coordinates_or_points)`: returns `Array` of (x,y) pair `Array`s transformed with `#transform_point` method
128
+ - `#inverse_transform_point(x_or_point, y=nil)`: returns inverse transform of a point (x,y) coordinates (clones self and inverts clone, and then transforms point). Note that result is a close approximation, but should be good enough for GUI mouse-click-point containment checks.
129
+ - `#inverse_transform_points(*xy_coordinates_or_points)`: returns inverse transforms of a point `Array` of (x,y) coordinates
130
+
131
+ Example:
132
+
133
+ ```ruby
134
+ xxp = 2
135
+ xyp = 3
136
+ yxp = 4
137
+ yyp = 5
138
+ xt = 6
139
+ yt = 7
140
+ affine_transform1 = PerfectShape::AffineTransform.new(xxp: xxp, xyp: xyp, yxp: yxp, yyp: yyp, xt: xt, yt: yt) # (x,y)-operation kwarg names
141
+ affine_transform2 = PerfectShape::AffineTransform.new(m11: xxp, m12: xyp, m21: yxp, m22: yyp, m13: xt, m23: yt) # traditional matrix element kwarg names
142
+ affine_transform3 = PerfectShape::AffineTransform.new(xxp, xyp, yxp, yyp, xt, yt) # standard arguments
143
+
144
+ affine_transform2.matrix_3d == affine_transform1.matrix_3d # => true
145
+ affine_transform3.matrix_3d == affine_transform1.matrix_3d # => true
146
+
147
+ affine_transform = PerfectShape::AffineTransform.new.translate!(30, 20).scale!(2, 3)
148
+
149
+ affine_transform.transform_point(10, 10) # => approximately [50, 50]
150
+ affine_transform.inverse_transform_point(50, 50) # => approximately [10, 10]
151
+ ```
90
152
 
91
153
  ### `PerfectShape::Point`
92
154
 
@@ -101,6 +163,7 @@ Includes `PerfectShape::PointLocation`
101
163
  Points are simply represented by an `Array` of `[x,y]` coordinates when used within other shapes, but when needing point-specific operations like `point_distance`, the `PerfectShape::Point` class can come in handy.
102
164
 
103
165
  - `::point_distance(x, y, px, py)`: Returns the distance from a point to another point
166
+ - `::normalize_point(x_or_point, y = nil)`: Normalizes point args whether two-number `point` `Array` or `x`, `y` args, returning normalized point `Array` of two `BigDecimal`'s
104
167
  - `::new(x_or_point=nil, y_arg=nil, x: nil, y: nil)`: constructs a point with (x,y) pair (default: 0,0) whether specified as `Array` of (x,y) pair, flat `x,y` args, or `x:, y:` kwargs.
105
168
  - `#min_x`: min x (always x)
106
169
  - `#min_y`: min y (always y)
@@ -108,11 +171,12 @@ Points are simply represented by an `Array` of `[x,y]` coordinates when used wit
108
171
  - `#max_y`: max y (always y)
109
172
  - `#width`: width (always 0)
110
173
  - `#height`: height (always 0)
174
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
111
175
  - `#center_x`: center x (always x)
112
176
  - `#center_y`: center y (always y)
113
177
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
114
178
  - `#==(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.
179
+ - `#contain?(x_or_point, y=nil, outline: true, 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. `outline` option makes no difference on point
116
180
  - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to another point
117
181
 
118
182
  Example:
@@ -140,9 +204,9 @@ Includes `PerfectShape::MultiPoint`
140
204
 
141
205
  ![line](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/line.png)
142
206
 
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)”.
144
- - `::point_segment_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
145
- - `::point_segment_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
207
+ - `::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, 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)”.
208
+ - `::point_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
209
+ - `::point_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
146
210
  - `::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
147
211
  - `#min_x`: min x
148
212
  - `#min_y`: min y
@@ -150,13 +214,15 @@ Includes `PerfectShape::MultiPoint`
150
214
  - `#max_y`: max y
151
215
  - `#width`: width (from min x to max x)
152
216
  - `#height`: height (from min y to max y)
217
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
153
218
  - `#center_x`: center x
154
219
  - `#center_y`: center y
155
220
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
156
221
  - `#==(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.
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)”.
159
- - `#point_segment_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
222
+ - `#contain?(x_or_point, y=nil, outline: true, 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. `outline` option makes no difference on line
223
+ - `#intersect?(rectangle)`: Returns `true` if intersecting with interior of rectangle or `false` otherwise. This is useful for GUI optimization checks of whether a shape appears in a GUI viewport rectangle and needs redrawing
224
+ - `#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, 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)”.
225
+ - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
160
226
 
161
227
  Example:
162
228
 
@@ -191,16 +257,17 @@ Includes `PerfectShape::MultiPoint`
191
257
  - `#max_y`: max y
192
258
  - `#width`: width (from min x to max x)
193
259
  - `#height`: height (from min y to max y)
260
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
194
261
  - `#center_x`: center x
195
262
  - `#center_y`: center y
196
263
  - `#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
264
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
198
265
  - `#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`)
266
+ - `#curve_center_point`: point at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
267
+ - `#curve_center_x`: point x coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
268
+ - `#curve_center_y`: point y coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
202
269
  - `#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.
270
+ - `#point_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.
204
271
 
205
272
  Example:
206
273
 
@@ -239,16 +306,17 @@ Includes `PerfectShape::MultiPoint`
239
306
  - `#max_y`: max y
240
307
  - `#width`: width (from min x to max x)
241
308
  - `#height`: height (from min y to max y)
309
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
242
310
  - `#center_x`: center x
243
311
  - `#center_y`: center y
244
312
  - `#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)
245
313
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
246
314
  - `#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`)
315
+ - `#curve_center_point`: point at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
316
+ - `#curve_center_x`: point x coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
317
+ - `#curve_center_y`: point y coordinate at the center of the curve outline (not the center of the bounding box area like `center_x` and `center_y`)
250
318
  - `#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.
319
+ - `#point_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.
252
320
 
253
321
  Example:
254
322
 
@@ -284,6 +352,7 @@ Includes `PerfectShape::RectangularShape`
284
352
  - `#y`: top-left y
285
353
  - `#width`: width
286
354
  - `#height`: height
355
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
287
356
  - `#center_x`: center x
288
357
  - `#center_y`: center y
289
358
  - `#min_x`: min x
@@ -294,6 +363,7 @@ Includes `PerfectShape::RectangularShape`
294
363
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
295
364
  - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: checks if point is inside when `outline` is `false` or if point is on the outline when `outline` is `true`. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a rectangle shape from its outline more successfully
296
365
  - `#edges`: edges of rectangle as `PerfectShape::Line` objects
366
+ - `#out_state(x_or_point, y = nil)`: Returns "out state" of specified point (x,y) (whether it lies to the left, right, top, bottom of rectangle). If point is outside rectangle, it returns a bit mask combination of `Rectangle::OUT_LEFT`, `Rectangle::OUT_RIGHT`, `Rectangle::OUT_TOP`, or `Rectangle::OUT_BOTTOM`. Otherwise, it returns `0` if point is inside the rectangle.
297
367
 
298
368
  Example:
299
369
 
@@ -328,6 +398,7 @@ Extends `PerfectShape::Rectangle`
328
398
  - `#length`: length
329
399
  - `#width`: width (equal to length)
330
400
  - `#height`: height (equal to length)
401
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
331
402
  - `#center_x`: center x
332
403
  - `#center_y`: center y
333
404
  - `#min_x`: min x
@@ -380,6 +451,7 @@ Open Arc | Chord Arc | Pie Arc
380
451
  - `#height`: height
381
452
  - `#start`: start angle in degrees
382
453
  - `#extent`: extent angle in degrees
454
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
383
455
  - `#center_x`: center x
384
456
  - `#center_y`: center y
385
457
  - `#radius_x`: radius along the x-axis
@@ -499,6 +571,7 @@ Extends `PerfectShape::Arc`
499
571
  - `#y`: top-left y
500
572
  - `#width`: width
501
573
  - `#height`: height
574
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
502
575
  - `#center_x`: center x
503
576
  - `#center_y`: center y
504
577
  - `#radius_x`: radius along the x-axis
@@ -558,6 +631,7 @@ Extends `PerfectShape::Ellipse`
558
631
  - `#diameter`: diameter
559
632
  - `#width`: width (equal to diameter)
560
633
  - `#height`: height (equal to diameter)
634
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
561
635
  - `#center_x`: center x
562
636
  - `#center_y`: center y
563
637
  - `#radius`: radius
@@ -623,6 +697,7 @@ A polygon can be thought of as a special case of [path](#perfectshapepath) that
623
697
  - `#max_y`: max y
624
698
  - `#width`: width (from min x to max x)
625
699
  - `#height`: height (from min y to max y)
700
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
626
701
  - `#center_x`: center x
627
702
  - `#center_y`: center y
628
703
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height of shape
@@ -670,12 +745,14 @@ Includes `PerfectShape::MultiPoint`
670
745
  - `#max_y`: max y
671
746
  - `#width`: width (from min x to max x)
672
747
  - `#height`: height (from min y to max y)
748
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
673
749
  - `#center_x`: center x
674
750
  - `#center_y`: center y
675
751
  - `#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
752
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
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))
753
+ - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it 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)). 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 path shape from its outline more successfully
678
754
  - `#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)
755
+ - `#disconnected_shapes`: Disconnected shapes have their start point filled in so that each shape does not depend on the previous shape to determine its start point. Also, if a point is followed by a non-point shape, it is removed since it is augmented to the following shape as its start point. Lastly, if the path is closed, an extra shape is added to represent the line connecting the last point to the first
679
756
 
680
757
  Example:
681
758
 
@@ -690,8 +767,16 @@ path_shapes << PerfectShape::CubicBezierCurve.new(points: [[370, 50], [430, 220]
690
767
 
691
768
  shape = PerfectShape::Path.new(shapes: path_shapes, closed: false, winding_rule: :wind_even_odd)
692
769
 
693
- shape.contain?(225, 160) # => true
694
- shape.contain?([225, 160]) # => true
770
+ shape.contain?(275, 165) # => true
771
+ shape.contain?([275, 165]) # => true
772
+ shape.contain?(275, 165, outline: true) # => false
773
+ shape.contain?([275, 165], outline: true) # => false
774
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y, outline: true) # => true
775
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x, shape.disconnected_shapes[1].curve_center_y], outline: true) # => true
776
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true) # => false
777
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true) # => false
778
+ shape.contain?(shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y, outline: true, distance_tolerance: 1) # => true
779
+ shape.contain?([shape.disconnected_shapes[1].curve_center_x + 1, shape.disconnected_shapes[1].curve_center_y], outline: true, distance_tolerance: 1) # => true
695
780
  ```
696
781
 
697
782
  ### `PerfectShape::CompositeShape`
@@ -712,11 +797,12 @@ A composite shape is simply an aggregate of multiple shapes (e.g. square and pol
712
797
  - `#max_y`: max y
713
798
  - `#width`: width (from min x to max x)
714
799
  - `#height`: height (from min y to max y)
800
+ - `#center_point`: center point as `Array` of `[center_x, center_y]` coordinates
715
801
  - `#center_x`: center x
716
802
  - `#center_y`: center y
717
803
  - `#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)
718
804
  - `#==(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
805
+ - `#contain?(x_or_point, y=nil, outline: false, distance_tolerance: 0)`: When `outline` is `false`, it checks if point is inside any of the shapes owned by the composite shape. Otherwise, when `outline` is `true`, it checks if point is on the outline of any of the shapes owned by the composite shape. `distance_tolerance` can be used as a fuzz factor when `outline` is `true`, for example, to help GUI users mouse-click-select a composite shape from its outline more successfully
720
806
 
721
807
  Example:
722
808
 
@@ -729,10 +815,27 @@ shapes << PerfectShape::Polygon.new(points: [[120, 215], [170, 165], [220, 215]]
729
815
 
730
816
  shape = PerfectShape::CompositeShape.new(shapes: shapes)
731
817
 
732
- shape.contain?(170, 265) # => true
733
- shape.contain?([170, 265]) # => true
734
- shape.contain?(170, 190) # => true
735
- shape.contain?([170, 190]) # => true
818
+ shape.contain?(170, 265) # => true inside square
819
+ shape.contain?([170, 265]) # => true inside square
820
+ shape.contain?(170, 265, outline: true) # => false
821
+ shape.contain?([170, 265], outline: true) # => false
822
+ shape.contain?(170, 315, outline: true) # => true
823
+ shape.contain?([170, 315], outline: true) # => true
824
+ shape.contain?(170, 316, outline: true) # => false
825
+ shape.contain?([170, 316], outline: true) # => false
826
+ shape.contain?(170, 316, outline: true, distance_tolerance: 1) # => true
827
+ shape.contain?([170, 316], outline: true, distance_tolerance: 1) # => true
828
+
829
+ shape.contain?(170, 190) # => true inside polygon
830
+ shape.contain?([170, 190]) # => true inside polygon
831
+ shape.contain?(170, 190, outline: true) # => false
832
+ shape.contain?([170, 190], outline: true) # => false
833
+ shape.contain?(145, 190, outline: true) # => true
834
+ shape.contain?([145, 190], outline: true) # => true
835
+ shape.contain?(145, 189, outline: true) # => false
836
+ shape.contain?([145, 189], outline: true) # => false
837
+ shape.contain?(145, 189, outline: true, distance_tolerance: 1) # => true
838
+ shape.contain?([145, 189], outline: true, distance_tolerance: 1) # => true
736
839
  ```
737
840
 
738
841
  ## Process
@@ -742,7 +845,10 @@ shape.contain?([170, 190]) # => true
742
845
  ## Resources
743
846
 
744
847
  - Rubydoc: https://www.rubydoc.info/gems/perfect-shape
745
- - AWT Geom Javadoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
848
+ - Point in Polygon: https://en.wikipedia.org/wiki/Point_in_polygon
849
+ - Even-Odd Rule: https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
850
+ - Nonzero Rule: https://en.wikipedia.org/wiki/Nonzero-rule
851
+ - IEEE 754-1985 Remainder: https://en.wikipedia.org/wiki/IEEE_754-1985
746
852
 
747
853
  ## TODO
748
854
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.3
1
+ 0.5.0
data/lib/perfect-shape.rb CHANGED
@@ -23,8 +23,8 @@ $LOAD_PATH.unshift File.expand_path('.', __dir__)
23
23
 
24
24
  require 'bigdecimal'
25
25
  require 'equalizer'
26
+ require 'matrix'
26
27
 
27
- # Perfect Shape algorithms are mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
28
28
  module PerfectShape
29
29
  end
30
30