UNPKG

10.6 kBJavaScriptView Raw
1import { Vector3 } from './Vector3.js';
2
3const _vector = new Vector3();
4const _segCenter = new Vector3();
5const _segDir = new Vector3();
6const _diff = new Vector3();
7
8const _edge1 = new Vector3();
9const _edge2 = new Vector3();
10const _normal = new Vector3();
11
12function Ray( origin, direction ) {
13
14 this.origin = ( origin !== undefined ) ? origin : new Vector3();
15 this.direction = ( direction !== undefined ) ? direction : new Vector3( 0, 0, - 1 );
16
17}
18
19Object.assign( Ray.prototype, {
20
21 set: function ( origin, direction ) {
22
23 this.origin.copy( origin );
24 this.direction.copy( direction );
25
26 return this;
27
28 },
29
30 clone: function () {
31
32 return new this.constructor().copy( this );
33
34 },
35
36 copy: function ( ray ) {
37
38 this.origin.copy( ray.origin );
39 this.direction.copy( ray.direction );
40
41 return this;
42
43 },
44
45 at: function ( t, target ) {
46
47 if ( target === undefined ) {
48
49 console.warn( 'THREE.Ray: .at() target is now required' );
50 target = new Vector3();
51
52 }
53
54 return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );
55
56 },
57
58 lookAt: function ( v ) {
59
60 this.direction.copy( v ).sub( this.origin ).normalize();
61
62 return this;
63
64 },
65
66 recast: function ( t ) {
67
68 this.origin.copy( this.at( t, _vector ) );
69
70 return this;
71
72 },
73
74 closestPointToPoint: function ( point, target ) {
75
76 if ( target === undefined ) {
77
78 console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
79 target = new Vector3();
80
81 }
82
83 target.subVectors( point, this.origin );
84
85 const directionDistance = target.dot( this.direction );
86
87 if ( directionDistance < 0 ) {
88
89 return target.copy( this.origin );
90
91 }
92
93 return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
94
95 },
96
97 distanceToPoint: function ( point ) {
98
99 return Math.sqrt( this.distanceSqToPoint( point ) );
100
101 },
102
103 distanceSqToPoint: function ( point ) {
104
105 const directionDistance = _vector.subVectors( point, this.origin ).dot( this.direction );
106
107 // point behind the ray
108
109 if ( directionDistance < 0 ) {
110
111 return this.origin.distanceToSquared( point );
112
113 }
114
115 _vector.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
116
117 return _vector.distanceToSquared( point );
118
119 },
120
121 distanceSqToSegment: function ( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
122
123 // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
124 // It returns the min distance between the ray and the segment
125 // defined by v0 and v1
126 // It can also set two optional targets :
127 // - The closest point on the ray
128 // - The closest point on the segment
129
130 _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
131 _segDir.copy( v1 ).sub( v0 ).normalize();
132 _diff.copy( this.origin ).sub( _segCenter );
133
134 const segExtent = v0.distanceTo( v1 ) * 0.5;
135 const a01 = - this.direction.dot( _segDir );
136 const b0 = _diff.dot( this.direction );
137 const b1 = - _diff.dot( _segDir );
138 const c = _diff.lengthSq();
139 const det = Math.abs( 1 - a01 * a01 );
140 let s0, s1, sqrDist, extDet;
141
142 if ( det > 0 ) {
143
144 // The ray and segment are not parallel.
145
146 s0 = a01 * b1 - b0;
147 s1 = a01 * b0 - b1;
148 extDet = segExtent * det;
149
150 if ( s0 >= 0 ) {
151
152 if ( s1 >= - extDet ) {
153
154 if ( s1 <= extDet ) {
155
156 // region 0
157 // Minimum at interior points of ray and segment.
158
159 const invDet = 1 / det;
160 s0 *= invDet;
161 s1 *= invDet;
162 sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
163
164 } else {
165
166 // region 1
167
168 s1 = segExtent;
169 s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
170 sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
171
172 }
173
174 } else {
175
176 // region 5
177
178 s1 = - segExtent;
179 s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
180 sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
181
182 }
183
184 } else {
185
186 if ( s1 <= - extDet ) {
187
188 // region 4
189
190 s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
191 s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
192 sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
193
194 } else if ( s1 <= extDet ) {
195
196 // region 3
197
198 s0 = 0;
199 s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
200 sqrDist = s1 * ( s1 + 2 * b1 ) + c;
201
202 } else {
203
204 // region 2
205
206 s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
207 s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
208 sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
209
210 }
211
212 }
213
214 } else {
215
216 // Ray and segment are parallel.
217
218 s1 = ( a01 > 0 ) ? - segExtent : segExtent;
219 s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
220 sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
221
222 }
223
224 if ( optionalPointOnRay ) {
225
226 optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );
227
228 }
229
230 if ( optionalPointOnSegment ) {
231
232 optionalPointOnSegment.copy( _segDir ).multiplyScalar( s1 ).add( _segCenter );
233
234 }
235
236 return sqrDist;
237
238 },
239
240 intersectSphere: function ( sphere, target ) {
241
242 _vector.subVectors( sphere.center, this.origin );
243 const tca = _vector.dot( this.direction );
244 const d2 = _vector.dot( _vector ) - tca * tca;
245 const radius2 = sphere.radius * sphere.radius;
246
247 if ( d2 > radius2 ) return null;
248
249 const thc = Math.sqrt( radius2 - d2 );
250
251 // t0 = first intersect point - entrance on front of sphere
252 const t0 = tca - thc;
253
254 // t1 = second intersect point - exit point on back of sphere
255 const t1 = tca + thc;
256
257 // test to see if both t0 and t1 are behind the ray - if so, return null
258 if ( t0 < 0 && t1 < 0 ) return null;
259
260 // test to see if t0 is behind the ray:
261 // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
262 // in order to always return an intersect point that is in front of the ray.
263 if ( t0 < 0 ) return this.at( t1, target );
264
265 // else t0 is in front of the ray, so return the first collision point scaled by t0
266 return this.at( t0, target );
267
268 },
269
270 intersectsSphere: function ( sphere ) {
271
272 return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
273
274 },
275
276 distanceToPlane: function ( plane ) {
277
278 const denominator = plane.normal.dot( this.direction );
279
280 if ( denominator === 0 ) {
281
282 // line is coplanar, return origin
283 if ( plane.distanceToPoint( this.origin ) === 0 ) {
284
285 return 0;
286
287 }
288
289 // Null is preferable to undefined since undefined means.... it is undefined
290
291 return null;
292
293 }
294
295 const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
296
297 // Return if the ray never intersects the plane
298
299 return t >= 0 ? t : null;
300
301 },
302
303 intersectPlane: function ( plane, target ) {
304
305 const t = this.distanceToPlane( plane );
306
307 if ( t === null ) {
308
309 return null;
310
311 }
312
313 return this.at( t, target );
314
315 },
316
317 intersectsPlane: function ( plane ) {
318
319 // check if the ray lies on the plane first
320
321 const distToPoint = plane.distanceToPoint( this.origin );
322
323 if ( distToPoint === 0 ) {
324
325 return true;
326
327 }
328
329 const denominator = plane.normal.dot( this.direction );
330
331 if ( denominator * distToPoint < 0 ) {
332
333 return true;
334
335 }
336
337 // ray origin is behind the plane (and is pointing behind it)
338
339 return false;
340
341 },
342
343 intersectBox: function ( box, target ) {
344
345 let tmin, tmax, tymin, tymax, tzmin, tzmax;
346
347 const invdirx = 1 / this.direction.x,
348 invdiry = 1 / this.direction.y,
349 invdirz = 1 / this.direction.z;
350
351 const origin = this.origin;
352
353 if ( invdirx >= 0 ) {
354
355 tmin = ( box.min.x - origin.x ) * invdirx;
356 tmax = ( box.max.x - origin.x ) * invdirx;
357
358 } else {
359
360 tmin = ( box.max.x - origin.x ) * invdirx;
361 tmax = ( box.min.x - origin.x ) * invdirx;
362
363 }
364
365 if ( invdiry >= 0 ) {
366
367 tymin = ( box.min.y - origin.y ) * invdiry;
368 tymax = ( box.max.y - origin.y ) * invdiry;
369
370 } else {
371
372 tymin = ( box.max.y - origin.y ) * invdiry;
373 tymax = ( box.min.y - origin.y ) * invdiry;
374
375 }
376
377 if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
378
379 // These lines also handle the case where tmin or tmax is NaN
380 // (result of 0 * Infinity). x !== x returns true if x is NaN
381
382 if ( tymin > tmin || tmin !== tmin ) tmin = tymin;
383
384 if ( tymax < tmax || tmax !== tmax ) tmax = tymax;
385
386 if ( invdirz >= 0 ) {
387
388 tzmin = ( box.min.z - origin.z ) * invdirz;
389 tzmax = ( box.max.z - origin.z ) * invdirz;
390
391 } else {
392
393 tzmin = ( box.max.z - origin.z ) * invdirz;
394 tzmax = ( box.min.z - origin.z ) * invdirz;
395
396 }
397
398 if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
399
400 if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
401
402 if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
403
404 //return point closest to the ray (positive side)
405
406 if ( tmax < 0 ) return null;
407
408 return this.at( tmin >= 0 ? tmin : tmax, target );
409
410 },
411
412 intersectsBox: function ( box ) {
413
414 return this.intersectBox( box, _vector ) !== null;
415
416 },
417
418 intersectTriangle: function ( a, b, c, backfaceCulling, target ) {
419
420 // Compute the offset origin, edges, and normal.
421
422 // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
423
424 _edge1.subVectors( b, a );
425 _edge2.subVectors( c, a );
426 _normal.crossVectors( _edge1, _edge2 );
427
428 // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
429 // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
430 // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
431 // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
432 // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
433 let DdN = this.direction.dot( _normal );
434 let sign;
435
436 if ( DdN > 0 ) {
437
438 if ( backfaceCulling ) return null;
439 sign = 1;
440
441 } else if ( DdN < 0 ) {
442
443 sign = - 1;
444 DdN = - DdN;
445
446 } else {
447
448 return null;
449
450 }
451
452 _diff.subVectors( this.origin, a );
453 const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
454
455 // b1 < 0, no intersection
456 if ( DdQxE2 < 0 ) {
457
458 return null;
459
460 }
461
462 const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
463
464 // b2 < 0, no intersection
465 if ( DdE1xQ < 0 ) {
466
467 return null;
468
469 }
470
471 // b1+b2 > 1, no intersection
472 if ( DdQxE2 + DdE1xQ > DdN ) {
473
474 return null;
475
476 }
477
478 // Line intersects triangle, check if ray does.
479 const QdN = - sign * _diff.dot( _normal );
480
481 // t < 0, no intersection
482 if ( QdN < 0 ) {
483
484 return null;
485
486 }
487
488 // Ray intersects triangle.
489 return this.at( QdN / DdN, target );
490
491 },
492
493 applyMatrix4: function ( matrix4 ) {
494
495 this.origin.applyMatrix4( matrix4 );
496 this.direction.transformDirection( matrix4 );
497
498 return this;
499
500 },
501
502 equals: function ( ray ) {
503
504 return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
505
506 }
507
508} );
509
510
511export { Ray };