Skip to content

Instantly share code, notes, and snippets.

@blackslate
Created November 27, 2015 15:20
Show Gist options
  • Save blackslate/46cf06ecdbf719e5c311 to your computer and use it in GitHub Desktop.
Save blackslate/46cf06ecdbf719e5c311 to your computer and use it in GitHub Desktop.
THREE.Ray.prototype.closestPointToRay
THREE.Ray.prototype.closestPointToRay = function (that, details) {
// that: THREE.Ray()
// details: (optional) object
// { pointOnThisRay: <THREE.Vector3>
// , pointOnThatRay: <THREE.Vector3>
// , midPoint: <THREE.Vector3>
// , distanceBetweenClosestPoints: <float>
// }
// For an explanation of the vector mathematics, see:
// http://morroworks.com/Content/Docs/Rays%20closest%20point.pdf
// @return undefined if rays are invalid or parallel
// or THREE.Vector3() point on this ray which is closest
// to that ray.
if (!(that instanceof THREE.Ray)) {
return
}
var thisDirection = this.direction
var thatDirection = that.direction
if (!thisDirection.clone().cross(thatDirection).length()) {
// Rays are parallel
return
}
if ( !thisDirection.dot(thisDirection)
|| !thatDirection.dot(thatDirection)) {
// At least one of the rays is just a point with no direction
return
}
var closestPoint = new THREE.Vector3()
var thisOrigin = this.origin
var thatOrigin = that.origin
var sameOrigin = thisOrigin.equals(thatOrigin)
if (sameOrigin) {
// Simple case
closestPoint.copy(thisOrigin)
} else {
var a = thisDirection.clone().normalize()
var b = thatDirection.clone().normalize()
var c = thatOrigin.clone().sub(thisOrigin)
var p = a.dot(b)
var q = a.dot(c)
var r = b.dot(c)
var s = a.dot(a) // already known to be non-zero
var t = b.dot(b) // already known to be non-zero
var divisor = (s * t - p * p)
if (!divisor) {
// The two rays are colinear. They are "closest" at all points
// This case should already have been excluded by the .cross()
// check made at the start.
return
}
var d = (q * t - p * r) / divisor
closestPoint.copy(thisOrigin).add(a.multiplyScalar(d))
}
if ( typeof details === "object" ) {
details.pointOnThisRay = closestPoint
if (sameOrigin) {
// Should all points be the same object or clones?
details.pointOnThatRay = closestPoint
details.midPoint = closestPoint
details.distanceBetweenClosestPoints = 0
} else {
// TODO: Add other details
d = (p * q - r * s) / divisor
var thatPoint = new THREE.Vector3().copy(thatOrigin).add(b.multiplyScalar(d))
details.pointOnThatRay = thatPoint
details.midPoint = closestPoint.clone()
.add(thatPoint)
.divideScalar(2)
details.distanceBetweenClosestPoints = closestPoint.distanceTo(thatPoint)
}
}
return closestPoint
}
@bhouston
Copy link

I would not special case the sameOrigin, it is a very rare case and it should be handled by the general case code well. Not having it special case will speed up the majority cases.

@bhouston
Copy link

You check for parallel rays twice, first here:

if (!thisDirection.clone().cross(thatDirection).length()) {
    // Rays are parallel
    return 
  }

then you check again here (collinear means parallel):

if (!divisor) {
      // The two rays are colinear. They are "closest" at all points
      // This case should already have been excluded by the .cross()
      // check made at the start.
      return
    }

@bhouston
Copy link

I am not sure that a and b need to be normalized in the following code, the reason is because there is a.dot(a), and b.dot(b), which should be necessary if a and b where normalized:

    var a = thisDirection.clone().normalize()
    var b = thatDirection.clone().normalize()
    var c = thatOrigin.clone().sub(thisOrigin)

    var p = a.dot(b)
    var q = a.dot(c)
    var r = b.dot(c)
    var s = a.dot(a) // already known to be non-zero
    var t = b.dot(b) // already known to be non-zero

Not creating new a and b vectors would be very efficient because JavaScript memory allocations are costly.

@bhouston
Copy link

If a and b are not normalized, it may be more efficient to replace this line of code:

closestPoint.copy(thisOrigin).add(a.multiplyScalar(d))

With something like this, because d would be in the parameter space of the current ray:

this.at( d, closestPoint );

@bhouston
Copy link

This check is not needed:

if ( !thisDirection.dot(thisDirection)
    || !thatDirection.dot(thatDirection)) {
    // At least one of the rays is just a point with no direction
    return
  }

Because it is already covered by the check that divisor is 0 later on.

@bhouston
Copy link

For speed purposes, it may just be easier to return the 'd' parameterization rather than computing all these derived values:

 if ( typeof details === "object" ) {
    details.pointOnThisRay = closestPoint

    if (sameOrigin) {
      // Should all points be the same object or clones?
      details.pointOnThatRay = closestPoint
      details.midPoint = closestPoint
      details.distanceBetweenClosestPoints = 0
    } else {
      // TODO: Add other details
      d = (p * q - r * s) / divisor
      var thatPoint = new THREE.Vector3().copy(thatOrigin).add(b.multiplyScalar(d))
      details.pointOnThatRay = thatPoint
      details.midPoint = closestPoint.clone()
                                     .add(thatPoint)
                                     .divideScalar(2)
      details.distanceBetweenClosestPoints = closestPoint.distanceTo(thatPoint)
    }
  }

The 'd' parameter can be used as I described above to get the point on the plane, thus it is a sufficient result --- although I am not sure how to represent the failure case in an efficient fashion -- I guess one could return null for the failure case (parallel lines) and a value number otherwise:

this.at( d, closestPoint );

@bhouston
Copy link

I would write it something like this - I haven't checked this for simple mistakes, but it should be relatively close:

THREE.Ray.prototype.closestPointToRay = function ( that ) {

    var p = this.direction.dot(that.direction);
    var s = this.direction.lengthSq();
    var t = that.direction.lengthSq();

    var divisor = (s * t - p * p)

    if ( Math.abs( divisor ) < THREE.Epsilon ) {
      // The two rays are colinear. They are "closest" at all points
      // This case should already have been excluded by the .cross()
      // check made at the start.
      return null;
    }

    // only calculate these if we need to...
    var c = that.origin.clone().sub(this.origin);
    var q = this.direction.dot(c);
    var r = that.direction.dot(c);

    return (q * t - p * r) / divisor;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment