コード例 #1
0
        //All algorithms return a negative distance if inside the collider.
        public static bool DistanceBetween(float3 point, SphereCollider sphere, float maxDistance, out PointDistanceResultInternal result)
        {
            float3 delta          = sphere.center - point;
            float  pcDistanceSq   = math.lengthsq(delta);  //point center distance
            bool   distanceIsZero = pcDistanceSq == 0.0f;
            float  invPCDistance  = math.select(math.rsqrt(pcDistanceSq), 0.0f, distanceIsZero);
            float3 inNormal       = math.select(delta * invPCDistance, new float3(0, 1, 0), distanceIsZero);  // choose an arbitrary normal when the distance is zero
            float  distance       = pcDistanceSq * invPCDistance - sphere.radius;

            result = new PointDistanceResultInternal
            {
                hitpoint = point + inNormal * distance,
                distance = distance,
                normal   = -inNormal,
            };
            return(distance <= maxDistance);
        }
コード例 #2
0
        //All algorithms return a negative distance if inside the collider.

        /*public static bool DistanceBetween(float3 point, SphereCollider sphere, float maxDistance, out PointDistanceResultInternal result)
         * {
         *  float3 delta = sphere.center - point;
         *  float pcDistanceSq = math.lengthsq(delta);  //point center distance
         *  bool distanceIsZero = pcDistanceSq == 0.0f;
         *  float invPCDistance = math.select(math.rsqrt(pcDistanceSq), 0.0f, distanceIsZero);
         *  float3 inNormal = math.select(delta * invPCDistance, new float3(0, 1, 0), distanceIsZero);  // choose an arbitrary normal when the distance is zero
         *  float distance = pcDistanceSq * invPCDistance - sphere.radius;
         *  result = new PointDistanceResultInternal
         *  {
         *      hitpoint = point + inNormal * distance,
         *      distance = distance,
         *      normal = -inNormal,
         *  };
         *  return distance <= maxDistance;
         * }*/

        /*public static bool DistanceBetween(float3 point, CapsuleCollider capsule, float maxDistance, out PointDistanceResultInternal result)
         * {
         *  //Strategy: Project p onto the capsule's line clamped to the segment. Then inflate point on line as sphere
         *  float3 edge = capsule.pointB - capsule.pointA;
         *  float3 ap = point - capsule.pointA;
         *  float dot = math.dot(ap, edge);
         *  float edgeLengthSq = math.lengthsq(edge);
         *  dot = math.clamp(dot, 0f, edgeLengthSq);
         *  float3 pointOnSegment = capsule.pointA + edge * dot / edgeLengthSq;
         *  SphereCollider sphere = new SphereCollider(pointOnSegment, capsule.radius);
         *  return DistanceBetween(point, sphere, maxDistance, out result);
         * }*/

        /*public static bool DistanceBetween(float3 point, CylinderCollider cylinder, float maxDistance, out PointDistanceResultInternal result)
         * {
         *  //Strategy: Project p onto the capsule's line.
         *  //If on the segment, do point vs sphere.
         *  //Otherwise do point vs disk
         *  float3 edge = cylinder.pointB - cylinder.pointA;
         *  float3 ap = point - cylinder.pointA;
         *  float3 unitEdge = math.normalize(edge);
         *  float dot = math.dot(ap, unitEdge);  //dot is distance of projected point from pointA
         *  float3 pointOnLine = cylinder.pointA + unitEdge * dot;
         *  if (dot < 0f)
         *  {
         *      //Todo: Optimize math
         *      float3 pointOnLineToPoint = point - pointOnLine;  //This gives us our direction from the center of the cap to the edge towards the query point.
         *      if (math.lengthsq(pointOnLineToPoint) > cylinder.radius * cylinder.radius)
         *      {
         *          float3 roundNormal = math.normalize(pointOnLineToPoint);
         *          float3 capNormal = -unitEdge;
         *          result.normal = (roundNormal + capNormal) / math.SQRT2;  //Summing orthogonal unit vectors has a length of sqrt2
         *          result.hitpoint = cylinder.pointA + roundNormal * cylinder.radius;
         *          result.distance = math.distance(point, result.hitpoint);
         *      }
         *      else
         *      {
         *          result.normal = -unitEdge;
         *          result.distance = -dot;
         *          result.hitpoint = point + unitEdge * result.distance;
         *      }
         *      return result.distance <= maxDistance;
         *  }
         *  if (dot * dot > math.lengthsq(edge))
         *  {
         *      //Todo: Optimize math
         *      float3 pointOnLineToPoint = point - pointOnLine;  //This gives us our direction from the center of the cap to the edge towards the query point.
         *      if (math.lengthsq(pointOnLineToPoint) > cylinder.radius * cylinder.radius)
         *      {
         *          float3 roundNormal = math.normalize(pointOnLineToPoint);
         *          float3 capNormal = unitEdge;
         *          result.normal = (roundNormal + capNormal) / math.SQRT2;  //Summing orthogonal unit vectors has a length of sqrt2
         *          result.hitpoint = cylinder.pointB + roundNormal * cylinder.radius;
         *          result.distance = math.distance(point, result.hitpoint);
         *      }
         *      else
         *      {
         *          result.normal = unitEdge;
         *          result.distance = math.distance(pointOnLine, cylinder.pointB);
         *          result.hitpoint = point - unitEdge * result.distance;
         *      }
         *      return result.distance <= maxDistance;
         *  }
         *  else
         *  {
         *      SphereCollider sphere = new SphereCollider(pointOnLine, cylinder.radius);
         *      return DistanceBetween(point, sphere, maxDistance, out result);
         *  }
         * }*/

        public static bool DistanceBetween(float3 point, BoxCollider box, float maxDistance, out PointDistanceResultInternal result)
        {
            //Idea: The positive octant of the box contains 7 feature regions: 3 faces, 3 edges, and inside.
            //The other octants are identical except with flipped signs. So if we unflip signs,
            //calculate the distance for these 7 regions, and then flip signs again, we get a valid result.
            //We use feature regions rather than feature types to avoid swizzling since that would require a second branch.
            float3 osPoint    = point - box.center;                         //os = origin space
            bool3  isNegative = osPoint < 0f;
            float3 ospPoint   = math.select(osPoint, -osPoint, isNegative); //osp = origin space positive
            int    region     = math.csum(math.select(new int3(4, 2, 1), int3.zero, ospPoint < box.halfSize));

            switch (region)
            {
            case 0:
            {
                //Inside the box. Get the closest wall.
                float3 delta   = box.halfSize - ospPoint;
                float  min     = math.cmin(delta);
                bool3  minMask = min == delta;
                //Prioritize y first, then z, then x if multiple distances perfectly match.
                //Todo: Should this be configurabe?
                minMask.xz     &= !minMask.y;
                minMask.x      &= !minMask.z;
                result.hitpoint = math.select(ospPoint, box.halfSize, minMask);
                result.distance = -min;
                result.normal   = math.select(0f, 1f, minMask);
                break;
            }

            case 1:
            {
                //xy in box, z outside
                //Closest feature is the z-face
                result.distance = ospPoint.z - box.halfSize.z;
                result.hitpoint = new float3(ospPoint.xy, box.halfSize.z);
                result.normal   = new float3(0f, 0f, 1f);
                break;
            }

            case 2:
            {
                //xz in box, y outside
                //Closest feature is the y-face
                result.distance = ospPoint.y - box.halfSize.y;
                result.hitpoint = new float3(ospPoint.x, box.halfSize.y, ospPoint.z);
                result.normal   = new float3(0f, 1f, 0f);
                break;
            }

            case 3:
            {
                //x in box, yz outside
                //Closest feature is the x-axis edge
                result.distance = math.distance(ospPoint.yz, box.halfSize.yz);
                result.hitpoint = new float3(ospPoint.x, box.halfSize.yz);
                result.normal   = new float3(0f, math.SQRT2 / 2f, math.SQRT2 / 2f);
                break;
            }

            case 4:
            {
                //yz in box, x outside
                //Closest feature is the x-face
                result.distance = ospPoint.x - box.halfSize.x;
                result.hitpoint = new float3(box.halfSize.x, ospPoint.yz);
                result.normal   = new float3(1f, 0f, 0f);
                break;
            }

            case 5:
            {
                //y in box, xz outside
                //Closest feature is the y-axis edge
                result.distance = math.distance(ospPoint.xz, box.halfSize.xz);
                result.hitpoint = new float3(box.halfSize.x, ospPoint.y, box.halfSize.y);
                result.normal   = new float3(math.SQRT2 / 2f, 0f, math.SQRT2 / 2f);
                break;
            }

            case 6:
            {
                //z in box, xy outside
                //Closest feature is the z-axis edge
                result.distance = math.distance(ospPoint.xy, box.halfSize.xy);
                result.hitpoint = new float3(box.halfSize.xy, ospPoint.z);
                result.normal   = new float3(math.SQRT2 / 2f, math.SQRT2 / 2f, 0f);
                break;
            }

            default:
            {
                //xyz outside box
                ////Closest feature is the osp corner
                result.distance = math.distance(ospPoint, box.halfSize);
                result.hitpoint = box.halfSize;
                result.normal   = math.normalize(math.float3(1f));
                break;
            }
            }
            result.hitpoint = math.select(result.hitpoint, -result.hitpoint, isNegative) + box.center;
            result.normal   = math.select(result.normal, -result.normal, isNegative);
            return(result.distance <= maxDistance);
        }
