private static GunAmmoGroup[][] GetAmmoGroupings(GunGroup[][] gunCombos, IEnumerable<AmmoBox> boxes)
        {
            double smallestDemand = gunCombos[0].Min(o => o.DemandPerGun);      // only need to look at one of the combos, because each combo has all guns
            double smallestCaliber = gunCombos[0].Min(o => o.Caliber);

            // Commit the boxes to an array that will be reused for each combination of guns
            AmmoBox[] sortedBoxes = boxes.
                Where(o => o.QuantityMax >= smallestDemand && Math1D.Min(o.ScaleActual.X, o.ScaleActual.Y, o.ScaleActual.Z) >= smallestCaliber).        // only keep what will fit the smallest gun
                OrderByDescending(o => o.QuantityMax).      // order the boxes descending so that the largest gets assigned first
                ToArray();

            // Get the distance between each ammo box and each gun
            Tuple<long, long, double>[] ammo_gun_distance = gunCombos[0].
                SelectMany(o => o.Guns).
                SelectMany(gun => sortedBoxes.Select(box => Tuple.Create(box.Token, gun.Token, (box.Position - gun.Position).Length))).
                ToArray();

            return gunCombos.
                AsParallel().
                Select(o => GetAmmoGroupings_Assign(o, sortedBoxes, ammo_gun_distance)).       // Assign all the boxes to this arrangment of guns
                ToArray();
        }