geometry 6.2 → 6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,131 @@
1
+ require_relative 'point'
2
+
3
+ module Geometry
4
+ =begin rdoc
5
+ An object repesenting a {Point} that is one unit away from the origin, along each
6
+ axis, in N-dimensional space
7
+
8
+ A {PointOne} object is a {Point} that will always compare equal to one and unequal to
9
+ everything else, regardless of size. It's similar to the
10
+ {http://en.wikipedia.org/wiki/Null_Object_pattern Null Object Pattern}, but for ones.
11
+ =end
12
+ class PointOne
13
+ def eql?(other)
14
+ if other.respond_to? :all?
15
+ other.all? {|e| e.eql? 1}
16
+ else
17
+ other == 1
18
+ end
19
+ end
20
+ alias == eql?
21
+
22
+ def coerce(other)
23
+ if other.is_a? Numeric
24
+ [other, 1]
25
+ elsif other.is_a? Array
26
+ [other, Array.new(other.size, 1)]
27
+ elsif other.is_a? Vector
28
+ [other, Vector[*Array.new(other.size, 1)]]
29
+ else
30
+ [Point[other], Point[Array.new(other.size, 1)]]
31
+ end
32
+ end
33
+
34
+ def is_a?(klass)
35
+ (klass == Point) || super
36
+ end
37
+ alias :kind_of? :is_a?
38
+
39
+ # This is a hack to get Array#== to work properly. It works on ruby 2.0 and 1.9.3.
40
+ def to_ary
41
+ []
42
+ end
43
+
44
+ # @group Accessors
45
+ # @param [Integer] i Index into the {Point}'s elements
46
+ # @return [Numeric] Element i (starting at 0)
47
+ def [](i)
48
+ 1
49
+ end
50
+
51
+ # @attribute [r] x
52
+ # @return [Numeric] X-component
53
+ def x
54
+ 1
55
+ end
56
+
57
+ # @attribute [r] y
58
+ # @return [Numeric] Y-component
59
+ def y
60
+ 1
61
+ end
62
+
63
+ # @attribute [r] z
64
+ # @return [Numeric] Z-component
65
+ def z
66
+ 1
67
+ end
68
+ # @endgroup
69
+
70
+ # @group Arithmetic
71
+
72
+ # @group Unary operators
73
+ def +@
74
+ self
75
+ end
76
+
77
+ def -@
78
+ -1
79
+ end
80
+ # @endgroup
81
+
82
+ def +(other)
83
+ case other
84
+ when Numeric
85
+ Point.iso(other + 1)
86
+ when Size
87
+ Point[other.map {|a| a + 1 }]
88
+ else
89
+ if other.respond_to?(:map)
90
+ other.map {|a| a + 1 }
91
+ else
92
+ Point[other + 1]
93
+ end
94
+ end
95
+ end
96
+
97
+ def -(other)
98
+ if other.is_a? Size
99
+ Point[other.map {|a| 1 - a }]
100
+ elsif other.respond_to? :map
101
+ other.map {|a| 1 - a }
102
+ elsif other == 1
103
+ Point.zero
104
+ else
105
+ Point.iso(1 - other)
106
+ end
107
+ end
108
+
109
+ def *(other)
110
+ raise OperationNotDefined unless other.is_a? Numeric
111
+ other
112
+ end
113
+
114
+ def /(other)
115
+ raise OperationNotDefined unless other.is_a? Numeric
116
+ raise ZeroDivisionError if 0 == other
117
+ 1 / other
118
+ end
119
+ # @endgroup
120
+
121
+ # @group Enumerable
122
+
123
+ # Return the first, or first n, elements (always 0)
124
+ # @param n [Number] the number of elements to return
125
+ def first(n=nil)
126
+ Array.new(n, 1) rescue 1
127
+ end
128
+ # @endgroup
129
+ end
130
+ end
131
+
@@ -1,4 +1,5 @@
1
1
  require_relative 'point'
2
+ require_relative 'point_iso'
2
3
 
3
4
  module Geometry
4
5
  =begin rdoc
@@ -30,6 +31,11 @@ everything else, regardless of size. You can think of it as an application of th
30
31
  end
31
32
  end
32
33
 
34
+ def is_a?(klass)
35
+ (klass == Point) || super
36
+ end
37
+ alias :kind_of? :is_a?
38
+
33
39
  # This is a hack to get Array#== to work properly. It works on ruby 2.0 and 1.9.3.
34
40
  def to_ary
35
41
  []
@@ -75,7 +81,9 @@ everything else, regardless of size. You can think of it as an application of th
75
81
 
76
82
  def +(other)
77
83
  case other
78
- when Array, Numeric then other
84
+ when Array then other
85
+ when Numeric
86
+ Point.iso(other)
79
87
  else
80
88
  Point[other]
81
89
  end
@@ -84,6 +92,8 @@ everything else, regardless of size. You can think of it as an application of th
84
92
  def -(other)
85
93
  if other.is_a? Size
86
94
  -Point[other]
95
+ elsif other.is_a? Numeric
96
+ Point.iso(-other)
87
97
  elsif other.respond_to? :-@
88
98
  -other
89
99
  elsif other.respond_to? :map
@@ -102,6 +112,14 @@ everything else, regardless of size. You can think of it as an application of th
102
112
  end
103
113
  # @endgroup
104
114
 
115
+ # @group Enumerable
116
+
117
+ # Return the first, or first n, elements (always 0)
118
+ # @param n [Number] the number of elements to return
119
+ def first(n=nil)
120
+ Array.new(n, 0) rescue 0
121
+ end
122
+ # @endgroup
105
123
  end
106
124
  end
107
125
 
@@ -18,11 +18,11 @@ but there's currently nothing that enforces simplicity.
18
18
  class Polygon < Polyline
19
19
 
20
20
  # Construct a new Polygon from Points and/or Edges
21
- # The constructor will try to convert all of its arguments into Points and
22
- # Edges. Then successive Points will be collpased into Edges. Successive
23
- # Edges that share a common vertex will be added to the new Polygon. If
24
- # there's a gap between Edges it will be automatically filled with a new
25
- # Edge. The resulting Polygon will then be closed if it isn't already.
21
+ # The constructor will try to convert all of its arguments into {Points} and
22
+ # {Edges}. Then successive {Points} will be collpased into {Edges}. Successive
23
+ # {Edges} that share a common vertex will be added to the new {Polygon}. If
24
+ # there's a gap between {Edges} it will be automatically filled with a new
25
+ # {Edge}. The resulting Polygon will then be closed, if it isn't already.
26
26
  # @overload initialize(Edge, Edge, ...)
27
27
  # @return [Polygon]
28
28
  # @overload initialize(Point, Point, ...)
@@ -116,7 +116,7 @@ but there's currently nothing that enforces simplicity.
116
116
  if (a.first == b.first) and (a.last == b.last) # Equal edges
117
117
  elsif (a.first == b.last) and (a.last == b.first) # Ignore equal but opposite edges
118
118
  else
119
- if a.vector.normalize == b.vector.normalize # Same direction?
119
+ if a.direction == b.direction # Same direction?
120
120
  offsetA += 1 if ringA.insert_boundary(indexA + 1 + offsetA, b.first)
121
121
  offsetB += 1 if ringB.insert_boundary(indexB + 1 + offsetB, a.last)
122
122
  else # Opposite direction
@@ -15,6 +15,10 @@ also like a {Path} in that it isn't necessarily closed.
15
15
  class Polyline
16
16
  attr_reader :edges, :vertices
17
17
 
18
+ # @!attribute points
19
+ # @return [Array<Point>] all of the vertices of the {Polyline} (alias of #vertices)
20
+ alias :points :vertices
21
+
18
22
  # Construct a new Polyline from Points and/or Edges
19
23
  # @note The constructor will try to convert all of its arguments into {Point}s and
20
24
  # {Edge}s. Then successive {Point}s will be collpased into {Edge}s. Successive
@@ -123,6 +123,12 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
123
123
  Point[(max.x+min.x)/2, (max.y+min.y)/2]
124
124
  end
125
125
 
126
+ # @!attribute closed?
127
+ # @return [Bool] always true
128
+ def closed?
129
+ true
130
+ end
131
+
126
132
  # @return [Array<Edge>] The {Rectangle}'s four edges (counterclockwise)
127
133
  def edges
128
134
  point0, point2 = *@points
@@ -1,4 +1,3 @@
1
- require_relative 'cluster_factory'
2
1
  require_relative 'polygon'
3
2
 
4
3
  module Geometry
@@ -10,53 +9,49 @@ A {RegularPolygon} is a lot like a {Polygon}, but more regular.
10
9
  == Usage
11
10
  polygon = Geometry::RegularPolygon.new sides:4, center:[1,2], radius:3
12
11
  polygon = Geometry::RegularPolygon.new sides:6, center:[1,2], diameter:6
12
+
13
+ polygon = Geometry::RegularPolygon.new sides:4, center:[1,2], inradius:3
14
+ polygon = Geometry::RegularPolygon.new sides:6, center:[1,2], indiameter:6
13
15
  =end
14
16
 
15
17
  class RegularPolygon < Polygon
16
- include ClusterFactory
17
-
18
18
  # @return [Point] The {RegularPolygon}'s center point
19
19
  attr_reader :center
20
20
 
21
21
  # @return [Number] The {RegularPolygon}'s number of sides
22
22
  attr_reader :edge_count
23
23
 
24
- # @return [Number] The {RegularPolygon}'s radius
25
- attr_reader :radius
26
-
27
24
  # @overload new(sides, center, radius)
28
25
  # Construct a {RegularPolygon} using a center point and radius
29
26
  # @option options [Number] :sides The number of edges
30
27
  # @option options [Point] :center (PointZero) The center point of the {RegularPolygon}
31
- # @option options [Number] :radius The radius of the {RegularPolygon}
28
+ # @option options [Number] :radius The circumradius of the {RegularPolygon}
29
+ # @overload new(sides, center, inradius)
30
+ # Construct a {RegularPolygon} using a center point and radius
31
+ # @option options [Number] :sides The number of edges
32
+ # @option options [Point] :center (PointZero) The center point of the {RegularPolygon}
33
+ # @option options [Number] :inradius The inradius of the {RegularPolygon}
32
34
  # @overload new(sides, center, diameter)
33
35
  # Construct a {RegularPolygon} using a center point and diameter
34
36
  # @option options [Number] :sides The number of edges
35
37
  # @option options [Point] :center (PointZero) 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] : Point.zero
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}
38
+ # @option options [Number] :diameter The circumdiameter of the {RegularPolygon}
39
+ # @overload new(sides, center, indiameter)
40
+ # Construct a {RegularPolygon} using a center point and diameter
41
+ # @option options [Number] :sides The number of edges
42
+ # @option options [Point] :center (PointZero) The center point of the {RegularPolygon}
43
+ # @option options [Number] :indiameter The circumdiameter of the {RegularPolygon}
56
44
  # @return [RegularPolygon] A new {RegularPolygon} object
