geometry 5 → 6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
- # @param [Point] point0 A corner (ie. bottom-left)
20
- # @param [Point] point1 The other corner (ie. top-right)
21
- def initialize(point0, point1)
22
- point0, point1 = Point[point0], Point[point1]
23
- raise(ArgumentError, "Point sizes must match (#{point0.size} != #{point1.size}") unless point0.size == point1.size
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 = [point0.x, point1.x].minmax
27
- miny, maxy = [point0.y, point1.y].minmax
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 (clockwise)
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[minx,maxy], Point[maxx, maxy], Point[maxx, miny]]
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
@@ -2,6 +2,10 @@ require 'matrix'
2
2
 
3
3
  # Monkeypatch Vector to overcome some deficiencies
4
4
  class Vector
5
+ X = Vector[1,0,0]
6
+ Y = Vector[0,1,0]
7
+ Z = Vector[0,0,1]
8
+
5
9
  # @group Unary operators
6
10
  def +@
7
11
  self
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
- it "must create an Arc object from a Point and a radius" do
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
- it "must create an Arc from center, start and end points" do
15
- arc = Geometry::Arc.new [1,2], [3,4], [5,6]
16
- arc.must_be_kind_of Geometry::Arc
17
- arc.center.must_equal Point[1,2]
18
- arc.first.must_equal Point[3,4]
19
- arc.last.must_equal Point[5,6]
20
- end
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
@@ -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 a center and diameter" do
40
- let(:circle) { Circle.new [1,2], :diameter => 4 }
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
@@ -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
@@ -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