geometry 4 → 5

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.
@@ -1,3 +1,4 @@
1
+ require_relative 'cluster_factory'
1
2
  require_relative 'edge'
2
3
  require_relative 'point'
3
4
  require_relative 'size'
@@ -19,6 +20,8 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
19
20
  =end
20
21
 
21
22
  class Rectangle
23
+ include ClusterFactory
24
+
22
25
  # @return [Point] The {Rectangle}'s center
23
26
  attr_reader :center
24
27
  # @return [Number] Height of the {Rectangle}
@@ -30,10 +33,6 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
30
33
  # @return [Number] Width of the {Rectangle}
31
34
  attr_reader :width
32
35
 
33
- class << self
34
- alias :original_new :new
35
- end
36
-
37
36
  # @overload new(width, height)
38
37
  # Creates a {Rectangle} of the given width and height, centered on the origin
39
38
  # @param [Number] height Height
@@ -151,10 +150,6 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
151
150
  # @return [Size] The {Size} of the {Rectangle}
152
151
  attr_accessor :size
153
152
 
154
- def self.new(*args)
155
- original_new(*args)
156
- end
157
-
158
153
  # @overload new(width, height)
159
154
  # Creates a {Rectangle} of the given width and height, centered on the origin
160
155
  # @param [Number] height Height
@@ -220,10 +215,6 @@ The {Rectangle} class cluster represents your typical arrangement of 4 corners a
220
215
  # @return [Size] The {Size} of the {Rectangle}
221
216
  attr_accessor :size
222
217
 
223
- def self.new(*args)
224
- original_new(*args)
225
- end
226
-
227
218
  # @overload new(width, height)
228
219
  # Creates a {Rectangle} of the given width and height with its origin at [0,0]
229
220
  # @param [Number] height Height
@@ -1,9 +1,11 @@
1
+ require 'matrix'
2
+
1
3
  module Geometry
2
4
  =begin
3
5
  A generalized representation of a rotation transformation.
4
6
  =end
5
7
  class Rotation
6
- # !@attribute [r] dimensions
8
+ # @attribute [r] dimensions
7
9
  # @return [Integer]
8
10
  attr_reader :dimensions
9
11
  attr_reader :x, :y, :z
@@ -34,13 +36,38 @@ A generalized representation of a rotation transformation.
34
36
 
35
37
  raise ArgumentError, "Dimensionality mismatch" unless all_axes_options.first.size <= @dimensions
36
38
  if all_axes_options.first.size < @dimensions
37
- @x, @y, @z = [@x, @y, @z].map {|a| (a && (a.size < @dimensions)) ? Array.new(@dimensions) {|i| a[i] || 0 } : a }
39
+ @x, @y, @z = [@x, @y, @z].map {|a| (a && (a.size != 0) && (a.size < @dimensions)) ? Array.new(@dimensions) {|i| a[i] || 0 } : a }
38
40
  end
41
+
42
+ raise ArgumentError, "Too many axes specified (expected #{@dimensions - 1} but got #{all_axes_options.size}" unless all_axes_options.size == (@dimensions - 1)
39
43
  end
40
44
  end
41
45
 
42
46
  def identity?
43
47
  (!@x && !@y && !@z) || ([@x, @y, @z].select {|a| a}.all? {|a| a.respond_to?(:magnitude) ? (1 == a.magnitude) : (1 == a.size)})
44
48
  end
49
+
50
+ # @attribute [r] matrix
51
+ # @return [Matrix]
52
+ def matrix
53
+ # Force all axes to be Vectors
54
+ x,y,z = [@x, @y, @z].map {|a| a.is_a?(Array) ? Vector[*a] : a}
55
+
56
+ # Force all axes to exist
57
+ if x and y
58
+ z = x ** y
59
+ elsif x and z
60
+ y = x ** z
61
+ elsif y and z
62
+ x = y ** z
63
+ end
64
+
65
+ rows = []
66
+ [x, y, z].each_with_index {|a, i| rows.push(a.to_a) if i < @dimensions }
67
+
68
+ raise ArgumentError, "Number of axes must match the dimensions of each axis" unless @dimensions == rows.size
69
+
70
+ Matrix[*rows]
71
+ end
45
72
  end