57
- def initialize(edge_count, center, radius)
58
- @center = Point[center]
59
- @edge_count = edge_count
45
+ def initialize(edge_count:nil, sides:nil, center:nil, radius:nil, diameter:nil, indiameter:nil, inradius:nil)
46
+ @edge_count = edge_count || sides
47
+ raise ArgumentError, "RegularPolygon requires an edge count" unless @edge_count
48
+
49
+ raise ArgumentError, "RegularPolygon.new requires a radius or a diameter" unless diameter || indiameter || inradius || radius
50
+
51
+ @center = center ? Point[center] : Point.zero
52
+ @diameter = diameter
53
+ @indiameter = indiameter
54
+ @inradius = inradius
60
55
  @radius = radius
61
56
  end
62
57
 
@@ -65,6 +60,12 @@ A {RegularPolygon} is a lot like a {Polygon}, but more regular.
65
60
  end
66
61
  alias :== :eql?
67
62
 
63
+ # Check to see if the {Polygon} is closed (always true)
64
+ # @return [True] Always true because a {Polygon} is always closed
65
+ def closed?
66
+ true
67
+ end
68
+
68
69
  # @!group Accessors
69
70
  # @return [Rectangle] The smallest axis-aligned {Rectangle} that bounds the receiver
70
71
  def bounds
