geometry 4 → 5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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