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.



7
8
9
# File 'lib/mittsu/math/ray.rb', line 7

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.



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

def direction
  @direction
end

#originObject

Returns the value of attribute origin.



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

def origin
  @origin
end

Instance Method Details

#==(ray) ⇒ Object



282
283
284
# File 'lib/mittsu/math/ray.rb', line 282

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

#apply_matrix4(matrix4) ⇒ Object



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

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



23
24
25
# File 'lib/mittsu/math/ray.rb', line 23

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

#cloneObject



286
287
288
# File 'lib/mittsu/math/ray.rb', line 286

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

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



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

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



17
18
19
20
21
# File 'lib/mittsu/math/ray.rb', line 17

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



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
128
129
# File 'lib/mittsu/math/ray.rb', line 53

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



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

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



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

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



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
229
230
# File 'lib/mittsu/math/ray.rb', line 192

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



181
182
183
184
185
# File 'lib/mittsu/math/ray.rb', line 181

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



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

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



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
271
272
# File 'lib/mittsu/math/ray.rb', line 232

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)


187
188
189
190
# File 'lib/mittsu/math/ray.rb', line 187

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

#intersection_plane?(plane) ⇒ Boolean

Returns:

  • (Boolean)


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

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)


131
132
133
# File 'lib/mittsu/math/ray.rb', line 131

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

#recast(t) ⇒ Object



27
28
29
30
31
# File 'lib/mittsu/math/ray.rb', line 27

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

#set(origin, direction) ⇒ Object



11
12
13
14
15
# File 'lib/mittsu/math/ray.rb', line 11

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