easy_geometry 0.1.0
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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +26 -0
- data/README +2 -0
- data/easy_geometry.gemspec +17 -0
- data/lib/.ruby-version +1 -0
- data/lib/easy_geometry.rb +12 -0
- data/lib/easy_geometry/d2/line.rb +73 -0
- data/lib/easy_geometry/d2/linear_entity.rb +355 -0
- data/lib/easy_geometry/d2/point.rb +193 -0
- data/lib/easy_geometry/d2/polygon.rb +638 -0
- data/lib/easy_geometry/d2/ray.rb +111 -0
- data/lib/easy_geometry/d2/segment.rb +119 -0
- data/lib/easy_geometry/d2/triangle.rb +375 -0
- data/lib/easy_geometry/d2/vector.rb +71 -0
- data/spec/d2/line_spec.rb +339 -0
- data/spec/d2/point_spec.rb +211 -0
- data/spec/d2/polygon_spec.rb +291 -0
- data/spec/d2/ray_spec.rb +439 -0
- data/spec/d2/segment_spec.rb +453 -0
- data/spec/d2/triangle_spec.rb +224 -0
- data/spec/d2/vector_spec.rb +91 -0
- data/spec/spec_helper.rb +101 -0
- metadata +88 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
module D2
|
|
3
|
+
# A Ray is a semi-line in the space with a source point and a direction.
|
|
4
|
+
class Ray < LinearEntity
|
|
5
|
+
|
|
6
|
+
# Is other GeometryEntity contained in this Ray?
|
|
7
|
+
#
|
|
8
|
+
# Parameters:
|
|
9
|
+
# GeometryEntity
|
|
10
|
+
#
|
|
11
|
+
# Returns:
|
|
12
|
+
# true if `other` is on this Line.
|
|
13
|
+
# false otherwise.
|
|
14
|
+
#
|
|
15
|
+
def contains?(other)
|
|
16
|
+
other = Point.new(other[0], other[1]) if other.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
if other.is_a?(Point)
|
|
19
|
+
if Point.is_collinear?(other, self.p1, self.p2)
|
|
20
|
+
# if we're in the direction of the ray, our
|
|
21
|
+
# direction vector dot the ray's direction vector
|
|
22
|
+
# should be non-negative
|
|
23
|
+
return (self.p2 - self.p1).dot(other - self.p1) >= 0
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if other.is_a?(Ray)
|
|
28
|
+
if Point.is_collinear?(self.p1, self.p2, other.p1, other.p2)
|
|
29
|
+
return (self.p2 - self.p1).dot(other.p2 - other.p1) > 0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if other.is_a?(Segment)
|
|
34
|
+
return true if self.contains?(other.p1) && self.contains?(other.p2)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
return false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Finds the shortest distance between the ray and a point.
|
|
41
|
+
#
|
|
42
|
+
# Raises
|
|
43
|
+
# ======
|
|
44
|
+
# TypeError is raised if `other` is not a Point
|
|
45
|
+
def distance(other)
|
|
46
|
+
raise TypeError, "Distance between Ray and #{ other.class } is not defined" unless other.is_a?(Point)
|
|
47
|
+
|
|
48
|
+
return 0 if self.contains?(other)
|
|
49
|
+
|
|
50
|
+
proj = Line.new(self.p1, self.p2).projection_point(other)
|
|
51
|
+
if self.contains?(proj)
|
|
52
|
+
return (other - proj).abs
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
(other - self.source).abs
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns True if self and other are the same mathematical entities
|
|
59
|
+
def ==(other)
|
|
60
|
+
return false unless other.is_a?(Ray)
|
|
61
|
+
self.source == other.source && self.contains?(other.p2)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The point from which the ray emanates.
|
|
65
|
+
def source
|
|
66
|
+
self.p1
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The x direction of the ray.
|
|
70
|
+
#
|
|
71
|
+
# Returns:
|
|
72
|
+
# Positive infinity if the ray points in the positive x direction,
|
|
73
|
+
# negative infinity if the ray points in the negative x direction,
|
|
74
|
+
# or 0 if the ray is vertical.
|
|
75
|
+
def xdirection
|
|
76
|
+
return @xdirection if defined?(@xdirection)
|
|
77
|
+
|
|
78
|
+
if self.p1.x < self.p2.x
|
|
79
|
+
@xdirection = BigDecimal('Infinity')
|
|
80
|
+
elsif self.p1.x == self.p2.x
|
|
81
|
+
@xdirection = 0
|
|
82
|
+
else
|
|
83
|
+
@xdirection = -BigDecimal('Infinity')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@xdirection
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The y direction of the ray.
|
|
90
|
+
#
|
|
91
|
+
# Returns:
|
|
92
|
+
# Positive infinity if the ray points in the positive y direction,
|
|
93
|
+
# negative infinity if the ray points in the negative y direction,
|
|
94
|
+
# or 0 if the ray is vertical.
|
|
95
|
+
def ydirection
|
|
96
|
+
return @ydirection if defined?(@ydirection)
|
|
97
|
+
|
|
98
|
+
if self.p1.y < self.p2.y
|
|
99
|
+
@ydirection = BigDecimal('Infinity')
|
|
100
|
+
elsif self.p1.y == self.p2.y
|
|
101
|
+
@ydirection = 0
|
|
102
|
+
else
|
|
103
|
+
@ydirection = -BigDecimal('Infinity')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@ydirection
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
module D2
|
|
3
|
+
# A segment in a 2-dimensional Euclidean space.
|
|
4
|
+
class Segment < LinearEntity
|
|
5
|
+
|
|
6
|
+
# Is the other GeometryEntity contained within this Segment?
|
|
7
|
+
#
|
|
8
|
+
# Parameters:
|
|
9
|
+
# GeometryEntity
|
|
10
|
+
#
|
|
11
|
+
# Returns:
|
|
12
|
+
# true if `other` is in this Segment.
|
|
13
|
+
# false otherwise.
|
|
14
|
+
#
|
|
15
|
+
def contains?(other)
|
|
16
|
+
other = Point.new(other[0], other[1]) if other.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
if other.is_a?(Point)
|
|
19
|
+
if Point.is_collinear?(other, self.p1, self.p2)
|
|
20
|
+
# if it is collinear and is in the bounding box of the
|
|
21
|
+
# segment then it must be on the segment
|
|
22
|
+
vert = (1/self.slope).zero?
|
|
23
|
+
|
|
24
|
+
if vert
|
|
25
|
+
return (self.p1.y - other.y) * (self.p2.y - other.y) <= 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
return (self.p1.x - other.x) * (self.p2.x - other.x) <= 0
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
if other.is_a?(Segment)
|
|
33
|
+
return self.contains?(other.p1) && self.contains?(other.p2)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
return false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns True if self and other are the same mathematical entities
|
|
40
|
+
def ==(other)
|
|
41
|
+
return false unless other.is_a?(Segment)
|
|
42
|
+
[p1, p2].sort_by {|p| [p.x, p.y]} == [other.p1, other.p2].sort_by {|p| [p.x, p.y]}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def <=>(other)
|
|
46
|
+
return self.p2 <=> other.p2 if self.p1 == other.p1
|
|
47
|
+
self.p1 <=> other.p1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Finds the shortest distance between a line segment and a point.
|
|
51
|
+
#
|
|
52
|
+
# Parameters:
|
|
53
|
+
# Point
|
|
54
|
+
#
|
|
55
|
+
# Returns:
|
|
56
|
+
# Number
|
|
57
|
+
#
|
|
58
|
+
# Raises
|
|
59
|
+
# ======
|
|
60
|
+
# TypeError is raised if `other` is not a Point
|
|
61
|
+
def distance(other)
|
|
62
|
+
other = Point.new(other[0], other[1]) if other.is_a?(Array)
|
|
63
|
+
raise TypeError, "Distance between Segment and #{ other.class } is not defined" unless other.is_a?(Point)
|
|
64
|
+
|
|
65
|
+
vp1 = other - self.p1
|
|
66
|
+
vp2 = other - self.p2
|
|
67
|
+
|
|
68
|
+
dot_prod_sign_1 = self.direction.to_point.dot(vp1) >= 0
|
|
69
|
+
dot_prod_sign_2 = self.direction.to_point.dot(vp2) <= 0
|
|
70
|
+
|
|
71
|
+
if dot_prod_sign_1 && dot_prod_sign_2
|
|
72
|
+
return Line.new(self.p1, self.p2).distance(other)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if dot_prod_sign_1 && !dot_prod_sign_2
|
|
76
|
+
return vp2.abs
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if !dot_prod_sign_1 && dot_prod_sign_2
|
|
80
|
+
return vp1.abs
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The length of the line segment.
|
|
85
|
+
def length
|
|
86
|
+
@length ||= p1.distance(p2)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The midpoint of the line segment.
|
|
90
|
+
def midpoint
|
|
91
|
+
@midpoint ||= p1.midpoint(p2)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# The perpendicular bisector of this segment.
|
|
95
|
+
# If no point is specified or the point specified is not on the
|
|
96
|
+
# bisector then the bisector is returned as a Line.
|
|
97
|
+
# Otherwise a Segment is returned that joins the point specified and the
|
|
98
|
+
# intersection of the bisector and the segment.
|
|
99
|
+
#
|
|
100
|
+
# Parameters:
|
|
101
|
+
# Point
|
|
102
|
+
#
|
|
103
|
+
# Returns:
|
|
104
|
+
# Line or Segment
|
|
105
|
+
#
|
|
106
|
+
def perpendicular_bisector(point=nil)
|
|
107
|
+
l = self.perpendicular_line(self.midpoint)
|
|
108
|
+
|
|
109
|
+
if !point.nil?
|
|
110
|
+
point = Point.new(point[0], point[1]) if point.is_a?(Array)
|
|
111
|
+
raise TypeError, "This method is not defined for #{ point.class }" unless point.is_a?(Point)
|
|
112
|
+
return Segment.new(point, self.midpoint) if l.contains?(point)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
return l
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
module EasyGeometry
|
|
2
|
+
module D2
|
|
3
|
+
# A polygon with three vertices and three sides.
|
|
4
|
+
class Triangle < Polygon
|
|
5
|
+
attr_reader :vertices
|
|
6
|
+
|
|
7
|
+
EQUITY_TOLERANCE = 0.0000000000001
|
|
8
|
+
|
|
9
|
+
def initialize(*args)
|
|
10
|
+
@vertices = preprocessing_args(args)
|
|
11
|
+
remove_consecutive_duplicates
|
|
12
|
+
remove_collinear_points
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Is another triangle similar to this one.
|
|
16
|
+
#
|
|
17
|
+
# Parameters:
|
|
18
|
+
# Triangle
|
|
19
|
+
#
|
|
20
|
+
# Returns:
|
|
21
|
+
# bool
|
|
22
|
+
#
|
|
23
|
+
def is_similar?(other)
|
|
24
|
+
return false unless other.is_a?(Triangle)
|
|
25
|
+
|
|
26
|
+
s1_1, s1_2, s1_3 = self.sides.map {|side| side.length}
|
|
27
|
+
s2 = other.sides.map {|side| side.length}
|
|
28
|
+
|
|
29
|
+
are_similar?(s1_1, s1_2, s1_3, *s2) ||
|
|
30
|
+
are_similar?(s1_1, s1_3, s1_2, *s2) ||
|
|
31
|
+
are_similar?(s1_2, s1_1, s1_3, *s2) ||
|
|
32
|
+
are_similar?(s1_2, s1_3, s1_1, *s2) ||
|
|
33
|
+
are_similar?(s1_3, s1_1, s1_2, *s2) ||
|
|
34
|
+
are_similar?(s1_3, s1_2, s1_1, *s2)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Are all the sides the same length?
|
|
38
|
+
# Precision - 10e-13
|
|
39
|
+
#
|
|
40
|
+
# Returns:
|
|
41
|
+
# bool
|
|
42
|
+
#
|
|
43
|
+
def is_equilateral?
|
|
44
|
+
lengths = self.sides.map { |side| side.length }
|
|
45
|
+
lengths = lengths.map { |l| l - lengths.first }
|
|
46
|
+
return lengths.reject { |l| l.abs < EQUITY_TOLERANCE }.length == 0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Are two or more of the sides the same length?
|
|
50
|
+
#
|
|
51
|
+
# Returns:
|
|
52
|
+
# bool
|
|
53
|
+
#
|
|
54
|
+
def is_isosceles?
|
|
55
|
+
has_dups(self.sides.map { |side| side.length })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Are all the sides of the triangle of different lengths?
|
|
59
|
+
#
|
|
60
|
+
# Returns:
|
|
61
|
+
# bool
|
|
62
|
+
#
|
|
63
|
+
def is_scalene?
|
|
64
|
+
!has_dups(self.sides.map { |side| side.length })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Is the triangle right-angled.
|
|
68
|
+
#
|
|
69
|
+
# Returns:
|
|
70
|
+
# bool
|
|
71
|
+
#
|
|
72
|
+
def is_right?
|
|
73
|
+
s = self.sides
|
|
74
|
+
|
|
75
|
+
s[0].perpendicular_to?(s[1]) ||
|
|
76
|
+
s[1].perpendicular_to?(s[2]) ||
|
|
77
|
+
s[0].perpendicular_to?(s[2])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The altitudes of the triangle.
|
|
81
|
+
#
|
|
82
|
+
# An altitude of a triangle is a segment through a vertex,
|
|
83
|
+
# perpendicular to the opposite side, with length being the
|
|
84
|
+
# height of the vertex measured from the line containing the side.
|
|
85
|
+
#
|
|
86
|
+
# Returns:
|
|
87
|
+
# Hash (The hash consists of keys which are vertices and values
|
|
88
|
+
# which are Segments.)
|
|
89
|
+
#
|
|
90
|
+
def altitudes
|
|
91
|
+
return @altitudes if defined?(@altitudes)
|
|
92
|
+
|
|
93
|
+
@altitudes = {
|
|
94
|
+
self.vertices[0] => self.sides[1].perpendicular_segment(self.vertices[0]),
|
|
95
|
+
self.vertices[1] => self.sides[2].perpendicular_segment(self.vertices[1]),
|
|
96
|
+
self.vertices[2] => self.sides[0].perpendicular_segment(self.vertices[2])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@altitudes
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The orthocenter of the triangle.
|
|
103
|
+
#
|
|
104
|
+
# The orthocenter is the intersection of the altitudes of a triangle.
|
|
105
|
+
# It may lie inside, outside or on the triangle.
|
|
106
|
+
#
|
|
107
|
+
# Returns:
|
|
108
|
+
# Point
|
|
109
|
+
#
|
|
110
|
+
def orthocenter
|
|
111
|
+
return @orthocenter if defined?(@orthocenter)
|
|
112
|
+
|
|
113
|
+
a = self.altitudes
|
|
114
|
+
a1 = a[self.vertices[0]]; a2 = a[self.vertices[1]]
|
|
115
|
+
|
|
116
|
+
l1 = Line.new(a1.p1, a1.p2)
|
|
117
|
+
l2 = Line.new(a2.p1, a2.p2)
|
|
118
|
+
|
|
119
|
+
@orthocenter = l1.intersection(l2)[0]
|
|
120
|
+
@orthocenter
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The circumcenter of the triangle
|
|
124
|
+
#
|
|
125
|
+
# The circumcenter is the center of the circumcircle.
|
|
126
|
+
#
|
|
127
|
+
# Returns:
|
|
128
|
+
# Point or nil
|
|
129
|
+
#
|
|
130
|
+
def circumcenter
|
|
131
|
+
return @circumcenter if defined?(@circumcenter)
|
|
132
|
+
|
|
133
|
+
a, b, c = self.sides.map { |side| side.perpendicular_bisector }
|
|
134
|
+
|
|
135
|
+
@circumcenter = a.intersection(b)[0]
|
|
136
|
+
@circumcenter
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# The radius of the circumcircle of the triangle.
|
|
140
|
+
#
|
|
141
|
+
# Returns:
|
|
142
|
+
# int
|
|
143
|
+
#
|
|
144
|
+
def circumradius
|
|
145
|
+
@circumradius ||= self.vertices[0].distance(self.circumcenter)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The circle which passes through the three vertices of the triangle.
|
|
149
|
+
#
|
|
150
|
+
# Returns:
|
|
151
|
+
# Circle
|
|
152
|
+
#
|
|
153
|
+
def circumcircle
|
|
154
|
+
# Circle.new(self.circumcenter, self.circumradius)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The angle bisectors of the triangle.
|
|
158
|
+
#
|
|
159
|
+
# An angle bisector of a triangle is a straight line through a vertex
|
|
160
|
+
# which cuts the corresponding angle in half.
|
|
161
|
+
#
|
|
162
|
+
# Returns:
|
|
163
|
+
# Hash (each key is a vertex (Point) and each value is the corresponding
|
|
164
|
+
# bisector (Segment).)
|
|
165
|
+
#
|
|
166
|
+
def bisectors
|
|
167
|
+
s = self.sides.map { |side| Line.new(side.p1, side.p2) }
|
|
168
|
+
c = self.incenter
|
|
169
|
+
|
|
170
|
+
inter1 = Line.new(self.vertices[0], c).intersection(s[1]).first
|
|
171
|
+
inter2 = Line.new(self.vertices[1], c).intersection(s[2]).first
|
|
172
|
+
inter3 = Line.new(self.vertices[2], c).intersection(s[0]).first
|
|
173
|
+
|
|
174
|
+
{
|
|
175
|
+
self.vertices[0] => Segment.new(self.vertices[0], inter1),
|
|
176
|
+
self.vertices[1] => Segment.new(self.vertices[1], inter2),
|
|
177
|
+
self.vertices[2] => Segment.new(self.vertices[2], inter3),
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# The center of the incircle.
|
|
182
|
+
#
|
|
183
|
+
# The incircle is the circle which lies inside the triangle and touches
|
|
184
|
+
# all three sides.
|
|
185
|
+
#
|
|
186
|
+
# Returns:
|
|
187
|
+
# Point
|
|
188
|
+
#
|
|
189
|
+
def incenter
|
|
190
|
+
return @incenter if defined?(@incenter)
|
|
191
|
+
|
|
192
|
+
s = self.sides
|
|
193
|
+
l = [1, 2, 0].map { |i| s[i].length }
|
|
194
|
+
p = l.sum
|
|
195
|
+
|
|
196
|
+
x_arr = self.vertices.map { |v| v.x / p }
|
|
197
|
+
y_arr = self.vertices.map { |v| v.y / p }
|
|
198
|
+
|
|
199
|
+
x = l[0] * x_arr[0] + l[1] * x_arr[1] + l[2] * x_arr[2]
|
|
200
|
+
y = l[0] * y_arr[0] + l[1] * y_arr[1] + l[2] * y_arr[2]
|
|
201
|
+
|
|
202
|
+
@incenter = Point.new(x, y)
|
|
203
|
+
@incenter
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The radius of the incircle.
|
|
207
|
+
#
|
|
208
|
+
# Returns:
|
|
209
|
+
# int
|
|
210
|
+
#
|
|
211
|
+
def inradius
|
|
212
|
+
@inradius ||= 2 * self.area / self.perimeter
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The incircle of the triangle.
|
|
216
|
+
#
|
|
217
|
+
# The incircle is the circle which lies inside the triangle and touches
|
|
218
|
+
# all three sides.
|
|
219
|
+
#
|
|
220
|
+
# Returns:
|
|
221
|
+
# Circle
|
|
222
|
+
#
|
|
223
|
+
def incircle
|
|
224
|
+
# Circle.new(self.incenter, self.inradius)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# The radius of excircles of a triangle.
|
|
228
|
+
#
|
|
229
|
+
# An excircle of the triangle is a circle lying outside the triangle,
|
|
230
|
+
# tangent to one of its sides and tangent to the extensions of the
|
|
231
|
+
# other two.
|
|
232
|
+
#
|
|
233
|
+
# Returns:
|
|
234
|
+
# Hash
|
|
235
|
+
#
|
|
236
|
+
def exradii
|
|
237
|
+
return @exradii if defined?(@exradii)
|
|
238
|
+
a = self.sides[0].length
|
|
239
|
+
b = self.sides[1].length
|
|
240
|
+
c = self.sides[2].length
|
|
241
|
+
s = (a + b + c) / 2
|
|
242
|
+
area = self.area
|
|
243
|
+
|
|
244
|
+
@exradii = {
|
|
245
|
+
self.sides[0] => area / (s - a),
|
|
246
|
+
self.sides[1] => area / (s - b),
|
|
247
|
+
self.sides[2] => area / (s - c)
|
|
248
|
+
}
|
|
249
|
+
@exradii
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# The medians of the triangle.
|
|
253
|
+
#
|
|
254
|
+
# A median of a triangle is a straight line through a vertex and the
|
|
255
|
+
# midpoint of the opposite side, and divides the triangle into two
|
|
256
|
+
# equal areas.
|
|
257
|
+
#
|
|
258
|
+
# Returns:
|
|
259
|
+
# Hash (each key is a vertex (Point) and each value is the median (Segment)
|
|
260
|
+
# at that point.)
|
|
261
|
+
#
|
|
262
|
+
def medians
|
|
263
|
+
@medians ||= {
|
|
264
|
+
self.vertices[0] => Segment.new(self.vertices[0], self.sides[1].midpoint),
|
|
265
|
+
self.vertices[1] => Segment.new(self.vertices[1], self.sides[2].midpoint),
|
|
266
|
+
self.vertices[2] => Segment.new(self.vertices[2], self.sides[0].midpoint)
|
|
267
|
+
}
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# The medial triangle of the triangle.
|
|
271
|
+
# The triangle which is formed from the midpoints of the three sides.
|
|
272
|
+
#
|
|
273
|
+
# Returns:
|
|
274
|
+
# Triangle
|
|
275
|
+
#
|
|
276
|
+
def medial
|
|
277
|
+
@medial ||= Triangle.new(
|
|
278
|
+
self.sides[0].midpoint,
|
|
279
|
+
self.sides[1].midpoint,
|
|
280
|
+
self.sides[2].midpoint
|
|
281
|
+
)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# The nine-point circle of the triangle.
|
|
285
|
+
#
|
|
286
|
+
# Nine-point circle is the circumcircle of the medial triangle, which
|
|
287
|
+
# passes through the feet of altitudes and the middle points of segments
|
|
288
|
+
# connecting the vertices and the orthocenter.
|
|
289
|
+
#
|
|
290
|
+
# Returns:
|
|
291
|
+
# Circle
|
|
292
|
+
#
|
|
293
|
+
def nine_point_circle
|
|
294
|
+
# Circle.new(*self.medial.vertices)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# The Euler line of the triangle.
|
|
298
|
+
# The line which passes through circumcenter, centroid and orthocenter.
|
|
299
|
+
#
|
|
300
|
+
# Returns:
|
|
301
|
+
# Line (or Point for equilateral triangles in which case all
|
|
302
|
+
# centers coincide)
|
|
303
|
+
#
|
|
304
|
+
def eulerline
|
|
305
|
+
return self.orthocenter if self.is_equilateral?
|
|
306
|
+
Line.new(self.orthocenter, self.circumcenter)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
private
|
|
310
|
+
|
|
311
|
+
def has_dups(arr)
|
|
312
|
+
(0...arr.length).each do |i|
|
|
313
|
+
return true if (arr[i] - arr[i - 1]).abs < EQUITY_TOLERANCE
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
return false
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def are_similar?(u1, u2, u3, v1, v2, v3)
|
|
320
|
+
e1 = u1 / v1
|
|
321
|
+
e2 = u2 / v2
|
|
322
|
+
e3 = u3 / v3
|
|
323
|
+
|
|
324
|
+
e1 == e2 && e2 == e3
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# preprocessing_args - convert coordinates to points if necessary.
|
|
328
|
+
def preprocessing_args(args)
|
|
329
|
+
args.map do |v|
|
|
330
|
+
if v.is_a?(Array) && v.length == 2
|
|
331
|
+
Point.new(*v)
|
|
332
|
+
elsif v.is_a?(Point)
|
|
333
|
+
v
|
|
334
|
+
else
|
|
335
|
+
raise TypeError, "Arguments should be arrays with coordinates or Points."
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def remove_consecutive_duplicates
|
|
341
|
+
nodup = []
|
|
342
|
+
@vertices.each do |p|
|
|
343
|
+
next if !nodup.empty? && p == nodup[-1]
|
|
344
|
+
nodup << p
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
if nodup.length > 1 && nodup[-1] == nodup[0]
|
|
348
|
+
nodup.pop # last point was same as first
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
@vertices = nodup
|
|
352
|
+
validate
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def remove_collinear_points
|
|
356
|
+
i = 0
|
|
357
|
+
while i < vertices.length
|
|
358
|
+
a, b, c = vertices[i], vertices[i - 1], vertices[i - 2]
|
|
359
|
+
if Point.is_collinear?(a, b, c)
|
|
360
|
+
vertices.delete_at(i - 1)
|
|
361
|
+
vertices.delete_at(i - 2) if a == c
|
|
362
|
+
else
|
|
363
|
+
i += 1
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
validate
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def validate
|
|
371
|
+
raise ArgumentError, 'Triangle instantiates with three points' if vertices.length != 3
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|