46
73
  end
@@ -29,9 +29,9 @@ everything else, regardless of dimensionality.
29
29
  end
30
30
  end
31
31
 
32
- # !@group Arithmetic
32
+ # @group Arithmetic
33
33
 
34
- # !@group Unary operators
34
+ # @group Unary operators
35
35
  def +@
36
36
  self
37
37
  end
@@ -39,7 +39,7 @@ everything else, regardless of dimensionality.
39
39
  def -@
40
40
  self
41
41
  end
42
- # !@endgroup
42
+ # @endgroup
43
43
 
44
44
  def +(other)
45
45
  other
@@ -62,7 +62,7 @@ everything else, regardless of dimensionality.
62
62
  raise ZeroDivisionError if 0 == other
63
63
  self
64
64
  end
65
- # !@endgroup
65
+ # @endgroup
66
66
 
67
67
  end
68
68
  end
@@ -31,7 +31,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
31
31
  end
32
32
 
33
33
  # !@group Accessors
34
- # !@attribute [r] origin
34
+ # @attribute [r] origin
35
35
  # @return [Point] The lower left corner
36
36
  def origin
37
37
  @points.first
@@ -46,12 +46,12 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
46
46
  min, max = @points.minmax {|a,b| a.x <=> b.x}
47
47
  max.x - min.x
48
48
  end
49
- # !@endgroup
49
+ # @endgroup
50
50
  end
51
51
 
52
52
  # A {Square} created with a center point and a size
53
53
  class CenteredSquare < Square
54
- # !@attribute [r] center
54
+ # @attribute [r] center
55
55
  # @return [Point] The center of the {Square}
56
56
  attr_reader :center
57
57
 
@@ -62,14 +62,14 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
62
62
  @size = size
63
63
  end
64
64
 
65
- # !@group Accessors
66
- # !@attribute [r] origin
65
+ # @group Accessors
66
+ # @attribute [r] origin
67
67
  # @return [Point] The lower left corner
68
68
  def origin
69
69
  Point[@center.x - size/2, @center.y - size/2]
70
70
  end
71
71
 
72
- # !@attribute [r] points
72
+ # @attribute [r] points
73
73
  # @return [Array<Point>] The {Square}'s four points (clockwise)
74
74
  def points
75
75
  half_size = @size/2
@@ -88,7 +88,7 @@ The {Square} class cluster is like the {Rectangle} class cluster, but not longer
88
88
  def width
89
89
  @size
90
90
  end
91
- # !@endgroup
91
+ # @endgroup
92
92
  end
93
93
 
94
94
  # A {Square} created with an origin point and a size
