Code/General/BBoxExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace ExtendedBox.General;
public static class BBoxExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox Grow(this BBox bbox, float value, bool clampAtCenter = false) =>
bbox.Grow(new Vector3(value), clampAtCenter);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox Grow(this BBox bbox, in Vector3 value, bool clampAtCenter = false)
{
var result = bbox;
result.Mins -= value;
result.Maxs += value;
if(clampAtCenter)
{
var center = bbox.Center;
result.Mins = result.Mins.ComponentMin(center);
result.Maxs = result.Maxs.ComponentMax(center);
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox Shrink(this BBox bbox, float value, bool clampAtCenter = false) =>
bbox.Shrink(new Vector3(value), clampAtCenter);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox Shrink(this BBox bbox, in Vector3 value, bool clampAtCenter = false)
{
var result = bbox;
result.Mins += value;
result.Maxs -= value;
if(clampAtCenter)
{
var center = bbox.Center;
result.Mins = result.Mins.ComponentMin(center);
result.Maxs = result.Maxs.ComponentMax(center);
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox AddOrCreate(this BBox? @this, Vector3 point)
{
if(@this.HasValue)
return @this.Value.AddPoint(point);
return new(point, point);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox AddOrCreate(this BBox? @this, BBox bbox)
{
if(@this.HasValue)
return @this.Value.AddBBox(bbox);
return bbox;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox? AddOrCreate(this BBox? @this, IEnumerable<Vector3> points)
{
if(!points.Any())
return @this;
return @this.AddOrCreate(BBox.FromPoints(points));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox? AddOrCreate(this BBox? @this, IEnumerable<BBox> boxes)
{
if(!boxes.Any())
return @this;
return @this.AddOrCreate(BBox.FromBoxes(boxes));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBox GetIntersection(this BBox @this, BBox other)
{
if([email protected](other))
return new(Vector3Int.Zero, Vector3Int.Zero);
BBox result = new()
{
Mins = @this.Mins.ComponentMax(other.Mins),
Maxs = @this.Maxs.ComponentMin(other.Maxs)
};
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool AlmostEqual(this BBox @this, in BBox other, float delta = 0.0001f) =>
@this.Mins.AlmostEqual(other.Mins, delta) && @this.Maxs.AlmostEqual(other.Maxs, delta);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBoxInt FloorToInt(this BBox bbox) => new(bbox.Mins.FloorToInt(), bbox.Maxs.FloorToInt());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBoxInt RoundToInt(this BBox bbox) => new(bbox.Mins.RoundToInt(), bbox.Maxs.RoundToInt());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBoxInt CeilToInt(this BBox bbox) => new(bbox.Mins.CeilToInt(), bbox.Maxs.CeilToInt());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BBoxInt ExpandToInt(this BBox bbox) => new(bbox.Mins.FloorToInt(), bbox.Maxs.CeilToInt());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Trace(this BBox bbox, Ray ray, float distance, out float hitDistance, out Vector3Int hitNormal)
{
hitDistance = 0f;
hitNormal = Vector3Int.Zero;
Vector3 invDir = new Vector3(1.0f / ray.Forward.x, 1.0f / ray.Forward.y, 1.0f / ray.Forward.z);
Vector3 t0 = (bbox.Mins - ray.Position) * invDir;
Vector3 t1 = (bbox.Maxs - ray.Position) * invDir;
Vector3 tNearV = Vector3.Min(t0, t1);
Vector3 tFarV = Vector3.Max(t0, t1);
float tEnter = MathF.Max(MathF.Max(tNearV.x, tNearV.y), tNearV.z);
float tExit = MathF.Min(MathF.Min(tFarV.x, tFarV.y), tFarV.z);
if(tEnter <= tExit && tExit > 0f && tEnter < distance)
{
hitDistance = tEnter < 0f ? 0f : tEnter;
if(tEnter > 0f)
{
if(tEnter == tNearV.x)
hitNormal = new Vector3Int(ray.Forward.x > 0 ? -1 : 1, 0, 0);
else if(tEnter == tNearV.y)
hitNormal = new Vector3Int(0, ray.Forward.y > 0 ? -1 : 1, 0);
else
hitNormal = new Vector3Int(0, 0, ray.Forward.z > 0 ? -1 : 1);
}
else
{
hitNormal = Vector3Int.Zero;
}
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Trace(this BBox target, Ray ray, float distance, BBox extents, out float hitDistance)
{
return target.Trace(ray, distance, extents, out hitDistance, out _);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Trace(this BBox target, Ray ray, float distance, BBox extents, out float hitDistance, out Vector3Int hitNormal)
{
Vector3 min = target.Mins - extents.Maxs;
Vector3 max = target.Maxs - extents.Mins;
Vector3 invDir = new(1.0f / ray.Forward.x, 1.0f / ray.Forward.y, 1.0f / ray.Forward.z);
Vector3 t0 = (min - ray.Position) * invDir;
Vector3 t1 = (max - ray.Position) * invDir;
Vector3 tNearV = Vector3.Min(t0, t1);
Vector3 tFarV = Vector3.Max(t0, t1);
float tEnter = MathF.Max(MathF.Max(tNearV.x, tNearV.y), tNearV.z);
float tExit = MathF.Min(MathF.Min(tFarV.x, tFarV.y), tFarV.z);
if(tEnter <= tExit && tExit > 0f && tEnter < distance)
{
hitDistance = MathF.Max(0f, tEnter);
if(tEnter > 0f)
{
if(tEnter == tNearV.x)
hitNormal = new Vector3Int(ray.Forward.x < 0 ? 1 : -1, 0, 0);
else if(tEnter == tNearV.y)
hitNormal = new Vector3Int(0, ray.Forward.y < 0 ? 1 : -1, 0);
else
hitNormal = new Vector3Int(0, 0, ray.Forward.z < 0 ? 1 : -1);
}
else
{
hitNormal = Vector3Int.Zero;
}
return true;
}
hitDistance = 0;
hitNormal = Vector3Int.Zero;
return false;
}
}