public static Vector3 ComputeNewTailPosition(Circle3 intersection, Vector3 tailPosition)
        {
            // http://stackoverflow.com/questions/300871/best-way-to-find-a-point-on-a-circle-closest-to-a-given-point
            // Project child's position onto the plane
            var newTailPosition = tailPosition
                                  - Vector3.Dot(intersection.upVector, tailPosition - intersection.origin) * intersection.upVector;
            var v           = newTailPosition - intersection.origin;
            var newPosition = intersection.origin + intersection.radius * v.normalized;

            return(newPosition);
        }
        public static SpringBone.CollisionStatus CheckForCollisionAndReact
        (
            Vector3 localHeadPosition,
            ref Vector3 localTailPosition,
            float localTailRadius,
            Vector3 sphereLocalOrigin,
            float sphereRadius
        )
        {
            var combinedRadius = sphereRadius + localTailRadius;

            if ((localTailPosition - sphereLocalOrigin).sqrMagnitude >= combinedRadius * combinedRadius)
            {
                // Not colliding
                return(SpringBone.CollisionStatus.NoCollision);
            }

            var originToHead = localHeadPosition - sphereLocalOrigin;

            if (originToHead.sqrMagnitude <= sphereRadius * sphereRadius)
            {
                // The head is inside the sphere, so just try to push the tail out
                localTailPosition =
                    sphereLocalOrigin + (localTailPosition - sphereLocalOrigin).normalized * combinedRadius;
                return(SpringBone.CollisionStatus.HeadIsEmbedded);
            }

            var localHeadRadius = (localTailPosition - localHeadPosition).magnitude;
            var intersection    = new Circle3();

            if (ComputeIntersection(
                    localHeadPosition,
                    localHeadRadius,
                    sphereLocalOrigin,
                    combinedRadius,
                    ref intersection))
            {
                localTailPosition = ComputeNewTailPosition(intersection, localTailPosition);
            }

            return(SpringBone.CollisionStatus.TailCollision);
        }
        private static bool FindTangentPoint
        (
            Transform transform,
            Vector3 localSphereOrigin,
            float localSphereRadius,
            Vector3 worldFixedPoint,
            Vector3 worldMovingPoint,
            float worldSegmentRadius,
            ref Vector3 tangentPoint
        )
        {
            var fixedPoint     = transform.InverseTransformPoint(worldFixedPoint) - localSphereOrigin;
            var movingPoint    = transform.InverseTransformPoint(worldMovingPoint) - localSphereOrigin;
            var otherRadius    = transform.InverseTransformDirection(worldSegmentRadius, 0f, 0f).magnitude;
            var combinedRadius = localSphereRadius + otherRadius;

            var ta = new Vector2(0f, 0f);
            var tb = new Vector2(0f, 0f);
            var dd = fixedPoint.magnitude;

            if (!Circle3.FindCircleTangentPoints(dd, combinedRadius, ref ta, ref tb))
            {
                // The fixed point is inside the sphere!
                return(false);
            }

            // todo: It seems like we should be able to rotate based on the angle in 3D directly somehow?
            var xVector       = fixedPoint / dd;
            var fixedToMoving = movingPoint - fixedPoint;
            var yVector       = (fixedToMoving - Vector3.Project(fixedToMoving, xVector)).normalized;
            var tangentPoint1 = ta.x * xVector + ta.y * yVector;
            var tangentPoint2 = tb.x * xVector + tb.y * yVector;
            var isTangentPoint1CloserToMover = (tangentPoint1 - movingPoint).sqrMagnitude < (tangentPoint2 - movingPoint).sqrMagnitude;
            var localTangentPoint            = isTangentPoint1CloserToMover ? tangentPoint1 : tangentPoint2;

            tangentPoint = transform.TransformPoint(localTangentPoint + localSphereOrigin);
            return(true);
        }
        // private

        // http://mathworld.wolfram.com/Sphere-SphereIntersection.html
        public static bool ComputeIntersection
        (
            Vector3 originA,
            float radiusA,
            Vector3 originB,
            float radiusB,
            ref Circle3 intersection
        )
        {
            var aToB = originB - originA;
            var dSqr = aToB.sqrMagnitude;
            var d    = Mathf.Sqrt(dSqr);

            if (d <= 0f)
            {
                return(false);
            }

            var radiusASqr = radiusA * radiusA;
            var radiusBSqr = radiusB * radiusB;

            // Assume a is at the origin and b is at (d, 0 0)
            var denominator        = 0.5f / d;
            var subTerm            = dSqr - radiusBSqr + radiusASqr;
            var x                  = subTerm * denominator;
            var squaredTerm        = subTerm * subTerm;
            var intersectionRadius = Mathf.Sqrt(4f * dSqr * radiusASqr - squaredTerm) * denominator;

            var upVector = aToB / d;
            var origin   = originA + x * upVector;

            intersection.origin   = origin;
            intersection.upVector = upVector;
            intersection.radius   = intersectionRadius;

            return(true);
        }
        public SpringBone.CollisionStatus CheckForCollisionAndReact
        (
            Vector3 moverHeadPosition,
            ref Vector3 moverPosition,
            float moverRadius
        )
        {
            if ((linkedRenderer != null &&
                 !linkedRenderer.enabled) ||
                radius <= 0.0001f)
            {
                return(SpringBone.CollisionStatus.NoCollision);
            }

            if (needToCacheTransform)
            {
                CacheTransform();
            }

            // Lower than start cap
            var localHeadPosition  = worldToLocal.MultiplyPoint3x4(moverHeadPosition);
            var localMoverPosition = worldToLocal.MultiplyPoint3x4(moverPosition);
            var localMoverRadius   = moverRadius * radiusScale;

            var moverIsAboveTop = localMoverPosition.y >= height;
            var useSphereCheck  = (localMoverPosition.y <= 0f) | moverIsAboveTop;

            if (useSphereCheck)
            {
                var sphereOrigin = new Vector3(0f, 0f, 0f);
                sphereOrigin.y = moverIsAboveTop ? height : 0f;

                var combinedRadius = localMoverRadius + radius;
                if ((localMoverPosition - sphereOrigin).sqrMagnitude >= combinedRadius * combinedRadius)
                {
                    // Not colliding
                    return(SpringBone.CollisionStatus.NoCollision);
                }

                var originToHead   = localHeadPosition - sphereOrigin;
                var isHeadEmbedded = originToHead.sqrMagnitude <= radius * radius;

#if UNITY_EDITOR
                RecordSphereCollision(
                    sphereOrigin,
                    localMoverPosition,
                    moverRadius,
                    isHeadEmbedded ?
                    SpringBone.CollisionStatus.HeadIsEmbedded :
                    SpringBone.CollisionStatus.TailCollision);
#endif

                if (isHeadEmbedded)
                {
                    // The head is inside the sphere, so just try to push the tail out
                    localMoverPosition =
                        sphereOrigin + (localMoverPosition - sphereOrigin).normalized * combinedRadius;
                    moverPosition = transform.TransformPoint(localMoverPosition);
                    return(SpringBone.CollisionStatus.HeadIsEmbedded);
                }

                var localHeadRadius = (localMoverPosition - localHeadPosition).magnitude;
                var intersection    = new Circle3();
                if (SpringSphereCollider.ComputeIntersection(
                        localHeadPosition,
                        localHeadRadius,
                        sphereOrigin,
                        combinedRadius,
                        ref intersection))
                {
                    localMoverPosition = SpringSphereCollider.ComputeNewTailPosition(intersection, localMoverPosition);
                    moverPosition      = transform.TransformPoint(localMoverPosition);
                }

                return(SpringBone.CollisionStatus.TailCollision);
            }

            // Cylinder
            var collisionStatus = CheckForCylinderCollisionAndReact(
                localHeadPosition, ref moverPosition, localMoverRadius, localMoverPosition);
            return(collisionStatus);
        }