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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -3
- data/README.md +294 -14
- data/VERSION +1 -1
- data/lib/perfect_shape/composite_shape.rb +72 -0
- data/lib/perfect_shape/cubic_bezier_curve.rb +120 -0
- data/lib/perfect_shape/line.rb +4 -4
- data/lib/perfect_shape/multi_point.rb +2 -2
- data/lib/perfect_shape/path.rb +84 -63
- data/lib/perfect_shape/point.rb +4 -4
- data/lib/perfect_shape/polygon.rb +68 -62
- data/lib/perfect_shape/quadratic_bezier_curve.rb +190 -0
- data/lib/perfect_shape/rectangle.rb +10 -2
- data/perfect-shape.gemspec +8 -5
- metadata +10 -8
@@ -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
|
-
|
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
|
data/perfect-shape.gemspec
CHANGED
@@ -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.
|
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.
|
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 = "
|
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,
|
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.
|
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.
|
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:
|
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,
|
101
|
-
|
102
|
-
|
103
|
-
|
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.
|
154
|
+
rubygems_version: 3.3.1
|
153
155
|
signing_key:
|
154
156
|
specification_version: 4
|
155
157
|
summary: Perfect Shape - Geometric Algorithms
|