Class: Mittsu::Ray

Inherits:
Object
  • Object
show all
Defined in:
lib/mittsu/math/ray.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(origin = Mittsu::Vector3.new, direction = Mittsu::Vector3.new) ⇒ Ray

Returns a new instance of Ray.



5
6
7
# File 'lib/mittsu/math/ray.rb', line 5

def initialize(origin = Mittsu::Vector3.new, direction = Mittsu::Vector3.new)
  @origin, @direction = origin, direction
end

Instance Attribute Details

#directionObject

Returns the value of attribute direction.



3
4
5
# File 'lib/mittsu/math/ray.rb', line 3

def direction
  @direction
end

#originObject

Returns the value of attribute origin.



3
4
5
# File 'lib/mittsu/math/ray.rb', line 3

def origin
  @origin
end

Instance Method Details

#==(ray) ⇒ Object



280
281
282
# File 'lib/mittsu/math/ray.rb', line 280

def ==(ray)
  ray.origin == @origin && ray.direction == @direction
end

#apply_matrix4(matrix4) ⇒ Object



272
273
274
275
276
277
278
# File 'lib/mittsu/math/ray.rb', line 272

def apply_matrix4(matrix4)
  @direction.add(@origin).apply_matrix4(matrix4)
  @origin.apply_matrix4(matrix4)
  @direction.sub(@origin)
  @direction.normalize
  self
end

#at(t, target = Mittsu::Vector3.new) ⇒ Object



21
22
23
# File 'lib/mittsu/math/ray.rb', line 21

def at(t, target = Mittsu::Vector3.new)
  target.copy(@direction).multiply_scalar(t).add(@origin)
end

#cloneObject



284
285
286
# File 'lib/mittsu/math/ray.rb', line 284

def clone
  Mittsu::Ray.new.copy(self)
end

#closest_point_to_point(point, target = Mittsu::Vector3.new) ⇒ Object



31
32
33
34
35
36
37
38
# File 'lib/mittsu/math/ray.rb', line 31

def closest_point_to_point(point, target = Mittsu::Vector3.new)
  target.sub_vectors(point, @origin)
  direction_distance = target.dot(@direction)
  if direction_distance < 0
    return target.copy(@origin)
  end
  target.copy(@direction).multiply_scalar(direction_distance).add(@origin)
end

#copy(ray) ⇒ Object



15
16
17
18
19
# File 'lib/mittsu/math/ray.rb', line 15

def copy(ray)
  @origin.copy(ray.origin)
  @direction.copy(ray.direction)
  self
end

#distance_sq_to_segment(v0, v1, point_on_ray = nil, point_on_segment = nil) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/mittsu/math/ray.rb', line 51

def distance_sq_to_segment(v0, v1, point_on_ray = nil, point_on_segment = nil)
  seg_center = Mittsu::Vector3.new
  seg_dir = Mittsu::Vector3.new
  diff = Mittsu::Vector3.new
  # from http:#www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp
  # It returns the min distance between the ray and the segment
  # defined by v0 and v1
  # It can also set two optional targets :
  # - The closest point on the ray
  # - The closest point on the segment
  seg_center.copy(v0).add(v1).multiply_scalar(0.5)
  seg_dir.copy(v1).sub(v0).normalize
  diff.copy(@origin).sub(seg_center)
  seg_extent = v0.distance_to(v1) * 0.5
  a01 = -@direction.dot(seg_dir)
  b0 = diff.dot(@direction)
  b1 = -diff.dot(seg_dir)
  c = diff.length_sq
  det = (1.0 - a01 * a01).abs
  if det > 0
    # The ray and segment are not parallel.
    s0 = a01 * b1 - b0
    s1 = a01 * b0 - b1
    ext_det = seg_extent * det
    if s0 >= 0
      if s1 >= -ext_det
        if s1 <= ext_det
          # region 0
          # Minimum at interior points of ray and segment.
          inv_det = 1.0 / det
          s0 *= inv_det
          s1 *= inv_det
          sqr_dist = s0 * (s0 + a01 * s1 + 2.0 * b0) + s1 * (a01 * s0 + s1 + 2.0 * b1) + c
        else
          # region 1
          s1 = seg_extent
          s0 = [0.0, -(a01 * s1 + b0)].max
          sqr_dist = - s0 * s0 + s1 * (s1 + 2.0 * b1) + c
        end
      else
        # region 5
        s1 = - seg_extent
        s0 = [0.0, -(a01 * s1 + b0)].max
        sqr_dist = -s0 * s0 + s1 * (s1 + 2.0 * b1) + c
      end
    else
      if s1 <= - ext_det
        # region 4
        s0 = [0.0, -(-a01 * seg_extent + b0)].max
        s1 = (s0 > 0) ? -seg_extent : [[-seg_extent, -b1].max, seg_extent].min
        sqr_dist = - s0 * s0 + s1 * (s1 + 2 * b1) + c
      elsif s1 <= ext_det
        # region 3
        s0 = 0.0
        s1 = [[-seg_extent, -b1].max, seg_extent].min
        sqr_dist = s1 * (s1 + 2.0 * b1) + c
      else
        # region 2
        s0 = [0.0, -(a01 * seg_extent + b0)].max
        s1 = (s0 > 0) ? seg_extent : [[-seg_extent, -b1].max, seg_extent].min
        sqr_dist = -s0 * s0 + s1 * (s1 + 2.0 * b1) + c
      end
    end
  else
    # Ray and segment are parallel.
    s1 = (a01 > 0) ? -seg_extent : seg_extent
    s0 = [0.0, -(a01 * s1 + b0)].max
    sqr_dist = -s0 * s0 + s1 * (s1 + 2.0 * b1) + c
  end
  if point_on_ray
    point_on_ray.copy(@direction).multiply_scalar(s0).add(@origin)
  end
  if point_on_segment
    point_on_segment.copy(seg_dir).multiply_scalar(s1).add(seg_center)
  end
  sqr_dist