コード例 #3
0
        public static bool DistanceBetween(float3 point, CapsuleCollider capsule, float maxDistance, out PointDistanceResultInternal result)
        {
            //Strategy: Project p onto the capsule's line clamped to the segment. Then inflate point on line as sphere
            float3 edge         = capsule.pointB - capsule.pointA;
            float3 ap           = point - capsule.pointA;
            float  dot          = math.dot(ap, edge);
            float  edgeLengthSq = math.lengthsq(edge);

            dot = math.clamp(dot, 0f, edgeLengthSq);
            float3         pointOnSegment = capsule.pointA + edge * dot / edgeLengthSq;
            SphereCollider sphere         = new SphereCollider(pointOnSegment, capsule.radius);

            return(DistanceBetween(point, sphere, maxDistance, out result));
        }
コード例 #4
0
        //Distance is unsigned, triangle is "double-sided"

        /*public static bool DistanceBetween(float3 point, TriangleCollider triangle, float maxDistance, out PointDistanceResultInternal result)
         * {
         *  float3 ab = triangle.pointB - triangle.pointA;
         *  float3 bc = triangle.pointC - triangle.pointB;
         *  float3 ca = triangle.pointA - triangle.pointC;
         *  float3 ap = point - triangle.pointA;
         *  float3 bp = point - triangle.pointB;
         *  float3 cp = point - triangle.pointC;
         *
         *  //project point onto plane
         *  //if clockwise, normal faces "up"
         *  float3 planeNormal = math.normalize(math.cross(ab, ca));
         *  float projectionDot = math.dot(planeNormal, point - triangle.pointA);
         *  float3 projectedPoint = point - projectionDot * planeNormal;
         *
         *  //calculate edge planes aligned with triangle normal without the normalization (since it isn't required)
         *  //normals face "inward"
         *  float3 abUnnormal = math.cross(ab, planeNormal);
         *  float3 bcUnnormal = math.cross(bc, planeNormal);
         *  float3 caUnnormal = math.cross(ca, planeNormal);
         *
         *  float3 dots = new float3(math.dot(abUnnormal, ap),
         *                           math.dot(bcUnnormal, bp),
         *                           math.dot(caUnnormal, cp));
         *  int region = math.csum(math.select(new int3(1, 2, 4), int3.zero, dots >= 0f));  //Todo: bitmask?
         *  switch (region)
         *  {
         *      case 0:
         *          {
         *              //all inside, hit plane
         *              result.hitpoint = projectedPoint;
         *              result.distance = math.abs(projectionDot);
         *              break;
         *          }
         *      case 1:
         *          {
         *              //outside ab plane
         *              float abLengthSq = math.lengthsq(ab);
         *              float dot = math.clamp(math.dot(ap, ab), 0f, abLengthSq);
         *              result.hitpoint = triangle.pointA + ab * dot / abLengthSq;
         *              result.distance = math.distance(point, result.hitpoint);
         *              break;
         *          }
         *      case 2:
         *          {
         *              //outside bc plane
         *              float bcLengthSq = math.lengthsq(bc);
         *              float dot = math.clamp(math.dot(bp, bc), 0f, bcLengthSq);
         *              result.hitpoint = triangle.pointB + bc * dot / bcLengthSq;
         *              result.distance = math.distance(point, result.hitpoint);
         *              break;
         *          }
         *      case 3:
         *          {
         *              //outside ab and bc so closest to point b
         *              result.hitpoint = triangle.pointB;
         *              result.distance = math.distance(point, triangle.pointB);
         *              break;
         *          }
         *      case 4:
         *          {
         *              //outside ca plane
         *              float caLengthSq = math.lengthsq(ca);
         *              float dot = math.clamp(math.dot(cp, ca), 0f, caLengthSq);
         *              result.hitpoint = triangle.pointC + ca * dot / caLengthSq;
         *              result.distance = math.distance(point, result.hitpoint);
         *              break;
         *          }
         *      case 5:
         *          {
         *              //outside ab and ca so closest to point a
         *              result.hitpoint = triangle.pointA;
         *              result.distance = math.distance(point, triangle.pointA);
         *              break;
         *          }
         *      case 6:
         *          {
         *              //outside bc and ca so closest to point c
         *              result.hitpoint = triangle.pointC;
         *              result.distance = math.distance(point, triangle.pointC);
         *              break;
         *          }
         *      default:
         *          {
         *              //How the heck did we get here?
         *              throw new InvalidOperationException();
         *              result.hitpoint = projectedPoint;
         *              result.distance = 2f * maxDistance;
         *              break;
         *          }
         *  }
         *  result.normal = math.select(planeNormal, -planeNormal, math.dot(result.hitpoint - point, planeNormal) < 0);
         *  return result.distance <= maxDistance;
         * }*/

        //Distance is unsigned, quad is "double-sided"
        public static bool DistanceBetween(float3 point, simdFloat3 quadPoints, float maxDistance, out PointDistanceResultInternal result)
        {
            simdFloat3 abcd     = new simdFloat3(quadPoints.a, quadPoints.b, quadPoints.c, quadPoints.d);
            simdFloat3 abbccdda = abcd.bcda - abcd.abcd;
            simdFloat3 abcdp    = point - abcd;

            //project point onto plane
            //if clockwise, normal faces "up"
            float3 planeNormal    = math.normalize(math.cross(abbccdda.a, abbccdda.d));  //ab, da
            float  projectionDot  = math.dot(planeNormal, abcdp.a);
            float3 projectedPoint = point - projectionDot * planeNormal;

            //calculate edge planes aligned with quad normal without the normalization (since it isn't required)
            //normals face "inward"
            simdFloat3 abbccddaUnnormal = simd.cross(abbccdda, planeNormal);
            float4     dotsEdges        = simd.dot(abbccddaUnnormal, abcdp);

            if (math.bitmask(dotsEdges < 0f) == 0)  //if (math.all(dotsEdges >= 0))
            {
                result.hitpoint = projectedPoint;
                result.distance = math.abs(projectionDot);
            }
            else
            {
                float3 acUnnormal = math.cross(quadPoints.c - quadPoints.a, planeNormal);              //faces D
                float3 bdUnnormal = math.cross(quadPoints.d - quadPoints.b, planeNormal);              //faces A
                float2 dotsDiags  = new float2(math.dot(acUnnormal, abcdp.a), math.dot(bdUnnormal, abcdp.b));
                int    region     = math.csum(math.select(new int2(1, 2), int2.zero, dotsDiags >= 0)); //Todo: bitmask?
                switch (region)
                {
                case 0:
                {
                    //closest to da
                    var   da         = abbccdda.d;
                    var   dp         = abcdp.d;
                    float daLengthSq = math.lengthsq(da);
                    float dot        = math.clamp(math.dot(dp, da), 0f, daLengthSq);
                    result.hitpoint = quadPoints.d + da * dot / daLengthSq;
                    result.distance = math.distance(point, result.hitpoint);
                    break;
                }

                case 1:
                {
                    //closest to ab
                    var   ab         = abbccdda.a;
                    var   ap         = abcdp.a;
                    float abLengthSq = math.lengthsq(ab);
                    float dot        = math.clamp(math.dot(ap, ab), 0f, abLengthSq);
                    result.hitpoint = quadPoints.a + ab * dot / abLengthSq;
                    result.distance = math.distance(point, result.hitpoint);
                    break;
                }

                case 2:
                {
                    //closest to bc
                    var   bc         = abbccdda.b;
                    var   bp         = abcdp.b;
                    float bcLengthSq = math.lengthsq(bc);
                    float dot        = math.clamp(math.dot(bp, bc), 0f, bcLengthSq);
                    result.hitpoint = quadPoints.b + bc * dot / bcLengthSq;
                    result.distance = math.distance(point, result.hitpoint);
                    break;
                }

                case 3:
                {
                    //closest to cd
                    var   cd         = abbccdda.c;
                    var   cp         = abcdp.c;
                    float cdLengthSq = math.lengthsq(cd);
                    float dot        = math.clamp(math.dot(cp, cd), 0f, cdLengthSq);
                    result.hitpoint = quadPoints.c + cd * dot / cdLengthSq;
                    result.distance = math.distance(point, result.hitpoint);
                    break;
                }

                default:
                {
                    //How the heck did we get here?
                    //throw new InvalidOperationException();
                    result.hitpoint = projectedPoint;
                    result.distance = maxDistance * 2f;
                    break;
                }
                }
            }

            result.normal = math.select(planeNormal, -planeNormal, math.dot(result.hitpoint - point, planeNormal) < 0);
            return(result.distance <= maxDistance);
        }