using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace MinecraftClient.Physics { /// /// Axis-aligned bounding box, mirrors net.minecraft.world.phys.AABB. /// Immutable — mutating methods return new instances. /// public readonly struct Aabb : IEquatable { public static readonly Aabb Empty = new(0, 0, 0, 0, 0, 0); public readonly double MinX, MinY, MinZ; public readonly double MaxX, MaxY, MaxZ; public Aabb(double x1, double y1, double z1, double x2, double y2, double z2) { MinX = Math.Min(x1, x2); MinY = Math.Min(y1, y2); MinZ = Math.Min(z1, z2); MaxX = Math.Max(x1, x2); MaxY = Math.Max(y1, y2); MaxZ = Math.Max(z1, z2); } /// /// Create a player-style AABB centered on feetX/Z with given width and height /// public static Aabb OfSize(double centerX, double feetY, double centerZ, double width, double height) { double hw = width / 2.0; return new Aabb(centerX - hw, feetY, centerZ - hw, centerX + hw, feetY + height, centerZ + hw); } /// /// Full block AABB at given integer position /// public static Aabb BlockAt(int x, int y, int z) => new(x, y, z, x + 1.0, y + 1.0, z + 1.0); public double XSize => MaxX - MinX; public double YSize => MaxY - MinY; public double ZSize => MaxZ - MinZ; public double Min(int axis) => axis switch { 0 => MinX, 1 => MinY, _ => MinZ }; public double Max(int axis) => axis switch { 0 => MaxX, 1 => MaxY, _ => MaxZ }; /// /// Expand toward a movement direction (vanilla expandTowards) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb ExpandTowards(double dx, double dy, double dz) { double minX = MinX, minY = MinY, minZ = MinZ; double maxX = MaxX, maxY = MaxY, maxZ = MaxZ; if (dx < 0) minX += dx; else if (dx > 0) maxX += dx; if (dy < 0) minY += dy; else if (dy > 0) maxY += dy; if (dz < 0) minZ += dz; else if (dz > 0) maxZ += dz; return new Aabb(minX, minY, minZ, maxX, maxY, maxZ); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb ExpandTowards(Vec3d v) => ExpandTowards(v.X, v.Y, v.Z); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb Inflate(double x, double y, double z) => new(MinX - x, MinY - y, MinZ - z, MaxX + x, MaxY + y, MaxZ + z); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb Inflate(double v) => Inflate(v, v, v); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb Deflate(double x, double y, double z) => Inflate(-x, -y, -z); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb Move(double dx, double dy, double dz) => new(MinX + dx, MinY + dy, MinZ + dz, MaxX + dx, MaxY + dy, MaxZ + dz); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Aabb Move(Vec3d v) => Move(v.X, v.Y, v.Z); /// /// Strict overlap test (vanilla uses < and >, not <=) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Intersects(Aabb other) => MinX < other.MaxX && MaxX > other.MinX && MinY < other.MaxY && MaxY > other.MinY && MinZ < other.MaxZ && MaxZ > other.MinZ; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Intersects(double x1, double y1, double z1, double x2, double y2, double z2) => MinX < x2 && MaxX > x1 && MinY < y2 && MaxY > y1 && MinZ < z2 && MaxZ > z1; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(double x, double y, double z) => x >= MinX && x < MaxX && y >= MinY && y < MaxY && z >= MinZ && z < MaxZ; /// /// Collide this AABB along a single axis against another AABB. /// Returns the clamped movement distance. /// /// /// Clip entity movement along X against a block shape (other). /// Vanilla semantics: VoxelShape.collide(Axis.X, entityBox, movement). /// public double CollideX(Aabb other, double movement) { if (other.MaxY <= MinY || other.MinY >= MaxY || other.MaxZ <= MinZ || other.MinZ >= MaxZ) return movement; if (movement > 0.0 && other.MinX >= MaxX) { double d = other.MinX - MaxX; if (d < movement) movement = d; } else if (movement < 0.0 && other.MaxX <= MinX) { double d = other.MaxX - MinX; if (d > movement) movement = d; } return movement; } public double CollideY(Aabb other, double movement) { if (other.MaxX <= MinX || other.MinX >= MaxX || other.MaxZ <= MinZ || other.MinZ >= MaxZ) return movement; if (movement > 0.0 && other.MinY >= MaxY) { double d = other.MinY - MaxY; if (d < movement) movement = d; } else if (movement < 0.0 && other.MaxY <= MinY) { double d = other.MaxY - MinY; if (d > movement) movement = d; } return movement; } public double CollideZ(Aabb other, double movement) { if (other.MaxX <= MinX || other.MinX >= MaxX || other.MaxY <= MinY || other.MinY >= MaxY) return movement; if (movement > 0.0 && other.MinZ >= MaxZ) { double d = other.MinZ - MaxZ; if (d < movement) movement = d; } else if (movement < 0.0 && other.MaxZ <= MinZ) { double d = other.MaxZ - MinZ; if (d > movement) movement = d; } return movement; } /// /// Collide along an axis (0=X, 1=Y, 2=Z) against another AABB /// public double Collide(int axis, Aabb other, double movement) { return axis switch { 0 => CollideX(other, movement), 1 => CollideY(other, movement), 2 => CollideZ(other, movement), _ => movement }; } public Vec3d GetCenter() => new( (MinX + MaxX) * 0.5, (MinY + MaxY) * 0.5, (MinZ + MaxZ) * 0.5); public Vec3d GetBottomCenter() => new( (MinX + MaxX) * 0.5, MinY, (MinZ + MaxZ) * 0.5); public bool Equals(Aabb other) => MinX == other.MinX && MinY == other.MinY && MinZ == other.MinZ && MaxX == other.MaxX && MaxY == other.MaxY && MaxZ == other.MaxZ; public override bool Equals(object? obj) => obj is Aabb a && Equals(a); public override int GetHashCode() => HashCode.Combine(MinX, MinY, MinZ, MaxX, MaxY, MaxZ); public override string ToString() => $"AABB[{MinX:F3},{MinY:F3},{MinZ:F3} -> {MaxX:F3},{MaxY:F3},{MaxZ:F3}]"; public static bool operator ==(Aabb a, Aabb b) => a.Equals(b); public static bool operator !=(Aabb a, Aabb b) => !a.Equals(b); } }