geometry 3 → 4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +10 -1
- data/Rakefile +11 -0
- data/geometry.gemspec +1 -1
- data/lib/geometry.rb +8 -1
- data/lib/geometry/arc.rb +31 -0
- data/lib/geometry/circle.rb +1 -1
- data/lib/geometry/edge.rb +1 -1
- data/lib/geometry/line.rb +2 -2
- data/lib/geometry/point.rb +67 -7
- data/lib/geometry/point_zero.rb +69 -0
- data/lib/geometry/polygon.rb +1 -0
- data/lib/geometry/rectangle.rb +38 -10
- data/lib/geometry/rotation.rb +46 -0
- data/lib/geometry/size_zero.rb +69 -0
- data/lib/geometry/square.rb +103 -0
- data/lib/geometry/transformation.rb +106 -0
- data/lib/geometry/vector.rb +14 -0
- data/test/{test_geometry.rb → geometry.rb} +6 -5
- data/test/geometry/arc.rb +13 -0
- data/test/geometry/circle.rb +6 -6
- data/test/geometry/edge.rb +15 -15
- data/test/{test_line.rb → geometry/line.rb} +18 -18
- data/test/geometry/point.rb +135 -46
- data/test/geometry/point_zero.rb +151 -0
- data/test/geometry/polygon.rb +10 -7
- data/test/geometry/rectangle.rb +60 -55
- data/test/geometry/rotation.rb +35 -0
- data/test/geometry/size.rb +46 -46
- data/test/geometry/size_zero.rb +151 -0
- data/test/geometry/square.rb +56 -0
- data/test/geometry/transformation.rb +90 -0
- data/test/geometry/vector.rb +17 -0
- metadata +28 -11
- data/test/helper.rb +0 -2
- data/test/test_unit_extensions.rb +0 -23
data/README.markdown
CHANGED
@@ -19,9 +19,13 @@ Primitives
|
|
19
19
|
----------
|
20
20
|
|
21
21
|
- Point
|
22
|
+
- Size
|
22
23
|
- Line
|
24
|
+
- Edge
|
23
25
|
- Circle
|
24
26
|
- Rectangle
|
27
|
+
- Polygon
|
28
|
+
- Transformation
|
25
29
|
|
26
30
|
Examples
|
27
31
|
--------
|
@@ -29,7 +33,6 @@ Examples
|
|
29
33
|
### Point
|
30
34
|
```ruby
|
31
35
|
point = Geometry::Point[3,4] # 2D Point at coordinate 3, 4
|
32
|
-
point = Geometry.point(1,2) # Functional constructor
|
33
36
|
|
34
37
|
# Copy constructors
|
35
38
|
point2 = Geometry::Point[point]
|
@@ -66,3 +69,9 @@ Examples
|
|
66
69
|
# A circle at Point[1,2] with a radius of 3
|
67
70
|
circle = Geometry::Circle.new [1,2], 3
|
68
71
|
```
|
72
|
+
|
73
|
+
### Polygon
|
74
|
+
```ruby
|
75
|
+
# A polygon that looks a lot like a square
|
76
|
+
polygon = Geometry::Polygon.new [0,0], [1,0], [1,1], [0,1]
|
77
|
+
```
|
data/Rakefile
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs.push "lib"
|
6
|
+
t.test_files = FileList['test/**/*.rb']
|
7
|
+
t.verbose = true
|
8
|
+
end
|
2
9
|
|
3
10
|
task :fixdates do
|
4
11
|
branch = `git branch --no-color -r --merged`.strip
|
@@ -14,6 +21,10 @@ task :trim_whitespace do
|
|
14
21
|
system(%Q[git status --short | awk '{if ($1 != "D" && $1 != "R") print $2}' | grep -e '.*\.rb$' | xargs sed -i '' -e 's/[ \t]*$//g;'])
|
15
22
|
end
|
16
23
|
|
24
|
+
task :docs do
|
25
|
+
`yard`
|
26
|
+
end
|
27
|
+
|
17
28
|
task :uninstall do
|
18
29
|
`gem uninstall geometry`
|
19
30
|
end
|
data/geometry.gemspec
CHANGED
data/lib/geometry.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
+
require_relative 'geometry/arc'
|
1
2
|
require_relative 'geometry/circle'
|
2
|
-
require_relative 'geometry/point'
|
3
3
|
require_relative 'geometry/line'
|
4
|
+
require_relative 'geometry/point'
|
5
|
+
require_relative 'geometry/point_zero'
|
4
6
|
require_relative 'geometry/polygon'
|
5
7
|
require_relative 'geometry/rectangle'
|
8
|
+
require_relative 'geometry/rotation'
|
6
9
|
require_relative 'geometry/size'
|
10
|
+
require_relative 'geometry/size_zero'
|
11
|
+
require_relative 'geometry/square'
|
12
|
+
require_relative 'geometry/transformation'
|
13
|
+
require_relative 'geometry/vector'
|
7
14
|
|
8
15
|
module Geometry
|
9
16
|
# @overload Line(array0, array1)
|
data/lib/geometry/arc.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'point'
|
2
|
+
|
3
|
+
module Geometry
|
4
|
+
|
5
|
+
=begin rdoc
|
6
|
+
Arcs are Circles that don't quite go all the way around
|
7
|
+
|
8
|
+
== Usage
|
9
|
+
An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis and goes to the Y-axis (counter-clockwise)
|
10
|
+
arc = Geometry::Arc.new [1,1], 2, 0, 90
|
11
|
+
=end
|
12
|
+
|
13
|
+
class Arc
|
14
|
+
attr_reader :center
|
15
|
+
attr_reader :radius
|
16
|
+
attr_reader :start_angle, :end_angle
|
17
|
+
|
18
|
+
# Construct a new {Arc}
|
19
|
+
# @overload initialize(center, radius, start_angle, end_angle)
|
20
|
+
# @param [Point] center The {Point} at the center of it all
|
21
|
+
# @param [Numeric] radius Radius
|
22
|
+
# @param [Numeric] start_angle Starting angle
|
23
|
+
# @param [Numeric] end_angle Ending angle
|
24
|
+
def initialize(center, radius, start_angle, end_angle)
|
25
|
+
@center = Point[center]
|
26
|
+
@radius = radius
|
27
|
+
@start_angle = start_angle
|
28
|
+
@end_angle = end_angle
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/geometry/circle.rb
CHANGED
@@ -21,7 +21,7 @@ Circles come in all shapes and sizes, but they're usually round.
|
|
21
21
|
# @param [Number] radius The radius of the Circle
|
22
22
|
# @return [Circle] A new Circle object
|
23
23
|
def initialize(center, radius)
|
24
|
-
@center =
|
24
|
+
@center = Point[center]
|
25
25
|
@radius = radius
|
26
26
|
end
|
27
27
|
end
|
data/lib/geometry/edge.rb
CHANGED
@@ -16,7 +16,7 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
|
|
16
16
|
# Construct a new {Edge} object from any two things that can be converted
|
17
17
|
# to a {Point}.
|
18
18
|
def initialize(point0, point1)
|
19
|
-
@first, @last = [point0,
|
19
|
+
@first, @last = [Point[point0], Point[point1]]
|
20
20
|
end
|
21
21
|
|
22
22
|
# Two Edges are equal if both have equal {Point}s in the same order
|
data/lib/geometry/line.rb
CHANGED
@@ -67,7 +67,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
67
67
|
# @private
|
68
68
|
class PointSlopeLine < Line
|
69
69
|
def initialize(point, slope)
|
70
|
-
@point =
|
70
|
+
@point = Point[point]
|
71
71
|
@slope = slope
|
72
72
|
end
|
73
73
|
def to_s
|
@@ -111,7 +111,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
|
|
111
111
|
attr_reader :first, :last
|
112
112
|
|
113
113
|
def initialize(point0, point1)
|
114
|
-
@first, @last = [point0,
|
114
|
+
@first, @last = [Point[point0], Point[point1]]
|
115
115
|
end
|
116
116
|
def inspect
|
117
117
|
'Line(' + @first.inspect + ', ' + @last.inspect + ')'
|
data/lib/geometry/point.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'matrix'
|
2
2
|
|
3
3
|
module Geometry
|
4
|
+
DimensionMismatch = Class.new(StandardError)
|
5
|
+
OperationNotDefined = Class.new(StandardError)
|
6
|
+
|
4
7
|
=begin rdoc
|
5
8
|
An object repesenting a Point in N-dimensional space
|
6
9
|
|
@@ -23,16 +26,33 @@ geometry class (x, y, z).
|
|
23
26
|
# @overload [](Point)
|
24
27
|
# @overload [](Vector)
|
25
28
|
def self.[](*array)
|
29
|
+
return array[0] if array[0].is_a? Point
|
26
30
|
array = array[0] if array[0].is_a?(Array)
|
27
31
|
array = array[0].to_a if array[0].is_a?(Vector)
|
28
32
|
super *array
|
29
33
|
end
|
30
34
|
|
35
|
+
# Creates and returns a new {PointZero} instance
|
36
|
+
# @return [PointZero] A new {PointZero} instance
|
37
|
+
def self.zero
|
38
|
+
PointZero.new
|
39
|
+
end
|
40
|
+
|
31
41
|
# Allow comparison with an Array, otherwise do the normal thing
|
32
|
-
def
|
42
|
+
def eql?(other)
|
33
43
|
return @elements == other if other.is_a?(Array)
|
34
44
|
super other
|
35
45
|
end
|
46
|
+
alias == eql?
|
47
|
+
|
48
|
+
def coerce(other)
|
49
|
+
case other
|
50
|
+
when Array then [Point[*other], self]
|
51
|
+
when Vector then [Point[*other], self]
|
52
|
+
else
|
53
|
+
raise TypeError, "#{self.class} can't be coerced into #{other.class}"
|
54
|
+
end
|
55
|
+
end
|
36
56
|
|
37
57
|
def inspect
|
38
58
|
'Point' + @elements.inspect
|
@@ -41,34 +61,74 @@ geometry class (x, y, z).
|
|
41
61
|
'Point' + @elements.to_s
|
42
62
|
end
|
43
63
|
|
64
|
+
# !@group Accessors
|
65
|
+
# @param [Integer] i Index into the {Point}'s elements
|
66
|
+
# @return [Numeric] Element i (starting at 0)
|
67
|
+
def [](i)
|
68
|
+
@elements[i]
|
69
|
+
end
|
70
|
+
|
71
|
+
# !@attribute [r] x
|
44
72
|
# @return [Numeric] X-component
|
45
73
|
def x
|
46
74
|
@elements[0]
|
47
75
|
end
|
48
76
|
|
77
|
+
# !@attribute [r] y
|
49
78
|
# @return [Numeric] Y-component
|
50
79
|
def y
|
51
80
|
@elements[1]
|
52
81
|
end
|
53
82
|
|
83
|
+
# !@attribute [r] z
|
54
84
|
# @return [Numeric] Z-component
|
55
85
|
def z
|
56
86
|
@elements[2]
|
57
87
|
end
|
88
|
+
# !@endgroup
|
89
|
+
|
90
|
+
# !@group Arithmetic
|
91
|
+
|
92
|
+
# !@group Unary operators
|
93
|
+
def +@
|
94
|
+
self
|
95
|
+
end
|
58
96
|
|
59
|
-
|
60
|
-
|
61
|
-
|
97
|
+
def -@
|
98
|
+
Point[@elements.map {|e| -e }]
|
99
|
+
end
|
100
|
+
# !@endgroup
|
62
101
|
|
63
102
|
def +(other)
|
64
|
-
|
103
|
+
case other
|
104
|
+
when Numeric
|
105
|
+
raise DimensionMismatch, "A scalar can't be added to a Point of dimension greater than 1" if size != 1
|
106
|
+
Point[@elements.first + other]
|
107
|
+
when PointZero
|
108
|
+
self
|
109
|
+
else
|
110
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
111
|
+
raise DimensionMismatch, "Can't add #{other} to #{self}" if size != other.size
|
112
|
+
Point[Array.new(size) {|i| @elements[i] + other[i] }]
|
113
|
+
end
|
65
114
|
end
|
66
115
|
|
67
116
|
def -(other)
|
68
|
-
|
117
|
+
case other
|
118
|
+
when Numeric
|
119
|
+
raise DimensionMismatch, "A scalar can't be subtracted from a Point of dimension greater than 1" if size != 1
|
120
|
+
Point[@elements.first - other]
|
121
|
+
when PointZero
|
122
|
+
self
|
123
|
+
else
|
124
|
+
raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
|
125
|
+
raise DimensionMismatch, "Can't subtract #{other} from #{self}" if size != other.size
|
126
|
+
Point[Array.new(size) {|i| @elements[i] - other[i] }]
|
127
|
+
end
|
69
128
|
end
|
70
129
|
|
71
|
-
#
|
130
|
+
# !@endgroup
|
72
131
|
|
73
132
|
end
|
74
133
|
end
|
134
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative 'point'
|
2
|
+
|
3
|
+
module Geometry
|
4
|
+
=begin rdoc
|
5
|
+
An object repesenting a {Point} at the origin in N-dimensional space
|
6
|
+
|
7
|
+
A {PointZero} object is a {Point} that will always compare equal to zero and unequal to
|
8
|
+
everything else, regardless of size.
|
9
|
+
=end
|
10
|
+
class PointZero
|
11
|
+
def eql?(other)
|
12
|
+
if other.respond_to? :all?
|
13
|
+
other.all? {|e| e.eql? 0}
|
14
|
+
else
|
15
|
+
other == 0
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias == eql?
|
19
|
+
|
20
|
+
def coerce(other)
|
21
|
+
if other.is_a? Numeric
|
22
|
+
[other, 0]
|
23
|
+
elsif other.is_a? Array
|
24
|
+
[other, Array.new(other.size,0)]
|
25
|
+
elsif other.is_a? Vector
|
26
|
+
[other, Vector[*Array.new(other.size,0)]]
|
27
|
+
else
|
28
|
+
[Point[other], Point[Array.new(other.size,0)]]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# !@group Arithmetic
|
33
|
+
|
34
|
+
# !@group Unary operators
|
35
|
+
def +@
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def -@
|
40
|
+
self
|
41
|
+
end
|
42
|
+
# !@endgroup
|
43
|
+
|
44
|
+
def +(other)
|
45
|
+
other
|
46
|
+
end
|
47
|
+
|
48
|
+
def -(other)
|
49
|
+
if other.respond_to? :-@
|
50
|
+
-other
|
51
|
+
elsif other.respond_to? :map
|
52
|
+
other.map {|a| -a }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def *(other)
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def /(other)
|
61
|
+
raise OperationNotDefined unless other.is_a? Numeric
|
62
|
+
raise ZeroDivisionError if 0 == other
|
63
|
+
self
|
64
|
+
end
|
65
|
+
# !@endgroup
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/lib/geometry/polygon.rb
CHANGED
@@ -13,6 +13,7 @@ An object representing a closed set of vertices and edges.
|
|
13
13
|
|
14
14
|
class Polygon
|
15
15
|
attr_reader :edges, :vertices
|
16
|
+
alias :points :vertices
|
16
17
|
|
17
18
|
# Construct a new Polygon from Points and/or Edges
|
18
19
|
# The constructor will try to convert all of its arguments into Points and
|
data/lib/geometry/rectangle.rb
CHANGED
@@ -9,12 +9,12 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
9
9
|
== Usage
|
10
10
|
|
11
11
|
=== Constructors
|
12
|
-
rect = Rectangle[[1,2], [2,3]]
|
13
|
-
rect = Rectangle[[1,2], Size[1,1]]
|
14
|
-
rect = Rectangle[1,2,2,3]
|
12
|
+
rect = Rectangle[[1,2], [2,3]] # Using two corners
|
13
|
+
rect = Rectangle[[1,2], Size[1,1]] # Using origin and size
|
14
|
+
rect = Rectangle[1,2,2,3] # Using four sides
|
15
15
|
|
16
|
-
rect = Rectangle[10, 20]
|
17
|
-
rect = Rectangle[Size[10, 20]]
|
16
|
+
rect = Rectangle[10, 20] # origin = [0,0], size = [10, 20]
|
17
|
+
rect = Rectangle[Size[10, 20]] # origin = [0,0], size = [10, 20]
|
18
18
|
|
19
19
|
=end
|
20
20
|
|
@@ -81,8 +81,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
81
81
|
# @param [Point] point0 A corner (ie. bottom-left)
|
82
82
|
# @param [Point] point1 The other corner (ie. top-right)
|
83
83
|
def initialize(point0, point1)
|
84
|
-
point0 =
|
85
|
-
point1 =
|
84
|
+
point0 = Point[point0]
|
85
|
+
point1 = Point[point1]
|
86
86
|
raise(ArgumentError, "Point sizes must match") unless point0.size == point1.size
|
87
87
|
|
88
88
|
# Reorder the points to get lower-left and upper-right
|
@@ -100,9 +100,11 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
100
100
|
end
|
101
101
|
|
102
102
|
# @group Accessors
|
103
|
+
|
104
|
+
# @return [Point] The {Rectangle}'s center
|
103
105
|
def center
|
104
106
|
min, max = @points.minmax {|a,b| a.y <=> b.y}
|
105
|
-
Point[(max.x+min.x)/2, (max.y+min.y)/2]
|
107
|
+
Point[(max.x+min.x)/2.0, (max.y+min.y)/2.0]
|
106
108
|
end
|
107
109
|
|
108
110
|
# @return [Array<Edge>] The {Rectangle}'s four edges
|
@@ -116,6 +118,14 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
116
118
|
Edge.new(point3, point0)]
|
117
119
|
end
|
118
120
|
|
121
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
122
|
+
def points
|
123
|
+
point0, point2 = *@points
|
124
|
+
point1 = Point[point0.x,point2.y]
|
125
|
+
point3 = Point[point2.x, point0.y]
|
126
|
+
[point0, point1, point2, point3]
|
127
|
+
end
|
128
|
+
|
119
129
|
def origin
|
120
130
|
minx = @points.min {|a,b| a.x <=> b.x}
|
121
131
|
miny = @points.min {|a,b| a.y <=> b.y}
|
@@ -173,8 +183,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
173
183
|
# @group Accessors
|
174
184
|
# @return [Array<Edge>] The {Rectangle}'s four edges
|
175
185
|
def edges
|
176
|
-
point0 = @center - @size/2
|
177
|
-
point2 = @center + @size/2
|
186
|
+
point0 = @center - @size/2.0
|
187
|
+
point2 = @center + @size/2.0
|
178
188
|
point1 = Point[point0.x,point2.y]
|
179
189
|
point3 = Point[point2.x, point0.y]
|
180
190
|
[Edge.new(point0, point1),
|
@@ -183,6 +193,15 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
183
193
|
Edge.new(point3, point0)]
|
184
194
|
end
|
185
195
|
|
196
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
197
|
+
def points
|
198
|
+
point0 = @center - @size/2.0
|
199
|
+
point2 = @center + @size/2.0
|
200
|
+
point1 = Point[point0.x,point2.y]
|
201
|
+
point3 = Point[point2.x, point0.y]
|
202
|
+
[point0, point1, point2, point3]
|
203
|
+
end
|
204
|
+
|
186
205
|
def height
|
187
206
|
@size.height
|
188
207
|
end
|
@@ -249,6 +268,15 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
|
|
249
268
|
Edge.new(point3, point0)]
|
250
269
|
end
|
251
270
|
|
271
|
+
# @return [Array<Point>] The {Rectangle}'s four points (clockwise)
|
272
|
+
def points
|
273
|
+
point0 = @origin
|
274
|
+
point2 = @origin + @size
|
275
|
+
point1 = Point[point0.x,point2.y]
|
276
|
+
point3 = Point[point2.x, point0.y]
|
277
|
+
[point0, point1, point2, point3]
|
278
|
+
end
|
279
|
+
|
252
280
|
def height
|
253
281
|
@size.height
|
254
282
|
end
|