Class: EasyGeometry::D2::LinearEntity

Inherits:
Object
  • Object
show all
Defined in:
lib/easy_geometry/d2/linear_entity.rb

Overview

A base class for all linear entities (Line, Ray and Segment) in 2-dimensional Euclidean space.

Direct Known Subclasses

Line, Ray, Segment

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(point1, point2) ⇒ LinearEntity

Examples: LinearEntity.new(Point.new(0, 0), Point.new(1, 2)) LinearEntity.new([0, 0], [1, 2])



11
12
13
14
15
16
# File 'lib/easy_geometry/d2/linear_entity.rb', line 11

def initialize(point1, point2)
  @p1 = point1; @p2 = point2
  
  check_input_points!
  validate!
end

Instance Attribute Details

#p1Object (readonly)

Returns the value of attribute p1.



6
7
8
# File 'lib/easy_geometry/d2/linear_entity.rb', line 6

def p1
  @p1
end

#p2Object (readonly)

Returns the value of attribute p2.



6
7
8
# File 'lib/easy_geometry/d2/linear_entity.rb', line 6

def p2
  @p2
end

Instance Method Details

#angle_between(other) ⇒ Object

Return the non-reflex angle formed by rays emanating from the origin with directions the same as the direction vectors of the linear entities.

From the dot product of vectors v1 and v2 it is known that:

“dot(v1, v2) = |v1|*|v2|*cos(A)“

where A is the angle formed between the two vectors. We can get the directional vectors of the two lines and readily find the angle between the two using the above formula.

Parameters:

LinearEntity

Returns:

angle in radians

Raises:

  • (TypeError)


46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/easy_geometry/d2/linear_entity.rb', line 46

def angle_between(other)
  raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
  
  v1 = self.direction
  v2 = other.direction

  # Convert numerator to BigDecimal for more precision.
  numerator   = BigDecimal(v1.dot(v2).to_f.to_s)
  denominator = v1.to_point.abs * v2.to_point.abs

  return Math.acos(numerator / denominator)
end

#directionObject

The direction vector of the LinearEntity. Returns:

Vector; the ray from the origin to this point is the

direction of ‘self`.



23
24
25
# File 'lib/easy_geometry/d2/linear_entity.rb', line 23

def direction
  @direction ||= Vector.new(p2.x - p1.x, p2.y - p1.y)
end

#intersection(other) ⇒ Object

The intersection with another geometrical entity

Parameters:

Point or LinearEntity

Returns:

Array of geometrical entities

Raises:

  • (TypeError)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/easy_geometry/d2/linear_entity.rb', line 107

def intersection(other)
  other = Point.new(other[0], other[1]) if other.is_a?(Array)

  # Other is a Point.
  if other.is_a?(Point)
    return [other] if self.contains?(other)
    return []
  end

  # Other is a LinearEntity
  if other.is_a?(LinearEntity)
    # break into cases based on whether
    # the lines are parallel, non-parallel intersecting, or skew
    rank = Point.affine_rank(self.p1, self.p2, other.p1, other.p2)
    if rank == 1
      # we're collinear
      return [other] if self.is_a?(Line)
      return [self]  if other.is_a?(Line)  
          
      if self.is_a?(Ray) && other.is_a?(Ray)
        return intersect_parallel_rays(self, other)
      end

      if self.is_a?(Ray) && other.is_a?(Segment)
        return intersect_parallel_ray_and_segment(self, other)
      end

      if self.is_a?(Segment) && other.is_a?(Ray)
        return intersect_parallel_ray_and_segment(other, self)
      end

      if self.is_a?(Segment) && other.is_a?(Segment)
        return intersect_parallel_segments(self, other)
      end

    elsif rank == 2
      # we're in the same plane
      l1 = Line.new(self.p1, self.p2)
      l2 = Line.new(other.p1, other.p2)

      # check to see if we're parallel. If we are, we can't
      # be intersecting, since the collinear case was already
      # handled
      return [] if l1.parallel_to?(l2)
     
      # Use Cramers rule:
      # https://en.wikipedia.org/wiki/Cramer%27s_rule
      det = l1.a * l2.b - l2.a * l1.b
      det = det
      x = (l1.b * l2.c - l1.c * l2.b) / det
      y = (l2.a * l1.c - l2.c * l1.a ) / det

      intersection_point = Point.new(x, y)

      # if we're both lines, we can skip a containment check
      return [intersection_point] if self.is_a?(Line) && other.is_a?(Line)
          
      if self.contains?(intersection_point) && other.contains?(intersection_point)
        return [intersection_point] 
      end
              
      return []
    else
      # we're skew
      return []
    end
  end

  if other.respond_to?(:intersection)
    return other.intersection(self)
  end

  raise TypeError, "Intersection between LinearEntity and #{ other.class } is not defined"
end

#parallel_line(point) ⇒ Object

Create a new Line parallel to this linear entity which passes through the point p

Parameters:

Point

Returns:

Line

Raises:

  • (TypeError)


191
192
193
194
195
196
# File 'lib/easy_geometry/d2/linear_entity.rb', line 191

def parallel_line(point)
  point = Point.new(point[0], point[1]) if point.is_a?(Array)
  raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)

  Line.new(point, point + self.direction.to_point)
end

#parallel_to?(other) ⇒ Boolean

Are two LinearEntity parallel?

Parameters:

LinearEntity

Returns:

true if self and other LinearEntity are parallel.
false otherwise.

Returns:

  • (Boolean)

Raises:

  • (TypeError)


68
69
70
71
# File 'lib/easy_geometry/d2/linear_entity.rb', line 68

def parallel_to?(other)
  raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
  self.direction.cross_product(other.direction) == 0
end

#perpendicular_line(point) ⇒ Object

Create a new Line perpendicular to this linear entity which passes through the ‘point`.