end

#distance_to_plane(plane) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/mittsu/math/ray.rb', line 166

def distance_to_plane(plane)
  denominator = plane.normal.dot(@direction)
  if denominator.zero?
    # line is coplanar, return origin
    return 0.0 if plane.distance_to_point(@origin).zero?
    # Null is preferable to nil since nil means.... it is nil
    return nil
  end
  t = -(@origin.dot(plane.normal) + plane.constant) / denominator
  # Return if the ray never intersects the plane
  t >= 0 ? t : nil
end

#distance_to_point(point) ⇒ Object



40
41
42
43
44
45
46
47
48
49
# File 'lib/mittsu/math/ray.rb', line 40

def distance_to_point(point)
  v1 = Mittsu::Vector3.new
  direction_distance = v1.sub_vectors(point, @origin).dot(@direction)
  # point behind the ray
  if direction_distance < 0
    return @origin.distance_to(point)
  end
  v1.copy(@direction).multiply_scalar(direction_distance).add(@origin)
  v1.distance_to(point)
end

#intersect_box(box, target = Mittsu::Vector3.new) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/mittsu/math/ray.rb', line 190

def intersect_box(box, target = Mittsu::Vector3.new)
  # http:#www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
  invdirx = 1.0 / @direction.x
  invdiry = 1.0 / @direction.y
  invdirz = 1.0 / @direction.z
  origin = @origin
  if invdirx >= 0
    tmin = (box.min.x - origin.x) * invdirx
    tmax = (box.max.x - origin.x) * invdirx
  else
    tmin = (box.max.x - origin.x) * invdirx
    tmax = (box.min.x - origin.x) * invdirx
  end
  if invdiry >= 0
    tymin = (box.min.y - origin.y) * invdiry
    tymax = (box.max.y - origin.y) * invdiry
  else
    tymin = (box.max.y - origin.y) * invdiry
    tymax = (box.min.y - origin.y) * invdiry
  end
  return nil if tmin > tymax || tymin > tmax
  # These lines also handle the case where tmin or tmax is NaN
  # (result of 0 * Infinity). x != x returns true if x is NaN
  tmin = tymin if tymin > tmin || tmin != tmin
  tmax = tymax if tymax < tmax || tmax != tmax
  if invdirz >= 0
    tzmin = (box.min.z - origin.z) * invdirz
    tzmax = (box.max.z - origin.z) * invdirz
  else
    tzmin = (box.max.z - origin.z) * invdirz
    tzmax = (box.min.z - origin.z) * invdirz
  end
  return nil if tmin > tzmax || tzmin > tmax
  tmin = tzmin if tzmin > tmin || tmin != tmin
  tmax = tzmax if tzmax < tmax || tmax != tmax
  #return point closest to the ray (positive side)
  return nil if tmax < 0
  self.at(tmin >= 0 ? tmin : tmax, target)
end

#intersect_plane(plane, target = Mittsu::Vector3.new) ⇒ Object



179
180
181
182
183
# File 'lib/mittsu/math/ray.rb', line 179

