perfect-shape 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,190 @@
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/multi_point'
24
+
25
+ module PerfectShape
26
+ # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/QuadCurve2D.html
27
+ class QuadraticBezierCurve < Shape
28
+ class << self
29
+ # Calculates the number of times the quadratic bézier curve from (x1,y1) to (x2,y2)
30
+ # crosses the ray extending to the right from (x,y).
31
+ # If the point lies on a part of the curve,
32
+ # then no crossings are counted for that intersection.
33
+ # the level parameter should be 0 at the top-level call and will count
34
+ # up for each recursion level to prevent infinite recursion
35
+ # +1 is added for each crossing where the Y coordinate is increasing
36
+ # -1 is added for each crossing where the Y coordinate is decreasing
37
+ def point_crossings(x1, y1, xc, yc, x2, y2, px, py, level = 0)
38
+ return 0 if (py < y1 && py < yc && py < y2)
39
+ return 0 if (py >= y1 && py >= yc && py >= y2)
40
+ # Note y1 could equal y2...
41
+ return 0 if (px >= x1 && px >= xc && px >= x2)
42
+ if (px < x1 && px < xc && px < x2)
43
+ if (py >= y1)
44
+ return 1 if (py < y2)
45
+ else
46
+ # py < y1
47
+ return -1 if (py >= y2)
48
+ end
49
+ # py outside of y11 range, and/or y1==y2
50
+ return 0
51
+ end
52
+ # double precision only has 52 bits of mantissa
53
+ return PerfectShape::Line.point_crossings(x1, y1, x2, y2, px, py) if (level > 52)
54
+ x1c = BigDecimal((x1 + xc).to_s) / 2
55
+ y1c = BigDecimal((y1 + yc).to_s) / 2
56
+ xc1 = BigDecimal((xc + x2).to_s) / 2
57
+ yc1 = BigDecimal((yc + y2).to_s) / 2
58
+ xc = BigDecimal((x1c + xc1).to_s) / 2
59
+ yc = BigDecimal((y1c + yc1).to_s) / 2
60
+ # [xy]c are NaN if any of [xy]0c or [xy]c1 are NaN
61
+ # [xy]0c or [xy]c1 are NaN if any of [xy][0c1] are NaN
62
+ # These values are also NaN if opposing infinities are added
63
+ return 0 if (xc.nan? || yc.nan?)
64
+ point_crossings(x1, y1, x1c, y1c, xc, yc, px, py, level+1) +
65
+ point_crossings(xc, yc, xc1, yc1, x2, y2, px, py, level+1);
66
+ end
67
+ end
68
+
69
+ include MultiPoint
70
+ include Equalizer.new(:points)
71
+
72
+ # Checks if quadratic bézier curve contains point (two-number Array or x, y args)
73
+ #
74
+ # @param x The X coordinate of the point to test.
75
+ # @param y The Y coordinate of the point to test.
76
+ #
77
+ # @return {@code true} if the point lies within the bound of
78
+ # the quadratic bézier curve, {@code false} if the point lies outside of the
79
+ # quadratic bézier curve's bounds.
80
+ def contain?(x_or_point, y = nil)
81
+ x, y = normalize_point(x_or_point, y)
82
+ return unless x && y
83
+
84
+ x1 = points[0][0]
85
+ y1 = points[0][1]
86
+ xc = points[1][0]
87
+ yc = points[1][1]
88
+ x2 = points[2][0]
89
+ y2 = points[2][1]
90
+
91
+ # We have a convex shape bounded by quad curve Pc(t)
92
+ # and ine Pl(t).
93
+ #
94
+ # P1 = (x1, y1) - start point of curve
95
+ # P2 = (x2, y2) - end point of curve
96
+ # Pc = (xc, yc) - control point
97
+ #
98
+ # Pq(t) = P1*(1 - t)^2 + 2*Pc*t*(1 - t) + P2*t^2 =
99
+ # = (P1 - 2*Pc + P2)*t^2 + 2*(Pc - P1)*t + P1
100
+ # Pl(t) = P1*(1 - t) + P2*t
101
+ # t = [0:1]
102
+ #
103
+ # P = (x, y) - point of interest
104
+ #
105
+ # Let's look at second derivative of quad curve equation:
106
+ #
107
+ # Pq''(t) = 2 * (P1 - 2 * Pc + P2) = Pq''
108
+ # It's constant vector.
109
+ #
110
+ # Let's draw a line through P to be parallel to this
111
+ # vector and find the intersection of the quad curve
112
+ # and the line.
113
+ #
114
+ # Pq(t) is point of intersection if system of equations
115
+ # below has the solution.
116
+ #
117
+ # L(s) = P + Pq''*s == Pq(t)
118
+ # Pq''*s + (P - Pq(t)) == 0
119
+ #
120
+ # | xq''*s + (x - xq(t)) == 0
121
+ # | yq''*s + (y - yq(t)) == 0
122
+ #
123
+ # This system has the solution if rank of its matrix equals to 1.
124
+ # That is, determinant of the matrix should be zero.
125
+ #
126
+ # (y - yq(t))*xq'' == (x - xq(t))*yq''
127
+ #
128
+ # Let's solve this equation with 't' variable.
129
+ # Also let kx = x1 - 2*xc + x2
130
+ # ky = y1 - 2*yc + y2
131
+ #
132
+ # t0q = (1/2)*((x - x1)*ky - (y - y1)*kx) /
133
+ # ((xc - x1)*ky - (yc - y1)*kx)
134
+ #
135
+ # Let's do the same for our line Pl(t):
136
+ #
137
+ # t0l = ((x - x1)*ky - (y - y1)*kx) /
138
+ # ((x2 - x1)*ky - (y2 - y1)*kx)
139
+ #
140
+ # It's easy to check that t0q == t0l. This fact means
141
+ # we can compute t0 only one time.
142
+ #
143
+ # In case t0 < 0 or t0 > 1, we have an intersections outside
144
+ # of shape bounds. So, P is definitely out of shape.
145
+ #
146
+ # In case t0 is inside [0:1], we should calculate Pq(t0)
147
+ # and Pl(t0). We have three points for now, and all of them
148
+ # lie on one line. So, we just need to detect, is our point
149
+ # of interest between points of intersections or not.
150
+ #
151
+ # If the denominator in the t0q and t0l equations is
152
+ # zero, then the points must be collinear and so the
153
+ # curve is degenerate and encloses no area. Thus the
154
+ # result is false.
155
+ kx = x1 - 2 * xc + x2;
156
+ ky = y1 - 2 * yc + y2;
157
+ dx = x - x1;
158
+ dy = y - y1;
159
+ dxl = x2 - x1;
160
+ dyl = y2 - y1;
161
+
162
+ t0 = (dx * ky - dy * kx) / (dxl * ky - dyl * kx)
163
+ return false if (t0 < 0 || t0 > 1 || t0 != t0)
164
+
165
+ xb = kx * t0 * t0 + 2 * (xc - x1) * t0 + x1;
166
+ yb = ky * t0 * t0 + 2 * (yc - y1) * t0 + y1;
167
+ xl = dxl * t0 + x1;
168
+ yl = dyl * t0 + y1;
169
+
170
+ (x >= xb && x < xl) ||
171
+ (x >= xl && x < xb) ||
172
+ (y >= yb && y < yl) ||
173
+ (y >= yl && y < yb)
174
+ end
175
+
176
+ # Calculates the number of times the quad
177
+ # crosses the ray extending to the right from (x,y).
178
+ # If the point lies on a part of the curve,
179
+ # then no crossings are counted for that intersection.
180
+ # the level parameter should be 0 at the top-level call and will count
181
+ # up for each recursion level to prevent infinite recursion
182
+ # +1 is added for each crossing where the Y coordinate is increasing
183
+ # -1 is added for each crossing where the Y coordinate is decreasing
184
+ def point_crossings(x_or_point, y = nil, level = 0)
185
+ x, y = normalize_point(x_or_point, y)
186
+ return unless x && y
187
+ QuadraticBezierCurve.point_crossings(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1], x, y, level)
188
+ end
189
+ end
190
+ end
@@ -21,6 +21,7 @@
21
21
 