@@ -74,8 +75,9 @@ A {RegularPolygon} is a lot like a {Polygon}, but more regular.
74
75
  # @!attribute [r] diameter
75
76
  # @return [Numeric] The diameter of the {RegularPolygon}
76
77
  def diameter
77
- @radius*2
78
+ @diameter || (@radius && 2*@radius) || (@indiameter && @indiameter/cosine_half_angle)
78
79
  end
80
+ alias :circumdiameter :diameter
79
81
 
80
82
  # !@attribute [r] edges
81
83
  def edges
@@ -88,6 +90,7 @@ A {RegularPolygon} is a lot like a {Polygon}, but more regular.
88
90
  def vertices
89
91
  (0...2*Math::PI).step(2*Math::PI/edge_count).map {|angle| center + Point[Math::cos(angle), Math::sin(angle)]*radius }
90
92
  end
93
+ alias :points :vertices
91
94
 
92
95
  # @return [Point] The upper right corner of the bounding {Rectangle}
93
96
  def max
@@ -103,34 +106,38 @@ A {RegularPolygon} is a lot like a {Polygon}, but more regular.
103
106
  def minmax
104
107
  [self.min, self.max]
105
108
  end
106
- # @!endgroup
107
- end
108
-
109
- class DiameterRegularPolygon < RegularPolygon
110
- # @return [Number] The {RegularPolygon}'s diameter
111
- attr_reader :diameter
112
109
 
