geometry 3 → 4

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,9 +19,13 @@ Primitives
19
19
  ----------
20
20
 
21
21
  - Point
22
+ - Size
22
23
  - Line
24
+ - Edge
23
25
  - Circle
24
26
  - Rectangle
27
+ - Polygon
28
+ - Transformation
25
29
 
26
30
  Examples
27
31
  --------
@@ -29,7 +33,6 @@ Examples
29
33
  ### Point
30
34
  ```ruby
31
35
  point = Geometry::Point[3,4] # 2D Point at coordinate 3, 4
32
- point = Geometry.point(1,2) # Functional constructor
33
36
 
34
37
  # Copy constructors
35
38
  point2 = Geometry::Point[point]
@@ -66,3 +69,9 @@ Examples
66
69
  # A circle at Point[1,2] with a radius of 3
67
70
  circle = Geometry::Circle.new [1,2], 3
68
71
  ```
72
+
73
+ ### Polygon
74
+ ```ruby
75
+ # A polygon that looks a lot like a square
76
+ polygon = Geometry::Polygon.new [0,0], [1,0], [1,1], [0,1]
77
+ ```
data/Rakefile CHANGED
@@ -1,4 +1,11 @@
1
1
  require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.test_files = FileList['test/**/*.rb']
7
+ t.verbose = true
8
+ end
2
9
 
3
10
  task :fixdates do
4
11
  branch = `git branch --no-color -r --merged`.strip
@@ -14,6 +21,10 @@ task :trim_whitespace do
14
21
  system(%Q[git status --short | awk '{if ($1 != "D" && $1 != "R") print $2}' | grep -e '.*\.rb$' | xargs sed -i '' -e 's/[ \t]*$//g;'])
15
22
  end
16
23
 
24
+ task :docs do
25
+ `yard`
26
+ end
27
+
17
28
  task :uninstall do
18
29
  `gem uninstall geometry`
19
30
  end
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "geometry"
6
- s.version = '3'
6
+ s.version = '4'
7
7
  s.authors = ["Brandon Fosdick"]
8
8
  s.email = ["bfoz@bfoz.net"]
9
9
  s.homepage = "http://github.com/bfoz/geometry"
@@ -1,9 +1,16 @@
1
+ require_relative 'geometry/arc'
1
2
  require_relative 'geometry/circle'
2
- require_relative 'geometry/point'
3
3
  require_relative 'geometry/line'
4
+ require_relative 'geometry/point'
5
+ require_relative 'geometry/point_zero'
4
6
  require_relative 'geometry/polygon'
5
7
  require_relative 'geometry/rectangle'
8
+ require_relative 'geometry/rotation'
6
9
  require_relative 'geometry/size'
10
+ require_relative 'geometry/size_zero'
11
+ require_relative 'geometry/square'
12
+ require_relative 'geometry/transformation'
13
+ require_relative 'geometry/vector'
7
14
 
8
15
  module Geometry
9
16
  # @overload Line(array0, array1)
@@ -0,0 +1,31 @@
1
+ require_relative 'point'
2
+
3
+ module Geometry
4
+
5
+ =begin rdoc
6
+ Arcs are Circles that don't quite go all the way around
7
+
8
+ == Usage
9
+ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis and goes to the Y-axis (counter-clockwise)
10
+ arc = Geometry::Arc.new [1,1], 2, 0, 90
11
+ =end
12
+
13
+ class Arc
14
+ attr_reader :center
15
+ attr_reader :radius
16
+ attr_reader :start_angle, :end_angle
17
+
18
+ # Construct a new {Arc}
19
+ # @overload initialize(center, radius, start_angle, end_angle)
20
+ # @param [Point] center The {Point} at the center of it all
21
+ # @param [Numeric] radius Radius
22
+ # @param [Numeric] start_angle Starting angle
23
+ # @param [Numeric] end_angle Ending angle
24
+ def initialize(center, radius, start_angle, end_angle)
25
+ @center = Point[center]
26
+ @radius = radius
27
+ @start_angle = start_angle
28
+ @end_angle = end_angle
29
+ end
30
+ end
31
+ end
@@ -21,7 +21,7 @@ Circles come in all shapes and sizes, but they're usually round.
21
21
  # @param [Number] radius The radius of the Circle
