geometry 6.2 → 6.3

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,85 @@
1
+ require_relative 'point'
2
+
3
+ module Geometry
4
+ =begin rdoc
5
+ An object repesenting a {Size} of 1, in N-dimensional space
6
+
7
+ A {SizeOne} object is a {Size} that will always compare equal to one and unequal to
8
+ everything else, regardless of dimensionality. It's similar to the
9
+ {http://en.wikipedia.org/wiki/Null_Object_pattern Null Object Pattern}, but for ones.
10
+ =end
11
+ class SizeOne
12
+ def eql?(other)
13
+ if other.respond_to? :all?
14
+ other.all? {|e| e.eql? 1}
15
+ else
16
+ other == 1
17
+ end
18
+ end
19
+ alias == eql?
20
+
21
+ def coerce(other)
22
+ if other.is_a? Numeric
23
+ [other, 1]
24
+ elsif other.is_a? Array
25
+ [other, Array.new(other.size,1)]
26
+ elsif other.is_a? Vector
27
+ [other, Vector[*Array.new(other.size,1)]]
28
+ else
29
+ [Size[other], Size[Array.new(other.size,1)]]
30
+ end
31
+ end
32
+
33
+ # @group Arithmetic
34
+
35
+ # @group Unary operators
36
+ def +@
37
+ self
38
+ end
39
+
40
+ def -@
41
+ -1
42
+ end
43
+ # @endgroup
44
+
45
+ def +(other)
46
+ if other.respond_to?(:map)
47
+ other.map {|a| a + 1 }
48
+ else
49
+ other + 1
50
+ end
51
+ end
52
+
53
+ def -(other)
54
+ if other.is_a? Numeric
55
+ 1 - other
56
+ elsif other.respond_to? :map
57
+ other.map {|a| 1 - a }
58
+ else
59
+ 1 - other
60
+ end
61
+ end
62
+
63
+ def *(other)
64
+ raise OperationNotDefined unless other.is_a? Numeric
65
+ other
66
+ end
67
+
68
+ def /(other)
69
+ raise OperationNotDefined unless other.is_a? Numeric
70
+ raise ZeroDivisionError if 0 == other
71
+ 1 / other
72
+ end
73
+ # @endgroup
74
+
75
+ # @group Enumerable
76
+
77
+ # Return the first, or first n, elements (always 0)
78
+ # @param n [Number] the number of elements to return
79
+ def first(n=nil)
80
+ Array.new(n, 1) rescue 1
81
+ end
82
+ # @endgroup
83
+ end
84
+ end
85
+
@@ -65,6 +65,14 @@ everything else, regardless of dimensionality. You can think of it as an applica
65
65
  end
66
66
  # @endgroup
67
67
 
68
+ # @group Enumerable
69
+
70
+ # Return the first, or first n, elements (always 0)
71
+ # @param n [Number] the number of elements to return
72
+ def first(n=nil)
73
+ Array.new(n, 0) rescue 0
74
+ end
75
+ # @endgroup
68
76
  end
69
77
  end
70
78
 
@@ -69,6 +69,12 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
69
69
  end
70
70
 
71
71
  # !@group Accessors
72
+ # @!attribute closed?
73
+ # @return [Bool] always true
74
+ def closed?
75
+ true
76
+ end
77
+
72
78
  # @return [Point] The upper right corner of the bounding {Rectangle}
73
79
  def max
74
80
  @points.last
@@ -35,6 +35,27 @@ An isoscoles right {Triangle} created with an origin and leg length
35
35
  RightTriangle.new args[0], args[1], args[1]
36
36
  end
37
37
  end
38
+
39
+ # @!attribute closed?
40
+ # @return [Bool] always true
41
+ def closed?
42
+ true
43
+ end
44
+
45
+ # @return [Point] The upper-right corner of the bounding rectangle that encloses the {Polyline}
46
+ def max
47
+ points.reduce {|memo, vertex| Point[[memo.x, vertex.x].max, [memo.y, vertex.y].max] }
48
+ end
49
+
50
+ # @return [Point] The lower-left corner of the bounding rectangle that encloses the {Polyline}
51
+ def min
52
+ points.reduce {|memo, vertex| Point[[memo.x, vertex.x].min, [memo.y, vertex.y].min] }
53
+ end
54
+
55
+ # @return [Array<Point>] The lower-left and upper-right corners of the enclosing bounding rectangle
56
+ def minmax
57
+ points.reduce([points.first, points.first]) {|memo, vertex| [Point[[memo.first.x, vertex.x].min, [memo.first.y, vertex.y].min], Point[[memo.last.x, vertex.x].max, [memo.last.y, vertex.y].max]] }
58
+ end
38
59
  end
39
60
 
40
61
  # {http://en.wikipedia.org/wiki/Equilateral_triangle Equilateral Triangle}
@@ -0,0 +1,69 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/annulus'
3
+
4
+ describe Geometry::Annulus do
5
+ it 'must complain when constructed with only a center' do
6
+ -> { Geometry::Annulus.new center:Point[1,2] }.must_raise ArgumentError
7
+ end
8
+
9
+ it 'must also be known as a Ring' do
10
+ Geometry::Ring.new(Point[1,2], inner_radius:5, radius:10).must_be_instance_of Geometry::Annulus
11
+ end
12
+
13
+ describe 'when constructed with a named center' do
14
+ subject { Geometry::Annulus.new center:Point[1,2], inner_radius:5, radius:10 }
15
+
16
+ it 'must have a center' do
17
+ subject.center.must_equal Point[1,2]
18
+ end
19
+ end
20
+
21
+ describe 'when constructed with a center, inner_radius and radius' do
22
+ subject { Geometry::Annulus.new Point[1,2], inner_radius:5, radius:10 }
23
+
24
+ it 'must have a center' do
25
+ subject.center.must_equal Point[1,2]
26
+ end
27
+
28
+ it 'must have an inner diameter' do
29
+ subject.inner_diameter.must_equal 10
30
+ end
31
+
32
+ it 'must have an inner radius' do
33
+ subject.inner_radius.must_equal 5
34
+ end
35
+
36
+ it 'must have an outer diameter' do
37
+ subject.outer_diameter.must_equal 20
38
+ end
39
+
40
+ it 'must have a radius' do
41
+ subject.radius.must_equal 10
42
+ subject.outer_radius.must_equal 10
43
+ end
44
+ end
45
+
46
+ describe 'when constructed with a center, inner_diameter and diameter' do
47
+ subject { Geometry::Annulus.new Point[1,2], inner_diameter:5, diameter:10 }
48
+
49
+ it 'must have a center' do
50
+ subject.center.must_equal Point[1,2]
51
+ end
52
+
53
+ it 'must have an inner diameter' do
54
+ subject.inner_diameter.must_equal 5
55
+ end
56
+
57
+ it 'must have an inner radius' do
58
+ subject.inner_radius.must_equal 2.5
59
+ end
60
+
61
+ it 'must have an outer diameter' do
62
+ subject.outer_diameter.must_equal 10
63
+ end
64
+
65
+ it 'must have a radius' do
66
+ subject.radius.must_equal 5
67
+ end
68
+ end
69
+ end
@@ -88,6 +88,38 @@ describe Geometry::Circle do
88
88
  it "must calculate the correct radius" do
89
89
  circle.radius.must_equal 2
90
90
  end
91
+
92
+ it 'must have the correct min values' do
93
+ circle.min.must_equal Point[-2, -2]
94
+ circle.min.must_be_instance_of Geometry::PointIso
95
+ end
96
+
97
+ it 'must have the correct max values' do
98
+ circle.max.must_equal Point[2, 2]
99
+ circle.max.must_be_instance_of Geometry::PointIso
100
+ end
101
+
102
+ it 'must have the correct minmax values' do
103
+ circle.minmax.must_equal [Point[-2, -2], Point[2,2]]
104
+ end
105
+ end
106
+
107
+ describe 'when constructed with a Rational diameter and no center' do
108
+ let(:circle) { Circle.new :diameter => Rational(5,3) }
109
+
110
+ it 'must have the correct min values' do
111
+ circle.min.must_equal Point[-5/6, -5/6]
112
+ circle.min.must_be_instance_of Geometry::PointIso
113
+ end
114
+
115
+ it 'must have the correct max values' do
116
+ circle.max.must_equal Point[5/6, 5/6]
117
+ circle.max.must_be_instance_of Geometry::PointIso
118
+ end
119
+
120
+ it 'must have the correct minmax values' do
121
+ circle.minmax.must_equal [Point[-5/6, -5/6], Point[5/6,5/6]]
122
+ end
91
123
  end
92
124
 
93
125
  describe "properties" do
@@ -97,6 +129,10 @@ describe Geometry::Circle do
97
129
  subject.bounds.must_equal Rectangle.new([-1,0], [3,4])
98
130
  end
99
131
 
132
+ it 'must always be closed' do
133
+ subject.closed?.must_equal true
134
+ end
135
+
100
136
  it "must have a minmax property that returns the corners of the bounding rectangle" do
101
137
  subject.minmax.must_equal [Point[-1,0], Point[3,4]]
102
138
  end
@@ -22,4 +22,9 @@ describe Geometry::Obround do
22
22
  obround.must_equal Obround.new([1,2], [3,4])
23
23
  end
24
24
  end
25
+
26
+ it 'must always be closed' do
27
+ obround = Geometry::Obround.new 2, 3
28
+ obround.closed?.must_equal true
29
+ end
25
30
  end
@@ -2,9 +2,18 @@ require 'minitest/autorun'
2
2
  require 'geometry/point'
3
3
 
4
4
  describe Geometry::Point do
5
+ PointOne = Geometry::PointOne
5
6
  PointZero = Geometry::PointZero
6
7
 
7
8
  describe "class methods" do
9
+ it 'must generate a PointOne' do
10
+ Point.one.must_be_instance_of PointOne
11
+ end
12
+
13
+ it 'must generate a Point full of ones' do
14
+ Point.one(3).must_equal Point[1,1,1]
15
+ end
16
+
8
17
  it "must generate a PointZero" do
9
18
  Point.zero.must_be_instance_of(PointZero)
10
19
  end
@@ -56,12 +65,22 @@ describe Geometry::Point do
56
65
  assert_equal(13, point.x)
57
66
  assert_equal(14, point.y)
58
67
  end
59
- it "allow indexed element access" do
60
- point = Geometry::Point[5,6]
61
- assert_equal(2, point.size)
62
- assert_equal(5, point[0])
63
- assert_equal(6, point[1])
68
+
69
+ describe 'when array access' do
70
+ it 'must allow indexed access' do
71
+ point = Geometry::Point[5,6]
72
+ point.size.must_equal 2
73
+ point[0].must_equal 5
74
+ point[1].must_equal 6
75
+ end
76
+
77
+ it 'must slize with a start index and a length' do
78
+ point = Geometry::Point[5, 6, 7]
79
+ slice = point[1,2]
80
+ slice.length.must_equal 2
81
+ end
64
82
  end
83
+
65
84
  it "allow named element access" do
66
85
  point = Geometry::Point[5,6,7]
67
86
  assert_equal(3, point.size)
@@ -132,6 +151,10 @@ describe Geometry::Point do
132
151
  (Vector[5,6] + right).must_equal Vector[8,10]
133
152
  end
134
153
 
154
+ it 'must add a PointOne' do
155
+ (left + Point.one).must_equal Point[2,3]
156
+ end
157
+
135
158
  it "must return self when adding a PointZero" do
136
159
  (left + Point.zero).must_equal left
137
160
  end
@@ -159,6 +182,10 @@ describe Geometry::Point do
159
182
  lambda { left - [1,2,3,4] }.must_raise Geometry::DimensionMismatch
160
183
  end
161
184
 
185
+ it 'must subtract a PointOne' do
186
+ (left - Point.one).must_equal Point[0,1]
187
+ end
188
+
162
189
  it "must return self when subtracting a PointZero" do
163
190
  (left - Point.zero).must_equal left
164
191
  end
@@ -220,6 +247,14 @@ describe Geometry::Point do
220
247
  Point[3,2].wont_equal point
221
248
  end
222
249
 
250
+ it 'must compare to a PointOne' do
251
+ point.wont_equal Point.one
252
+ Point.one.wont_equal point
253
+
254
+ Point[1,1].must_equal Point.one
255
+ Point.one.must_equal Point[1,1]
256
+ end
257
+
223
258
  it "must compare equal to an equal Vector" do
224
259
  point.must_equal Vector[1,2]
225
260
  Vector[1,2].must_equal point
@@ -0,0 +1,189 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/point_iso'
3
+
4
+ describe Geometry::PointIso do
5
+ let(:value) { 5 }
6
+ subject { Geometry::PointIso.new(5) }
7
+
8
+ describe 'arithmetic' do
9
+ let(:left) { Point[1,2] }
10
+ let(:right) { Point[3,4] }
11
+
12
+ it 'must pretend to be a Point' do
13
+ subject.is_a?(Point).must_equal true
14
+ subject.kind_of?(Point).must_equal true
15
+
16
+ subject.is_a?(Geometry::PointIso).must_equal true
17
+ subject.kind_of?(Geometry::PointIso).must_equal true
18
+
19
+ subject.instance_of?(Point).must_equal false
20
+ subject.instance_of?(Geometry::PointIso).must_equal true
21
+ end
22
+
23
+ it 'must have +@' do
24
+ (+subject).must_be :eql?, value
25
+ (+subject).must_be_instance_of(Geometry::PointIso)
26
+ end
27
+
28
+ it 'must have unary negation' do
29
+ (-subject).must_be :eql?, -value
30
+ (-subject).must_be_instance_of(Geometry::PointIso)
31
+ end
32
+
33
+ describe 'Accessors' do
34
+ it 'must return 1 for array access' do
35
+ subject[3].must_equal value
36
+ end
37
+
38
+ it 'must return 1 for named element access' do
39
+ subject.x.must_equal value
40
+ subject.y.must_equal value
41
+ subject.z.must_equal value
42
+ end
43
+ end
44
+
45
+ it 'must add a number' do
46
+ (subject + 3).must_equal (value + 3)
47
+ (3 + subject).must_equal (3 + value)
48
+ end
49
+
50
+ it 'return a Point when adding two Points' do
51
+ (subject + right).must_be_kind_of Point
52
+ (left + subject).must_be_kind_of Point
53
+ end
54
+
55
+ it 'must return an Array when adding an array' do
56
+ (subject + [5,6]).must_equal [value+5, value+6]
57
+ # ([5,6] + subject).must_equal [10, 11]
58
+ end
59
+
60
+ it 'must return a Point when adding a Size' do
61
+ (subject + Size[5,6]).must_be_instance_of(Point)
62
+ (subject + Size[5,6]).must_equal Point[value+5, value+6]
63
+ end
64
+
65
+ describe 'when subtracting' do
66
+ it 'must subtract a number' do
67
+ (subject - 3).must_equal (value - 3)
68
+ (3 - subject).must_equal -2
69
+ end
70
+
71
+ it 'return a Point when subtracting two Points' do
72
+ (subject - right).must_equal Point[value - right.x, value - right.y]
73
+ (left - subject).must_equal Point[left.x - value, left.y - value]
74
+ end
75
+
76
+ it 'must return a Point when subtracting an array' do
77
+ (subject - [5,6]).must_equal [0, -1]
78
+ # ([5,6] - subject).must_equal [4,5]
79
+ end
80
+
81
+ it 'must return a Point when subtracting a Size' do
82
+ (subject - Size[5,6]).must_be_instance_of(Point)
83
+ (subject - Size[5,6]).must_equal Point[0,-1]
84
+ end
85
+ end
86
+
87
+ it 'must multiply by a scalar' do
88
+ (subject * 3).must_equal 15
89
+ (subject * 3.0).must_equal 15.0
90
+ end
91
+
92
+ it 'must refuse to multiply by a Point' do
93
+ -> { subject * Point[1, 2] }.must_raise Geometry::OperationNotDefined
94
+ end
95
+
96
+ it 'must refuse to multiply by a Vector' do
97
+ -> { subject * Vector[2, 3] }.must_raise Geometry::OperationNotDefined
98
+ end
99
+
100
+ it 'must divide by a scalar' do
101
+ (subject / 3).must_equal 5/3
102
+ (subject / 4.0).must_equal 5/4.0
103
+ end
104
+
105
+ it 'must raise an exception when divided by 0' do
106
+ -> { subject / 0 }.must_raise ZeroDivisionError
107
+ end
108
+
109
+ describe 'division' do
110
+ it 'must raise an exception for Points' do
111
+ lambda { subject / Point[1,2] }.must_raise Geometry::OperationNotDefined
112
+ end
113
+
114
+ it 'must raise an exception for Vectors' do
115
+ lambda { subject / Vector[1,2] }.must_raise Geometry::OperationNotDefined
116
+ end
117
+ end
118
+ end
119
+
120
+ describe 'coercion' do
121
+ it 'must coerce Arrays into Points' do
122
+ subject.coerce([3,4]).must_equal [Point[3,4], Point[5, 5]]
123
+ end
124
+
125
+ it 'must coerce Vectors into Vectors' do
126
+ subject.coerce(Vector[3,4]).must_equal [Vector[3,4], Vector[5, 5]]
127
+ end
128
+
129
+ it 'must coerce Points into Points' do
130
+ subject.coerce(Point[5,6]).must_equal [Point[5,6], Point[5, 5]]
131
+ end
132
+ end
133
+
134
+ describe 'comparison' do
135
+ it 'must be equal to the same value' do
136
+ subject.must_be :eql?, 5
137
+ subject.must_be :eql?, 5.0
138
+ end
139
+
140
+ it 'must not be equal to a number of a different value' do
141
+ 0.wont_equal subject
142
+ 3.14.wont_equal subject
143
+ end
144
+
145
+ it 'must be equal to an Array of the same value' do
146
+ subject.must_be :==, [5,5]
147
+ subject.must_be :eql?, [5,5]
148
+ subject.must_be :===, [5,5]
149
+ [5,5].must_equal subject
150
+ subject.must_equal [5,5]
151
+ end
152
+
153
+ it 'must not be equal to an Array of other values' do
154
+ subject.wont_equal [3, 2, 1]
155
+ [3, 2, 1].wont_equal subject
156
+ end
157
+
158
+ it 'must not be equal to a Point at the origin' do
159
+ subject.wont_be :==, Point[0,0]
160
+ subject.wont_be :eql?, Point[0,0]
161
+ subject.wont_be :===, Point[0,0]
162
+ Point[0,0].wont_equal subject
163
+ subject.wont_equal Point[0,0]
164
+ end
165
+
166
+ it 'must not be equal to a Point not at the origin' do
167
+ subject.wont_equal Point[3,2]
168
+ Point[3,2].wont_equal subject
169
+ end
170
+
171
+ it 'must be equal to a Point of subjects' do
172
+ subject.must_be :==, Point[value, value]
173
+ subject.must_be :eql?, Point[value, value]
174
+ subject.must_be :===, Point[value, value]
175
+ Point[value, value].must_equal subject
176
+ subject.must_equal Point[value, value]
177
+ end
178
+
179
+ it 'must be equal to an Vector of the same value' do
180
+ subject.must_be :eql?, Vector[value, value]
181
+ Vector[5, 5].must_equal subject
182
+ end
183
+
184
+ it 'must not be equal to a Vector of other values' do
185
+ subject.wont_equal Vector[3,2]
186
+ Vector[3,2].wont_equal subject
187
+ end
188
+ end
189
+ end