22
22
  require 'perfect_shape/shape'
23
23
  require 'perfect_shape/rectangular_shape'
24
+ require 'perfect_shape/line'
24
25
 
25
26
  module PerfectShape
26
27
  # Mostly ported from java.awt.geom: https://docs.oracle.com/javase/8/docs/api/java/awt/geom/Rectangle2D.html
@@ -36,10 +37,17 @@ module PerfectShape
36
37
  # @return {@code true} if the point lies within the bound of
37
38
  # the rectangle, {@code false} if the point lies outside of the
38
39
  # rectangle's bounds.
39
- def contain?(x_or_point, y = nil)
40
+ def contain?(x_or_point, y = nil, outline: false, distance_tolerance: 0)
40
41
  x, y = normalize_point(x_or_point, y)
41
42
  return unless x && y
42
- x.between?(self.x, self.x + self.width) && y.between?(self.y, self.y + self.height)
43
+ if outline
44
+ Line.new(points: [[self.x, self.y], [self.x + width, self.y]]).contain?(x, y, distance_tolerance: distance_tolerance) or
45
+ Line.new(points: [[self.x + width, self.y], [self.x + width, self.y + height]]).contain?(x, y, distance_tolerance: distance_tolerance) or
46
+ Line.new(points: [[self.x + width, self.y + height], [self.x, self.y + height]]).contain?(x, y, distance_tolerance: distance_tolerance) or
47
+ Line.new(points: [[self.x, self.y + height], [self.x, self.y]]).contain?(x, y, distance_tolerance: distance_tolerance)
48
+ else
49
+ x.between?(self.x, self.x + width) && y.between?(self.y, self.y + height)
50
+ end
43
51
  end
