private Vector3 BuildOrientationBehaviorVelocity(
        FlockingNeighborDetector neighborDetector)
    {
        Vector3 summedNeighborVelocity = Vector3.zero;
        float   summedNeighborConsiderationFractions = 0.0f;

        foreach (var neighbor in neighborDetector.GetNeighbors())
        {
            var neighborFlightController = neighbor.flockingDesires.GetComponent <FlightController>();

            if (neighborFlightController != null)
            {
                float neighborCurrentSpeed = IdlingSpeed;                 // neighborFlightController.CurrentSpeed; // BUG! Boids were feeding back into each other and creating explosive velocities, so for now we can't read CurrentSpeed for this.

                Vector3 neighborActualVelocity = (neighborCurrentSpeed * neighbor.flockingDesires.transform.forward);

                // Blend in our consideration of the neighbor so as to avoid twitching when they're first sighted.
                Vector3 neighborWeightedVelocity = (neighbor.currentConsiderationFraction * neighborActualVelocity);

                summedNeighborVelocity += neighborWeightedVelocity;
                summedNeighborConsiderationFractions += neighbor.currentConsiderationFraction;
            }
        }

        Vector3 meanNeighborVelocity = (summedNeighborVelocity / Mathf.Max(summedNeighborConsiderationFractions, 1.0f));

        return(meanNeighborVelocity);
    }
    private Vector3 BuildCohesionBehaviorVelocity(
        FlockingNeighborDetector neighborDetector)
    {
        Vector3 summedSelfToNeighborDeltas           = Vector3.zero;
        float   summedNeighborConsiderationFractions = 1.0f;       // NOTE: We're including ourselves in the center-of-mass calculation.

        foreach (var neighbor in neighborDetector.GetNeighbors())
        {
            Vector3 selfToNeighborDelta = (neighbor.flockingDesires.transform.position - transform.position);

            // Blend in our consideration of the neighbor so as to avoid twitching when they're first sighted.
            summedSelfToNeighborDeltas           += (neighbor.currentConsiderationFraction * selfToNeighborDelta);
            summedNeighborConsiderationFractions += neighbor.currentConsiderationFraction;
        }

        Vector3 selfToCenterOfMass         = (summedSelfToNeighborDeltas / summedNeighborConsiderationFractions);
        float   selfToCenterOfMassDistance = selfToCenterOfMass.magnitude;

        float initialNeighborThrottleFraction  = Mathf.Clamp01(summedNeighborConsiderationFractions);
        float peakSatisfactionThrottleFraction = Mathf.Clamp01(selfToCenterOfMassDistance / CohesionSatisfactionFalloffDistance);

        float throttleFraction = (initialNeighborThrottleFraction * peakSatisfactionThrottleFraction);
        float throttleSpeed    = (CohesionMaxSpeed * throttleFraction);

        Vector3 cohesionDirection = (selfToCenterOfMass / Mathf.Max(selfToCenterOfMassDistance, Mathf.Epsilon));
        Vector3 cohesionVelocity  = (throttleSpeed * cohesionDirection);

        return(cohesionVelocity);
    }
    private Vector3 BuildAvoidanceBehaviorVelocity(
        FlockingNeighborDetector neighborDetector)
    {
        Vector3 naiveNeighborhoodAvoidanceVelocity = Vector3.zero;

        foreach (var neighbor in neighborDetector.GetNeighbors())
        {
            Vector3 selfToNeighborDirection      = Vector3.zero;
            float   normalizedDistanceToNeighbor = 0.0f;

            // Choose our direction and distance, but explicitly handling edge cases such as sharing the same
            // position as our neighbor, which do terrible things (eg. NaN-vectors) when left unhandled.
            {
                Vector3 selfToNeighborDelta    = (neighbor.flockingDesires.transform.position - transform.position);
                float   selfToNeighborDistance = selfToNeighborDelta.magnitude;

                if (selfToNeighborDistance <= AvoidanceRandomPanicDistance)
                {
                    selfToNeighborDirection      = Random.onUnitSphere;
                    normalizedDistanceToNeighbor = AvoidanceRandomPanicDistance;
                }
                else
                {
                    selfToNeighborDirection      = (selfToNeighborDelta / selfToNeighborDistance);
                    normalizedDistanceToNeighbor = (selfToNeighborDistance / AvoidanceBaseDistance);
                }
            }

            float neighborAvoidanceSpeed =
                Mathf.Min(
                    (AvoidanceBaseSpeed / normalizedDistanceToNeighbor),
                    AvoidanceMaxSpeed);

            // Blend in our consideration of the neighbor so as to avoid twitching when they're first sighted.
            naiveNeighborhoodAvoidanceVelocity += (
                (neighbor.currentConsiderationFraction * neighborAvoidanceSpeed) *
                (-1 * selfToNeighborDirection));
        }

        float naiveNeighborhoodAvoidanceSpeed = naiveNeighborhoodAvoidanceVelocity.magnitude;

        Vector3 neighborhoodAvoidanceDirection = (naiveNeighborhoodAvoidanceVelocity / Mathf.Max(naiveNeighborhoodAvoidanceSpeed, Mathf.Epsilon));

        Vector3 finalNeighborhoodAvoidanceVelocity = (Mathf.Min(naiveNeighborhoodAvoidanceSpeed, AvoidanceMaxSpeed) * neighborhoodAvoidanceDirection);

        return(finalNeighborhoodAvoidanceVelocity);
    }