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.
data/.gitignore CHANGED
@@ -4,6 +4,3 @@
4
4
  Gemfile.lock
5
5
  pkg/*
6
6
  doc
7
-
8
- .DS_Store
9
- *.xcodeproj
@@ -23,9 +23,11 @@ Primitives
23
23
  - Line
24
24
  - Edge
25
25
  - Circle
26
- - Rectangle
27
- - Polygon
26
+ - Rectangle, Square
27
+ - Path, [Polyline](http://en.wikipedia.org/wiki/Polyline), [Polygon](http://en.wikipedia.org/wiki/Polygon)
28
28
  - Transformation
29
+ - [Triangle](http://en.wikipedia.org/wiki/Triangle)
30
+ - [Obround](http://en.wiktionary.org/wiki/obround)
29
31
 
30
32
  Examples
31
33
  --------
data/Rakefile CHANGED
@@ -20,11 +20,3 @@ end
20
20
  task :trim_whitespace do
21
21
  system(%Q[git status --short | awk '{if ($1 != "D" && $1 != "R") print $2}' | grep -e '.*\.rb$' | xargs sed -i '' -e 's/[ \t]*$//g;'])
22
22
  end
23
-
24
- task :docs do
25
- `yard`
26
- end
27
-
28
- task :uninstall do
29
- `gem uninstall geometry`
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 = '4'
6
+ s.version = '5'
7
7
  s.authors = ["Brandon Fosdick"]
8
8
  s.email = ["bfoz@bfoz.net"]
9
9
  s.homepage = "http://github.com/bfoz/geometry"
@@ -1,15 +1,18 @@
1
1
  require_relative 'geometry/arc'
2
2
  require_relative 'geometry/circle'
3
3
  require_relative 'geometry/line'
4
+ require_relative 'geometry/path'
4
5
  require_relative 'geometry/point'
5
6
  require_relative 'geometry/point_zero'
6
7
  require_relative 'geometry/polygon'
8
+ require_relative 'geometry/polyline'
7
9
  require_relative 'geometry/rectangle'
8
10
  require_relative 'geometry/rotation'
9
11
  require_relative 'geometry/size'
10
12
  require_relative 'geometry/size_zero'
11
13
  require_relative 'geometry/square'
12
14
  require_relative 'geometry/transformation'
15
+ require_relative 'geometry/triangle'
13
16
  require_relative 'geometry/vector'
14
17
 
15
18
  module Geometry
@@ -1,5 +1,8 @@
1
1
  require_relative 'point'
2
2
 
3
+ require_relative 'cluster_factory'
4
+ require_relative 'point'
5
+
3
6
  module Geometry
4
7
 
5
8
  =begin rdoc
@@ -11,10 +14,31 @@ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis an
11
14
  =end
12
15
 
13
16
  class Arc
17
+ include ClusterFactory
18
+
14
19
  attr_reader :center
15
20
  attr_reader :radius
16
21
  attr_reader :start_angle, :end_angle
17
22
 
23
+ # @overload new(center_point, start_point, end_point)
24
+ # Create a new {Arc} given center, start and end {Point}s
25
+ # @param [Point] center_point The {Point} at the center
26
+ # @param [Point] start_point The {Arc} starts at the start {Point}
27
+ # @param [Point] end_point The {Point} where it all ends
28
+ # @overload initialize(center, radius, start_angle, end_angle)
29
+ # Create a new {Arc} given a center, a radius and start and end angles
30
+ # @param [Point] center The {Point} at the center of it all
31
+ # @param [Numeric] radius Radius
32
+ # @param [Numeric] start_angle Starting angle
33
+ # @param [Numeric] end_angle Ending angle
34
+ def self.new(*args)
35
+ if 4 == args.size
36
+ original_new(*args)
37
+ elsif 3 == args.size
38
+ ThreePointArc.new(*args)
39
+ end
40
+ end
41
+
18
42
  # Construct a new {Arc}
19
43
  # @overload initialize(center, radius, start_angle, end_angle)
20
44
  # @param [Point] center The {Point} at the center of it all
@@ -27,5 +51,40 @@ An {Arc} with its center at [1,1] and a radius of 2 that starts at the X-axis an
27
51
  @start_angle = start_angle
28
52
  @end_angle = end_angle
29
53
  end
54
+
55
+ # @return [Point] The starting point of the {Arc}
56
+ def first
57
+ @center + @radius * Vector[Math.cos(@start_angle), Math.sin(@start_angle)]
58
+ end
59
+
60
+ # @return [Point] The end point of the {Arc}
61
+ def last
62
+ @center + @radius * Vector[Math.cos(@end_angle), Math.sin(@end_angle)]
63
+ end
64
+ end
65
+
66
+ class ThreePointArc < Arc
67
+ attr_reader :center
68
+ attr_reader :start, :end
69
+
70
+ # Contruct a new {Arc} given center, start and end {Point}s
71
+ # Always assumes that the {Arc} is counter-clockwise. Reverse the order
72
+ # of the start and end points to get an {Arc} that goes around the other way.
73
+ # @overload initialize(center_point, start_point, end_point)
74
+ # @param [Point] center_point The {Point} at the center
75
+ # @param [Point] start_point The {Arc} starts at the start {Point}
76
+ # @param [Point] end_point The {Point} where it all ends
77
+ def initialize(center_point, start_point, end_point)
78
+ @center, @start, @end = [center_point, start_point, end_point].map {|p| Point[p]}
79
+ raise ArgumentError unless [@center, @start, @end].all? {|p| p.is_a?(Point)}
80
+ end
81
+
82
+ # The starting point of the {Arc}
83
+ # @return [Point]
84
+ alias :first :start
85
+
86
+ # The end point of the {Arc}
87
+ # @return [Point]
88
+ alias :last :end
30
89
  end
31
90
  end
@@ -1,3 +1,4 @@
1
+ require_relative 'cluster_factory'
1
2
  require_relative 'point'
2
3
 
3
4
  module Geometry
@@ -6,23 +7,88 @@ module Geometry
6
7
  Circles come in all shapes and sizes, but they're usually round.
7
8
 
8
9
  == Usage
9
- circle = Geometry::Circle.new [1,1], 2
10
+ circle = Geometry::Circle.new [1,2], 3
11
+ circle = Geometry::Circle.new [1,2], :radius => 3
12
+ circle = Geometry::Circle.new [1,2], :diameter => 6
13
+ circle = Geometry::Circle.new :center => [1,2], :diameter => 6
10
14
  =end
11
15
 
12
16
  class Circle
13
- # @return [Point] The Circle's center point
17
+ include ClusterFactory
18
+
19
+ # @return [Point] The {Circle}'s center point
14
20
  attr_reader :center
15
21
 
16
- # @return [Number] The Circle's radius
22
+ # @return [Number] The {Circle}'s radius
17
23
  attr_reader :radius
18
24
 
25
+ # @overload new(circle, radius)
26
+ # Construct a {Circle} using a centerpoint and radius
27
+ # @param [Point] center The center point of the {Circle}
28
+ # @param [Number] radius The radius of the {Circle}
29
+ # @overload new(options)
30
+ # Construct a circle using named center and radius parameters
31
+ # @option options [Point] :center
32
+ # @option options [Number] :radius
33
+ # @overload new(options)
34
+ # Construct a circle using named center and diameter parameters
35
+ # @option options [Point] :center
36
+ # @option options [Number] :diameter
37
+ def self.new(*args, &block)
38
+ options, args = args.partition {|a| a.is_a? Hash}
39
+ options = options.reduce({}, :merge)
40
+ center, radius = args[0..1]
41
+
42
+ center = Point[center || options[:center]]
43
+ raise ArgumentError, "Circle.new requires a center" unless center
44
+
45
+ radius ||= options[:radius]
46
+
47
+ if radius
48
+ self.allocate.tap {|circle| circle.send :initialize, center, radius, &block }
49
+ elsif options.has_key?(:diameter)
50
+ CenterDiameterCircle.new center, options[:diameter], &block
51
+ else
52
+ raise ArgumentError, "Circle.new requires a radius or a diameter"
53
+ end
54
+ end
55
+
19
56
  # Construct a new {Circle} from a centerpoint and radius
20
- # @param [Point] center The center point of the Circle
21
- # @param [Number] radius The radius of the Circle
22
- # @return [Circle] A new Circle object
57
+ # @param [Point] center The center point of the {Circle}
58
+ # @param [Number] radius The radius of the {Circle}
59
+ # @return [Circle] A new {Circle} object
23
60
  def initialize(center, radius)
24
61
  @center = Point[center]
25
62
  @radius = radius
26
63
  end
64
+
65
+ # @!group Accessors
66
+ # @!attribute [r] diameter
67
+ # @return [Numeric] The diameter of the {Circle}
68
+ def diameter
69
+ @radius*2
70
+ end
71
+ # @!endgroup
72
+ end
73
+
74
+ class CenterDiameterCircle < Circle
75
+ # @return [Number] The {Circle}'s diameter
76
+ attr_reader :diameter
77
+
78
+ # Construct a new {Circle} from a centerpoint and a diameter
79
+ # @param [Point] center The center point of the {Circle}
80
+ # @param [Number] diameter The radius of the {Circle}
81
+ # @return [Circle] A new {Circle} object
82
+ def initialize(center, diameter)
83
+ @center = Point[center]
84
+ @diameter = diameter
85
+ end
86
+
87
+ # @!group Accessors
88
+ # @return The {Circle}'s radius
89
+ def radius
90
+ @diameter/2
91
+ end
92
+ # @!endgroup
27
93
  end
28
94
  end
@@ -0,0 +1,15 @@
1
+ # Include this module in the base class of a class cluster to handle swizzling
2
+ # of ::new
3
+ module ClusterFactory
4
+ def self.included(parent)
5
+ class << parent
6
+ alias :original_new :new
7
+
8
+ def inherited(subclass)
9
+ class << subclass
10
+ alias :new :original_new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -50,6 +50,67 @@ An edge. It's a line segment between 2 points. Generally part of a {Polygon}.
50
50
  end
51
51
  alias :to_s :inspect
52
52
 
53
+ # @return [Bool] Returns true if the passed {Edge} is parallel to the receiver
54
+ def parallel?(edge)
55
+ v1, v2 = self.direction, edge.direction
56
+ winding = v1[0]*v2[1] - v1[1]*v2[0]
57
+ if 0 == winding # collinear?
58
+ if v1 == v2
59
+ 1 # same direction
60
+ else
61
+ -1 # opposite direction
62
+ end
63
+ else
64
+ false
65
+ end
66
+ end
67
+
68
+ # @param [Edge] other The other {Edge} to check
69
+ # @return [Bool] Returns true if the receiver and the passed {Edge} share an endpoint
70
+ def connected?(other)
71
+ (@first == other.last) || (@last == other.first) || (@first == other.first) || (@last == other.last)
72
+ end
73
+
74
+ # @return [Vector] A unit {Vector} pointing from first to last
75
+ def direction
76
+ self.vector.normalize
77
+ end
78
+
79
+ # Find the intersection of two {Edge}s (http://bloggingmath.wordpress.com/2009/05/29/line-segment-intersection/)
80
+ # @return [Point] The intersection of the two {Edge}s, nil if they don't intersect, true if they're collinear and overlapping, and false if they're collinear and non-overlapping
81
+ def intersection(other)
82
+ p0, p1 = self.first, self.last
83
+ p2, p3 = other.first, other.last
84
+ v1, v2 = self.vector, other.vector
85
+
86
+ denominator = v1[0] * v2[1] - v2[0] * v1[1] # v1 x v2
87
+ p = p0 - p2
88
+ if denominator == 0 # collinear, so check for overlap
89
+ if 0 == (-v1[1] * p.x + v1[0] * p.y) # collinear?
90
+ # The edges are collinear, but do they overlap?
91
+ # Project them onto the x and y axes to find out
92
+ left1, right1 = [self.first[0], self.last[0]].sort
93
+ bottom1, top1 = [self.first[1], self.last[1]].sort
94
+ left2, right2 = [other.first[0], other.last[0]].sort
95
+ bottom2, top2 = [other.first[1], other.last[1]].sort
96
+
97
+ !((left2 > right1) || (right2 < left1) || (top2 < bottom1) || (bottom2 > top1))
98
+ else
99
+ nil
100
+ end
101
+ else
102
+ s = (-v1[1] * p.x + v1[0] * p.y) / denominator # v1 x (p0 - p2) / denominator
103
+ t = ( v2[0] * p.y - v2[1] * p.x) / denominator # v2 x (p0 - p2) / denominator
104
+
105
+ p0 + v1 * t if ((0..1) === s) && ((0..1) === t)
106
+ end
107
+ end
108
+
109
+ # @return [Vector] A {Vector} pointing from first to last
110
+ def vector
111
+ Vector[*((last-first).to_a)]
112
+ end
113
+
53
114
  def to_a
54
115
  [@first, @last]
55
116
  end
@@ -0,0 +1,55 @@
1
+ require 'geometry/arc'
2
+ require 'geometry/edge'
3
+
4
+ module Geometry
5
+ =begin
6
+ An object representing a set of connected elements, each of which could be an
7
+ {Edge} or an {Arc}. Unlike a {Polygon}, a {Path} is not guaranteed to be closed.
8
+ =end
9
+ class Path
10
+ attr_reader :elements
11
+
12
+ # Construct a new Path from {Point}s, {Edge}s, and {Arc}s
13
+ # Successive {Point}s will be converted to {Edge}s.
14
+ def initialize(*args)
15
+ args.map! {|a| (a.is_a?(Array) or a.is_a?(Vector)) ? Point[a] : a}
16
+ args.each {|a| raise ArgumentError, "Unknown argument type #{a.class}" unless a.is_a?(Point) or a.is_a?(Edge) or a.is_a?(Arc) }
17
+
18
+ @elements = []
19
+
20
+ first = args.shift
21
+ @elements.push first if first.is_a?(Edge) or first.is_a?(Arc)
22
+
23
+ args.reduce(first) do |previous, n|
24
+ case n
25
+ when Point
26
+ case previous
27
+ when Point then @elements.push Edge.new(previous, n)
28
+ when Arc, Edge then @elements.push Edge.new(previous.last, n) unless previous.last == n
29
+ end
30
+ @elements.last
31
+ when Edge
32
+ case previous
33
+ when Point then @elements.push Edge.new(previous, n.first)
34
+ when Arc, Edge then @elements.push Edge.new(previous.last, n.first) unless previous.last == n.first
35
+ end
36
+ @elements.push(n).last
37
+ when Arc
38
+ case previous
39
+ when Point
40
+ if previous == n.first
41
+ raise ArgumentError, "Duplicated point before an Arc"
42
+ else
43
+ @elements.push Edge.new(previous, n.first)
44
+ end
45
+ when Arc, Edge
46
+ @elements.push Edge.new(previous.last, n.first) unless previous.last == n.first
47
+ end
48
+ @elements.push(n).last
49
+ else
50
+ raise ArgumentError, "Unsupported argument type: #{n}"
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -40,10 +40,21 @@ geometry class (x, y, z).
40
40
 
41
41
  # Allow comparison with an Array, otherwise do the normal thing
42
42
  def eql?(other)
43
+ return @elements.eql? other if other.is_a?(Array)
44
+ super other
45
+ end
46
+
47
+ # Allow comparison with an Array, otherwise do the normal thing
48
+ def ==(other)
43
49
  return @elements == other if other.is_a?(Array)
44
50
  super other
45
51
  end
46
- alias == eql?
52
+
53
+ # Combined comparison operator
54
+ # @return [Point] The <=> operator is applied to the elements of the arguments pairwise and the results are returned in a Point
55
+ def <=>(other)
56
+ Point[self.to_a.zip(other.to_a).map {|a,b| a <=> b}.compact]
57
+ end
47
58
 
48
59
  def coerce(other)
49
60
  case other
@@ -61,35 +72,35 @@ geometry class (x, y, z).
61
72
  'Point' + @elements.to_s
62
73
  end
63
74
 
64
- # !@group Accessors
75
+ # @group Accessors
65
76
  # @param [Integer] i Index into the {Point}'s elements
66
77
  # @return [Numeric] Element i (starting at 0)
67
78
  def [](i)
68
79
  @elements[i]
69
80
  end
70
81
 
71
- # !@attribute [r] x
82
+ # @attribute [r] x
72
83
  # @return [Numeric] X-component
73
84
  def x
74
85
  @elements[0]
75
86
  end
76
87
 
77
- # !@attribute [r] y
88
+ # @attribute [r] y
78
89
  # @return [Numeric] Y-component
79
90
  def y
80
91
  @elements[1]
81
92
  end
82
93
 
83
- # !@attribute [r] z
94
+ # @attribute [r] z
84
95
  # @return [Numeric] Z-component
85
96
  def z
86
97
  @elements[2]
87
98
  end
88
- # !@endgroup
99
+ # @endgroup
89
100
 
90
- # !@group Arithmetic
101
+ # @group Arithmetic
91
102
 
92
- # !@group Unary operators
103
+ # @group Unary operators
93
104
  def +@
94
105
  self
95
106
  end
@@ -97,7 +108,7 @@ geometry class (x, y, z).
97
108
  def -@
98
109
  Point[@elements.map {|e| -e }]
99
110
  end
100
- # !@endgroup
111
+ # @endgroup
101
112
 
102
113
  def +(other)
103
114
  case other
@@ -127,7 +138,7 @@ geometry class (x, y, z).
127
138
  end
128
139
  end
129
140
 
130
- # !@endgroup
141
+ # @endgroup
131
142
 
132
143
  end
133
144
  end