Files
watrbx-game-engine/Rendering/RbxG3D/Frustum.cpp
T
2025-09-18 17:55:52 -04:00

149 lines
4.1 KiB
C++

#include "RbxG3D/Frustum.h"
#include "rbx/Debug.h"
#include "util/Math.h"
#include "util/Extents.h"
using G3D::Plane;
using G3D::Vector3;
using namespace RBX;
// creates a frustum in a local coordinate space with the apex at the origin
// translates the frustum into the space defined by apex, dir and up
// performing the floating point operations in a local space helps reduce numerical errors when all of the points are far from the origin
Frustum::Frustum(const Vector3& apex, const Vector3& dir, const Vector3& up, const float nearDist, const float farDist, const float fovx, const float fovy)
{
Matrix3 rotation;
// up and dir should already be normalized and perpendicular to each other
RBXASSERT(dir.isUnit());
RBXASSERT(up.isUnit());
// due to rounding errors the cross product may not be a unit vector even though it should be.
const Vector3 right = up.cross(dir).unit();
rotation.setColumn(0, right);
rotation.setColumn(1, up);
rotation.setColumn(2, -dir);
// Near plane (wind backwards so normal faces into frustum)
// Recall that nearPlane, farPlane are positive numbers, so
// we need to negate them to produce actual z values.
faceArray.append(Plane(Vector3(0.0f, 0.0f, -1.0f), Vector3(0,0,-nearDist)));
// Right plane
faceArray.append(Plane(Vector3(-cosf(fovx/2.0f), 0.0f, -sinf(fovx/2.0f)), Vector3::zero()));
// Left plane
faceArray.append(Plane(Vector3(-faceArray.last().normal().x, 0.0f, faceArray.last().normal().z), Vector3::zero()));
// Bottom plane
faceArray.append(Plane(Vector3(0.0f, cosf(fovy/2.0f), -sinf(fovy/2.0f)), Vector3::zero()));
// Top plane
faceArray.append(Plane(Vector3(0.0f, -faceArray.last().normal().y, faceArray.last().normal().z), Vector3::zero()));
// Far plane
if (farDist < inf())
{
faceArray.append(Plane(Vector3(0.0f, 0.0f, 1.0f), Vector3(0.0f, 0.0f, -farDist)));
}
// Transform planes to world space
for (int face = 0; face < faceArray.size(); ++face)
{
// Since there is no scale factor, we don't have to
// worry about the inverse transpose of the normal.
Vector3 normal;
float d;
faceArray[face].getEquation(normal, d);
Vector3 newNormal = rotation * normal;
if (G3D::isFinite(d)) {
d = (newNormal * -d + apex).dot(newNormal);
faceArray[face] = Plane(newNormal, newNormal * d);
} else {
// When d is infinite, we can't multiply 0's by it without
// generating NaNs.
faceArray[face] = Plane::fromEquation(newNormal.x, newNormal.y, newNormal.z, d);
}
}
}
bool Frustum::containsAABB(const Extents& aabb) const
{
for (int ii = 0; ii < 8; ++ii)
{
Vector3 corner = aabb.getCorner(ii);
if (!containsPoint(corner))
{
return false;
}
}
return true;
}
bool Frustum::intersectsAABB(const RBX::Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const
{
RBX::Extents worldBox = aabb.toWorldSpace(extentsFrame);
Vector3 extent = worldBox.size() * 0.5f;
Vector3 center = worldBox.center();
for (int ii = 0; ii < 6; ++ii)
{
const Plane& plane = faceArray[ii];
float d = plane.normal().dot(center) - plane.distance();
float r = Vector3(fabsf(plane.normal().x), fabsf(plane.normal().y), fabsf(plane.normal().z)).dot(extent);
// box is in negative half-space => outside the frustum
if (d + r < 0)
return false;
}
return true;
}
bool Frustum::containsAABB(const Extents& aabb, const G3D::CoordinateFrame& extentsFrame) const
{
for (int ii = 0; ii < 8; ++ii)
{
Vector3 corner = aabb.getCorner(ii);
Vector3 world = extentsFrame.pointToWorldSpace(corner);
if (!containsPoint(world))
{
return false;
}
}
return true;
}
bool Frustum::containsPoint(const Vector3& point) const
{
for (int ii = 0; ii < faceArray.size(); ++ii)
{
const Plane& plane = faceArray[ii];
if (!plane.halfSpaceContains(point))
{
return false;
}
}
return true;
}
bool Frustum::intersectsSphere(const Vector3& center, float radius) const
{
for (int ii = 0; ii < faceArray.size(); ++ii)
{
const Plane& p = faceArray[ii];
Plane offsetplane(p.normal(), p.distance() - radius);
if (!offsetplane.halfSpaceContains(center))
{
return false;
}
}
return true;
}