Code/General/BBoxInt.cs
using Sandbox;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace ExtendedBox.General;

public struct BBoxInt : IEquatable<BBoxInt>
{
    [JsonInclude]
    public Vector3Int Mins;
    [JsonInclude]
    public Vector3Int Maxs;

    [JsonIgnore]
    public IEnumerable<Vector3Int> Corners
    {
        get
        {
            yield return new(Mins.x, Mins.y, Mins.z);
            yield return new(Maxs.x, Mins.y, Mins.z);
            yield return new(Maxs.x, Maxs.y, Mins.z);
            yield return new(Mins.x, Maxs.y, Mins.z);
            yield return new(Mins.x, Mins.y, Maxs.z);
            yield return new(Maxs.x, Mins.y, Maxs.z);
            yield return new(Maxs.x, Maxs.y, Maxs.z);
            yield return new(Mins.x, Maxs.y, Maxs.z);
        }
    }

    [JsonIgnore]
    public readonly Vector3 Center => Mins + Size * 0.5f;
    [JsonIgnore]
    public readonly Vector3Int Size => Maxs - Mins;
    [JsonIgnore]
    public readonly Vector3 Extents => Size * 0.5f;

    [JsonIgnore]
    public readonly Vector3 RandomPointInside => Random.Shared.VectorInCube(in this);

    [JsonIgnore]
    public readonly Vector3 RandomPointOnEdge
    {
        get
        {
            Vector3 size = Size;
            Vector3 vector = size;
            vector.x *= Game.Random.Float(0f, 1f);
            vector.y *= Game.Random.Float(0f, 1f);
            vector.z *= Game.Random.Float(0f, 1f);
            switch(Random.Shared.Int(0, 5))
            {
                case 0:
                    vector.x = 0f;
                    break;
                case 1:
                    vector.y = 0f;
                    break;
                case 2:
                    vector.z = 0f;
                    break;
                case 3:
                    vector.x = size.x;
                    break;
                case 4:
                    vector.y = size.y;
                    break;
                case 5:
                    vector.z = size.z;
                    break;
            }

            return Mins + vector;
        }
    }

    [JsonIgnore]
    public readonly float Volume
    {
        get
        {
            Vector3 vector = Size.Abs();
            return vector.x * vector.y * vector.z;
        }
    }

    #region constructors
    public BBoxInt(Vector3Int mins, Vector3Int maxs)
    {
        Mins = Vector3Int.Min(mins, maxs);
        Maxs = Vector3Int.Max(mins, maxs);
    }


    public static BBoxInt FromHeightAndRadius(int height, Vector2Int radius)
    {
        var mins = new Vector3Int(-radius.x, -radius.y, 0);
        var maxs = new Vector3Int(radius.x, radius.y, height);

        return new(mins, maxs);
    }

    public static BBoxInt FromMinsAndSize(in Vector3Int mins, Vector3Int size = default)
    {
        BBoxInt result = default;
        result.Mins = mins;
        result.Maxs = mins + size;
        return result;
    }

    public static BBoxInt FromPositionAndRadius(in Vector3Int center, Vector3Int radius = default)
    {
        BBoxInt result = default;
        result.Mins = center - radius;
        result.Maxs = center + radius;
        return result;
    }

    public static BBoxInt FromBoxes(IEnumerable<BBoxInt> boxes)
    {
        using IEnumerator<BBoxInt> enumerator = boxes.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        BBoxInt result = enumerator.Current;
        while(enumerator.MoveNext())
        {
            BBoxInt bbox = enumerator.Current;
            result = result.AddBBox(in bbox);
        }

        return result;
    }

    public static BBoxInt FromPoints(IEnumerable<Vector3Int> points)
    {
        using IEnumerator<Vector3Int> enumerator = points.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        Vector3Int center = enumerator.Current;
        BBoxInt result = new(center, center);
        while(enumerator.MoveNext())
        {
            center = enumerator.Current;
            result = result.AddPoint(center);
        }

        return result;
    }

