perfect-shape 0.0.1 → 0.0.5

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: 00c1add66582b68b1c6ec2d15b91c9af7840daf97b7e40662210683690d1a082
4
- data.tar.gz: 2b789617e71a321cd7b736abfcfd59d7c46dbbf91fa0b4e2e5f581704e28527a
3
+ metadata.gz: b287e3d8aee766314feeccc80abe127dc1ec6e8d169af61e8c6efc1097a2b5c2
4
+ data.tar.gz: 96b934dde2686223634244d6ff550b578c15971b2198cb94fb0cec3ded97e06c
5
5
  SHA512:
6
- metadata.gz: 6c7b3efef675a42f6d33aae8d2d208e7224c44f9d8e64064dc9f18b0a3594b4cf2b3addb360376e5837f9478c5e61b18d85b989a16c122ed1d762f88c849075a
7
- data.tar.gz: 6b87d053a77eab1900c67ffb1a5800a878f9f06de7e9713d72f9ecedf5fc6e463e2cb885d1f6e1cfd7eca9eeb08b1321e6e97009564d6c4082a7a542db2d8ea2
6
+ metadata.gz: 6743fed8d87b2ceb4bd69b91fd2ecc3b6655eca986e4a67c2aa755d53f29de60ffbb6fcc539fc45e480f55e06c176cad1885dd3c4af0330a42218b8291837763
7
+ data.tar.gz: b3669ef0aabb19553bad2dc51cdca4b1c188d05cca41c73c13570f0faff19d03d373f7727c423531700cd85e562b340003995558f2c7e4c8c195947e44b232fa
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Change Log
2
2
 
3
+ ## 0.0.5
4
+
5
+ - `PerfectShape::Ellipse`
6
+ - `PerfectShape::Ellipse#contain?`
7
+
8
+ ## 0.0.4
9
+
10
+ - `PerfectShape::Arc#center_x` / `PerfectShape::Arc#center_y` / `PerfectShape::Arc#radius_x` / `PerfectShape::Arc#radius_y`
11
+ - `PerfectShape::Rectangle#center_x` / `PerfectShape::Arc#center_y`
12
+ - `PerfectShape::Square#center_x` / `PerfectShape::Square#center_y`
13
+ - `PerfectShape::Arc#initialize(center_x: , center_y: , radius_x: , radius_y: )`
14
+ - `PerfectShape::Math::normalize_degrees(degrees)` extracted from `PerfectShape::Arc`
15
+
16
+ ## 0.0.3
17
+
18
+ - `PerfectShape::Square`
19
+ - `PerfectShape::Square#contain?(x_or_point, y=nil)`
20
+
21
+ ## 0.0.2
22
+
23
+ - `PerfectShape::Rectangle`
24
+ - `PerfectShape::Rectangle#contain?(x_or_point, y=nil)`
25
+
3
26
  ## 0.0.1
4
27
 
5
28
  - `PerfectShape::Math`
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
- # Perfect Shape 0.0.1
1
+ # Perfect Shape 0.0.5
2
2
  ## Geometric Algorithms