22
22
  # @return [Circle] A new Circle object
23
23
  def initialize(center, radius)
24
- @center = center.is_a?(Point) ? center : Point[center]
24
+ @center = Point[center]
25
25
  @radius = radius
26
26
  end
27
27
  end
@@ -16,7 +16,7 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
16
16
  # Construct a new {Edge} object from any two things that can be converted
17
17
  # to a {Point}.
18
18
  def initialize(point0, point1)
19
- @first, @last = [point0, point1].map {|p| p.is_a?(Point) ? p : Point[p] }
19
+ @first, @last = [Point[point0], Point[point1]]
20
20
  end
21
21
 
22
22
  # Two Edges are equal if both have equal {Point}s in the same order
@@ -67,7 +67,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
67
67
  # @private
68
68
  class PointSlopeLine < Line
69
69
  def initialize(point, slope)
70
- @point = point.is_a?(Geometry::Point) ? point : Geometry.Point(point)
70
+ @point = Point[point]
71
71
  @slope = slope
72
72
  end
73
73
  def to_s
@@ -111,7 +111,7 @@ Supports two-point, slope-intercept, and point-slope initializer forms
111
111
  attr_reader :first, :last
112
112
 
113
113
  def initialize(point0, point1)
114
- @first, @last = [point0, point1].map {|p| p.is_a?(Point) ? p : Point[p] }
114
+ @first, @last = [Point[point0], Point[point1]]
115
115
  end
116
116
  def inspect
117
117
  'Line(' + @first.inspect + ', ' + @last.inspect + ')'
@@ -1,6 +1,9 @@
1
1
  require 'matrix'
2
2
 
3
3
  module Geometry
4
+ DimensionMismatch = Class.new(StandardError)
5
+ OperationNotDefined = Class.new(StandardError)
6
+
4
7
  =begin rdoc
5
8
  An object repesenting a Point in N-dimensional space
6
9
 
@@ -23,16 +26,33 @@ geometry class (x, y, z).
23
26
  # @overload [](Point)
24
27
  # @overload [](Vector)
25
28
  def self.[](*array)
29
+ return array[0] if array[0].is_a? Point
26
30
  array = array[0] if array[0].is_a?(Array)
27
31
  array = array[0].to_a if array[0].is_a?(Vector)
28
32
  super *array
29
33
  end
30
34
 
35
+ # Creates and returns a new {PointZero} instance
36
+ # @return [PointZero] A new {PointZero} instance
37
+ def self.zero
38
+ PointZero.new
39
+ end
40
+
31
41
  # Allow comparison with an Array, otherwise do the normal thing
32
- def ==(other)
42
+ def eql?(other)
33
43
  return @elements == other if other.is_a?(Array)
34
44
  super other
35
45
  end
46
+ alias == eql?
47
+
48
+ def coerce(other)
49
+ case other
50
+ when Array then [Point[*other], self]
51
+ when Vector then [Point[*other], self]
52
+ else
53
+ raise TypeError, "#{self.class} can't be coerced into #{other.class}"
54
+ end
55
+ end
36
56
 
37
57
  def inspect
38
58
  'Point' + @elements.inspect
@@ -41,34 +61,74 @@ geometry class (x, y, z).
41
61
  'Point' + @elements.to_s
42
62
  end
43
63
 
64
+ # !@group Accessors
65
+ # @param [Integer] i Index into the {Point}'s elements
66
+ # @return [Numeric] Element i (starting at 0)
67
+ def [](i)
68
+ @elements[i]
69
+ end
70
+
71
+ # !@attribute [r] x
44
72
  # @return [Numeric] X-component
45
73
  def x
46
74
  @elements[0]
47
75
  end
48
76
 
77
+ # !@attribute [r] y
49
78
  # @return [Numeric] Y-component
50
79
  def y
51
80
  @elements[1]
52
81
  end
53
82
 
83
+ # !@attribute [r] z
54
84
  # @return [Numeric] Z-component
55
85
  def z
56
86
  @elements[2]
57
87
  end
