perfect-shape 0.3.5 → 0.4.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: ab1f48d0551ed07a6a2b5f2d51abba6c6596eef82e156e68df57489319104e4c
4
- data.tar.gz: bb67e137bbf23df2ed291a574ef45ae97113dd140d0a17269c9b60c27b7261e9
3
+ metadata.gz: 13f257899f946daccf27fa951ed4208ce8143d2060b24fcb6198ba0e8f738646
4
+ data.tar.gz: 1f9a0d7a1f210da0ee0e7b88514352b7d90c7893b05fe9b84145672989cd8eae
5
5
  SHA512:
6
- metadata.gz: 6d365e47635c7daa094f02849df24696784b667c55bffa479bdbaacc41abf1b9ad1114084a4ed7bd0145e6dd18ad233f9b09e6bad8792828417aca49db2076c4
7
- data.tar.gz: d8440bb0d35e1539dcf5c8cad79e2930d74e10e32a408391085027d773c0ea7e4272a6011a7244677c035c8f6a7954606065611282ae8cd14aab71cbbe6b2bba
6
+ metadata.gz: 2af7d835fdaf126b8ebb630fbc08400aab9a7b464c5d18ad3f09035f142940bb1db1fbe4c0a2a1bc33bf4a773f2ce920fb34e36d57a191143020cdd36357b483
7
+ data.tar.gz: 693a20f1d100e70a8d429c8c02fddcfbdb0b2f2507cff92a0ddbc8c962db7758be5cee94df954ba5a89bba7619bdd9b2ee95b31b17896289e31d90a22d7eeb53
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.4.0
4
+
5
+ - `PerfectShape::AffineTransform#new`
6
+ - `PerfectShape::AffineTransform#==`
7
+ - `PerfectShape::AffineTransform#transform_point`
8
+ - `PerfectShape::AffineTransform#transform_points`
9
+ - `PerfectShape::AffineTransform#identity!` (alias: `reset!`)
10
+ - `PerfectShape::AffineTransform#invert!`
11
+ - `PerfectShape::AffineTransform#invertible?`
12
+ - `PerfectShape::AffineTransform#multiply!`
13
+ - `PerfectShape::AffineTransform#translate!`
14
+ - `PerfectShape::AffineTransform#scale!`
15
+ - `PerfectShape::AffineTransform#rotate!`
16
+ - `PerfectShape::AffineTransform#shear!` (alias: `skew!`)
17
+ - `PerfectShape::AffineTransform#clone`
18
+ - `PerfectShape::AffineTransform#inverse_transform_point`
19
+ - `PerfectShape::AffineTransform#inverse_transform_points`
20
+
3
21
  ## 0.3.5
4
22
 
5
23
  - 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)`)
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
- # Perfect Shape 0.3.5
1
+ # Perfect Shape 0.4.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 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.5
17
+ gem install perfect-shape -v 0.4.0
18
18
  ```
19
19
 
20
20
  Or include in Bundler `Gemfile`:
21
21
 
22
22
  ```ruby
23
- gem 'perfect-shape', '~> 0.3.5'
23
+ gem 'perfect-shape', '~> 0.4.0'
24
24
  ```
25
25
 
26
26
  And, run:
@@ -57,7 +57,6 @@ This is a base class for all shapes. It is not meant to be used directly. Subcla
57
57
  - `#center_y`: center y
58
58
  - `#bounding_box`: bounding box is a rectangle with x = min x, y = min y, and width/height just as those of shape
59
59
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
60
- - `#normalize_point(x_or_point, y = nil)`: normalizes point into an `Array` of `[x,y]` coordinates
61
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
62
61
 
63
62
  ### `PerfectShape::PointLocation`
@@ -86,6 +85,71 @@ Includes `PerfectShape::PointLocation`
86
85
  - `#max_x`: max x
87
86
  - `#max_y`: max y
88
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
+ ```
152
+
89
153
  ### `PerfectShape::Point`
90
154
 
91
155
  Class
@@ -99,6 +163,7 @@ Includes `PerfectShape::PointLocation`
99
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.
100
164
 
101
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
102
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.
103
168
  - `#min_x`: min x (always x)
