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.
@@ -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