44
52
  end
45
53
  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.1.0 ruby lib
5
+ # stub: perfect-shape 0.3.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "perfect-shape".freeze
9
- s.version = "0.1.0"
9
+ s.version = "0.3.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 = "2021-12-22"
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, polyline, polyquad, polycubic, and paths containing lines, quadratic b\u00E9zier curves, and cubic b\u00E9zier 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-08"
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
16
16
  s.email = "andy.am@gmail.com".freeze
17
17
  s.extra_rdoc_files = [
18
18
  "CHANGELOG.md",
@@ -27,6 +27,8 @@ Gem::Specification.new do |s|
27
27
  "lib/perfect-shape.rb",
28
28
  "lib/perfect_shape/arc.rb",
29
29
  "lib/perfect_shape/circle.rb",
30
+ "lib/perfect_shape/composite_shape.rb",
31
+ "lib/perfect_shape/cubic_bezier_curve.rb",
30
32
  "lib/perfect_shape/ellipse.rb",
31
33
  "lib/perfect_shape/line.rb",
32
34
  "lib/perfect_shape/math.rb",
@@ -35,6 +37,7 @@ Gem::Specification.new do |s|
35
37
  "lib/perfect_shape/point.rb",
36
38
  "lib/perfect_shape/point_location.rb",
37
39
  "lib/perfect_shape/polygon.rb",
40
+ "lib/perfect_shape/quadratic_bezier_curve.rb",
38
41
  "lib/perfect_shape/rectangle.rb",
39
42
  "lib/perfect_shape/rectangular_shape.rb",
40
43
  "lib/perfect_shape/shape.rb",
@@ -43,7 +46,7 @@ Gem::Specification.new do |s|
43
46
  ]
44
47
  s.homepage = "http://github.com/AndyObtiva/perfect-shape".freeze
45
48
  s.licenses = ["MIT".freeze]
46
- s.rubygems_version = "3.2.31".freeze
49
+ s.rubygems_version = "3.3.1".freeze
47
50
  s.summary = "Perfect Shape - Geometric Algorithms".freeze
48
51
 
49
52
  if s.respond_to? :specification_version then
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.1.0
4
+ version: 0.3.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: 2021-12-22 00:00:00.000000000 Z
11
+ date: 2022-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: equalizer
@@ -97,11 +97,10 @@ dependencies:
97
97
  description: Perfect Shape is a collection of pure Ruby geometric algorithms that
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
- pie), ellipse, circle, polygon, polyline, polyquad, polycubic, and paths containing
101
- lines, quadratic bézier curves, and cubic bézier curves (including both Ray Casting
102
- Algorithm, aka Even-odd Rule, and Winding Number Algorithm, aka Nonzero Rule). Additionally,
103
- it contains some purely mathematical algorithms like IEEEremainder (also known as
104
- IEEE-754 remainder).
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).
105
104
  email: andy.am@gmail.com
106
105
  executables: []
107
106
  extensions: []
@@ -117,6 +116,8 @@ files:
117
116
  - lib/perfect-shape.rb
118
117
  - lib/perfect_shape/arc.rb
119
118
  - lib/perfect_shape/circle.rb
119
+ - lib/perfect_shape/composite_shape.rb
120
+ - lib/perfect_shape/cubic_bezier_curve.rb
120
121
  - lib/perfect_shape/ellipse.rb
121
122
  - lib/perfect_shape/line.rb
122
123
  - lib/perfect_shape/math.rb
@@ -125,6 +126,7 @@ files:
125
126
  - lib/perfect_shape/point.rb
126
127
  - lib/perfect_shape/point_location.rb
127
128
  - lib/perfect_shape/polygon.rb
129
+ - lib/perfect_shape/quadratic_bezier_curve.rb
128
130
  - lib/perfect_shape/rectangle.rb
129
131
  - lib/perfect_shape/rectangular_shape.rb
130
132
  - lib/perfect_shape/shape.rb
@@ -149,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
151
  - !ruby/object:Gem::Version
150
152
  version: '0'
151
153
  requirements: []
152
- rubygems_version: 3.2.31
154
+ rubygems_version: 3.3.1
153
155
  signing_key:
154
156
  specification_version: 4
155
157
  summary: Perfect Shape - Geometric Algorithms