104
169
  - `#min_y`: min y (always y)
@@ -139,7 +204,7 @@ Includes `PerfectShape::MultiPoint`
139
204
 
140
205
  ![line](https://raw.githubusercontent.com/AndyObtiva/perfect-shape/master/images/line.png)
141
206
 
142
- - `::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)”.
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)”.
143
208
  - `::point_distance_square(x1, y1, x2, y2, px, py)`: Returns the square of distance from a point to a line segment.
144
209
  - `::point_distance(x1, y1, x2, y2, px, py)`: Returns the distance from a point to a line segment.
145
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
@@ -155,7 +220,7 @@ Includes `PerfectShape::MultiPoint`
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
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
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)”.
223
+ - `#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)”.
159
224
  - `#point_distance(x_or_point, y=nil)`: Returns the distance from a point to a line segment.
160
225
 
161
226
  Example:
@@ -735,7 +800,7 @@ A composite shape is simply an aggregate of multiple shapes (e.g. square and pol
735
800
  - `#center_y`: center y
736
801
  - `#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)
737
802
  - `#==(other)`: Returns `true` if equal to `other` or `false` otherwise
738
- - `#contain?(x_or_point, y=nil)`: 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
803
+ - `#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
739
804
 
740
805
  Example:
741
806
 
@@ -778,7 +843,10 @@ shape.contain?([145, 189], outline: true, distance_tolerance: 1) # => true
778
843
  ## Resources
779
844
 
780
845
  - Rubydoc: https://www.rubydoc.info/gems/perfect-shape
781
- - AWT Geom Javadoc (inspiration): https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
846
+ - Point in Polygon: https://en.wikipedia.org/wiki/Point_in_polygon
847
+ - Even-Odd Rule: https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
848
+ - Nonzero Rule: https://en.wikipedia.org/wiki/Nonzero-rule
849
+ - IEEE 754-1985 Remainder: https://en.wikipedia.org/wiki/IEEE_754-1985
782
850
 
783
851
  ## TODO
784
852
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.5
1
+ 0.4.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
 
@@ -0,0 +1,235 @@
1
+ # Copyright (c) 2021-2022 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'perfect_shape/math'
23
+ require 'perfect_shape/shape'
24
+ require 'perfect_shape/point'
25
+ require 'perfect_shape/multi_point'
26
+
27
+ module PerfectShape
28
+ # Represents an affine transform
29
+ class AffineTransform
30
+ include Equalizer.new(:xxp, :xyp, :yxp, :yyp, :xt, :yt)
31
+
32
+ attr_reader :xxp, :xyp, :yxp, :yyp, :xt, :yt
33
+ alias m11 xxp
34
+ alias m12 xyp
35
+ alias m21 yxp
36
+ alias m22 yyp
37
+ alias m13 xt
38
+ alias m23 yt
39
+
40
+ # Creates a new AffineTransform with the following Matrix:
41
+ #
42
+ # [ xxp xyp xt ]
43
+ # [ yxp yyp yt ]
44
+ #
45
+ # The Matrix is used to transform (x,y) point coordinates as follows:
46
+ #
47
+ # [ xxp xyp xt ] * [x] = [ xxp * x + xyp * y + xt ]
48
+ # [ yxp yyp yt ] * [y] = [ yxp * x + yyp * y + yt ]
49
+ #
50
+ # xxp is the x coordinate x product (m11)
51
+ # xyp is the x coordinate y product (m12)
52
+ # yxp is the y coordinate x product (m21)
53
+ # yyp is the y coordinate y product (m22)
54
+ # xt is the x coordinate translation (m13)
55
+ # yt is the y coordinate translation (m23)
56
+ #
57
+ # The constructor accepts either the (x,y)-operation related argument/kwarg names
58
+ # or traditional Matrix element kwarg names
59
+ #
60
+ # Example with (x,y)-operation kwarg names:
61
+ #
62
+ # AffineTransform.new(xxp: 2, xyp: 3, yxp: 4, yyp: 5, xt: 6, yt: 7)
63
+ #
64
+ # Example with traditional Matrix element kwarg names:
65
+ #
66
+ # AffineTransform.new(m11: 2, m12: 3, m21: 4, m22: 5, m13: 6, m23: 7)
67
+ #
68
+ # Example with standard arguments:
69
+ #
70
+ # AffineTransform.new(2, 3, 4, 5, 6, 7)
71
+ #
72
+ # If no arguments are supplied, it constructs an identity matrix
73
+ # (i.e. like calling `::new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: 0, yt: 0)`)
74
+ def initialize(xxp_element = nil, xyp_element = nil, yxp_element = nil, yyp_element = nil, xt_element = nil, yt_element = nil,
75
+ xxp: nil, xyp: nil, yxp: nil, yyp: nil, xt: nil, yt: nil,
76
+ m11: nil, m12: nil, m21: nil, m22: nil, m13: nil, m23: nil)
77
+ self.xxp = xxp_element || xxp || m11 || 1
78
+ self.xyp = xyp_element || xyp || m12 || 0
79
+ self.yxp = yxp_element || yxp || m21 || 0
80
+ self.yyp = yyp_element || yyp || m22 || 1
81
+ self.xt = xt_element || xt || m13 || 0
82
+ self.yt = yt_element || yt || m23 || 0
83
+ end
84
+
85
+ def xxp=(value)
86
+ @xxp = BigDecimal(value.to_s)
87
+ end
88
+ alias m11= xxp=
89
+
90
+ def xyp=(value)
91
+ @xyp = BigDecimal(value.to_s)
92
+ end
93
+ alias m12= xyp=
94
+
95
+ def yxp=(value)
96
+ @yxp = BigDecimal(value.to_s)
97
+ end
98
+ alias m21= yxp=
99
+
100
+ def yyp=(value)
101
+ @yyp = BigDecimal(value.to_s)
102
+ end
103
+ alias m22= yyp=
104
+
105
+ def xt=(value)
106
+ @xt = BigDecimal(value.to_s)
107
+ end
108
+ alias m13= xt=
109
+
110
+ def yt=(value)
111
+ @yt = BigDecimal(value.to_s)
112
+ end
113
+ alias m23= yt=
114
+
115
+ # Resets to identity matrix
116
+ # Returns self to support fluent interface chaining
117
+ def identity!
118
+ self.xxp = 1
119
+ self.xyp = 0
120
+ self.yxp = 0
121
+ self.yyp = 1
122
+ self.xt = 0
123
+ self.yt = 0
124
+
125
+ self
126
+ end
127
+ alias reset! identity!
128
+
129
+ # Inverts AffineTransform matrix if invertible
130
+ # Raises an error if affine transform matrix is not invertible
131
+ # Returns self to support fluent interface chaining
132
+ def invert!
133
+ raise 'Cannot invert (matrix is not invertible)!' if !invertible?
134
+
135
+ self.matrix_3d = matrix_3d.inverse
136
+
137
+ self
138
+ end
139
+
140
+ def invertible?
141
+ (m11 * m22 - m12 * m21) != 0
142
+ end
143
+
144
+ # Multiplies by other AffineTransform
145
+ def multiply!(other_affine_transform)
146
+ self.matrix_3d = matrix_3d*other_affine_transform.matrix_3d
147
+
148
+ self
149
+ end
150
+
151
+ # Translates AffineTransform
152
+ def translate!(x_or_point, y = nil)
153
+ x, y = Point.normalize_point(x_or_point, y)
154
+ return unless x && y
155
+
156
+ translation_affine_transform = AffineTransform.new(xxp: 1, xyp: 0, yxp: 0, yyp: 1, xt: x, yt: y)
157
+ multiply!(translation_affine_transform)
158
+
159
+ self
160
+ end
161
+
162
+ # Scales AffineTransform
163
+ def scale!(x_or_point, y = nil)
164
+ x, y = Point.normalize_point(x_or_point, y)
165
+ return unless x && y
166
+
167
+ scale_affine_transform = AffineTransform.new(xxp: x, xyp: 0, yxp: 0, yyp: y, xt: 0, yt: 0)
168
+ multiply!(scale_affine_transform)
169
+
170
+ self
171
+ end
172
+
173
+ # Rotates AffineTransform counter-clockwise for positive angle value in degrees
174
+ # or clockwise for negative angle value in degrees
175
+ def rotate!(degrees)
176
+ degrees = Math.normalize_degrees(degrees)
177
+ radians = Math.degrees_to_radians(degrees)
178
+
179
+ rotation_affine_transform = AffineTransform.new(xxp: Math.cos(radians), xyp: -Math.sin(radians), yxp: Math.sin(radians), yyp: Math.cos(radians), xt: 0, yt: 0)
180
+ multiply!(rotation_affine_transform)
181
+
182
+ self
183
+ end
184
+
185
+ # Shears AffineTransform by (x,y) amount
186
+ def shear!(x_or_point, y = nil)
187
+ x, y = Point.normalize_point(x_or_point, y)
188
+ return unless x && y
189
+
190
+ shear_affine_transform = AffineTransform.new(xxp: 1 + x*y, xyp: x, yxp: y, yyp: 1, xt: 0, yt: 0)
191
+ multiply!(shear_affine_transform)
192
+
193
+ self
194
+ end
195
+ alias skew! shear!
196
+
197
+ # Sets elements from a Ruby Matrix representing Affine Transform matrix elements in 3D
198
+ def matrix_3d=(the_matrix_3d)
199
+ self.xxp = the_matrix_3d[0, 0]
200
+ self.xyp = the_matrix_3d[0, 1]
201
+ self.xt = the_matrix_3d[0, 2]
202
+ self.yxp = the_matrix_3d[1, 0]
203
+ self.yyp = the_matrix_3d[1, 1]
204
+ self.yt = the_matrix_3d[1, 2]
205
+ end
206
+
207
+ # Returns Ruby Matrix representing Affine Transform matrix elements in 3D
208
+ def matrix_3d
209
+ Matrix[[xxp, xyp, xt], [yxp, yyp, yt], [0, 0, 1]]
210
+ end
211
+
212
+ def transform_point(x_or_point, y = nil)
213
+ x, y = Point.normalize_point(x_or_point, y)
214
+ return unless x && y
215
+
216
+ [xxp*x + xyp*y + xt, yxp*x + yyp*y + yt]
217
+ end
218
+
219
+ def inverse_transform_point(x_or_point, y = nil)
220
+ clone.invert!.transform_point(x_or_point, y)
221
+ end
222
+
223
+ def transform_points(*xy_coordinates_or_points)
224
+ points = xy_coordinates_or_points.first.is_a?(Array) ? xy_coordinates_or_points.first : xy_coordinates_or_points
225
+ points = MultiPoint.normalize_point_array(points)
226
+ points.map { |point| transform_point(point) }
227
+ end
228
+
229
+ def inverse_transform_points(*xy_coordinates_or_points)
230
+ points = xy_coordinates_or_points.first.is_a?(Array) ? xy_coordinates_or_points.first : xy_coordinates_or_points
231
+ points = MultiPoint.normalize_point_array(points)
232
+ points.map { |point| inverse_transform_point(point) }
233
+ end
234
+ end
235
+ end
@@ -21,10 +21,10 @@
21
21
 
22
22
  require 'perfect_shape/shape'
23
23
  require 'perfect_shape/rectangular_shape'
24
+ require 'perfect_shape/point'
24
25
  require 'perfect_shape/line'
25
26
 
26
27
  module PerfectShape
27
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Arc2D.html
28
28
  class Arc < Shape
29
29
  include RectangularShape
30
30
  include Equalizer.new(:type, :x, :y, :width, :height, :start, :extent)
@@ -145,7 +145,7 @@ module PerfectShape
145
145
  # the arc, {@code false} if the point lies outside of the
146
146
  # arc's bounds.
147
147
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
148
- x, y = normalize_point(x_or_point, y)
148
+ x, y = Point.normalize_point(x_or_point, y)
149
149
  return unless x && y
150
150
  if outline
151
151
  if type == :pie && x == center_x && y == center_y
@@ -64,7 +64,7 @@ module PerfectShape
64
64
  # the composite shape or false if the point lies outside of the
65
65
  # path's bounds.
66
66
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
67
- x, y = normalize_point(x_or_point, y)
67
+ x, y = Point.normalize_point(x_or_point, y)
68
68
  return unless x && y
69
69
 
70
70
  shapes.any? { |shape| shape.contain?(x, y, outline: outline, distance_tolerance: distance_tolerance) }
@@ -20,10 +20,10 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'perfect_shape/shape'
23
+ require 'perfect_shape/point'
23
24
  require 'perfect_shape/multi_point'
24
25
 
25
26
  module PerfectShape
26
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/QuadCurve2D.html
27
27
  class CubicBezierCurve < Shape
28
28
  class << self
29
29
  # Calculates the number of times the cubic bézier curve from (x1,y1) to (x2,y2)
@@ -86,7 +86,7 @@ module PerfectShape
86
86
  # the cubic bézier curve, {@code false} if the point lies outside of the
87
87
  # cubic bézier curve's bounds.
88
88
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
89
- x, y = normalize_point(x_or_point, y)
89
+ x, y = Point.normalize_point(x_or_point, y)
90
90
  return unless x && y
91
91
 
92
92
  if outline
@@ -120,7 +120,7 @@ module PerfectShape
120
120
  # +1 is added for each crossing where the Y coordinate is increasing
121
121
  # -1 is added for each crossing where the Y coordinate is decreasing
122
122
  def point_crossings(x_or_point, y = nil, level = 0)
123
- x, y = normalize_point(x_or_point, y)
123
+ x, y = Point.normalize_point(x_or_point, y)
124
124
  return unless x && y
125
125
  CubicBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1], x, y, level)
126
126
  end
@@ -183,7 +183,7 @@ module PerfectShape
183
183
  end
184
184
 
185
185
  def point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
186
- x, y = normalize_point(x_or_point, y)
186
+ x, y = Point.normalize_point(x_or_point, y)
187
187
  return unless x && y
188
188
 
189
189
  point = Point.new(x, y)
@@ -22,7 +22,6 @@
22
22
  require 'perfect_shape/arc'
23
23
 
24
24
  module PerfectShape
25
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Ellipse2D.html
26
25
  class Ellipse < Arc
27
26
  MESSAGE_CANNOT_UPDATE_ATTRIUBTE = "Ellipse %s cannot be updated. If you want to update type, use Arc instead!"
28
27
 
@@ -65,7 +64,7 @@ module PerfectShape
65
64
  # ellipse's bounds.
66
65
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
67
66
  # This is implemented again even though super would have just worked to have an optimized algorithm for Ellipse.
68
- x, y = normalize_point(x_or_point, y)
67
+ x, y = Point.normalize_point(x_or_point, y)
69
68
  return unless x && y
70
69
  if outline
71
70
  super(x, y, outline: true, distance_tolerance: distance_tolerance)
@@ -20,10 +20,10 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'perfect_shape/shape'
23
+ require 'perfect_shape/point'
23
24
  require 'perfect_shape/multi_point'
24
25
 
25
26
  module PerfectShape
26
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Line2D.html
27
27
  class Line < Shape
28
28
  class << self
29
29
  # Returns an indicator of where the specified point (px,py) lies with respect to the line segment from
@@ -213,20 +213,20 @@ module PerfectShape
213
213
  # the line, {@code false} if the point lies outside of the
214
214
  # line's bounds.
215
215
  def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
216
- x, y = normalize_point(x_or_point, y)
216
+ x, y = Point.normalize_point(x_or_point, y)
217
217
  return unless x && y
218
218
  distance_tolerance = BigDecimal(distance_tolerance.to_s)
219
219
  point_distance(x, y) <= distance_tolerance
220
220
  end
221
221
 
222
222
  def point_distance(x_or_point, y = nil)
223
- x, y = normalize_point(x_or_point, y)
223
+ x, y = Point.normalize_point(x_or_point, y)
224
224
  return unless x && y
225
225
  Line.point_distance(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
226
226
  end
227
227
 
228
228
  def relative_counterclockwise(x_or_point, y = nil)
229
- x, y = normalize_point(x_or_point, y)
229
+ x, y = Point.normalize_point(x_or_point, y)
230
230
  return unless x && y
231
231
  Line.relative_counterclockwise(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
232
232
  end
@@ -237,7 +237,7 @@ module PerfectShape
237
237
  # +1 is returned for a crossing where the Y coordinate is increasing
238
238
  # -1 is returned for a crossing where the Y coordinate is decreasing
239
239
  def point_crossings(x_or_point, y = nil)
240
- x, y = normalize_point(x_or_point, y)
240
+ x, y = Point.normalize_point(x_or_point, y)
241
241
  return unless x && y
242
242
  Line.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], x, y)
243
243
  end
@@ -24,6 +24,19 @@ require 'perfect_shape/shape'
24
24
  module PerfectShape
25
25
  # Represents multi-point shapes like Line, Polygon, and Polyline
26
26
  module MultiPoint
27
+ class << self
28
+ def normalize_point_array(the_points)
29
+ if the_points.all? {|the_point| the_point.is_a?(Array)}
30
+ the_points
31
+ else
32
+ the_points = the_points.flatten
33
+ xs = the_points.each_with_index.select {|n, i| i.even?}.map(&:first)
34
+ ys = the_points.each_with_index.select {|n, i| i.odd?}.map(&:first)
35
+ xs.zip(ys)
36
+ end
37
+ end
38
+ end
39
+
27
40
  attr_reader :points
28
41
 
29
42
  def initialize(points: [])
@@ -32,11 +45,7 @@ module PerfectShape
32
45
 
33
46
  # Sets points, normalizing to an Array of Arrays of (x,y) pairs as BigDecimal
34
47
  def points=(the_points)
35
- unless the_points.first.is_a?(Array)
36
- xs = the_points.each_with_index.select {|n, i| i.even?}.map(&:first)
37
- ys = the_points.each_with_index.select {|n, i| i.odd?}.map(&:first)
38
- the_points = xs.zip(ys)
39
- end
48
+ the_points = MultiPoint.normalize_point_array(the_points)
40
49
  @points = the_points.map do |pair|
41
50
  [
42
51
  pair.first.is_a?(BigDecimal) ? pair.first : BigDecimal(pair.first.to_s),
@@ -27,7 +27,6 @@ require 'perfect_shape/cubic_bezier_curve'
27
27
  require 'perfect_shape/multi_point'
28
28
 
29
29
  module PerfectShape
30
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Path2D.html
31
30
  class Path < Shape
32
31
  include MultiPoint
33
32
  include Equalizer.new(:shapes, :closed, :winding_rule)
@@ -115,7 +114,7 @@ module PerfectShape
115
114
  # the path or false if the point lies outside of the
116
115
  # path's bounds.
117
116
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
118
- x, y = normalize_point(x_or_point, y)
117
+ x, y = Point.normalize_point(x_or_point, y)
119
118
  return unless x && y
120
119
 
121
120
  if outline
@@ -149,7 +148,7 @@ module PerfectShape
149
148
  # The caller must check for NaN values.
150
149
  # The caller may also reject infinite values as well.
151
150
  def point_crossings(x_or_point, y = nil)
152
- x, y = normalize_point(x_or_point, y)
151
+ x, y = Point.normalize_point(x_or_point, y)
153
152
  return unless x && y
154
153
  return 0 if shapes.count == 0
155
154
  movx = movy = curx = cury = endx = endy = 0
@@ -33,6 +33,21 @@ module PerfectShape
33
33
  py = py.is_a?(BigDecimal) ? py : BigDecimal(py.to_s)
34
34
  BigDecimal(Math.sqrt((px - x)**2 + (py - y)**2).to_s)
35
35
  end
36
+
37
+ # Normalizes point args whether two-number Array or x, y args returning
38
+ # normalized point array of two BigDecimal's
39
+ #
40
+ # @param x_or_point The point or X coordinate of the point to test.
41
+ # @param y The Y coordinate of the point to test.
42
+ #
43
+ # @return Array of x and y BigDecimal's representing point
44
+ def normalize_point(x_or_point, y = nil)
45
+ x = x_or_point
46
+ x, y = x if y.nil? && x_or_point.is_a?(Array) && x_or_point.size == 2
47
+ x = x.is_a?(BigDecimal) ? x : BigDecimal(x.to_s)
48
+ y = y.is_a?(BigDecimal) ? y : BigDecimal(y.to_s)
49
+ [x, y]
50
+ end
36
51
  end
37
52
 
38
53
  include PointLocation
@@ -68,14 +83,14 @@ module PerfectShape
68
83
  # @return {@code true} if the point is close enough within distance tolerance,
69
84
  # {@code false} if the point is too far.
70
85
  def contain?(x_or_point, y = nil, outline: true, distance_tolerance: 0)
71
- x, y = normalize_point(x_or_point, y)
86
+ x, y = Point.normalize_point(x_or_point, y)
72
87
  return unless x && y
73
88
  distance_tolerance = BigDecimal(distance_tolerance.to_s)
74
89
  point_distance(x, y) <= distance_tolerance
75
90
  end
76
91
 
77
92
  def point_distance(x_or_point, y = nil)
78
- x, y = normalize_point(x_or_point, y)
93
+ x, y = Point.normalize_point(x_or_point, y)
79
94
  return unless x && y
80
95
 
81
96
  Point.point_distance(self.x, self.y, x, y)
@@ -20,10 +20,10 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'perfect_shape/shape'
23
+ require 'perfect_shape/point'
23
24
  require 'perfect_shape/multi_point'
24
25
 
25
26
  module PerfectShape
26
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/Polygon.html
27
27
  class Polygon < Shape
28
28
  include MultiPoint
29
29
  include Equalizer.new(:points)
@@ -38,7 +38,7 @@ module PerfectShape
38
38
  # the polygon, {@code false} if the point lies outside of the
39
39
  # polygon's bounds.
40
40
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
41
- x, y = normalize_point(x_or_point, y)
41
+ x, y = Point.normalize_point(x_or_point, y)
42
42
  return unless x && y
43
43
  if outline
44
44
  edges.any? { |edge| edge.contain?(x, y, distance_tolerance: distance_tolerance) }
@@ -20,10 +20,10 @@
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
22
  require 'perfect_shape/shape'
23
+ require 'perfect_shape/point'
23
24
  require 'perfect_shape/multi_point'
24
25
 
25
26
  module PerfectShape
26
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/QuadCurve2D.html
27
27
  class QuadraticBezierCurve < Shape
28
28
  class << self
29
29
  # Calculates the number of times the quadratic bézier curve from (x1,y1) to (x2,y2)
@@ -80,7 +80,7 @@ module PerfectShape
80
80
  # the quadratic bézier curve, {@code false} if the point lies outside of the
81
81
  # quadratic bézier curve's bounds.
82
82
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
83
- x, y = normalize_point(x_or_point, y)
83
+ x, y = Point.normalize_point(x_or_point, y)
84
84
  return unless x && y
85
85
 
86
86
  x1 = points[0][0]
@@ -190,7 +190,7 @@ module PerfectShape
190
190
  # +1 is added for each crossing where the Y coordinate is increasing
191
191
  # -1 is added for each crossing where the Y coordinate is decreasing
192
192
  def point_crossings(x_or_point, y = nil, level = 0)
193
- x, y = normalize_point(x_or_point, y)
193
+ x, y = Point.normalize_point(x_or_point, y)
194
194
  return unless x && y
195
195
  QuadraticBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], x, y, level)
196
196
  end
@@ -247,7 +247,7 @@ module PerfectShape
247
247
  end
248
248
 
249
249
  def point_distance(x_or_point, y = nil, minimum_distance_threshold: OUTLINE_MINIMUM_DISTANCE_THRESHOLD)
250
- x, y = normalize_point(x_or_point, y)
250
+ x, y = Point.normalize_point(x_or_point, y)
251
251
  return unless x && y
252
252
 
253
253
  point = Point.new(x, y)
@@ -21,10 +21,10 @@
21
21
 
22
22
  require 'perfect_shape/shape'
23
23
  require 'perfect_shape/rectangular_shape'
24
+ require 'perfect_shape/point'
24
25
  require 'perfect_shape/line'
25
26
 
26
27
  module PerfectShape
27
- # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Rectangle2D.html
28
28
  class Rectangle < Shape
29
29
  include RectangularShape
30
30
  include Equalizer.new(:x, :y, :width, :height)
@@ -38,7 +38,7 @@ module PerfectShape
38
38
  # the rectangle, {@code false} if the point lies outside of the
39
39
  # rectangle's bounds.
40
40
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
41
- x, y = normalize_point(x_or_point, y)
41
+ x, y = Point.normalize_point(x_or_point, y)
42
42
  return unless x && y
43
43
 
44
44
  if outline
@@ -75,21 +75,6 @@ module PerfectShape
75
75
  Rectangle.new(x: min_x, y: min_y, width: width, height: height)
76
76
  end
77
77
 
78
- # Normalizes point args whether two-number Array or x, y args returning
79
- # normalized point array of two BigDecimal's
80
- #
81
- # @param x_or_point The point or X coordinate of the point to test.
82
- # @param y The Y coordinate of the point to test.
83
- #
84
- # @return Array of x and y BigDecimal's representing point
85
- def normalize_point(x_or_point, y = nil)
86
- x = x_or_point
87
- x, y = x if y.nil? && x_or_point.is_a?(Array) && x_or_point.size == 2
88
- x = x.is_a?(BigDecimal) ? x : BigDecimal(x.to_s)
89
- y = y.is_a?(BigDecimal) ? y : BigDecimal(y.to_s)
90
- [x, y]
91
- end
92
-
93
78
  # Subclasses must implement
94
79
  def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
95
80
  end
@@ -2,17 +2,17 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: perfect-shape 0.3.5 ruby lib
5
+ # stub: perfect-shape 0.4.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "perfect-shape".freeze
9
- s.version = "0.3.5"
9
+ s.version = "0.4.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Andy Maleh".freeze]
14
- s.date = "2022-01-11"
15
- s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing lines, quadratic b\u00E9zier curves, and cubic bezier curves (including both Ray Casting Algorithm, aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
14
+ s.date = "2022-01-16"
15
+ s.description = "Perfect Shape is a collection of pure Ruby geometric algorithms that are mostly useful for GUI manipulation like checking containment of a mouse click point in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, and paths containing lines, quadratic b\u00E9zier curves, and cubic bezier curves, potentially with affine transforms applied like translation, scale, rotation, shear/skew, and inversion (including both Ray Casting Algorithm, aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).".freeze
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
18
18
  "CHANGELOG.md",
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
25
25
  "README.md",
26
26
  "VERSION",
27
27
  "lib/perfect-shape.rb",
28
+ "lib/perfect_shape/affine_transform.rb",
28
29
  "lib/perfect_shape/arc.rb",
29
30
  "lib/perfect_shape/circle.rb",
30
31
  "lib/perfect_shape/composite_shape.rb",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perfect-shape
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Maleh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-11 00:00:00.000000000 Z
11
+ date: 2022-01-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: equalizer
@@ -98,9 +98,11 @@ description: Perfect Shape is a collection of pure Ruby geometric algorithms tha
98
98
  are mostly useful for GUI manipulation like checking containment of a mouse click
99
99
  point in popular geometry shapes such as rectangle, square, arc (open, chord, and
100
100
  pie), ellipse, circle, polygon, and paths containing lines, quadratic bézier curves,
101
- and cubic bezier curves (including both Ray Casting Algorithm, aka Even-odd Rule,
102
- and Winding Number Algorithm, aka Nonzero Rule). Additionally, it contains some
103
- purely mathematical algorithms like IEEEremainder (also known as IEEE-754 remainder).
101
+ and cubic bezier curves, potentially with affine transforms applied like translation,
102
+ scale, rotation, shear/skew, and inversion (including both Ray Casting Algorithm,
103
+ aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally,
104
+ it contains some purely mathematical algorithms like IEEEremainder (also known as
105
+ IEEE-754 remainder).
104
106
  email: andy.am@gmail.com
105
107
  executables: []
106
108
  extensions: []
@@ -114,6 +116,7 @@ files:
114
116
  - README.md
115
117
  - VERSION
116
118
  - lib/perfect-shape.rb
119
+ - lib/perfect_shape/affine_transform.rb
117
120
  - lib/perfect_shape/arc.rb
118
121
  - lib/perfect_shape/circle.rb
119
122
  - lib/perfect_shape/composite_shape.rb