Threejs 源码解析(Ray.js)

import { Vector3 } from './Vector3.js';

/**
 * @author bhouston / http://clara.io
 */
/**
 * @classdesc 射线类<br />
 * 注释内容部分参照 http://blog.csdn.net/omni360
 * @desc 创建一个原点为origin,方向为direction的射线
 * @param {THREE.Vector3} origin   起点坐标
 * @param {THREE.Vector3} direction 射线方向
 * @constructor
 */
function Ray( origin, direction ) {
   /*射线起点
   * */
   this.origin = ( origin !== undefined ) ? origin : new Vector3();
   /*射线方向向量
   * */
   this.direction = ( direction !== undefined ) ? direction : new Vector3();

}

Object.assign( Ray.prototype, {
   /*创建一个原点为orgin,方向为direction 的射线
   * */
   set: function ( origin, direction ) {

      this.origin.copy( origin );
      this.direction.copy( direction );

      return this;

   },
   /*克隆射线
   * */
   clone: function () {

      return new this.constructor().copy( this );

   },
   /*拷贝射线
   * */
   copy: function ( ray ) {

      this.origin.copy( ray.origin );
      this.direction.copy( ray.direction );

      return this;

   },
    /**
    * 返回 射线方向 从起点起长度为t的点
     * @param t    (float)    射线方向 到起点长度为t点
     * @param target
     */
   at: function ( t, target ) {

      if ( target === undefined ) {

         console.warn( 'THREE.Ray: .at() target is now required' );
         target = new Vector3();

      }

      return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );

   },
    /**
    *  重新设置终点,返回射线
     * @param v (Vector3)
     * @returns {lookAt}
     */
   lookAt: function ( v ) {

      this.direction.copy( v ).sub( this.origin ).normalize();

      return this;

   },
    /**
    * 在射线方向上,从起点 起t长度的点设为起点,返回新的射线
    * @param t(float)
     */
   recast: function () {

      var v1 = new Vector3();

      return function recast( t ) {

         this.origin.copy( this.at( t, v1 ) );

         return this;

      };

   }(),
    /**
    * 返回任意点到射线上的垂足
     * @param point (Vector3) 任意点
     * @param target (Vector3) optionalTarget
     */
   closestPointToPoint: function ( point, target ) {

      if ( target === undefined ) {

         console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
         target = new Vector3();

      }
        //点到起点的向量
      target.subVectors( point, this.origin );
        //点积到射线的单位向量上,得到的是 point投影在射线上的长度
      var directionDistance = target.dot( this.direction );
        //长度小于0,表示钝角,返回起点
      if ( directionDistance < 0 ) {

         return target.copy( this.origin );

      }
        //得到垂足
      return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );

   },
    /**
     * 返回任意点到射线的距离
     * @param point
     * @returns {number}
     */
   distanceToPoint: function ( point ) {

      return Math.sqrt( this.distanceSqToPoint( point ) );

   },
    /**
     *返回任意点到射线上的垂线的向量
     * @param point (Vector3)
     */
   distanceSqToPoint: function () {

      var v1 = new Vector3();

      return function distanceSqToPoint( point ) {
            //得到点投影在射线上的长度
         var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );

         // 点在射线后面,成钝角
         if ( directionDistance < 0 ) {
                //返回point 减 起点坐标,的长的平方(没开方,也就这样翻译了)
            return this.origin.distanceToSquared( point );

         }
            //得到,点投影到射线上的向量(点)
         v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
            //得到的向量减去点坐标,得到点投影到 射线上点的向量
         return v1.distanceToSquared( point );

      };

   }(),
    /**
     * @desc 返回有参数v0,v1组成的线段到当前射线的最小距离
     * @param {THREE.Vector3} v0
     * @param {THREE.Vector3} v1
     * @param {THREE.Vector3} optionalPointOnRay
     * @param {THREE.Vector3} optionalPointOnSegment
     * @returns {float}
     */
   distanceSqToSegment: function () {

      var segCenter = new Vector3();
      var segDir = new Vector3();
      var diff = new Vector3();

      return function distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {

         // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
         // 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

         segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
         segDir.copy( v1 ).sub( v0 ).normalize();
         diff.copy( this.origin ).sub( segCenter );

         var segExtent = v0.distanceTo( v1 ) * 0.5;
         var a01 = - this.direction.dot( segDir );
         var b0 = diff.dot( this.direction );
         var b1 = - diff.dot( segDir );
         var c = diff.lengthSq();
         var det = Math.abs( 1 - a01 * a01 );
         var s0, s1, sqrDist, extDet;

         if ( det > 0 ) {

            // The ray and segment are not parallel.

            s0 = a01 * b1 - b0;
            s1 = a01 * b0 - b1;
            extDet = segExtent * det;

            if ( s0 >= 0 ) {

               if ( s1 >= - extDet ) {

                  if ( s1 <= extDet ) {

                     // region 0
                     // Minimum at interior points of ray and segment.

                     var invDet = 1 / det;
                     s0 *= invDet;
                     s1 *= invDet;
                     sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;

                  } else {

                     // region 1

                     s1 = segExtent;
                     s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;

                  }

               } else {

                  // region 5

                  s1 = - segExtent;
                  s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
                  sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;

               }

            } else {

               if ( s1 <= - extDet ) {

                  // region 4

                  s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
                  s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
                  sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;

               } else if ( s1 <= extDet ) {

                  // region 3

                  s0 = 0;
                  s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
                  sqrDist = s1 * ( s1 + 2 * b1 ) + c;

               } else {

                  // region 2

                  s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
                  s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
                  sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;

               }

            }

         } else {

            // Ray and segment are parallel.

            s1 = ( a01 > 0 ) ? - segExtent : segExtent;
            s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
            sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;

         }

         if ( optionalPointOnRay ) {

            optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );

         }

         if ( optionalPointOnSegment ) {

            optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter );

         }

         return sqrDist;

      };

   }(),
    /**
     * @function
     * @desc 当前射线是否与参数sphere球体相交,相交返回交点,否则返回false
     * @param {THREE.Sphere} sphere
     * @param {THREE.Sphere} sphere
     * @returns {THREE.Vector3|boolean}
     */
   intersectSphere: function () {

      var v1 = new Vector3();

      return function intersectSphere( sphere, target ) {

         v1.subVectors( sphere.center, this.origin );
         var tca = v1.dot( this.direction );
         var d2 = v1.dot( v1 ) - tca * tca;
         var radius2 = sphere.radius * sphere.radius;

         if ( d2 > radius2 ) return null;

         var thc = Math.sqrt( radius2 - d2 );

         // t0 = first intersect point - entrance on front of sphere
         var t0 = tca - thc;

         // t1 = second intersect point - exit point on back of sphere
         var t1 = tca + thc;

         // test to see if both t0 and t1 are behind the ray - if so, return null
         if ( t0 < 0 && t1 < 0 ) return null;

         // 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.
         if ( t0 < 0 ) return this.at( t1, target );

         // else t0 is in front of the ray, so return the first collision point scaled by t0
         return this.at( t0, target );

      };

   }(),
    /**
     * @desc 当前射线是否与参数sphere球体相交
     * @param {THREE.Sphere} sphere
     * @returns {boolean}
     * 旧方法 是 isIntersectionSphere (不知道是不是作者改错了)
     */
   intersectsSphere: function ( sphere ) {
        //判断球心位置到射线的距离,如果小就相交,反之
      return this.distanceToPoint( sphere.center ) <= sphere.radius;

   },
    /**
     * @desc 返回当前射线是与参数plane平面的距离
     * @param {THREE.Plane} plane
     * @returns {float}
     */
   distanceToPlane: function ( plane ) {

      var denominator = plane.normal.dot( this.direction );

      if ( denominator === 0 ) {

         // line is coplanar, return origin
         if ( plane.distanceToPoint( this.origin ) === 0 ) {

            return 0;

         }

         // Null is preferable to undefined since undefined means.... it is undefined

         return null;

      }

      var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;

      // Return if the ray never intersects the plane

      return t >= 0 ? t : null;

   },
    /**
     * @desc 计算当前射线是与参数plane相交的交点
     * @param {THREE.Plane} plane
     * @param {boolean} optionalTarget
     * @returns {THREE.Vector3}
     */
   intersectPlane: function ( plane, target ) {

      var t = this.distanceToPlane( plane );

      if ( t === null ) {

         return null;

      }

      return this.at( t, target );

   },
    /**
     * @desc 当前射线是否与参数plane平面相交
     * @param {THREE.Plane} plane
     * @returns {boolean}
     */
   intersectsPlane: function ( plane ) {

      // check if the ray lies on the plane first

      var distToPoint = plane.distanceToPoint( this.origin );

      if ( distToPoint === 0 ) {

         return true;

      }

      var denominator = plane.normal.dot( this.direction );

      if ( denominator * distToPoint < 0 ) {

         return true;

      }

      // ray origin is behind the plane (and is pointing behind it)

      return false;

   },
    /**
     * @function
     * @desc 计算当前射线是与参数box相交的交点
     * @param {THREE.Box3} box
     * @param {boolean} optionalTarget
     * @returns {THREE.Vector3}
     */
   intersectBox: function ( box, target ) {

      var tmin, tmax, tymin, tymax, tzmin, tzmax;

      var invdirx = 1 / this.direction.x,
         invdiry = 1 / this.direction.y,
         invdirz = 1 / this.direction.z;

      var origin = this.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;

      }

      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;

      }

      if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;

      // These lines also handle the case where tmin or tmax is NaN
      // (result of 0 * Infinity). x !== x returns true if x is NaN

      if ( tymin > tmin || tmin !== tmin ) tmin = tymin;

      if ( tymax < tmax || tmax !== tmax ) tmax = tymax;

      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;

      }

      if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;

      if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;

      if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;

      //return point closest to the ray (positive side)

      if ( tmax < 0 ) return null;

      return this.at( tmin >= 0 ? tmin : tmax, target );

   },
    /**
     * @function
     * @desc 判断当前射线是与参数box相交
     * @param {THREE.Box3} box
     * @returns {boolean}
     */
   intersectsBox: ( function () {

      var v = new Vector3();

      return function intersectsBox( box ) {

         return this.intersectBox( box, v ) !== null;

      };

   } )(),
    /**
     * @function
     * @desc 计算当前射线和 a ,b ,c组成的三角形相交的交点
     * @param {THREE.Vector3} a
     * @param {THREE.Vector3} b
     * @param {THREE.Vector3} c
     * @param {boolean} backfaceCulling
     * @param {boolean} optionalTarget
     * @return {THREE.Vector3}
     */
   intersectTriangle: function () {

      // Compute the offset origin, edges, and normal.
      var diff = new Vector3();
      var edge1 = new Vector3();
      var edge2 = new Vector3();
      var normal = new Vector3();

      return function intersectTriangle( a, b, c, backfaceCulling, target ) {

         // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h

         edge1.subVectors( b, a );
         edge2.subVectors( c, a );
         normal.crossVectors( 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)
         var DdN = this.direction.dot( normal );
         var sign;

         if ( DdN > 0 ) {

            if ( backfaceCulling ) return null;
            sign = 1;

         } else if ( DdN < 0 ) {

            sign = - 1;
            DdN = - DdN;

         } else {

            return null;

         }

         diff.subVectors( this.origin, a );
         var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) );

         // b1 < 0, no intersection
         if ( DdQxE2 < 0 ) {

            return null;

         }

         var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) );

         // b2 < 0, no intersection
         if ( DdE1xQ < 0 ) {

            return null;

         }

         // b1+b2 > 1, no intersection
         if ( DdQxE2 + DdE1xQ > DdN ) {

            return null;

         }

         // Line intersects triangle, check if ray does.
         var QdN = - sign * diff.dot( normal );

         // t < 0, no intersection
         if ( QdN < 0 ) {

            return null;

         }

         // Ray intersects triangle.
         return this.at( QdN / DdN, target );

      };

   }(),
    /**
     * @desc 对射线进行仿射变换
     * @param {THREE.Matrix4} matrix4
     * @returns {THREE.Ray}
     */
   applyMatrix4: function ( matrix4 ) {

      this.origin.applyMatrix4( matrix4 );
      this.direction.transformDirection( matrix4 );

      return this;

   },

   equals: function ( ray ) {

      return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );

   }

} );


export { Ray };
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值