Skip to main content
3 of 3
replaced http://stackoverflow.com/ with https://stackoverflow.com/

After some others help and Google, I come up with this solution (written in C#).

AngleRange

public class AngleRange 
{
    private readonly double _from, _to;
    private readonly bool _full;

    public AngleRange (double from, double to)
    {
        _from = DoubleUtils.NormalizeAngle(from);
        _to = DoubleUtils.NormalizeAngle(to);
        _full = false;
    }

    public AngleRange (bool full)
    {
        _from = 0;
        _to = 0;
        _full = full;
    }

    public bool Inside(double target)
    {
        if (_full)
            return true;
        if (_from < _to)
            return _from.AboutLesserThanOrEqual(target) && target.AboutLesserThanOrEqual(_to);
        return _from.AboutLesserThanOrEqual(target) || target.AboutLesserThanOrEqual(_to);
    }

    public bool TryMerge(AngleRange newAngleRange, out AngleRange resultAngleRange)
    {
        if (newAngleRange._full)
        {
            resultAngleRange = newAngleRange;
            return true;
        }

        if (_full || Equals(newAngleRange))
        {
            resultAngleRange = this;
            return true;
        }

        var aStart = _from;
        var aEnd = aStart > _to ? _to + 360 : _to;
        var bStart = newAngleRange._from;
        var bEnd = bStart > newAngleRange._to 
                   ? newAngleRange._to + 360 : newAngleRange._to;

        var diffA = (aEnd - aStart)/2;
        var diffB = (bEnd - bStart)/2;
        var avgA = (aStart + aEnd)/2;
        var avgB = (bStart + bEnd)/2;
        var cosDiffA = Math.Cos(diffA.ToRadians());
        var cosDiffB = Math.Cos(diffB.ToRadians());

        var resultFlag = MergeFlag.None;

        if (Math.Cos((avgA - bStart).ToRadians()).AboutGreaterThanOrEqual(cosDiffA))
            resultFlag |= MergeFlag.BStartInsideA;
        if (Math.Cos((avgA - bEnd).ToRadians()).AboutGreaterThanOrEqual(cosDiffA))
            resultFlag |= MergeFlag.BEndInsideA;
        if (Math.Cos((avgB - aStart).ToRadians()).AboutGreaterThanOrEqual(cosDiffB))
            resultFlag |= MergeFlag.AStartInsideB;
        if (Math.Cos((avgB - aEnd).ToRadians()).AboutGreaterThanOrEqual(cosDiffB))
            resultFlag |= MergeFlag.AEndInsideB;

        if (NotHasFlags(resultFlag, MergeFlag.BStartInsideA, MergeFlag.BEndInsideA,
            MergeFlag.AEndInsideB, MergeFlag.AStartInsideB))
        {
            resultAngleRange = null;
            return false;
        }

        if (HasFlags(resultFlag, MergeFlag.BStartInsideA, MergeFlag.BEndInsideA,
            MergeFlag.AEndInsideB, MergeFlag.AStartInsideB))
        {
            resultAngleRange = new Shadow(true);
            return true;
        }

        if (HasFlags(resultFlag, MergeFlag.BStartInsideA, MergeFlag.BEndInsideA))
        {
            resultAngleRange = this;
            return true;
        }

        if (HasFlags(resultFlag, MergeFlag.AEndInsideB, MergeFlag.AStartInsideB))
        {
            resultAngleRange = newAngleRange;
            return true;
        }

        if (HasFlags(resultFlag, MergeFlag.AEndInsideB, MergeFlag.BStartInsideA))
        {
            resultAngleRange = new Shadow(aStart, bEnd);
            return true;
        }

        if (!HasFlags(resultFlag, MergeFlag.AStartInsideB, MergeFlag.BEndInsideA))
        {
            resultAngleRange = new Shadow(bStart, aEnd);
            return true;
        }
        throw new InvalidOperationException("This should never happen.");
    }

    private static bool HasFlags(MergeFlag resultFlag, params MergeFlag[] flags)
    {
        return flags.All(flag => (resultFlag & flag) == flag);
    }

    private static bool NotHasFlags(MergeFlag resultFlag, params MergeFlag[] flags)
    {
        return flags.All(flag => (resultFlag & flag) != flag);
    }
}

MergeFlag

[Flags]
private enum MergeFlag : short
{
    None = 0,
    BStartInsideA = 1 << 0,
    BEndInsideA = 1 << 1,
    AStartInsideB = 1 << 2,
    AEndInsideB = 1 << 3
}

Using AboutGreaterThanOrEqual() and AboutLesserThanOrEqual() instead of >= and <= because of rounding error on edge cases. It is a method combine thin AboutEqual() from here and > or <.

_full represents AngleRange is a full circle. ToRadians() is a method that coverts degree into Radians.

Joshua
  • 175
  • 8