geometry 3 → 4

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