Class: Geometry::Vector

Inherits:
Object
  • Object
show all
Defined in:
lib/geometry/vector/vector.rb

Direct Known Subclasses

GeoVector

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(x, y, z = 0) ⇒ Vector

Returns a new instance of Vector.



18
19
20
21
22
# File 'lib/geometry/vector/vector.rb', line 18

def initialize(x, y, z = 0)
  @x = x.to_f
  @y = y.to_f
  @z = z.to_f
end

Instance Attribute Details

#xObject

Returns the value of attribute x.



16
17
18
# File 'lib/geometry/vector/vector.rb', line 16

def x
  @x
end

#yObject

Returns the value of attribute y.



16
17
18
# File 'lib/geometry/vector/vector.rb', line 16

def y
  @y
end

#zObject

Returns the value of attribute z.



16
17
18
# File 'lib/geometry/vector/vector.rb', line 16

def z
  @z
end

Class Method Details

.from_polar(magnitude, angle, options = {}) ⇒ Object

Return an x/y vector based on a magnitude and a heading.



7
8
9
10
11
12
13
14
# File 'lib/geometry/vector/vector.rb', line 7

def self.from_polar(magnitude, angle, options = {})
  angle = Geometry.deg_to_rad(angle) if options[:unit] = :deg

  y = Math.sin(angle) * magnitude
  x = Math.cos(angle) * magnitude
  
  self.new(x,y)
end

Instance Method Details

#==(other) ⇒ Object



134
135
136
# File 'lib/geometry/vector/vector.rb', line 134

def ==(other)
  @x == other.x && @y == other.y && @z == other.z
end

#add(other) ⇒ Object Also known as: +



24
25
26
# File 'lib/geometry/vector/vector.rb', line 24

def add(other)      
  self.class.new(@x + other.x, @y + other.y, @z + other.z)
end

#angle(other) ⇒ Object

Return the angle (in radians) between self and the passed in vector.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/geometry/vector/vector.rb', line 99

def angle(other)

  # Two options here:
  #
  # 1.  Math.atan2(other.cross_length(self), dot(other)) 
  #
  #     This is stable but slower (x 1.5)
  #
  # 2.  Math.acos(dot(other) / (r * other.r)) 
  #
  #     This is faster but unstable around 0 and pi where the gradient of acos approaches 
  #     infinity. An alternative way to view this is that the gradient of cos approaches
  #     zero and small differences in angle can be indistinguishable at some number of 
  #     decimal places.
  #
  
  # Math.acos(dot(other) / (r * other.r)) 
  Math.atan2(other.cross_length(self), dot(other)) 
end

#cross(other) ⇒ Object Also known as: vector_product

Return the cross product of self and the passed in vector.



70
71
72
73
74
75
76
# File 'lib/geometry/vector/vector.rb', line 70

def cross(other)
  new_x = (@y * other.z) - (@z * other.y)
  new_y = (@z * other.x) - (@x * other.z)
  new_z = (@x * other.y) - (@y * other.x)

  self.class.new(new_x, new_y, new_z)
end

#cross_length(other) ⇒ Object

Return the magnitude of the cross product of self and the passed in vector.



81
82
83
84
85
86
87
88
89
90
91
# File 'lib/geometry/vector/vector.rb', line 81

def cross_length(other)
  # cross(other).magnitude

  # It is more efficient to not create a new Vector object since we are only returning 
  # a scalar value 
  new_x = (@y * other.z) - (@z * other.y)
  new_y = (@z * other.x) - (@x * other.z)
  new_z = (@x * other.y) - (@y * other.x)

  Math.sqrt(new_x ** 2 + new_y ** 2 + new_z ** 2)
end

#cross_normal(other) ⇒ Object

Return the unit vector of the cross product of self and the passed in vector.



94
95
96
# File 'lib/geometry/vector/vector.rb', line 94

def cross_normal(other)
  cross(other).normalize
end

#distance(other) ⇒ Object

Return the cartesian distance between self and the passed vector



120
121
122
# File 'lib/geometry/vector/vector.rb', line 120

def distance(other)
  (other - self).magnitude.abs
end

#distance_from_line(point_a, point_b) ⇒ Object

Calculate the distance of self from the infinite line passing through the two passed in points.



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/geometry/vector/vector.rb', line 139

def distance_from_line(point_a,point_b)
  
  # Define the line as the vector between two points
  line_vector  = point_b - point_a

  # Define a second vector representing the distance between self and the line start
  point_vector = self - point_a

  # The magnitude of the cross product is equal to the area of the parallelogram described
  # by the two vectors. Dividing by the line length gives the perpendicular distance.
  (line_vector.cross(point_vector).magnitude / line_vector.magnitude).abs