113
- # Construct a new {RegularPolygon} from a centerpoint and a diameter
114
- # @param [Number] edge_count The number of edges
115
- # @param [Point] center The center point of the {RegularPolygon}
116
- # @param [Number] diameter The radius of the {RegularPolygon}
117
- # @return [RegularPolygon] A new {RegularPolygon} object
118
- def initialize(edge_count, center, diameter)
119
- @center = center ? Point[center] : nil
120
- @edge_count = edge_count
121
- @diameter = diameter
110
+ # @!attribute indiameter
111
+ # @return [Number] the indiameter
112
+ def indiameter
113
+ @indiameter || (@inradius && 2*@inradius) || (@diameter && (@diameter * cosine_half_angle)) || (@radius && (2 * @radius * cosine_half_angle))
122
114
  end
123
115
 
124
- def eql?(other)
125
- (self.center == other.center) && (self.edge_count == other.edge_count) && (self.diameter == other.diameter)
116
+ # @!attribute inradius
117
+ # @return [Number] The inradius
118
+ def inradius
119
+ @inradius || (@indiameter && @indiameter/2) || (@radius && (@radius * cosine_half_angle))
126
120
  end
127
- alias :== :eql?
121
+ alias :apothem :inradius
128
122
 
129
- # @!group Accessors
130
- # @return [Number] The {RegularPolygon}'s radius
123
+ # @!attribute [r] radius
124
+ # @return [Number] The {RegularPolygon}'s radius
131
125
  def radius
132
- @diameter/2
126
+ @radius || (@diameter && @diameter/2) || (@inradius && (@inradius / cosine_half_angle)) || (@indiameter && @indiameter/cosine_half_angle/2)
127
+ end
128
+ alias :circumradius :radius
129
+
130
+ # @!attribute [r] side_length
131
+ # @return [Number] The length of each side
132
+ def side_length
133
+ 2 * circumradius * Math.sin(Math::PI/edge_count)
133
134
  end
