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 BuildDesiredVelocity(
        FlockingNeighborDetector neighborDetector)
    {
        Vector3 avoidanceVelocity = BuildAvoidanceBehaviorVelocity(neighborDetector);
        float   avoidanceSpeed    = avoidanceVelocity.magnitude;

        Vector3 cohesionVelocity = BuildCohesionBehaviorVelocity(neighborDetector);
        float   cohesionSpeed    = cohesionVelocity.magnitude;

        Vector3 idlingVelocity = BuildIdlingBehaviorVelocity();
        float   idlingSpeed    = idlingVelocity.magnitude;

        Vector3 orientationVelocity = BuildOrientationBehaviorVelocity(neighborDetector);
        float   orientationSpeed    = orientationVelocity.magnitude;

        float naiveTotalSpeed = (
            avoidanceSpeed +
            cohesionSpeed +
            idlingSpeed +
            orientationSpeed);

        if (debugLogAllDesireVectors)
        {
            Debug.LogFormat(
                "[avoidance {0}], [cohesion {1}], [idling {2}], [orientation {3}]",
                avoidanceVelocity,
                cohesionVelocity,
                idlingVelocity,
                orientationVelocity);
        }

        // To avoid having the behaviors increase our speed purely by merit of their existence, we weight each
        // behavior by its requested speed versus the others. A single behavior is still able to request an increased
        // overall speed, which will also wind up smoothly "drowning out" any behaviors making relatively minor responses.
        // Consideration: The behavior functions could return an explicit weight in addition to the velocity, which would permit requests to slow down.
        Vector3 desiredVelocity = (
            (avoidanceVelocity * (avoidanceSpeed / naiveTotalSpeed)) +
            (cohesionVelocity * (cohesionSpeed / naiveTotalSpeed)) +
            (idlingVelocity * (idlingSpeed / naiveTotalSpeed)) +
            (orientationVelocity * (orientationSpeed / naiveTotalSpeed)));

        if (debugDisplayAllDesireVectors)
        {
            Debug.DrawLine(transform.position, (transform.position + avoidanceVelocity), Color.red);
            Debug.DrawLine(transform.position, (transform.position + cohesionVelocity), Color.blue);
            Debug.DrawLine(transform.position, (transform.position + idlingVelocity), Color.grey);
            Debug.DrawLine(transform.position, (transform.position + orientationVelocity), Color.green);
            Debug.DrawLine(transform.position, (transform.position + desiredVelocity), Color.white);
        }

        return(desiredVelocity);
    }
    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);
    }