end

#distance_from_line_segment(point_a, point_b) ⇒ Object

Calculate the distance of self from the line segment starting and ending with the two passed in points.



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
181
182
183
184
185
# File 'lib/geometry/vector/vector.rb', line 153

def distance_from_line_segment(point_a,point_b)

  # Define the line as the vector between two points
  line_vector  = point_b - point_a
  
  # Define a second vector representing the distance between self and the line start
  point_vector = self - point_a

  # Determine if self falls within the perpendicular 'shadow' of the line by calculating
  # the projection of the point vector onto the line.
  #
  # The dot product divided by the magnitude of the line gives the absolute projection
  # of the point vector onto the line.
  #
  # Dividing again by the line magnitude gives the relative projection along the line, 
  # i.e. the ratio of the projection to the line. Values between 0-1 indicate that the
  # point falls within the perpendicular shadow.
  #
  projection_ratio = line_vector.dot(point_vector) / line_vector.magnitude ** 2

  if projection_ratio >= 1
    # The point is beyond point b, calculate distance to point b
    distance = (point_b - self).magnitude
  elsif projection_ratio <= 0
    # The point is beyond point a, calculate distance to point a
    distance = (point_a - self).magnitude
  else
    # The point is in the shadow of the line, return the perpendicular distance
    distance = line_vector.cross(point_vector).magnitude / line_vector.magnitude
  end

  return distance.abs
end

#distance_from_polyline(polyline) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/geometry/vector/vector.rb', line 187

def distance_from_polyline(polyline)
  
  # memoize the last processed point as both array and vector objects
  last_array  = polyline.first
  last_vector = self.class.new(last_array[0], last_array[1], last_array[2]) # this should support 3 arguments surely?!

  minimum_distance = 999999999999

  polyline[1..-1].each do |vertex|  

    next if vertex == last_array  

    start_vector = last_vector
    end_vector   = self.class.new(vertex[0],vertex[1], vertex[2]) # this should support 3 arguments surely?!

    this_segment_distance = distance_from_line_segment(start_vector, end_vector)

    if(this_segment_distance < minimum_distance)
      minimum_distance = this_segment_distance
    end

    last_array  = vertex
    last_vector = end_vector
  end

  return minimum_distance
end

#divide(n) ⇒ Object Also known as: /



39
40
41
# File 'lib/geometry/vector/vector.rb', line 39

def divide(n)      
  scale(1.0/n.to_f)
end

#dot(other) ⇒ Object Also known as: scalar_product

Return the dot product of self and the passed in vector.



64
65
66
# File 'lib/geometry/vector/vector.rb', line 64

def dot(other)      
  return (@x * other.x) + (@y * other.y) + (@z * other.z)
end

#headingObject



59
60
61
# File 'lib/geometry/vector/vector.rb', line 59

def heading
  Math.atan2(@y,@x)
end

#inspectObject



215
216
217
# File 'lib/geometry/vector/vector.rb', line 215

def inspect
  puts "[#{@x}, #{@y}, #{@z}]"
end

#magnitudeObject Also known as: r

Return the magnitude of self.



45
46
47
# File 'lib/geometry/vector/vector.rb', line 45

def magnitude
  Math.sqrt(@x ** 2 + @y ** 2 + @z ** 2)
end

#multiply(n) ⇒ Object Also known as: *



34
35
36
# File 'lib/geometry/vector/vector.rb', line 34

def multiply(n)
  scale(n.to_f)
end

#normalizeObject

Normalize self, that is, return the unit vector with the same direction as self.



55
56
57
# File 'lib/geometry/vector/vector.rb', line 55

def normalize
  divide(magnitude)
end

#orthogonal?(other) ⇒ Boolean

Returns true of the passed in vector is perpendicular to self.

Returns:

  • (Boolean)


125
126
127
# File 'lib/geometry/vector/vector.rb', line 125

def orthogonal?(other)
  dot(other) == 0
end

#parallel?(other) ⇒ Boolean

Returns true if the passed in vector is parallel to self.

Returns:

  • (Boolean)


130
131
132
# File 'lib/geometry/vector/vector.rb', line 130

def parallel?(other)
  cross(other).magnitude == 0
end

#scale(scalar) ⇒ Object



50
51
52
# File 'lib/geometry/vector/vector.rb', line 50

def scale(scalar)
  self.class.new(@x * scalar, @y * scalar, @z * scalar)
end

#subtract(other) ⇒ Object Also known as: -



29
30
31
# File 'lib/geometry/vector/vector.rb', line 29

def subtract(other)
  self.class.new(@x - other.x, @y - other.y, @z - other.z)
end