def intersect_plane(plane, target = Mittsu::Vector3.new)
  t = self.distance_to_plane(plane)
  return nil if t.nil?
  self.at(t, target)
end

#intersect_sphere(sphere, target = Mittsu::Vector3.new) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/mittsu/math/ray.rb', line 133

def intersect_sphere(sphere, target = Mittsu::Vector3.new)
  # from http:#www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-sphere-intersection/
  v1 = Mittsu::Vector3.new
  v1.sub_vectors(sphere.center, @origin)
  tca = v1.dot(@direction)
  d2 = v1.dot(v1) - tca * tca
  radius2 = sphere.radius * sphere.radius
  return nil if d2 > radius2
  thc = ::Math.sqrt(radius2 - d2)
  # t0 = first intersect point - entrance on front of sphere
  t0 = tca - thc
  # t1 = second intersect point - exit point on back of sphere
  t1 = tca + thc
  # test to see if both t0 and t1 are behind the ray - if so, return nil
  return nil if t0 < 0 && t1 < 0
  # test to see if t0 is behind the ray:
  # if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
  # in order to always return an intersect point that is in front of the ray.
  return self.at(t1, target) if t0 < 0
  # else t0 is in front of the ray, so return the first collision point scaled by t0
  self.at(t0, target)
end

#intersect_triangle(a, b, c, backface_culling, target = Mittsu::Vector3.new) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/mittsu/math/ray.rb', line 230

def intersect_triangle(a, b, c, backface_culling, target = Mittsu::Vector3.new)
  # Compute the offset origin, edges, and normal.
  diff = Mittsu::Vector3.new
  edge1 = Mittsu::Vector3.new
  edge2 = Mittsu::Vector3.new
  normal = Mittsu::Vector3.new
  # from http:#www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
  edge1.sub_vectors(b, a)
  edge2.sub_vectors(c, a)
  normal.cross_vectors(edge1, edge2)
  # Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
  # E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
  #   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
  #   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
  #   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
  d_dot_n = @direction.dot(normal)
  if d_dot_n > 0
    return nil if backface_culling
    sign = 1.0
  elsif d_dot_n < 0
    sign = -1.0
    d_dot_n = - d_dot_n
  else
    return nil
  end
  diff.sub_vectors(@origin, a)
  d_dot_q_x_e2 = sign * @direction.dot(edge2.cross_vectors(diff, edge2))
  # b1 < 0, no intersection
  return nil if d_dot_q_x_e2 < 0
  d_dot_e1_x_q = sign * @direction.dot(edge1.cross(diff))
  # b2 < 0, no intersection
  return nil if d_dot_e1_x_q < 0
  # b1+b2 > 1, no intersection
  return nil if d_dot_q_x_e2 + d_dot_e1_x_q > d_dot_n
  # Line intersects triangle, check if ray does.
  q_dot_n = -sign * diff.dot(normal)
  # t < 0, no intersection
  return nil if q_dot_n < 0
  # Ray intersects triangle.
  self.at(q_dot_n / d_dot_n, target)
end

#intersection_box?(box) ⇒ Boolean

Returns:

  • (Boolean)


185
186
187
188
# File 'lib/mittsu/math/ray.rb', line 185

def intersection_box?(box)
  v = Mittsu::Vector3.new
  !self.intersect_box(box, v).nil?
end

#intersection_plane?(plane) ⇒ Boolean

Returns:

  • (Boolean)


156
157
158
159
160
161
162
163
164
# File 'lib/mittsu/math/ray.rb', line 156

def intersection_plane?(plane)
  # check if the ray lies on the plane first
  dist_to_point = plane.distance_to_point(@origin)
  return true if dist_to_point.zero?
  denominator = plane.normal.dot(@direction)
  return true if denominator * dist_to_point < 0
  # ray origin is behind the plane (and is pointing behind it)
  false
end

#intersection_sphere?(sphere) ⇒ Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/mittsu/math/ray.rb', line 129

def intersection_sphere?(sphere)
  self.distance_to_point(sphere.center) <= sphere.radius
end

#recast(t) ⇒ Object



25
26
27
28
29
# File 'lib/mittsu/math/ray.rb', line 25

def recast(t)
  v1 = Mittsu::Vector3.new
  @origin.copy(self.at(t, v1))
  self
end

#set(origin, direction) ⇒ Object



9
10
11
12
13
# File 'lib/mittsu/math/ray.rb', line 9

def set(origin, direction)
  @origin.copy(origin)
  @direction.copy(direction)
  self
end