// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE // ReSharper disable once CheckNamespace namespace WallstopStudios.UnityHelpers.Core.Extension { using UnityEngine; using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters; public static partial class UnityExtensions { public static bool FastIntersects(this Bounds bounds, Bounds other) { // Degenerate bounds (zero volume) do not intersect Vector3 sizeA = bounds.size; Vector3 sizeB = other.size; if ( sizeA.x <= 0f || sizeA.y <= 0f || sizeA.z <= 0f || sizeB.x <= 0f || sizeB.y <= 0f || sizeB.z <= 0f ) { return false; } Vector3 boundsMin = bounds.min; Vector3 otherMax = other.max; if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y || otherMax.z < boundsMin.z) { return false; } Vector3 boundsMax = bounds.max; Vector3 otherMin = other.min; return boundsMax.x >= otherMin.x && boundsMax.y >= otherMin.y && boundsMax.z >= otherMin.z; } /// /// Fast 2D containment test for BoundsInt and FastVector3Int (ignores Z axis). /// /// The bounds to test containment in. /// The position to test. /// True if the position is inside the 2D bounds (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Four comparisons. /// Allocations: None. /// Unity Behavior: Uses half-open interval [min, max) for containment test. /// Edge Cases: Point on max boundary is NOT contained. Z coordinate is ignored. /// public static bool FastContains2D(this BoundsInt bounds, FastVector3Int position) { return position.x >= bounds.xMin && position.y >= bounds.yMin && position.x < bounds.xMax && position.y < bounds.yMax; } /// /// Fast 2D intersection test for BoundsInt (ignores Z axis). /// /// The first bounds. /// The second bounds to test intersection with. /// True if the 2D bounds intersect (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Optimized comparisons. /// Allocations: None. /// Unity Behavior: Uses BoundsInt min/max properties. /// Edge Cases: Zero-size bounds (size <= 0 in X or Y) cannot intersect and return false. /// Bounds that touch at an edge are considered intersecting (inclusive). Z axis is ignored. /// public static bool FastIntersects2D(this BoundsInt bounds, BoundsInt other) { // Zero-size bounds cannot intersect if (bounds.size.x <= 0 || bounds.size.y <= 0 || other.size.x <= 0 || other.size.y <= 0) { return false; } if (other.xMax < bounds.xMin || other.yMax < bounds.yMin) { return false; } return bounds.xMax >= other.xMin && bounds.yMax >= other.yMin; } /// /// Fast 2D containment test for Bounds and Vector2 (ignores Z axis). /// /// The bounds to test containment in. /// The 2D position to test. /// True if the position is inside the 2D bounds (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls beyond property access. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Optimized to cache min/max values. /// Allocations: None. /// Unity Behavior: Uses closed interval [min, max] for containment test (unlike BoundsInt). /// Edge Cases: Points on the boundary ARE contained. Z coordinate is ignored. /// public static bool FastContains2D(this Bounds bounds, Vector2 position) { Vector3 min = bounds.min; if (position.x < min.x || position.y < bounds.min.y) { return false; } Vector3 max = bounds.max; return position.x <= max.x && position.y <= max.y; } /// /// Fast 2D containment test to check if one Bounds contains another (ignores Z axis). /// /// The outer bounds. /// The inner bounds to test if contained. /// True if other is completely inside bounds (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls beyond property access. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Optimized to cache min/max values. /// Allocations: None. /// Unity Behavior: Uses Bounds min/max properties. /// Edge Cases: If other touches the boundary of bounds, it's still considered contained. /// Z axis is ignored. /// public static bool FastContains2D(this Bounds bounds, Bounds other) { Vector3 boundsMin = bounds.min; Vector3 otherMin = other.min; if (otherMin.x < boundsMin.x || otherMin.y < boundsMin.y) { return false; } Vector3 boundsMax = bounds.max; Vector3 otherMax = other.max; return otherMax.x <= boundsMax.x && otherMax.y <= boundsMax.y; } /// /// Fast 2D intersection test for Bounds (ignores Z axis). /// /// The first bounds. /// The second bounds to test intersection with. /// True if the 2D bounds intersect (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls beyond property access. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Optimized to cache min/max values. /// Allocations: None. /// Unity Behavior: Uses Bounds min/max properties. /// Edge Cases: Bounds that touch at edges are considered intersecting (inclusive). Z axis is ignored. /// public static bool FastIntersects2D(this Bounds bounds, Bounds other) { Vector3 boundsMin = bounds.min; Vector3 otherMax = other.max; if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y) { return false; } Vector3 boundsMax = bounds.max; Vector3 otherMin = other.min; return boundsMax.x >= otherMin.x && boundsMax.y >= otherMin.y; } /// /// Fast 2D overlap test for Bounds (ignores Z axis). Functionally identical to FastIntersects2D. /// /// The first bounds. /// The second bounds to test overlap with. /// True if the 2D bounds overlap (XY plane only); otherwise, false. /// /// Thread Safety: Thread-safe, no Unity API calls beyond property access. /// Null Handling: Not applicable for value types. /// Performance: O(1) - Optimized to cache min/max values. /// Allocations: None. /// Unity Behavior: Uses Bounds min/max properties. Identical to FastIntersects2D. /// Edge Cases: Bounds that touch but don't overlap return false. Z axis is ignored. /// public static bool Overlaps2D(this Bounds bounds, Bounds other) { Vector3 boundsMin = bounds.min; Vector3 otherMax = other.max; if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y) { return false; } Vector3 boundsMax = bounds.max; Vector3 otherMin = other.min; return boundsMax.x >= otherMin.x && boundsMax.y >= otherMin.y; } // ========================= // 3D Bounds helpers (opt-in tolerance) // ========================= /// /// Fast 3D point containment with optional tolerance and half-open semantics [min, max). /// A point on the max face is NOT contained. /// public static bool FastContains3D(this Bounds bounds, Vector3 p, float tolerance = 0f) { Vector3 min = bounds.min; Vector3 max = bounds.max; return p.x >= min.x - tolerance && p.x < max.x + tolerance && p.y >= min.y - tolerance && p.y < max.y + tolerance && p.z >= min.z - tolerance && p.z < max.z + tolerance; } /// /// Fast 3D containment test (box in box) with optional tolerance and inclusive semantics on max faces. /// Returns true if 'other' is fully inside or touching 'bounds' (with tolerance). /// public static bool FastContains3D(this Bounds bounds, Bounds other, float tolerance = 0f) { Vector3 min = bounds.min; Vector3 max = bounds.max; Vector3 omin = other.min; Vector3 omax = other.max; if ( omin.x < min.x - tolerance || omin.y < min.y - tolerance || omin.z < min.z - tolerance ) { return false; } return omax.x <= max.x + tolerance && omax.y <= max.y + tolerance && omax.z <= max.z + tolerance; } // ========================= // 3D Bounds helpers (opt-in tolerance) // ========================= /// /// Fast 3D bounds intersection with optional tolerance. /// Touching at faces is considered intersection (inclusive at boundaries). /// public static bool FastIntersects3D(this Bounds a, Bounds b, float tolerance = 0f) { // Degenerate bounds (zero volume) do not intersect Vector3 asize = a.size; Vector3 bsize = b.size; if ( asize.x <= 0f || asize.y <= 0f || asize.z <= 0f || bsize.x <= 0f || bsize.y <= 0f || bsize.z <= 0f ) { return false; } Vector3 amin = a.min; Vector3 bmax = b.max; if ( bmax.x < amin.x - tolerance || bmax.y < amin.y - tolerance || bmax.z < amin.z - tolerance ) { return false; } Vector3 amax = a.max; Vector3 bmin = b.min; return amax.x + tolerance >= bmin.x && amax.y + tolerance >= bmin.y && amax.z + tolerance >= bmin.z; } /// /// Fast 3D containment test (box in box) with optional tolerance and half-open semantics on max faces. /// Returns true only if 'other' is fully inside 'bounds' and does NOT touch the max faces. /// Equivalent to: other.min >= bounds.min and other.max < bounds.max (with tolerance). /// public static bool FastContainsHalfOpen3D( this Bounds bounds, Bounds other, float tolerance = 0f ) { Vector3 min = bounds.min; Vector3 max = bounds.max; Vector3 omin = other.min; Vector3 omax = other.max; if ( omin.x < min.x - tolerance || omin.y < min.y - tolerance || omin.z < min.z - tolerance ) { return false; } return omax.x < max.x - tolerance && omax.y < max.y - tolerance && omax.z < max.z - tolerance; } } }