88
+ # !@endgroup
89
+
90
+ # !@group Arithmetic
91
+
92
+ # !@group Unary operators
93
+ def +@
94
+ self
95
+ end
58
96
 
59
- # @group Arithmetic
60
- # Override the arithmetic operators to force them to return {Point}s instead
61
- # of {Vector}s
97
+ def -@
98
+ Point[@elements.map {|e| -e }]
99
+ end
100
+ # !@endgroup
62
101
 
63
102
  def +(other)
64
- Point[super]
103
+ case other
104
+ when Numeric
105
+ raise DimensionMismatch, "A scalar can't be added to a Point of dimension greater than 1" if size != 1
106
+ Point[@elements.first + other]
107
+ when PointZero
108
+ self
109
+ else
110
+ raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
111
+ raise DimensionMismatch, "Can't add #{other} to #{self}" if size != other.size
112
+ Point[Array.new(size) {|i| @elements[i] + other[i] }]
113
+ end
65
114
  end
66
115
 
67
116
  def -(other)
68
- Point[super]
117
+ case other
118
+ when Numeric
119
+ raise DimensionMismatch, "A scalar can't be subtracted from a Point of dimension greater than 1" if size != 1
120
+ Point[@elements.first - other]
121
+ when PointZero
122
+ self
123
+ else
124
+ raise OperationNotDefined, "#{other.class} must respond to :size and :[]" unless other.respond_to?(:size) && other.respond_to?(:[])
125
+ raise DimensionMismatch, "Can't subtract #{other} from #{self}" if size != other.size
126
+ Point[Array.new(size) {|i| @elements[i] - other[i] }]
127
+ end
69
128
  end
70
129
 
71
- # @endgroup
130
+ # !@endgroup
72
131
 
73
132
  end
74
133
  end
134
+
@@ -0,0 +1,69 @@
1
+ require_relative 'point'
2
+
3
+ module Geometry
4
+ =begin rdoc
5
+ An object repesenting a {Point} at the origin in N-dimensional space
6
+
7
+ A {PointZero} object is a {Point} that will always compare equal to zero and unequal to
8
+ everything else, regardless of size.
9
+ =end
10
+ class PointZero
11
+ def eql?(other)
12
+ if other.respond_to? :all?
13
+ other.all? {|e| e.eql? 0}
14
+ else
15
+ other == 0
16
+ end
17
+ end
18
+ alias == eql?
19
+
20
+ def coerce(other)
21
+ if other.is_a? Numeric
22
+ [other, 0]
23
+ elsif other.is_a? Array
24
+ [other, Array.new(other.size,0)]
25
+ elsif other.is_a? Vector
26
+ [other, Vector[*Array.new(other.size,0)]]
27
+ else
28
+ [Point[other], Point[Array.new(other.size,0)]]
29
+ end
30
+ end
31
+
32
+ # !@group Arithmetic
33
+
34
+ # !@group Unary operators
35
+ def +@
36
+ self
37
+ end
38
+
39
+ def -@
40
+ self
41
+ end
42
+ # !@endgroup
43
+
44
+ def +(other)
45
+ other
46
+ end
47
+
48
+ def -(other)
49
+ if other.respond_to? :-@
50
+ -other
51
+ elsif other.respond_to? :map
52
+ other.map {|a| -a }
53
+ end
54
+ end
55
+
56
+ def *(other)
57
+ self
58
+ end
59
+
60
+ def /(other)
61
+ raise OperationNotDefined unless other.is_a? Numeric
62
+ raise ZeroDivisionError if 0 == other
63
+ self
64
+ end
65
+ # !@endgroup
66
+
67
+ end
68
+ end
69
+
@@ -13,6 +13,7 @@ An object representing a closed set of vertices and edges.
13
13
 
14
14
  class Polygon
15
15
  attr_reader :edges, :vertices
16
+ alias :points :vertices
16
17
 
17
18
  # Construct a new Polygon from Points and/or Edges
18
19
  # The constructor will try to convert all of its arguments into Points and
@@ -9,12 +9,12 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
9
9
  == Usage
10
10
 
11
11
  === Constructors