Parameters:

Point

Returns:

Line

Raises:

  • (TypeError)


207
208
209
210
211
212
213
214
# File 'lib/easy_geometry/d2/linear_entity.rb', line 207

def perpendicular_line(point)
  point = Point.new(point[0], point[1]) if point.is_a?(Array)
  raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)

  # any two lines in R^2 intersect, so blindly making
  # a line through p in an orthogonal direction will work
  Line.new(point, point + self.direction.orthogonal_direction.to_point)
end

#perpendicular_segment(point) ⇒ Object

Create a perpendicular line segment from ‘point` to this line. The enpoints of the segment are `point` and the closest point in the line containing self. (If self is not a line, the point might not be in self.)

Parameters:

Point

Returns:

Segment or Point (if `point` is on this linear entity.)

Raises:

  • (TypeError)


227
228
229
230
231
232
233
234
235
236
237
# File 'lib/easy_geometry/d2/linear_entity.rb', line 227

def perpendicular_segment(point)
  point = Point.new(point[0], point[1]) if point.is_a?(Array)
  raise TypeError, 'Must pass only Point.' unless point.is_a?(Point)

  return point if self.contains?(point)
      
  l = self.perpendicular_line(point)
  p = Line.new(self.p1, self.p2).intersection(l).first

  Segment.new(point, p)
end

#perpendicular_to?(other) ⇒ Boolean

Are two linear entities perpendicular?

Parameters:

LinearEntity

Returns:

true if self and other LinearEntity are perpendicular.
false otherwise.

Returns:

  • (Boolean)

Raises:

  • (TypeError)


82
83
84
85
# File 'lib/easy_geometry/d2/linear_entity.rb', line 82

def perpendicular_to?(other)
  raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
  self.direction.dot(other.direction) == 0
end

#projection_point(p) ⇒ Object

Project a point onto this linear entity.

Parameters:

Point

Returns:

Point


289
290
291
# File 'lib/easy_geometry/d2/linear_entity.rb', line 289

def projection_point(p)
  Point.project(p - p1, self.direction.to_point) + p1
end

#similar_to?(other) ⇒ Boolean

Are two linear entities similar?

Return:

true if self and other are contained in the same line.

Returns:

  • (Boolean)

Raises:

  • (TypeError)


92
93
94
95
96
97
# File 'lib/easy_geometry/d2/linear_entity.rb', line 92

def similar_to?(other)
  raise TypeError, 'Must pass only LinearEntity objects.' unless other.is_a?(LinearEntity)
  
  l = Line.new(p1, p2)
  l.contains?(other)
end

#slopeObject

The slope of this linear entity, or infinity if vertical.

Returns:

number or BigDecimal('Infinity')


244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/easy_geometry/d2/linear_entity.rb', line 244

def slope
  return @slope if defined?(@slope)

  dx = p1.x - p2.x
  dy = p1.y - p2.y
  
  if dy == 0 
    @slope = 0.0 
  elsif dx == 0
    @slope = BigDecimal('Infinity')
  else
    @slope = dy / dx
  end

  @slope
end

#span_test(other) ⇒ Object

Test whether the point ‘other` lies in the positive span of `self`. A point x is ’in front’ of a point y if x.dot(y) >= 0.

Return

-1 if other is behind self.p1 
0 if other is self.p1
1 if other is in front of self.p1.

Raises:

  • (TypeError)


269
270
271
272
273
274
275
276
277
278
279
# File 'lib/easy_geometry/d2/linear_entity.rb', line 269

def span_test(other)
  other = Point.new(other[0], other[1]) if other.is_a?(Array)
  raise TypeError, 'Must pass only Point.' unless other.is_a?(Point)

  return 0 if self.p1 == other

  rel_pos = other - self.p1
  return 1 if self.direction.to_point.dot(rel_pos) > 0
  
  return -1
end