geometry 5 → 6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|