geometry 5 → 6
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -1
- data/README.markdown +26 -5
- data/Rakefile +3 -0
- data/geometry.gemspec +1 -1
- data/lib/geometry.rb +2 -33
- data/lib/geometry/arc.rb +21 -17
- data/lib/geometry/circle.rb +40 -12
- data/lib/geometry/edge.rb +22 -0
- data/lib/geometry/line.rb +21 -0
- data/lib/geometry/obround.rb +238 -0
- data/lib/geometry/path.rb +22 -10
- data/lib/geometry/point.rb +21 -9
- data/lib/geometry/point_zero.rb +39 -2
- data/lib/geometry/polygon.rb +180 -0
- data/lib/geometry/polyline.rb +5 -5
- data/lib/geometry/rectangle.rb +117 -54
- data/lib/geometry/regular_polygon.rb +104 -0
- data/lib/geometry/rotation.rb +5 -0
- data/lib/geometry/square.rb +22 -12
- data/lib/geometry/transformation.rb +12 -0
- data/lib/geometry/vector.rb +4 -0
- data/test/geometry.rb +0 -10
- data/test/geometry/arc.rb +18 -15
- data/test/geometry/circle.rb +55 -2
- data/test/geometry/edge.rb +31 -0
- data/test/geometry/line.rb +25 -0
- data/test/geometry/obround.rb +25 -0
- data/test/geometry/path.rb +4 -5
- data/test/geometry/point.rb +12 -12
- data/test/geometry/point_zero.rb +29 -3
- data/test/geometry/polygon.rb +61 -1
- data/test/geometry/polyline.rb +0 -1
- data/test/geometry/rectangle.rb +87 -32
- data/test/geometry/regular_polygon.rb +84 -0
- data/test/geometry/rotation.rb +8 -0
- data/test/geometry/size_zero.rb +2 -0
- data/test/geometry/square.rb +15 -5
- data/test/geometry/transformation.rb +13 -0
- data/test/geometry/vector.rb +18 -0
- metadata +15 -3
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative 'cluster_factory'
|
2
|
+
require_relative 'polygon'
|
3
|
+
|
4
|
+
module Geometry
|
5
|
+
=begin rdoc
|
6
|
+
A {RegularPolygon} is a lot like a {Polygon}, but more regular.
|
7
|
+
|
8
|
+
{http://en.wikipedia.org/wiki/Regular_polygon}
|
9
|
+
|
10
|
+
== Usage
|
11
|
+
polygon = Geometry::RegularPolygon.new sides:4, center:[1,2], radius:3
|
12
|
+
polygon = Geometry::RegularPolygon.new sides:6, center:[1,2], diameter:6
|
13
|
+
=end
|
14
|
+
|
15
|
+
class RegularPolygon < Polygon
|
16
|
+
include ClusterFactory
|
17
|
+
|
18
|
+
# @return [Point] The {RegularPolygon}'s center point
|
19
|
+
attr_reader :center
|
20
|
+
|
21
|
+
# @return [Number] The {RegularPolygon}'s number of sides
|
22
|
+
attr_reader :edge_count
|
23
|
+
|
24
|
+
# @return [Number] The {RegularPolygon}'s radius
|
25
|
+
attr_reader :radius
|
26
|
+
|
27
|
+
# @overload new(sides, center, radius)
|
28
|
+
# Construct a {RegularPolygon} using a center point and radius
|
29
|
+
# @option options [Number] :sides The number of edges
|
30
|
+
# @option options [Point] :center The center point of the {RegularPolygon}
|
31
|
+
# @option options [Number] :radius The radius of the {RegularPolygon}
|
32
|
+
# @overload new(sides, center, diameter)
|
33
|
+
# Construct a {RegularPolygon} using a center point and diameter
|
34
|
+
# @option options [Number] :sides The number of edges
|
35
|
+
# @option options [Point] :center The center point of the {RegularPolygon}
|
36
|
+
# @option options [Number] :diameter The diameter of the {RegularPolygon}
|
37
|
+
def self.new(options={}, &block)
|
38
|
+
raise ArgumentError, "RegularPolygon requires an edge count" unless options[:sides]
|
39
|
+
|
40
|
+
center = options[:center]
|
41
|
+
center = center ? Point[center] : nil
|
42
|
+
|
43
|
+
if options.has_key?(:radius)
|
44
|
+
self.allocate.tap {|polygon| polygon.send :initialize, options[:sides], center, options[:radius], &block }
|
45
|
+
elsif options.has_key?(:diameter)
|
46
|
+
DiameterRegularPolygon.new options[:sides], center, options[:diameter], &block
|
47
|
+
else
|
48
|
+
raise ArgumentError, "RegularPolygon.new requires a radius or a diameter"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Construct a new {RegularPolygon} from a centerpoint and radius
|
53
|
+
# @param [Number] edge_count The number of edges
|
54
|
+
# @param [Point] center The center point of the {Circle}
|
55
|
+
# @param [Number] radius The radius of the {Circle}
|
56
|
+
# @return [RegularPolygon] A new {RegularPolygon} object
|
57
|
+
def initialize(edge_count, center, radius)
|
58
|
+
@center = Point[center]
|
59
|
+
@edge_count = edge_count
|
60
|
+
@radius = radius
|
61
|
+
end
|
62
|
+
|
63
|
+
def eql?(other)
|
64
|
+
(self.center == other.center) && (self.edge_count == other.edge_count) && (self.radius == other.radius)
|
65
|
+
end
|
66
|
+
alias :== :eql?
|
67
|
+
|
68
|
+
# @!group Accessors
|
69
|
+
# @!attribute [r] diameter
|
70
|
+
# @return [Numeric] The diameter of the {RegularPolygon}
|
71
|
+
def diameter
|
72
|
+
@radius*2
|
73
|
+
end
|
74
|
+
# @!endgroup
|
75
|
+
end
|
76
|
+
|
77
|
+
class DiameterRegularPolygon < RegularPolygon
|
78
|
+
# @return [Number] The {RegularPolygon}'s diameter
|
79
|
+
attr_reader :diameter
|
80
|
+
|
81
|
+
# Construct a new {RegularPolygon} from a centerpoint and a diameter
|
82
|
+
# @param [Number] edge_count The number of edges
|
83
|
+
# @param [Point] center The center point of the {RegularPolygon}
|
84
|
+
# @param [Number] diameter The radius of the {RegularPolygon}
|
85
|
+
# @return [RegularPolygon] A new {RegularPolygon} object
|
86
|
+
def initialize(edge_count, center, diameter)
|
87
|
+
@center = center ? Point[center] : nil
|
88
|
+
@edge_count = edge_count
|
89
|
+
@diameter = diameter
|
90
|
+
end
|
91
|
+
|
92
|
+
def eql?(other)
|
93
|
+
(self.center == other.center) && (self.edge_count == other.edge_count) && (self.diameter == other.diameter)
|
94
|
+
end
|
95
|
+
alias :== :eql?
|
96
|
+
|
97
|
+
# @!group Accessors
|
98
|
+
# @return [Number] The {RegularPolygon}'s radius
|
99
|
+
def radius
|
100
|
+
@diameter/2
|
101
|
+
end
|
102
|
+
# @!endgroup
|
103
|
+
end
|
104
|
+
end
|
data/lib/geometry/rotation.rb
CHANGED
@@ -43,6 +43,11 @@ A generalized representation of a rotation transformation.
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
+
def eql?(other)
|
47
|
+
(self.x.eql? other.x) && (self.y.eql? other.y) && (self.z.eql? other.z)
|
48
|
+
end
|
49
|
+
alias :== :eql?
|
50
|
+
|
46
51
|
def identity?
|
47
52
|
(!@x && !@y && !@z) || ([@x, @y, @z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
|
48
53
|
end
|
data/lib/geometry/square.rb
CHANGED
@@ -8,23 +8,33 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
8
8
|
|
9
9
|
== Constructors
|
10
10
|
|
11
|
-
square = Square.new [1,2], [2,3] # Using two corners
|
12
|
-
square = Square.new [3,4], 5 # Using an origin point and a size
|
13
|
-
square = Square.new 6 # Using a size and an origin of [0,0]
|
11
|
+
square = Square.new from:[1,2], to:[2,3] # Using two corners
|
12
|
+
square = Square.new origin:[3,4], size:5 # Using an origin point and a size
|
14
13
|
=end
|
15
14
|
class Square
|
16
15
|
attr_reader :origin
|
17
16
|
|
18
17
|
# Creates a {Square} given two {Point}s
|
19
|
-
# @
|
20
|
-
# @
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
# @option options [Point] :from A corner (ie. bottom-left)
|
19
|
+
# @option options [Point] :to The other corner (ie. top-right)
|
20
|
+
# @option options [Point] :origin The lower left corner
|
21
|
+
# @option options [Number] :size Bigness
|
22
|
+
def initialize(options={})
|
23
|
+
origin = options[:from] || options[:origin]
|
24
|
+
origin = origin ? Point[origin] : PointZero.new
|
25
|
+
|
26
|
+
if options.has_key? :to
|
27
|
+
point1 = options[:to]
|
28
|
+
elsif options.has_key? :size
|
29
|
+
point1 = origin + options[:size]
|
30
|
+
end
|
31
|
+
|
32
|
+
point1 = Point[point1]
|
33
|
+
raise(ArgumentError, "Point sizes must match (#{origin.size} != #{point1.size})") unless origin.is_a?(PointZero) || (origin.size == point1.size)
|
24
34
|
|
25
35
|
# Reorder the points to get lower-left and upper-right
|
26
|
-
minx, maxx = [
|
27
|
-
miny, maxy = [
|
36
|
+
minx, maxx = [origin.x, point1.x].minmax
|
37
|
+
miny, maxy = [origin.y, point1.y].minmax
|
28
38
|
@points = [Point[minx, miny], Point[maxx, maxy]]
|
29
39
|
|
30
40
|
raise(NotSquareError) if height != width
|
@@ -70,7 +80,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
70
80
|
end
|
71
81
|
|
72
82
|
# @attribute [r] points
|
73
|
-
# @return [Array<Point>] The {Square}'s four points (
|
83
|
+
# @return [Array<Point>] The {Square}'s four points (counterclockwise)
|
74
84
|
def points
|
75
85
|
half_size = @size/2
|
76
86
|
minx = @center.x - half_size
|
@@ -78,7 +88,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
|
|
78
88
|
miny = @center.y - half_size
|
79
89
|
maxy = @center.y + half_size
|
80
90
|
|
81
|
-
[Point[minx,miny], Point[
|
91
|
+
[Point[minx,miny], Point[maxx, miny], Point[maxx, maxy], Point[minx,maxy]]
|
82
92
|
end
|
83
93
|
|
84
94
|
def height
|
@@ -82,6 +82,11 @@ system's X-axis:
|
|
82
82
|
@rotation.identity? && !(@scale || @translation)
|
83
83
|
end
|
84
84
|
|
85
|
+
def eql?(other)
|
86
|
+
(self.rotation.eql? other.rotation) && (self.scale.eql? other.scale) && (self.translation.eql? other.translation)
|
87
|
+
end
|
88
|
+
alias :== :eql?
|
89
|
+
|
85
90
|
# Compose the current {Transformation} with another one
|
86
91
|
def +(other)
|
87
92
|
if other.is_a?(Array) or other.is_a?(Vector)
|
@@ -102,5 +107,12 @@ system's X-axis:
|
|
102
107
|
end
|
103
108
|
end
|
104
109
|
end
|
110
|
+
|
111
|
+
# Transform and return a new {Point}
|
112
|
+
# @param [Point] point The {Point} to transform
|
113
|
+
# @return [Point] The transformed {Point}
|
114
|
+
def transform(point)
|
115
|
+
@translation + point
|
116
|
+
end
|
105
117
|
end
|
106
118
|
end
|
data/lib/geometry/vector.rb
CHANGED
data/test/geometry.rb
CHANGED
@@ -2,14 +2,4 @@ require 'minitest/autorun'
|
|
2
2
|
require 'geometry'
|
3
3
|
|
4
4
|
describe Geometry do
|
5
|
-
it "create a Point object" do
|
6
|
-
point = Geometry.point(2,1)
|
7
|
-
assert_kind_of(Geometry::Point, point)
|
8
|
-
end
|
9
|
-
|
10
|
-
it "create a Line object" do
|
11
|
-
line = Geometry.line([0,0], [10,10])
|
12
|
-
assert_kind_of(Geometry::Line, line)
|
13
|
-
assert_kind_of(Geometry::TwoPointLine, line)
|
14
|
-
end
|
15
5
|
end
|
data/test/geometry/arc.rb
CHANGED
@@ -2,21 +2,24 @@ require 'minitest/autorun'
|
|
2
2
|
require 'geometry/arc'
|
3
3
|
|
4
4
|
describe Geometry::Arc do
|
5
|
-
|
6
|
-
arc = Geometry::Arc.new [1,2], 3, 0, 90
|
7
|
-
arc.must_be_kind_of Geometry::Arc
|
8
|
-
arc.center.must_equal Point[1,2]
|
9
|
-
arc.radius.must_equal 3
|
10
|
-
arc.start_angle.must_equal 0
|
11
|
-
arc.end_angle.must_equal 90
|
12
|
-
end
|
5
|
+
Arc = Geometry::Arc
|
13
6
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
7
|
+
describe "when constructed" do
|
8
|
+
it "must accept a center point, radius, start and end angles" do
|
9
|
+
arc = Geometry::Arc.new center:[1,2], radius:3, start:0, end:90
|
10
|
+
arc.must_be_kind_of Geometry::Arc
|
11
|
+
arc.center.must_equal Point[1,2]
|
12
|
+
arc.radius.must_equal 3
|
13
|
+
arc.start_angle.must_equal 0
|
14
|
+
arc.end_angle.must_equal 90
|
15
|
+
end
|
21
16
|
|
17
|
+
it "must create an Arc from center, start and end points" do
|
18
|
+
arc = Geometry::Arc.new center:[1,2], start:[3,4], end:[5,6]
|
19
|
+
arc.must_be_kind_of Geometry::Arc
|
20
|
+
arc.center.must_equal Point[1,2]
|
21
|
+
arc.first.must_equal Point[3,4]
|
22
|
+
arc.last.must_equal Point[5,6]
|
23
|
+
end
|
24
|
+
end
|
22
25
|
end
|
data/test/geometry/circle.rb
CHANGED
@@ -18,6 +18,10 @@ describe Geometry::Circle do
|
|
18
18
|
it "must have a radius accessor" do
|
19
19
|
circle.radius.must_equal 3
|
20
20
|
end
|
21
|
+
|
22
|
+
it "must compare equal" do
|
23
|
+
circle.must_equal Circle.new([1,2], 3)
|
24
|
+
end
|
21
25
|
end
|
22
26
|
|
23
27
|
describe "when constructed with named center and radius arguments" do
|
@@ -34,10 +38,14 @@ describe Geometry::Circle do
|
|
34
38
|
it "must have a radius accessor" do
|
35
39
|
circle.radius.must_equal 3
|
36
40
|
end
|
41
|
+
|
42
|
+
it "must compare equal" do
|
43
|
+
(circle == Circle.new(:center => [1,2], :radius => 3)).must_equal true
|
44
|
+
end
|
37
45
|
end
|
38
46
|
|
39
|
-
describe "when constructed with
|
40
|
-
let(:circle) { Circle.new [1,2], :
|
47
|
+
describe "when constructed with named center and diameter arguments" do
|
48
|
+
let(:circle) { Circle.new center:[1,2], diameter:4 }
|
41
49
|
|
42
50
|
it "must be a CenterDiameterCircle" do
|
43
51
|
circle.must_be_instance_of(Geometry::CenterDiameterCircle)
|
@@ -55,5 +63,50 @@ describe Geometry::Circle do
|
|
55
63
|
it "must calculate the correct radius" do
|
56
64
|
circle.radius.must_equal 2
|
57
65
|
end
|
66
|
+
|
67
|
+
it "must compare equal" do
|
68
|
+
circle.must_equal Circle.new([1,2], :diameter => 4)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "when constructed with a diameter and no center" do
|
73
|
+
let(:circle) { Circle.new :diameter => 4 }
|
74
|
+
|
75
|
+
it "must be a CenterDiameterCircle" do
|
76
|
+
circle.must_be_instance_of(Geometry::CenterDiameterCircle)
|
77
|
+
circle.must_be_kind_of(Circle)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "must have a nil center" do
|
81
|
+
circle.center.must_be_kind_of Geometry::PointZero
|
82
|
+
end
|
83
|
+
|
84
|
+
it "must have a diameter" do
|
85
|
+
circle.diameter.must_equal 4
|
86
|
+
end
|
87
|
+
|
88
|
+
it "must calculate the correct radius" do
|
89
|
+
circle.radius.must_equal 2
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "properties" do
|
94
|
+
subject { Circle.new center:[1,2], :diameter => 4 }
|
95
|
+
|
96
|
+
it "must have a bounds property that returns a Rectangle" do
|
97
|
+
subject.bounds.must_equal Rectangle.new([-1,0], [3,4])
|
98
|
+
end
|
99
|
+
|
100
|
+
it "must have a minmax property that returns the corners of the bounding rectangle" do
|
101
|
+
subject.minmax.must_equal [Point[-1,0], Point[3,4]]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "must have a max property that returns the upper right corner of the bounding rectangle" do
|
105
|
+
subject.max.must_equal Point[3,4]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "must have a min property that returns the lower left corner of the bounding rectangle" do
|
109
|
+
subject.min.must_equal Point[-1,0]
|
110
|
+
end
|
58
111
|
end
|
59
112
|
end
|
data/test/geometry/edge.rb
CHANGED
@@ -58,6 +58,24 @@ describe Geometry::Edge do
|
|
58
58
|
Edge.new([0,0], [2,0]).parallel?(Edge.new([1,-1], [1,1])).must_equal false
|
59
59
|
end
|
60
60
|
|
61
|
+
describe "spaceship" do
|
62
|
+
it "ascending with a Point" do
|
63
|
+
edge = Edge.new [0,0], [1,1]
|
64
|
+
(edge <=> Point[0,0]).must_equal 0
|
65
|
+
(edge <=> Point[1,0]).must_equal -1
|
66
|
+
(edge <=> Point[0,1]).must_equal 1
|
67
|
+
(edge <=> Point[2,2]).must_equal nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "descending with a Point" do
|
71
|
+
edge = Edge.new [1,1], [0,0]
|
72
|
+
(edge <=> Point[0,0]).must_equal 0
|
73
|
+
(edge <=> Point[1,0]).must_equal 1
|
74
|
+
(edge <=> Point[0,1]).must_equal -1
|
75
|
+
(edge <=> Point[2,2]).must_equal nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
61
79
|
describe "when finding an intersection" do
|
62
80
|
it "must find the intersection of two end-intersecting Edges" do
|
63
81
|
intersection = Edge.new([0,0],[1,1]).intersection(Edge.new([0,1],[1,1]))
|
@@ -65,6 +83,16 @@ describe Geometry::Edge do
|
|
65
83
|
intersection.must_equal Geometry::Point[1,1]
|
66
84
|
end
|
67
85
|
|
86
|
+
it "must find the intersection of two collinear end-intersecting Edges" do
|
87
|
+
intersection = Edge.new([2,2], [0,2]).intersection(Edge.new([3,2], [2,2]))
|
88
|
+
intersection.must_be_kind_of Geometry::Point
|
89
|
+
intersection.must_equal Geometry::Point[2,2]
|
90
|
+
|
91
|
+
intersection = Edge.new([0,2], [2,2]).intersection(Edge.new([2,2], [3,2]))
|
92
|
+
intersection.must_be_kind_of Geometry::Point
|
93
|
+
intersection.must_equal Geometry::Point[2,2]
|
94
|
+
end
|
95
|
+
|
68
96
|
it "must find the itersection of two crossed Edges" do
|
69
97
|
edge1 = Edge.new [0.0, 0], [2.0, 2.0]
|
70
98
|
edge2 = Edge.new [2.0, 0], [0.0, 2.0]
|
@@ -90,5 +118,8 @@ describe Geometry::Edge do
|
|
90
118
|
Edge.new([0,0],[2,0]).intersection(Edge.new([1,1],[3,1])).must_equal nil
|
91
119
|
end
|
92
120
|
|
121
|
+
it "must return nil for two perpendicular but not interseting edges" do
|
122
|
+
Edge.new([0, 0], [2, 0]).intersection(Edge.new([3, 3], [3, 1])).must_equal nil
|
123
|
+
end
|
93
124
|
end
|
94
125
|
end
|
data/test/geometry/line.rb
CHANGED
@@ -2,6 +2,31 @@ require 'minitest/autorun'
|
|
2
2
|
require 'geometry/line'
|
3
3
|
|
4
4
|
describe Geometry::Line do
|
5
|
+
Line = Geometry::Line
|
6
|
+
Point = Geometry::Point
|
7
|
+
|
8
|
+
describe "when initializing" do
|
9
|
+
it "must accept two named points" do
|
10
|
+
line = Line.new(from:Point[0,0], to:Point[10,10])
|
11
|
+
line.must_be_kind_of(Line)
|
12
|
+
line.must_be_instance_of(Geometry::TwoPointLine)
|
13
|
+
line.first.must_equal Point[0,0]
|
14
|
+
line.last.must_equal Point[10,10]
|
15
|
+
end
|
16
|
+
|
17
|
+
it "must accept named start and end points" do
|
18
|
+
line = Line.new(start:Point[0,0], end:Point[10,10])
|
19
|
+
line.must_be_kind_of(Line)
|
20
|
+
line.must_be_instance_of(Geometry::TwoPointLine)
|
21
|
+
line.first.must_equal Point[0,0]
|
22
|
+
line.last.must_equal Point[10,10]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "must raise an exception when no arguments are given" do
|
26
|
+
-> { Line.new }.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
5
30
|
it "create a Line object from 2 Points" do
|
6
31
|
line = Geometry::Line[Geometry::Point[0,0], Geometry::Point[10,10]]
|
7
32
|
assert_kind_of(Geometry::Line, line)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'geometry/obround'
|
3
|
+
|
4
|
+
describe Geometry::Obround do
|
5
|
+
Obround = Geometry::Obround
|
6
|
+
|
7
|
+
describe "when constructed" do
|
8
|
+
it "must accept two Points" do
|
9
|
+
obround = Geometry::Obround.new [1,2], [3,4]
|
10
|
+
obround.must_be_kind_of Geometry::Obround
|
11
|
+
end
|
12
|
+
|
13
|
+
it "must accept a width and height" do
|
14
|
+
obround = Geometry::Obround.new 2, 3
|
15
|
+
obround.must_be_kind_of Geometry::Obround
|
16
|
+
obround.height.must_equal 3
|
17
|
+
obround.width.must_equal 2
|
18
|
+
end
|
19
|
+
|
20
|
+
it "must compare equal" do
|
21
|
+
obround = Geometry::Obround.new [1,2], [3,4]
|
22
|
+
obround.must_equal Obround.new([1,2], [3,4])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|