12
- rect = Rectangle[[1,2], [2,3]] # Using two corners
13
- rect = Rectangle[[1,2], Size[1,1]] # Using origin and size
14
- rect = Rectangle[1,2,2,3] # Using four sides
12
+ rect = Rectangle[[1,2], [2,3]] # Using two corners
13
+ rect = Rectangle[[1,2], Size[1,1]] # Using origin and size
14
+ rect = Rectangle[1,2,2,3] # Using four sides
15
15
 
16
- rect = Rectangle[10, 20] # origin = [0,0], size = [10, 20]
17
- rect = Rectangle[Size[10, 20]] # origin = [0,0], size = [10, 20]
16
+ rect = Rectangle[10, 20] # origin = [0,0], size = [10, 20]
17
+ rect = Rectangle[Size[10, 20]] # origin = [0,0], size = [10, 20]
18
18
 
19
19
  =end
20
20
 
@@ -81,8 +81,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
81
81
  # @param [Point] point0 A corner (ie. bottom-left)
82
82
  # @param [Point] point1 The other corner (ie. top-right)
83
83
  def initialize(point0, point1)
84
- point0 = point0.is_a?(Point) ? point0 : Point[point0]
85
- point1 = point1.is_a?(Point) ? point1 : Point[point1]
84
+ point0 = Point[point0]
85
+ point1 = Point[point1]
86
86
  raise(ArgumentError, "Point sizes must match") unless point0.size == point1.size
87
87
 
88
88
  # Reorder the points to get lower-left and upper-right
@@ -100,9 +100,11 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
100
100
  end
101
101
 
102
102
  # @group Accessors
103
+
104
+ # @return [Point] The {Rectangle}'s center
103
105
  def center
104
106
  min, max = @points.minmax {|a,b| a.y <=> b.y}
105
- Point[(max.x+min.x)/2, (max.y+min.y)/2]
107
+ Point[(max.x+min.x)/2.0, (max.y+min.y)/2.0]
106
108
  end
107
109
 
108
110
  # @return [Array<Edge>] The {Rectangle}'s four edges
@@ -116,6 +118,14 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
116
118
  Edge.new(point3, point0)]
117
119
  end
118
120
 
121
+ # @return [Array<Point>] The {Rectangle}'s four points (clockwise)
122
+ def points
123
+ point0, point2 = *@points
124
+ point1 = Point[point0.x,point2.y]
125
+ point3 = Point[point2.x, point0.y]
126
+ [point0, point1, point2, point3]
127
+ end
128
+
119
129
  def origin
120
130
  minx = @points.min {|a,b| a.x <=> b.x}
121
131
  miny = @points.min {|a,b| a.y <=> b.y}
@@ -173,8 +183,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
173
183
  # @group Accessors
174
184
  # @return [Array<Edge>] The {Rectangle}'s four edges
175
185
  def edges
176
- point0 = @center - @size/2
177
- point2 = @center + @size/2
186
+ point0 = @center - @size/2.0
187
+ point2 = @center + @size/2.0
178
188
  point1 = Point[point0.x,point2.y]
179
189
  point3 = Point[point2.x, point0.y]
180
190
  [Edge.new(point0, point1),
@@ -183,6 +193,15 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
183
193
  Edge.new(point3, point0)]
184
194
  end
185
195
 
196
+ # @return [Array<Point>] The {Rectangle}'s four points (clockwise)
197
+ def points
198
+ point0 = @center - @size/2.0
199
+ point2 = @center + @size/2.0
200
+ point1 = Point[point0.x,point2.y]
201
+ point3 = Point[point2.x, point0.y]
202
+ [point0, point1, point2, point3]
203
+ end
204
+
186
205
  def height
187
206
  @size.height
188
207
  end
@@ -249,6 +268,15 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
249
268
  Edge.new(point3, point0)]
250
269
  end
251
270
 
271
+ # @return [Array<Point>] The {Rectangle}'s four points (clockwise)
272
+ def points
273
+ point0 = @origin
274
+ point2 = @origin + @size
275
+ point1 = Point[point0.x,point2.y]
276
+ point3 = Point[point2.x, point0.y]
277
+ [point0, point1, point2, point3]
278
+ end
279
+
252
280
  def height
253
281
  @size.height
254
282
  end