135
+
136
+ private
137
+ def cosine_half_angle
138
+ Math.cos(Math::PI/edge_count)
139
+ end
140
+
134
141
  # @!endgroup
135
142
  end
136
143
  end
@@ -105,6 +105,7 @@ A generalized representation of a rotation transformation.
105
105
  # @param [Point] point the {Point} to rotate into the parent coordinate frame
106
106
  # @return [Point] the rotated {Point}
107
107
  def transform(point)
108
+ return point if point.is_a?(PointZero)
108
109
  m = matrix
109
110
  m ? Point[m * Point[point]] : point
110
111
  end
@@ -1,5 +1,8 @@
1
1
  require 'matrix'
2
2
 
3
+ require_relative 'size_one'
4
+ require_relative 'size_zero'
5
+
3
6
  module Geometry
4
7
  =begin
5
8
  An object representing the size of something.
@@ -29,13 +32,42 @@ methods (width, height and depth).
29
32
  array.flatten!
30
33
  super *array
31
34
  end
32
-
35
+
36
+ # Creates and returns a new {SizeOne} instance. Or, a {Size} full of ones if the size argument is given.
37
+ # @param size [Number] the size of the new {Size} full of ones
38
+ # @return [SizeOne] A new {SizeOne} instance
39
+ def self.one(size=nil)
40
+ size ? Size[Array.new(size, 1)] : SizeOne.new
41
+ end
42
+
43
+ # Creates and returns a new {SizeOne} instance. Or, a {Size} full of zeros if the size argument is given.
44
+ # @param size [Number] the size of the new {Size} full of zeros
45
+ # @return [SizeOne] A new {SizeOne} instance
46
+ def self.zero(size=nil)
47
+ size ? Size[Array.new(size, 0)] : SizeOne.new
48
+ end
49
+
33
50
  # Allow comparison with an Array, otherwise do the normal thing
34
51
  def ==(other)
35
52
  return @elements == other if other.is_a?(Array)
36
53
  super other
37
54
  end
38
55
 
56
+ # Override Vector#[] to allow for regular array slicing
57
+ def [](*args)
58
+ @elements[*args]
59
+ end
60
+
61
+ def coerce(other)
62
+ case other
63
+ when Array then [Size[*other], self]
64
+ when Numeric then [Size[Array.new(self.size, other)], self]
65
+ when Vector then [Size[*other], self]
66
+ else
67
+ raise TypeError, "#{self.class} can't be coerced into #{other.class}"
68
+ end
69
+ end
70
+
39
71
  def inspect
40
72
  'Size' + @elements.inspect
41
73
  end
@@ -91,13 +123,12 @@ methods (width, height and depth).
91
123
  left = top = -args.shift
92
124
  right = bottom = 0
93
125
  elsif 2 == args.size
94
- left = -args.shift
95
- top = -args.shift
96
- right = bottom = 0
126
+ left = right = -args.shift
127
+ top = bottom = -args.shift
97
128
  end
98
129
 
99
- left = -options[:x] if options[:x]
100
- top = -options[:y] if options[:y]
130
+ left = right = -options[:x] if options[:x]
131
+ top = bottom = -options[:y] if options[:y]
101
132
 
102
133
  top = -options[:top] if options[:top]
103
134
  left = -options[:left] if options[:left]
@@ -125,13 +156,12 @@ methods (width, height and depth).
125
156
  left = top = args.shift
126
157
  right = bottom = 0
127
158
  elsif 2 == args.size
128
- left = args.shift
129
- top = args.shift
130
- right = bottom = 0
159
+ left = right = args.shift
160
+ top = bottom = args.shift
131
161
  end
132
162
 
133
- left = options[:x] if options[:x]
134
- top = options[:y] if options[:y]
163
+ left = right = options[:x] if options[:x]
164
+ top = bottom = options[:y] if options[:y]
135
165
 
136
166
  top = options[:top] if options[:top]
137
167
  left = options[:left] if options[:left]