@@ -0,0 +1,78 @@
1
+ require_relative 'cluster_factory'
2
+ require_relative 'point'
3
+
4
+ module Geometry
5
+ =begin rdoc
6
+ A {http://en.wikipedia.org/wiki/Triangle Triangle} is not a square.
7
+
8
+ == Usage
9
+ A right {Triangle} with its right angle at the origin and sides of length 1
10
+ triangle = Geometry::Triangle.new [0,0], [1,0], [0,1]
11
+
12
+ An isoscoles right {Triangle} created with an origin and leg length
13
+ triangle = Geometry::Triangle.new [0,0], 1
14
+ =end
15
+
16
+ # @abstract Factory class that instantiates the appropriate subclasses
17
+ class Triangle
18
+
19
+ include ClusterFactory
20
+
21
+ # @overload new(point0, point1, point2)
22
+ # Contruct a {ScaleneTriangle} using three {Point}s
23
+ # @param [Point] point0 The first vertex of the {Triangle}
24
+ # @param [Point] point1 Another vertex of the {Triangle}
25
+ # @param [Point] point2 The final vertex of the {Triangle}
26
+ # @overload new(point, length)
27
+ # Construct a {RightTriangle} using a {Point} and the lengths of the sides
28
+ # @param [Point] point The location of the vertex at {Triangle}'s right angle
29
+ # @param [Number] base The length of the {Triangle}'s base leg
30
+ # @param [Number] height The length of the {Triangle}'s vertical leg
31
+ def self.new(*args)
32
+ if args.size == 3
33
+ ScaleneTriangle.new *args
34
+ elsif args.size == 2
35
+ RightTriangle.new args[0], args[1], args[1]
36
+ end
37
+ end
38
+ end
39
+
40
+ # {http://en.wikipedia.org/wiki/Equilateral_triangle Equilateral Triangle}
41
+ class EquilateralTriangle < Triangle
42
+ def self.new(*args)
43
+ original_new(*args)
44
+ end
45
+ end
46
+
47
+ class IsoscelesTriangle < Triangle
48
+ def self.new(*args)
49
+ original_new(*args)
50
+ end
51
+ end
52
+
53
+ # {http://en.wikipedia.org/wiki/Right_triangle Right Triangle}
54
+ class RightTriangle < Triangle
55
+ attr_reader :origin, :base, :height
56
+
57
+ # Construct a Right Triangle given a {Point} and the leg lengths
58
+ def initialize(origin, base, height)
59
+ @origin = Point[origin]
60
+ @base, @height = base, height
61
+ end
62
+
63
+ # An array of points corresponding to the vertices of the {Triangle} (clockwise)
64
+ # @return [Array<Point>] Vertices
65
+ def points
66
+ [@origin, @origin + Point[0,@height], @origin + Point[@base,0]]
67
+ end
68
+ end
69
+
70
+ class ScaleneTriangle < Triangle
71
+ attr_reader :points
72
+
73
+ # Construct a scalene {Triangle}
74
+ def initialize(point0, point1, point2)
75
+ @points = [point0, point1, point2].map {|p| Point[p] }
76
+ end
77
+ end
78
+ end
@@ -2,7 +2,7 @@ require 'matrix'
2
2
 
3
3
  # Monkeypatch Vector to overcome some deficiencies
4
4
  class Vector
5
- # !@group Unary operators
5
+ # @group Unary operators
6
6
  def +@
7
7
  self
8
8
  end
@@ -10,5 +10,21 @@ class Vector
10
10
  def -@
11
11
  Vector[*(@elements.map {|e| -e })]
12
12
  end
13
- # !@endgroup
13
+ # @endgroup
14
+
15
+ # Cross-product of two {Vector}s
16
+ # @return [Vector]
17
+ def cross(other)
18
+ Vector.Raise ErrDimensionMismatch unless @elements.size == other.size
19
+
20
+ case @elements.size
21
+ when 0 then raise ArgumentError, "Can't multply zero-length Vectors"
22
+ when 1 then @elements.first * other.first
23
+ when 2 then @elements.first * other[1] - @elements.last * other.first
24
+ when 3 then Vector[ @elements[1]*other[2] - @elements[2]*other[1],
25
+ @elements[2]*other[0] - @elements[0]*other[2],
26
+ @elements[0]*other[1] - @elements[1]*other[0]]
27
+ end
28
+ end
29
+ alias ** cross
14
30
  end
@@ -10,4 +10,13 @@ describe Geometry::Arc do
10
10
  arc.start_angle.must_equal 0
11
11
  arc.end_angle.must_equal 90
12
12
  end
13
+
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
21
+
13
22
  end
@@ -1,23 +1,59 @@
1
1
  require 'minitest/autorun'
2
2
  require 'geometry/circle'
3
3
 
4
- def Circle(*args)
5
- Geometry::Circle.new(*args)
6
- end
7
-
8
4
  describe Geometry::Circle do
9
- it "must create a Circle object from a Point and a radius" do
10
- circle = Circle [1,2], 3
11
- assert_kind_of(Geometry::Circle, circle)
5
+ Circle = Geometry::Circle
6
+
7
+ describe "when constructed with center and radius arguments" do
8
+ let(:circle) { Circle.new [1,2], 3 }
9
+
10
+ it "must create a Circle" do
11
+ circle.must_be_instance_of(Circle)
12
+ end
13
+
14
+ it "must have a center point accessor" do
15
+ circle.center.must_equal Point[1,2]
16
+ end
17
+
18
+ it "must have a radius accessor" do
19
+ circle.radius.must_equal 3
20
+ end
12
21
  end
13
22
 
14
- it "must have a center point accessor" do
15
- circle = Circle [1,2], 3
16
- assert_equal(circle.center, [1,2])
23
+ describe "when constructed with named center and radius arguments" do
24
+ let(:circle) { Circle.new :center => [1,2], :radius => 3 }
25
+
26
+ it "must create a Circle" do
27
+ circle.must_be_instance_of(Circle)
28
+ end
29
+
30
+ it "must have a center point accessor" do
31
+ circle.center.must_equal Point[1,2]
32
+ end
33
+
34
+ it "must have a radius accessor" do
35
+ circle.radius.must_equal 3
36
+ end
17
37
  end
18
38
 
19
- it "must have a radius accessor" do
20
- circle = Circle [1,2], 3
21
- assert_equal(3, circle.radius)
39
+ describe "when constructed with a center and diameter" do
40
+ let(:circle) { Circle.new [1,2], :diameter => 4 }
41
+
42
+ it "must be a CenterDiameterCircle" do
43
+ circle.must_be_instance_of(Geometry::CenterDiameterCircle)
44
+ circle.must_be_kind_of(Circle)
45
+ end
46
+
47
+ it "must have a center" do
48
+ circle.center.must_equal Point[1,2]
49
+ end
50
+
51
+ it "must have a diameter" do
52
+ circle.diameter.must_equal 4
53
+ end
54
+
55
+ it "must calculate the correct radius" do
56
+ circle.radius.must_equal 2
57
+ end
22
58
  end
23
59
  end
@@ -38,4 +38,57 @@ describe Geometry::Edge do
38
38
  edge = Edge([0,0], [1,1])
39
39
  assert_equal(1, edge.width)
40
40
  end
41
+
42
+ it "must convert an Edge to a Vector" do
43
+ Edge.new([0,0], [1,0]).vector.must_equal Vector[1,0]
44
+ end
45
+
46
+ it "must return the normalized direction of a vector" do
47
+ Edge.new([0,0], [1,0]).direction.must_equal Vector[1,0]
48
+ end
49
+
50
+ it "must return true for parallel edges" do
51
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([0,0], [1,0])).must_equal 1
52
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([1,0], [2,0])).must_equal 1
53
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([3,0], [4,0])).must_equal 1
54
+ Edge.new([0,0], [1,0]).parallel?(Edge.new([3,1], [4,1])).must_equal 1
55
+ end
56
+
57
+ it "must return false for non-parallel edges" do
58
+ Edge.new([0,0], [2,0]).parallel?(Edge.new([1,-1], [1,1])).must_equal false
59
+ end
60
+
61
+ describe "when finding an intersection" do
62
+ it "must find the intersection of two end-intersecting Edges" do
63
+ intersection = Edge.new([0,0],[1,1]).intersection(Edge.new([0,1],[1,1]))
64
+ intersection.must_be_kind_of Geometry::Point
65
+ intersection.must_equal Geometry::Point[1,1]
66
+ end
67
+
68
+ it "must find the itersection of two crossed Edges" do
69
+ edge1 = Edge.new [0.0, 0], [2.0, 2.0]
70
+ edge2 = Edge.new [2.0, 0], [0.0, 2.0]
71
+ intersection = edge1.intersection edge2
72
+ intersection.must_be_kind_of Geometry::Point
73
+ intersection.must_equal Geometry::Point[1,1]
74
+ end
75
+
76
+ it "must return nil for two edges that do not intersect" do
77
+ Edge.new([0,0],[1,0]).intersection(Edge.new([0,1],[1,1])).must_equal nil
78
+ end
79
+
80
+ it "must return true for two collinear and overlapping edges" do
81
+ Edge.new([0,0],[2,0]).intersection(Edge.new([1,0],[3,0])).must_equal true
82
+ end
83
+
84
+ it "must return false for collinear but non-overlapping edges" do
85
+ Edge.new([0,0],[2,0]).intersection(Edge.new([3,0],[4,0])).must_equal false
86
+ Edge.new([0,0],[0,2]).intersection(Edge.new([0,3],[0,4])).must_equal false
87
+ end
88
+
89
+ it "must return nil for two parallel but not collinear edges" do
90
+ Edge.new([0,0],[2,0]).intersection(Edge.new([1,1],[3,1])).must_equal nil
91
+ end
92
+
93
+ end
41
94
  end
