<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. BIM Math
  3. 3D geometry and solids
All lessons in this course

Lesson 33 of 44 14 minAdvanced

Bounding boxes: AABB and OBB

Wrap any element in a box that is quick to build and quick to test: the world-aligned AABB and the element’s own OBB, how each is built, and when the cheap one gives false alarms.

A wall, a duct or a whole family instance can have thousands of triangles. Before doing anything expensive with it (testing it for clashes, drawing it, picking it with the mouse), a program first asks a much cheaper question: could it possibly be involved? The answer comes from a bounding box, the smallest box that contains the element.

There are two kinds. An axis-aligned bounding box (AABB) has its edges along the world X, Y and Z axes, so six numbers describe it. An oriented bounding box (OBB) turns with the element and fits it tightly, but needs a centre, three axes and three half sizes. Turn the beam in the widget and watch the difference.

Interactive

AABB and OBB of a turning beam

columnAABB
beam (OBB)6.4 × 1 m, area 6.40 m²
AABB6.04 × 4.07 m
AABB area24.57 m² (3.8× the beam)
AABB vs columnoverlap
OBB vs columnapart
False alarm: at 30° the AABB is 3.8× the beam’s area and covers the column, but the beam’s own box (OBB) misses it. A quick AABB test can only say “maybe”; a tighter test has to decide.

The AABB: six numbers

An AABB is just the minimum and maximum of every coordinate: Min = (min x, min y, min z) and Max = (max x, max y, max z) over all the element’s points. Two AABBs overlap only if their intervals overlap on all three axes, which is six comparisons and no arithmetic at all. That is why every BIM program, game engine and spatial index uses them for the first, rough test.

