Skip to content

File frustum.hpp

File List > gui > frustum.hpp

Go to the documentation of this file

#pragma once

#include <QMatrix4x4>
#include <QVector4D>
#include <array>

#include "utilities/coordinate.hpp"

// Frustum culling for octree traversal. The six clip-space planes are extracted
// from the combined projection*view (clip) matrix using the Gribb-Hartmann
// method. A node is kept when its AABB is not fully outside any single plane.
// This is exact (no NDC divide, so it is valid for nodes behind the eye) and
// needs no slack tuning, unlike a screen-space corner heuristic.
struct Frustum {
  // Each plane is (a, b, c, d): a point is inside when a*x + b*y + c*z + d >= 0.
  std::array<QVector4D, 6> m_planes;

  static Frustum from_matrix(const QMatrix4x4& clip) {
    // QMatrix4x4::operator()(row, col) addresses the mathematical row/col.
    const auto row = [&clip](int r) {
      return QVector4D(clip(r, 0), clip(r, 1), clip(r, 2), clip(r, 3));
    };
    const QVector4D r0 = row(0);
    const QVector4D r1 = row(1);
    const QVector4D r2 = row(2);
    const QVector4D r3 = row(3);
    Frustum f;
    f.m_planes[0] = r3 + r0;  // left
    f.m_planes[1] = r3 - r0;  // right
    f.m_planes[2] = r3 + r1;  // bottom
    f.m_planes[3] = r3 - r1;  // top
    f.m_planes[4] = r3 + r2;  // near
    f.m_planes[5] = r3 - r2;  // far
    return f;
  }

  // Conservative AABB test: returns false only when the box is fully outside
  // (in the negative half-space of) some plane.
  bool intersects(const Extent3D& bounds) const {
    const float minx = static_cast<float>(bounds.minx);
    const float miny = static_cast<float>(bounds.miny);
    const float minz = static_cast<float>(bounds.minz);
    const float maxx = static_cast<float>(bounds.maxx);
    const float maxy = static_cast<float>(bounds.maxy);
    const float maxz = static_cast<float>(bounds.maxz);
    for (const QVector4D& plane : m_planes) {
      // Pick the corner furthest along the plane normal (the "positive vertex").
      const float px = plane.x() > 0.0f ? maxx : minx;
      const float py = plane.y() > 0.0f ? maxy : miny;
      const float pz = plane.z() > 0.0f ? maxz : minz;
      if (plane.x() * px + plane.y() * py + plane.z() * pz + plane.w() < 0.0f) {
        return false;
      }
    }
    return true;
  }
};