3
+ [![Gem Version](https://badge.fury.io/rb/perfect-shape.svg)](http://badge.fury.io/rb/perfect-shape)
3
4
 
4
- `PerfectShape` is a collection of pure Ruby geometric algorithms that are mostly useful for GUI (Graphical User Interface) manipulation like checking containment of a point in popular geometric shapes such as rectangle, square, arc, circle, polygon, polyline, and paths containing lines, bezier curves, and quadratic curves.
5
+ `PerfectShape` 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 in popular geometry shapes such as rectangle, square, arc (open, chord, and pie), ellipse, circle, polygon, polyline, polyquad, polycubic, and paths containing lines, bezier curves, and quadratic curves.
5
6
 
6
7
  Additionally, `PerfectShape::Math` contains some purely mathematical algorithms.
7
8
 
@@ -12,13 +13,13 @@ To ensure high accuracy, this library does all its mathematical operations with
12
13
  Run:
13
14
 
14
15
  ```
15
- gem install perfect-shape -v 0.0.1
16
+ gem install perfect-shape -v 0.0.5
16
17
  ```
17
18
 
18
19
  Or include in Bundler `Gemfile`:
19
20
 
20
21
  ```ruby
21
- gem 'perfect-shape', '~> 0.0.1'
22
+ gem 'perfect-shape', '~> 0.0.5'
22
23
  ```
23
24
 
24
25
  And, run:
@@ -31,24 +32,98 @@ bundle
31
32
 
32
33
  ### `PerfectShape::Math`
33
34
 
34
- - `::degrees_to_radians(angle)`
35
- - `::radians_to_degrees(angle)`
36
- - `::ieee_remainder(x, y)` (alias: `ieee754_remainder`)
35
+ - `::degrees_to_radians(angle)`: converts degrees to radians
36
+ - `::radians_to_degrees(angle)`: converts radians to degrees
37
+ - `::normalize_degrees(angle)`: normalizes the specified angle into the range -180 to 180.
38
+ - `::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)
37
39
 
38
40
  ### `PerfectShape::Line`
39
41
 
40
- - `::relative_ccw(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
42
+ - `::relative_ccw(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)”.
43
+
44
+ ### `PerfectShape::Rectangle`
45
+
46
+ Includes `PerfectShape::RectangularShape`
47
+
48
+ ![rectangle](images/rectangle.png)
49
+
50
+ - `::new(x: 0, y: 0, width: 1, height: 1)`: constructs a rectangle
51
+ - `#x`: top-left x
52
+ - `#y`: top-left y
53
+ - `#width`: width
54
+ - `#height`: height
55
+ - `#center_x`: center x
56
+ - `#center_y`: center y
57
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside
58
+
59
+ ### `PerfectShape::Square`
60
+
61
+ Extends `PerfectShape::Rectangle`
62
+
63
+ ![square](images/square.png)
64
+
65
+ - `::new(x: 0, y: 0, length: 1)`: constructs a square
66
+ - `#x`: top-left x
67
+ - `#y`: top-left y
68
+ - `#length`: length
69
+ - `#width`: width (equal to length)
70
+ - `#height`: height (equal to length)
71
+ - `#center_x`: center x
72
+ - `#center_y`: center y
73
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside
41
74
 
42
75
  ### `PerfectShape::Arc`
43
76
 
44
- - `::new(type: :open, x: 0, y: 0, width: 1, height: 1, start: 0, extent: 360)`: constructs an arc of type `:open` (default), `:chord`, or `:pie`
77
+ Includes `PerfectShape::RectangularShape`
78
+
79
+ Arcs can be of type `:open`, `:chord`, or `:pie`
80
+
81
+ Open Arc | Chord Arc | Pie Arc
82
+ ---------|-----------|--------
83
+ ![arc-open](images/arc-open.png) | ![arc-chord](images/arc-chord.png) | ![arc-pie](images/arc-pie.png)
84
+
85
+ - `::new(type: :open, x: 0, y: 0, width: 1, height: 1, start: 0, extent: 360, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)`: constructs an arc of type `:open` (default), `:chord`, or `:pie`
86
+ - `#type`: `:open`, `:chord`, or `:pie`
45
87
  - `#x`: top-left x of arc
46
88
  - `#y`: top-left y of arc
47
89
  - `#width`: width of arc
48
90
  - `#height`: height of arc
49
91
  - `#start`: start angle in degrees
50
92
  - `#extent`: extent angle in degrees
51
- - `#contain?(x_or_point, y=0)`: checks if point is in arc
93
+ - `#center_x`: center x
94
+ - `#center_y`: center y
95
+ - `#radius_x`: radius along the x-axis
96
+ - `#radius_y`: radius along the y-axis
97
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside
98
+
99
+ ### `PerfectShape::Ellipse`
100
+
101
+ Extends `PerfectShape::Arc`
102
+
103
+ ![ellipse](images/ellipse.png)
104
+
105
+ - `::new(x: 0, y: 0, width: 1, height: 1, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)`: constructs an ellipse
106
+ - `#x`: top-left x of arc
107
+ - `#y`: top-left y of arc
108
+ - `#width`: width of arc
109
+ - `#height`: height of arc
110
+ - `#center_x`: center x
111
+ - `#center_y`: center y
112
+ - `#radius_x`: radius along the x-axis
113
+ - `#radius_y`: radius along the y-axis
114
+ - `#type`: always `:open`
115
+ - `#start`: always `0`
116
+ - `#extent`: always `360`
117
+ - `#contain?(x_or_point, y=nil)`: checks if point is inside
118
+
119
+ ## Process
120
+
121
+ [Glimmer Process](https://github.com/AndyObtiva/glimmer/blob/master/PROCESS.md)
122
+
123
+ ## Resources
124
+
125
+ - Rubydoc: https://www.rubydoc.info/gems/perfect-shape
126
+ - AWT Geom JavaDoc: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
52
127
 
53
128
  ## TODO
54
129
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.5
data/lib/perfect-shape.rb CHANGED
@@ -19,6 +19,8 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ $LOAD_PATH.unshift File.expand_path('.', __dir__)
23
+
22
24
  require 'bigdecimal'
23
25
 
24
26
  # Perfect Shape algorithms are mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/package-summary.html
@@ -19,54 +19,131 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
- require_relative 'line'
22
+ require 'perfect_shape/shape'
23
+ require 'perfect_shape/rectangular_shape'
24
+ require 'perfect_shape/line'
23
25
 
24
26
  module PerfectShape
25
27
  # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Arc2D.html
26
- class Arc
28
+ class Arc < Shape
29
+ include RectangularShape
30
+
27
31
  TYPES = [:open, :chord, :pie]
28
- attr_accessor :type, :x, :y, :width, :height, :start, :extent
32
+ attr_accessor :type
33
+ attr_reader :start, :extent
29
34
 
30
- def initialize(type: :open, x: 0, y: 0, width: 1, height: 1, start: 0, extent: 360)
35
+ def initialize(type: :open, x: 0, y: 0, width: 1, height: 1, start: 0, extent: 360, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)
36
+ if center_x && center_y && radius_x && radius_y
37
+ self.center_x = center_x
38
+ self.center_y = center_y
39
+ self.radius_x = radius_x
40
+ self.radius_y = radius_y
41
+ else
42
+ super(x: x, y: y, width: width, height: height)
43
+ end
31
44
  @type = type
32
- self.x = x
33
- self.y = y
34
- self.width = width
35
- self.height = height
36
45
  self.start = start
37
46
  self.extent = extent
38
47
  end
39
48
 
49
+ def start=(value)
50
+ @start = BigDecimal(value.to_s)
51
+ end
52
+
53
+ def extent=(value)
54
+ @extent = BigDecimal(value.to_s)
55
+ end
56
+
57
+ def x
58
+ @center_x && @radius_x ? @center_x - @radius_x : super
59
+ end
60
+
61
+ def y
62
+ @center_y && @radius_y ? @center_y - @radius_y : super
63
+ end
64
+
65
+ def width
66
+ @radius_x ? @radius_x * BigDecimal('2.0') : super
67
+ end
68
+
69
+ def height
70
+ @radius_y ? @radius_y * BigDecimal('2.0') : super
71
+ end
72
+
73
+ # Sets x, normalizing to BigDecimal
40
74
  def x=(value)
41
- @x = BigDecimal(value.to_s)
75
+ super
76
+ @center_x = nil
77
+ self.width = width if @radius_x
42
78
  end
43
79
 
80
+ # Sets y, normalizing to BigDecimal
44
81
  def y=(value)
45
- @y = BigDecimal(value.to_s)
82
+ super
83
+ @center_y = nil
84
+ self.height = height if @radius_y
46
85
  end
47
86
 
87
+ # Sets width, normalizing to BigDecimal
48
88
  def width=(value)
49
- @width = BigDecimal(value.to_s)
89
+ super
90
+ @radius_x = nil
50
91
  end
51
92
 
93
+ # Sets height, normalizing to BigDecimal
52
94
  def height=(value)
53
- @height = BigDecimal(value.to_s)
95
+ super
96
+ @radius_y = nil
54
97
  end
55
98
 
56
- def start=(value)
57
- @start = BigDecimal(value.to_s)
99
+ def center_x
100
+ super || @center_x
58
101
  end
59
102
 
60
- def extent=(value)
61
- @extent = BigDecimal(value.to_s)
103
+ def center_y
104
+ super || @center_y
105
+ end
106
+
107
+ def radius_x
108
+ @width ? @width/BigDecimal('2.0') : @radius_x
109
+ end
110
+
111
+ def radius_y
112
+ @height ? @height/BigDecimal('2.0') : @radius_y
113
+ end
114
+
115
+ def center_x=(value)
116
+ @center_x = BigDecimal(value.to_s)
117
+ @x = nil
118
+ self.radius_x = radius_x if @width
119
+ end
120
+
121
+ def center_y=(value)
122
+ @center_y = BigDecimal(value.to_s)
123
+ @y = nil
124
+ self.radius_y = radius_y if @height
125
+ end
126
+
127
+ def radius_x=(value)
128
+ @radius_x = BigDecimal(value.to_s)
129
+ @width = nil
130
+ end
131
+
132
+ def radius_y=(value)
133
+ @radius_y = BigDecimal(value.to_s)
134
+ @height = nil
62
135
  end
63
136
 
64
137
  # Checks if arc contains point denoted by point (two-number Array or x, y args)
138
+ #
139
+ # @param x The X coordinate of the point to test.
140
+ # @param y The Y coordinate of the point to test.
141
+ #
142
+ # @return {@code true} if the point lies within the bound of
143
+ # the arc, {@code false} if the point lies outside of the
144
+ # arc's bounds.
65
145
  def contain?(x_or_point, y = nil)
66
- x = x_or_point
67
- x, y = x if y.nil? && x_or_point.is_a?(Array) && x_or_point.size == 2
68
- x = BigDecimal(x.to_s)
69
- y = BigDecimal(y.to_s)
146
+ x, y = normalize_point(x_or_point, y)
70
147
  return unless x && y
71
148
  # Normalize the coordinates compared to the ellipse
72
149
  # having a center at 0,0 and a radius of 0.5.
@@ -105,38 +182,19 @@ module PerfectShape
105
182
  inarc ? !inside : inside
106
183
  end
107
184
 
185
+ # Determines whether or not the specified angle is within the
186
+ # angular extents of the arc.
108
187
  def contain_angle?(angle)
109
188
  ang_ext = self.extent
110
189
  backwards = ang_ext < 0.0
111
190
  ang_ext = -ang_ext if backwards
112
191
  return true if ang_ext >= 360.0
113
192
 
114
- angle = normalize_degrees(angle) - normalize_degrees(start)
193
+ angle = Math.normalize_degrees(angle) - Math.normalize_degrees(start)
115
194
  angle = -angle if backwards
116
195
  angle += 360.0 if angle < 0.0
117
196
 
118
197
  (angle >= 0.0) && (angle < ang_ext)
119
198
  end
120
-
121
- def normalize_degrees(angle)
122
- if angle > 180.0
123
- if angle <= (180.0 + 360.0)
124
- angle = angle - 360.0
125
- else
126
- angle = Math.ieee_remainder(angle, 360.0)
127
- # IEEEremainder can return -180 here for some input values...
128
- angle = 180.0 if angle == -180.0
129
- end
130
- elsif angle <= -180.0
131
- if angle > (-180.0 - 360.0)
132
- angle = angle + 360.0
133
- else
134
- angle = Math.ieee_remainder(angle, 360.0)
135
- # IEEEremainder can return -180 here for some input values...
136
- angle = 180.0 if angle == -180.0
137
- end
138
- end
139
- angle
140
- end
141
199
  end
142
200
  end
@@ -0,0 +1,78 @@
1
+ # Copyright (c) 2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'perfect_shape/arc'
23
+
24
+ module PerfectShape
25
+ # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Ellipse2D.html
26
+ class Ellipse < Arc
27
+ MESSAGE_CANNOT_UPDATE_ATTRIUBTE = "Ellipse %s cannot be updated. If you want to update type, use Arc instead!"
28
+
29
+ def initialize(x: 0, y: 0, width: 1, height: 1, center_x: nil, center_y: nil, radius_x: nil, radius_y: nil)
30
+ super
31
+ @initialized = true
32
+ end
33
+
34
+ def type=(value)
35
+ if @initialized
36
+ raise MESSAGE_CANNOT_UPDATE_ATTRIUBTE % 'type'
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def start=(value)
43
+ if @initialized
44
+ raise MESSAGE_CANNOT_UPDATE_ATTRIUBTE % 'start'
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def extent=(value)
51
+ if @initialized
52
+ raise MESSAGE_CANNOT_UPDATE_ATTRIUBTE % 'extent'
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ # Checks if ellipse contains point denoted by point (two-number Array or x, y args)
59
+ #
60
+ # @param x The X coordinate of the point to test.
61
+ # @param y The Y coordinate of the point to test.
62
+ #
63
+ # @return {@code true} if the point lies within the bound of
64
+ # the ellipse, {@code false} if the point lies outside of the
65
+ # ellipse's bounds.
66
+ def contain?(x_or_point, y = nil)
67
+ x, y = normalize_point(x_or_point, y)
68
+ return unless x && y
69
+ ellw = self.width
70
+ return false if ellw <= 0.0
71
+ normx = (x - self.x) / ellw - 0.5
72
+ ellh = self.height
73
+ return false if ellh <= 0.0
74
+ normy = (y - self.y) / ellh - 0.5
75
+ (normx * normx + normy * normy) < 0.25
76
+ end
77
+ end
78
+ end
@@ -19,33 +19,23 @@
19
19
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
20
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
21
 
22
+ require 'perfect_shape/shape'
23
+
22
24
  module PerfectShape
23
25
  # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Line2D.html
24
- class Line
26
+ class Line < Shape
25
27
  class << self
26
- # Returns an indicator of where the specified point
27
- # {@code (px,py)} lies with respect to the line segment from
28
- # {@code (x1,y1)} to {@code (x2,y2)}.
29
- # The return value can be either 1, -1, or 0 and indicates
30
- # in which direction the specified line must pivot around its
31
- # first end point, {@code (x1,y1)}, in order to point at the
32
- # specified point {@code (px,py)}.
33
- # <p>A return value of 1 indicates that the line segment must
34
- # turn in the direction that takes the positive X axis towards
35
- # the negative Y axis. In the default coordinate system used by
36
- # Java 2D, this direction is counterclockwise.
37
- # <p>A return value of -1 indicates that the line segment must
38
- # turn in the direction that takes the positive X axis towards
39
- # the positive Y axis. In the default coordinate system, this
40
- # direction is clockwise.
41
- # <p>A return value of 0 indicates that the point lies
42
- # exactly on the line segment. Note that an indicator value
43
- # of 0 is rare and not useful for determining collinearity
44
- # because of floating point rounding issues.
45
- # <p>If the point is colinear with the line segment, but
46
- # not between the end points, then the value will be -1 if the point
47
- # lies "beyond {@code (x1,y1)}" or 1 if the point lies
48
- # "beyond {@code (x2,y2)}".
28
+ # Returns an indicator of where the specified point (px,py) lies with respect to the line segment from
29
+ # (x1,y1) to (x2,y2).
30
+ #
31
+ # 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).
32
+ # 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.
33
+ #
34
+ # 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.
35
+ #
36
+ # 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.
37
+ #
38
+ # 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)”.
49
39
  #
50
40
  # @param x1 the X coordinate of the start point of the
51
41
  # specified line segment
@@ -62,7 +52,6 @@ module PerfectShape
62
52
  # @return an integer that indicates the position of the third specified
63
53
  # coordinates with respect to the line segment formed
64
54
  # by the first two specified coordinates.
65
- # @since 1.2
66
55
  def relative_ccw(x1, y1, x2, y2, px, py)
67
56
  x2 -= x1;
68
57
  y2 -= y1;
@@ -1,6 +1,8 @@
1
1
  module PerfectShape
2
2
  # Perfect Shape Math utility methods
3
+ #
3
4
  # Mostly ported from java.lang.Math: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html
5
+ #
4
6
  # Also includes standard Ruby ::Math utility methods
5
7
  module Math
6
8
  class << self
@@ -13,18 +15,44 @@ module PerfectShape
13
15
  def degrees_to_radians(degrees)
14
16
  (Math::PI/180)*degrees
15
17
  end
18
+
19
+ # Normalizes the specified angle into the range -180 to 180.
20
+ def normalize_degrees(angle)
21
+ if angle > 180.0
22
+ if angle <= (180.0 + 360.0)
23
+ angle = angle - 360.0
24
+ else
25
+ angle = Math.ieee_remainder(angle, 360.0)
26
+ # IEEEremainder can return -180 here for some input values...
27
+ angle = 180.0 if angle == -180.0
28
+ end
29
+ elsif angle <= -180.0
30
+ if angle > (-180.0 - 360.0)
31
+ angle = angle + 360.0
32
+ else
33
+ angle = Math.ieee_remainder(angle, 360.0)
34
+ # IEEEremainder can return -180 here for some input values...
35
+ angle = 180.0 if angle == -180.0
36
+ end
37
+ end
38
+ angle
39
+ end
16
40
 
17
41
  # Computes the remainder operation on two arguments as prescribed by the IEEE 754 standard.
18
- # Algorithm: x – (round(x/y)*y).
42
+ #
43
+ # Algorithm is exactly: x – (round(x/y)*y)
44
+ #
19
45
  # The `round` part rounds to the nearest even number when it is a halfway between n & y (integer + 0.5 number)
46
+ #
20
47
  # The remainder value is mathematically equal to x - y × n, where n is the mathematical integer closest to the exact mathematical value of the quotient x/y, and if two mathematical integers are equally close to x/y, then n is the integer that is even. If the remainder is zero, its sign is the same as the sign of the first argument. Special cases:
48
+ #
21
49
  # If either argument is NaN, or the first argument is infinite, or the second argument is positive zero or negative zero, then the result is NaN.
50
+ #
22
51
  # If the first argument is finite and the second argument is infinite, then the result is the same as the first argument.
23
- # Parameters:
24
- # x - the dividend.
25
- # y - the divisor.
26
- # Returns:
27
- # the remainder when x is divided by y.
52
+ #
53
+ # @param x the dividend.
54
+ # @param y the divisor.
55
+ # @return the remainder when x is divided by y.
28
56
  def ieee754_remainder(x, y)
29
57
  x = BigDecimal(x.to_s)
30
58
  y = BigDecimal(y.to_s)
@@ -0,0 +1,44 @@
1
+ # Copyright (c) 2021 Andy Maleh
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'perfect_shape/shape'
23
+ require 'perfect_shape/rectangular_shape'
24
+
25
+ module PerfectShape
26
+ # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Rectangle2D.html
27
+ class Rectangle < Shape
28
+ include RectangularShape
29
+
30
+ # Checks if rectangle contains point denoted by point (two-number Array or x, y args)
31
+ #
32
+ # @param x The X coordinate of the point to test.
33
+ # @param y The Y coordinate of the point to test.
34
+ #
35
+ # @return {@code true} if the point lies within the bound of
36
+ # the rectangle, {@code false} if the point lies outside of the
37
+ # rectangle's bounds.
38
+ def contain?(x_or_point, y = nil)
39
+ x, y = normalize_point(x_or_point, y)
40
+ return unless x && y
41
+ x.between?(self.x, self.x + self.width) && y.between?(self.y, self.y + self.height)
42
+ end
43
+ end
44
+ end