Aabb.cs
var beam = Aabb.Of(new V3(0, 0, 3), new V3(6, 0, 3), new V3(6, 0.3, 3.5), new V3(0, 0.3, 3.5));var duct = new Aabb(new V3(2, -1, 3.2), new V3(2.4, 2, 3.6));var pipe = new Aabb(new V3(2, -1, 3.6), new V3(2.2, 2, 3.8)); Console.WriteLine($"beam: {beam}, size {beam.Size}");Console.WriteLine($"beam and duct overlap: {beam.Overlaps(duct)}");Console.WriteLine($"beam and pipe overlap: {beam.Overlaps(pipe)}");Console.WriteLine($"beam and pipe within 0.15 m: {beam.Inflate(0.15).Overlaps(pipe)}");Console.WriteLine($"box around beam and duct: {Aabb.Union(beam, duct)}"); readonly record struct Aabb(V3 Min, V3 Max){    public static Aabb Of(params V3[] pts)    {        V3 lo = pts[0], hi = pts[0];        foreach (var p in pts)        {            lo = new(Math.Min(lo.X, p.X), Math.Min(lo.Y, p.Y), Math.Min(lo.Z, p.Z));            hi = new(Math.Max(hi.X, p.X), Math.Max(hi.Y, p.Y), Math.Max(hi.Z, p.Z));        }        return new(lo, hi);    }     // Overlap on X, on Y and on Z. Boxes that only touch do not count.    public bool Overlaps(Aabb o) =>        Min.X < o.Max.X && o.Min.X < Max.X &&        Min.Y < o.Max.Y && o.Min.Y < Max.Y &&        Min.Z < o.Max.Z && o.Min.Z < Max.Z;     public Aabb Inflate(double d) => new(Min - new V3(d, d, d), Max + new V3(d, d, d));    public static Aabb Union(Aabb a, Aabb b) => Of(a.Min, a.Max, b.Min, b.Max);    public V3 Size => Max - Min;    public override string ToString() => $"{Min} .. {Max}";} readonly record struct V3(double X, double Y, double Z){    public static V3 operator +(V3 a, V3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);    public static V3 operator -(V3 a, V3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);    public static V3 operator *(V3 v, double k) => new(v.X * k, v.Y * k, v.Z * k);    public static double Dot(V3 a, V3 b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z;    public static V3 Cross(V3 a, V3 b) =>        new(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X);    public double Length => Math.Sqrt(Dot(this, this));    public V3 Normalized() => this * (1 / Length);    static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###");    public override string ToString() => $"({F(X)}, {F(Y)}, {F(Z)})";}
Output
beam: (0, 0, 3) .. (6, 0.3, 3.5), size (6, 0.3, 0.5)
beam and duct overlap: True
beam and pipe overlap: False
beam and pipe within 0.15 m: True
box around beam and duct: (0, -1, 3) .. (6, 2, 3.6)

Three tools in a few lines: Overlaps for the clash test, Inflate for clearance checks (a pipe that must stay 150 mm from the beam), and Union for the box around a group of elements, a whole storey or a whole model. The strict < means boxes that only touch, like a beam sitting on a column, are not reported; the clash detection lesson comes back to that choice.

What turning does to an AABB

An AABB is only tight while the element runs along the world axes. Turn it, and the box has to grow to keep containing it. For a beam of length L and width W turned by θ in plan, the AABB is L·|cos θ| + W·|sin θ| wide and L·|sin θ| + W·|cos θ| deep. A thin 6 m beam at 45° has an AABB about eleven times its own area, all of it empty space that can raise false alarms.

To get the AABB of a transformed box you can transform all eight corners and take the min and max. There is a shortcut: the new half sizes are the absolute values of the rotation matrix times the old half sizes, h′ = |R| · h.

TurnedBox.cs
var half = new V3(3, 0.15, 0.25);            // a 6 x 0.3 x 0.5 beam, centred on its origin foreach (double deg in new[] { 0.0, 30, 45, 90 }){    var r = RotZ(deg);     // 1: turn all 8 corners and keep the largest |x|, |y| and |z|    double hx = 0, hy = 0, hz = 0;    for (int i = 0; i < 8; i++)    {        var c = new V3(i % 2 == 0 ? -half.X : half.X, i / 2 % 2 == 0 ? -half.Y : half.Y, i < 4 ? -half.Z : half.Z);        var p = Apply(r, c);        hx = Math.Max(hx, Math.Abs(p.X)); hy = Math.Max(hy, Math.Abs(p.Y)); hz = Math.Max(hz, Math.Abs(p.Z));    }     // 2: the shortcut, |R| times the half sizes    var h = new V3(        Math.Abs(r[0, 0]) * half.X + Math.Abs(r[0, 1]) * half.Y + Math.Abs(r[0, 2]) * half.Z,        Math.Abs(r[1, 0]) * half.X + Math.Abs(r[1, 1]) * half.Y + Math.Abs(r[1, 2]) * half.Z,        Math.Abs(r[2, 0]) * half.X + Math.Abs(r[2, 1]) * half.Y + Math.Abs(r[2, 2]) * half.Z);     double grow = h.X * h.Y * h.Z / (half.X * half.Y * half.Z);    Console.WriteLine($"{deg,2} deg: corners {new V3(hx, hy, hz) * 2}, shortcut {h * 2}, volume x{grow:0.0}");} static double[,] RotZ(double d){    double t = d * Math.PI / 180, c = Math.Cos(t), s = Math.Sin(t);    return new[,] { { c, -s, 0 }, { s, c, 0 }, { 0, 0, 1 } };} static V3 Apply(double[,] m, V3 v) => new(    m[0, 0] * v.X + m[0, 1] * v.Y + m[0, 2] * v.Z,    m[1, 0] * v.X + m[1, 1] * v.Y + m[1, 2] * v.Z,    m[2, 0] * v.X + m[2, 1] * v.Y + m[2, 2] * v.Z); readonly record struct V3(double X, double Y, double Z){    public static V3 operator +(V3 a, V3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);    public static V3 operator -(V3 a, V3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);    public static V3 operator *(V3 v, double k) => new(v.X * k, v.Y * k, v.Z * k);    public static double Dot(V3 a, V3 b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z;    public static V3 Cross(V3 a, V3 b) =>        new(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X);    public double Length => Math.Sqrt(Dot(this, this));    public V3 Normalized() => this * (1 / Length);    static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###");    public override string ToString() => $"({F(X)}, {F(Y)}, {F(Z)})";}
Output
 0 deg: corners (6, 0.3, 0.5), shortcut (6, 0.3, 0.5), volume x1.0
30 deg: corners (5.346, 3.26, 0.5), shortcut (5.346, 3.26, 0.5), volume x9.7
45 deg: corners (4.455, 4.455, 0.5), shortcut (4.455, 4.455, 0.5), volume x11.0
90 deg: corners (0.3, 6, 0.5), shortcut (0.3, 6, 0.5), volume x1.0

The OBB: a box that turns with the element

An OBB stores a centre, three unit axes U, V and W at right angles, and a half size along each. Testing a point is a change of coordinates, exactly the world-to-local step from the transforms module: subtract the centre, dot with each axis, and compare with the half sizes.

ObbContains.cs
// A 6 m beam turned 30 deg in plan, centred at (10, 5, 3)double t = 30 * Math.PI / 180;var beam = new Obb(new V3(10, 5, 3),    new V3(Math.Cos(t), Math.Sin(t), 0), new V3(-Math.Sin(t), Math.Cos(t), 0), new V3(0, 0, 1),    new V3(3, 0.15, 0.25)); foreach (var p in new V3[] { new(12, 6, 3), new(12, 5, 3), new(10, 5, 3.3) })    Console.WriteLine($"{p}: local {beam.ToLocal(p)}, inside {beam.Contains(p)}"); readonly record struct Obb(V3 Centre, V3 U, V3 V, V3 W, V3 Half){    public V3 ToLocal(V3 p)    {        var d = p - Centre;        return new(V3.Dot(d, U), V3.Dot(d, V), V3.Dot(d, W));    }     public bool Contains(V3 p)    {        var l = ToLocal(p);        return Math.Abs(l.X) <= Half.X && Math.Abs(l.Y) <= Half.Y && Math.Abs(l.Z) <= Half.Z;    }} readonly record struct V3(double X, double Y, double Z){    public static V3 operator +(V3 a, V3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);    public static V3 operator -(V3 a, V3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z);    public static V3 operator *(V3 v, double k) => new(v.X * k, v.Y * k, v.Z * k);    public static double Dot(V3 a, V3 b) => a.X * b.X + a.Y * b.Y + a.Z * b.Z;    public static V3 Cross(V3 a, V3 b) =>        new(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X);    public double Length => Math.Sqrt(Dot(this, this));    public V3 Normalized() => this * (1 / Length);    static string F(double v) => Math.Abs(v) < 5e-4 ? "0" : v.ToString("0.###");    public override string ToString() => $"({F(X)}, {F(Y)}, {F(Z)})";}
Output
(12, 6, 3): local (2.232, -0.134, 0), inside True
(12, 5, 3): local (1.732, -1, 0), inside False
(10, 5, 3.3): local (0, 0, 0.3), inside False

The point (12, 5, 3) is inside the beam’s AABB, but its local coordinates put it 1 m from the beam’s centre line, 0.85 m beyond its side face. Only the OBB can tell.

Where does the OBB come from?

  • From the family: in BIM you usually have it for free. A family instance has a local bounding box in its own coordinates and a transform that places it. The local box’s centre and half sizes, turned and moved by that transform, give an exact OBB.
  • From the geometry: for a bare mesh, a good OBB can be found from the directions in which its points spread most (principal component analysis), or by trying the directions of its edges and keeping the smallest box. That is more work, so it is done once and stored.
  • From a convention: for linear elements like beams, ducts and pipes, the axis of the element is the obvious U, and the section gives V and W.
AABBOBB
Stored asMin and Max (6 numbers)Centre, 3 axes, 3 half sizes (15 numbers)
Fit for a turned elementLoose, grows with the angleTight at any angle
Overlap test6 comparisonsSeparating-axis test, up to 15 axes
Used forSpatial indexes, broad phase, cullingNarrow phase, picking, tighter clash checks

The usual strategy combines them: AABBs to throw away almost every pair quickly, OBBs (or the real geometry) to decide the few that are left. The clash detection lesson builds exactly that pipeline.

Quick check

A 6 m long, 0.3 m wide beam lies along the X axis. You turn it 45° in plan. What happens to its AABB?

Quick check

Two AABBs overlap on X and on Y but not on Z. Do the elements inside them clash?

Practice

A 6 × 0.3 m beam turns in plan. For θ = 0°, 15°, 30°, 45°, 60°, 75° and 90°, print the width and depth of its AABB and how many times the beam’s own area (1.8 m²) the AABB covers. At which angle is the AABB worst?

Show a hint

Width = L·|cos θ| + W·|sin θ| and depth = L·|sin θ| + W·|cos θ|, with L = 6 and W = 0.3.

Show the solution
Solution.cs
double l = 6, w = 0.3;for (int deg = 0; deg <= 90; deg += 15){    double t = deg * Math.PI / 180, c = Math.Abs(Math.Cos(t)), s = Math.Abs(Math.Sin(t));    double width = l * c + w * s, depth = l * s + w * c;    Console.WriteLine($"{deg,2} deg: {width:0.00} x {depth:0.00} m, {width * depth / (l * w):0.0} x the beam");}
Output
 0 deg: 6.00 x 0.30 m, 1.0 x the beam
15 deg: 5.87 x 1.84 m, 6.0 x the beam
30 deg: 5.35 x 3.26 m, 9.7 x the beam
45 deg: 4.45 x 4.45 m, 11.0 x the beam
60 deg: 3.26 x 5.35 m, 9.7 x the beam
75 deg: 1.84 x 5.87 m, 6.0 x the beam
90 deg: 0.30 x 6.00 m, 1.0 x the beam

Remember

  • A bounding box answers “could this element be involved?” before any expensive test.
  • An AABB is the min and max of every coordinate; two of them overlap only if they overlap on X, Y and Z.
  • Turning an element makes its AABB grow: a thin beam at 45° has an AABB about eleven times its area.
  • The AABB of a turned box has half sizes |R| · h; boxes of boxes keep growing, so recompute from the element.
  • An OBB (centre, three axes, three half sizes) fits at any angle; in BIM it comes from the family’s local box and its transform.
0 of 44 lessons completed