@@ -0,0 +1,67 @@
1
+ require 'minitest/autorun'
2
+ require 'geometry/path'
3
+
4
+ describe Geometry::Path do
5
+ describe "construction" do
6
+ it "must create a Path with no arguments" do
7
+ path = Geometry::Path.new
8
+ path.must_be_kind_of Geometry::Path
9
+ path.elements.wont_be_nil
10
+ path.elements.size.must_equal 0
11
+ end
12
+
13
+ it "must create a Path from Points" do
14
+ path = Geometry::Path.new Point[1,1], Point[2,2], Point[3,3]
15
+ path.elements.size.must_equal 2
16
+ path.elements.each {|a| a.must_be_kind_of Geometry::Edge }
17
+ end
18
+
19
+ it "with connected Edges" do
20
+ path = Geometry::Path.new Edge.new([1,1], [2,2]), Edge.new([2,2], [3,3])
21
+ path.elements.size.must_equal 2
22
+ path.elements.each {|a| a.must_be_kind_of Geometry::Edge }
23
+ end
24
+
25
+ it "with disjoint Edges" do
26
+ path = Geometry::Path.new Edge.new([1,1], [2,2]), Edge.new([3,3], [4,4])
27
+ path.elements.size.must_equal 3
28
+ path.elements.each {|a| a.must_be_kind_of Geometry::Edge }
29
+ end
30
+
31
+ it "with Points and Arcs" do
32
+ path = Geometry::Path.new [0,0], [1.0,0.0], Geometry::Arc.new([1,1], 1, -90*Math::PI/180, 0), [2.0,1.0], [1,2]
33
+ path.elements.size.must_equal 3
34
+ path.elements[0].must_be_kind_of Geometry::Edge
35
+ path.elements[1].must_be_kind_of Geometry::Arc
36
+ path.elements[2].must_be_kind_of Geometry::Edge
37
+ end
38
+
39
+ it "with Edges and Arcs" do
40
+ path = Geometry::Path.new Edge.new([0,0], [1.0,0.0]), Geometry::Arc.new([1,1], 1, -90*Math::PI/180, 0), Edge.new([2.0,1.0], [1,2])
41
+ path.elements.size.must_equal 3
42
+ path.elements[0].must_be_kind_of Geometry::Edge
43
+ path.elements[1].must_be_kind_of Geometry::Arc
44
+ path.elements[2].must_be_kind_of Geometry::Edge
45
+ end
46
+
47
+ it "with disjoint Edges and Arcs" do
48
+ path = Geometry::Path.new Edge.new([0,0], [1,0]), Geometry::Arc.new([2,1], 1, -90*Math::PI/180, 0), Edge.new([3,1], [1,2])
49
+ path.elements.size.must_equal 4
50
+ path.elements[0].must_be_kind_of Geometry::Edge
51
+ path.elements[1].must_be_kind_of Geometry::Edge
52
+ path.elements[2].must_be_kind_of Geometry::Arc
53
+ path.elements[3].must_be_kind_of Geometry::Edge
54
+ end
55
+
56
+ it "with disjoint Arcs" do
57
+ path = Geometry::Path.new Geometry::Arc.new([2,1], 1, -90*Math::PI/180, 0), Geometry::Arc.new([3,1], 1, -90*Math::PI/180, 0)
58
+ path.elements.size.must_equal 3
59
+ path.elements[0].must_be_kind_of Geometry::Arc
60
+ path.elements[1].must_be_kind_of Geometry::Edge
61
+ path.elements[2].must_be_kind_of Geometry::Arc
62
+
63
+ path.elements[0].last.must_equal path.elements[1].first
64
+ end
65
+
66
+ end
67
+ end