    public static BBoxInt FromPointsAndRadius(IEnumerable<Vector3Int> points, Vector3Int radius = default)
    {
        using IEnumerator<Vector3Int> enumerator = points.GetEnumerator();
        if(!enumerator.MoveNext())
        {
            return default;
        }

        Vector3Int center = enumerator.Current;
        BBoxInt result = FromPositionAndRadius(in center, radius);
        while(enumerator.MoveNext())
        {
            center = enumerator.Current;
            BBoxInt bbox = FromPositionAndRadius(in center, radius);
            result = result.AddBBox(in bbox);
        }

        return result;
    }
    #endregion

    #region convertation methods
    public readonly BBox Translate(in Vector3 point)
    {
        BBox result = this;
        result.Mins += point;
        result.Maxs += point;
        return result;
    }
    public readonly BBoxInt Translate(in Vector3Int point)
    {
        BBoxInt result = this;
        result.Mins += point;
        result.Maxs += point;
        return result;
    }

    public readonly BBox Rotate(in Rotation rotation)
    {
        BBox result = this;
        Rotation normal = rotation.Conjugate.Normal;
        Vector3 vector = Vector3.Forward * normal;
        Vector3 vector2 = Vector3.Right * normal;
        Vector3 vector3 = Vector3.Up * normal;
        Vector3 c = 0.5f * (result.Mins + result.Maxs);
        Vector3 vector4 = result.Maxs - c;
        Vector3 vector5 = rotation * c;
        Vector3 vector6 = new Vector3(MathF.Abs(vector4.x * vector.x) + MathF.Abs(vector4.y * vector.y) + MathF.Abs(vector4.z * vector.z), MathF.Abs(vector4.x * vector2.x) + MathF.Abs(vector4.y * vector2.y) + MathF.Abs(vector4.z * vector2.z), MathF.Abs(vector4.x * vector3.x) + MathF.Abs(vector4.y * vector3.y) + MathF.Abs(vector4.z * vector3.z));
        result.Mins = vector5 - vector6;
        result.Maxs = vector5 + vector6;
        return result;
    }

    public readonly BBox Transform(in Transform transform)
    {
        return Scale(in transform.Scale).Rotate(in transform.Rotation).Translate(in transform.Position);
    }

    public readonly BBox Scale(in Vector3 scale)
    {
        var mins = -scale * Extents + Center;
        var maxs = scale * Extents + Center;

        return new BBox(mins, maxs);
    }

    public readonly bool Contains(in BBoxInt b, bool includeMaxs = false)
    {
        if(includeMaxs)
        {
            return b.Mins.x >= Mins.x && b.Maxs.x <= Maxs.x &&
                b.Mins.y >= Mins.y && b.Maxs.y <= Maxs.y &&
                b.Mins.z >= Mins.z && b.Maxs.z <= Maxs.z;
        }


        return b.Mins.x >= Mins.x && b.Maxs.x < Maxs.x &&
            b.Mins.y >= Mins.y && b.Maxs.y < Maxs.y &&
            b.Mins.z >= Mins.z && b.Maxs.z < Maxs.z;
    }

    public readonly bool Contains(in BBox b, bool includeMaxs = false)
    {
        if(includeMaxs)
        {
            return b.Mins.x >= Mins.x && b.Maxs.x <= Maxs.x &&
                b.Mins.y >= Mins.y && b.Maxs.y <= Maxs.y &&
                b.Mins.z >= Mins.z && b.Maxs.z <= Maxs.z;
        }


        return b.Mins.x >= Mins.x && b.Maxs.x < Maxs.x &&
            b.Mins.y >= Mins.y && b.Maxs.y < Maxs.y &&
            b.Mins.z >= Mins.z && b.Maxs.z < Maxs.z;
    }

    public readonly bool Contains(in Vector3 b, bool includeMaxs = false)
    {
        if(includeMaxs)
        {
            return b.x >= Mins.x && b.x <= Maxs.x &&
                b.y >= Mins.y && b.y <= Maxs.y &&
                b.z >= Mins.z && b.z <= Maxs.z;
        }

        return b.x >= Mins.x && b.x < Maxs.x &&
            b.y >= Mins.y && b.y < Maxs.y &&
            b.z >= Mins.z && b.z < Maxs.z;
    }

    public readonly bool Overlaps(in BBox b)
    {
        return Mins.x < b.Maxs.x && b.Mins.x < Maxs.x &&
            Mins.y < b.Maxs.y && b.Mins.y < Maxs.y &&
            Mins.z < b.Maxs.z && b.Mins.z < Maxs.z;
    }

    public readonly BBox GetIntersection(BBox other) => other.GetIntersection(this);
    public readonly BBoxInt GetIntersection(BBoxInt other)
    {
        if(!Overlaps(other))
            return new(Vector3Int.Zero, Vector3Int.Zero);

        return new(Mins.ComponentMax(other.Mins), Maxs.ComponentMin(other.Maxs));
    }

    public readonly BBox AddPoint(in Vector3 point)
    {
        BBox result = this;
        result.Mins = Vector3.Min(Mins, in point);
        result.Maxs = Vector3.Max(Maxs, in point);
        return result;
    }

    public readonly BBoxInt AddPoint(in Vector3Int point)
    {
        BBoxInt result = this;
        result.Mins = Vector3Int.Min(Mins, point);
        result.Maxs = Vector3Int.Max(Maxs, point);
        return result;
    }

    public readonly BBox AddBBox(in BBox bbox)
    {
        BBox result = this;
        result.Mins = Vector3.Min(Mins, in bbox.Mins);
        result.Maxs = Vector3.Max(Maxs, in bbox.Maxs);
        return result;
    }

    public readonly BBoxInt AddBBox(in BBoxInt bbox)
    {
        BBoxInt result = this;
        result.Mins = Vector3Int.Min(Mins, bbox.Mins);
        result.Maxs = Vector3Int.Max(Maxs, bbox.Maxs);
        return result;
    }

    public readonly BBox Grow(in Vector3 skin)
    {
        BBox result = this;
        result.Mins -= skin;
        result.Maxs += skin;
        return result;
    }

    public readonly BBoxInt Grow(in Vector3Int skin)
    {
        BBoxInt result = this;
        result.Mins -= skin;
        result.Maxs += skin;
        return result;
    }

    public readonly Vector3 ClosestPoint(in Vector3 point)
    {
        return Vector3.Clamp(in point, Mins, Maxs);
    }
    #endregion

    #region operators
    public static BBox operator *(BBoxInt c1, Vector3 c2)
    {
        return new(c1.Mins * c2, c1.Maxs * c2);
    }

    public static BBoxInt operator *(BBoxInt c1, Vector3Int c2)
    {
        c1.Mins *= c2;
        c1.Maxs *= c2;
        return c1;
    }

    public static BBox operator +(BBoxInt c1, Vector3 c2)
    {
        return new(c1.Mins + c2, c1.Maxs + c2);
    }

    public static BBoxInt operator +(BBoxInt c1, Vector3Int c2)
    {
        c1.Mins += c2;
        c1.Maxs += c2;
        return c1;
    }

    public static bool operator ==(BBoxInt left, BBoxInt right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(BBoxInt left, BBoxInt right)
    {
        return !(left == right);
    }

    public static implicit operator BBox(BBoxInt bBoxInt) => new(bBoxInt.Mins, bBoxInt.Maxs);
    public static explicit operator BBoxInt(BBox bBoxInt) => new((Vector3Int)bBoxInt.Mins, (Vector3Int)bBoxInt.Maxs);
    #endregion


    public readonly bool Trace(in Ray ray, float distance, out float hitDistance)
    {
        return ((BBox)this).Trace(ray, distance, out hitDistance);
    }


    public override readonly string ToString()
    {
        return $"mins {Mins}, maxs {Maxs}";
    }


    public readonly BBox Snap(float distance)
    {
        return new BBox(Mins.SnapToGrid(distance), Maxs.SnapToGrid(distance));
    }

    public readonly BBoxInt Snap(int distance)
    {
        return new BBoxInt(Mins.SnapToGrid(distance), Maxs.SnapToGrid(distance));
    }



    public override readonly bool Equals(object? obj)
    {
        if(obj is BBoxInt o)
            return Equals(o);
        return false;
    }

    public readonly bool Equals(BBoxInt o)
    {
        return Mins == o.Mins && Maxs == o.Maxs;
    }

    public override readonly int GetHashCode()
    {
        return HashCode.Combine(Mins, Maxs);
    }
}