794 lines
29 KiB
C++
794 lines
29 KiB
C++
#include <J3ML/Geometry/Frustum.hpp>
|
|
#include <cmath>
|
|
#include <J3ML/Geometry/AABB.hpp>
|
|
#include <J3ML/Geometry/Polyhedron.hpp>
|
|
#include <J3ML/Geometry/LineSegment.hpp>
|
|
#include <J3ML/Geometry/Polygon.hpp>
|
|
#include <J3ML/Algorithm/GJK.hpp>
|
|
|
|
|
|
namespace J3ML::Geometry
|
|
{
|
|
|
|
void Frustum::GetCornerPoints(Vector3 *outPointArray) const
|
|
{
|
|
assert(outPointArray);
|
|
|
|
if (type == FrustumType::Perspective)
|
|
{
|
|
float tanhfov = std::tan(horizontalFov*0.5f);
|
|
float tanvfov = std::tan(verticalFov*0.5f);
|
|
float frontPlaneHalfWidth = tanhfov*nearPlaneDistance;
|
|
float frontPlaneHalfHeight = tanvfov*nearPlaneDistance;
|
|
float farPlaneHalfWidth = tanhfov*farPlaneDistance;
|
|
float farPlaneHalfHeight = tanvfov*farPlaneDistance;
|
|
|
|
Vector3 right = WorldRight();
|
|
|
|
Vector3 nearCenter = pos + front * nearPlaneDistance;
|
|
Vector3 nearHalfWidth = frontPlaneHalfWidth*right;
|
|
Vector3 nearHalfHeight = frontPlaneHalfHeight*up;
|
|
outPointArray[0] = nearCenter - nearHalfWidth - nearHalfHeight;
|
|
outPointArray[1] = nearCenter + nearHalfWidth - nearHalfHeight;
|
|
outPointArray[2] = nearCenter - nearHalfWidth + nearHalfHeight;
|
|
outPointArray[3] = nearCenter + nearHalfWidth + nearHalfHeight;
|
|
|
|
Vector3 farCenter = pos + front * farPlaneDistance;
|
|
Vector3 farHalfWidth = farPlaneHalfWidth*right;
|
|
Vector3 farHalfHeight = farPlaneHalfHeight*up;
|
|
outPointArray[4] = farCenter - farHalfWidth - farHalfHeight;
|
|
outPointArray[5] = farCenter + farHalfWidth - farHalfHeight;
|
|
outPointArray[6] = farCenter - farHalfWidth + farHalfHeight;
|
|
outPointArray[7] = farCenter + farHalfWidth + farHalfHeight;
|
|
}
|
|
else
|
|
{
|
|
Vector3 right = WorldRight();
|
|
Vector3 nearCenter = pos + front * nearPlaneDistance;
|
|
Vector3 farCenter = pos + front * farPlaneDistance;
|
|
Vector3 halfWidth = orthographicWidth * 0.5f * right;
|
|
Vector3 halfHeight = orthographicHeight * 0.5f * up;
|
|
|
|
outPointArray[0] = nearCenter - halfWidth - halfHeight;
|
|
outPointArray[1] = nearCenter + halfWidth - halfHeight;
|
|
outPointArray[2] = nearCenter - halfWidth + halfHeight;
|
|
outPointArray[3] = nearCenter + halfWidth + halfHeight;
|
|
outPointArray[4] = farCenter - halfWidth - halfHeight;
|
|
outPointArray[5] = farCenter + halfWidth - halfHeight;
|
|
outPointArray[6] = farCenter - halfWidth + halfHeight;
|
|
outPointArray[7] = farCenter + halfWidth + halfHeight;
|
|
}
|
|
}
|
|
|
|
void Frustum::ProjectToAxis(const Vector3 &direction, float &outMin, float &outMax) const
|
|
{
|
|
Vector3 corners[8];
|
|
GetCornerPoints(corners);
|
|
outMax = -INFINITY;
|
|
outMin = INFINITY;
|
|
for(int i = 0; i < 8; ++i)
|
|
{
|
|
float d = Vector3::Dot(direction, corners[i]);
|
|
outMax = std::max(outMax, d);
|
|
outMin = std::min(outMin, d);
|
|
}
|
|
}
|
|
|
|
LineSegment Frustum::Edge(int edgeIndex) const
|
|
{
|
|
assert(0 <= edgeIndex && edgeIndex <= 11);
|
|
switch(edgeIndex)
|
|
{
|
|
default: // For release builds where assume() is disabled, return always the first option if out-of-bounds.
|
|
case 0: return {CornerPoint(0), CornerPoint(1)};
|
|
case 1: return LineSegment(CornerPoint(0), CornerPoint(2));
|
|
case 2: return LineSegment(CornerPoint(0), CornerPoint(4));
|
|
case 3: return LineSegment(CornerPoint(1), CornerPoint(3));
|
|
case 4: return LineSegment(CornerPoint(1), CornerPoint(5));
|
|
case 5: return LineSegment(CornerPoint(2), CornerPoint(3));
|
|
case 6: return LineSegment(CornerPoint(2), CornerPoint(6));
|
|
case 7: return LineSegment(CornerPoint(3), CornerPoint(7));
|
|
case 8: return LineSegment(CornerPoint(4), CornerPoint(5));
|
|
case 9: return LineSegment(CornerPoint(4), CornerPoint(6));
|
|
case 10: return LineSegment(CornerPoint(5), CornerPoint(7));
|
|
case 11: return LineSegment(CornerPoint(6), CornerPoint(7));
|
|
}
|
|
}
|
|
|
|
Vector2 Frustum::ViewportToScreenSpace(float x, float y, int screenWidth, int screenHeight) {
|
|
return {(x + 1.f) * 0.5f * (screenWidth - 1.f), (1.f - y) * 0.5f * (screenHeight - 1.f)};
|
|
}
|
|
|
|
Vector2 Frustum::ViewportToScreenSpace(const Vector2 &point, int screenWidth, int screenHeight) {
|
|
return ViewportToScreenSpace(point.x, point.y, screenWidth, screenHeight);
|
|
}
|
|
|
|
Vector2 Frustum::ScreenToViewportSpace(float x, float y, int screenWidth, int screenHeight) {
|
|
return {x * 2.f / (screenWidth -1.f) - 1.f, 1.f - y * 2.f / (screenHeight - 1.f)};
|
|
}
|
|
|
|
Vector2 Frustum::ScreenToViewportSpace(const Vector2 &point, int screenWidth, int screenHeight) {
|
|
return ScreenToViewportSpace(point.x, point.y, screenWidth, screenHeight);
|
|
}
|
|
|
|
Vector3 Frustum::ExtremePoint(const Vector3 &direction, float &projectionDistance) const
|
|
{
|
|
Vector3 corners[8];
|
|
GetCornerPoints(corners);
|
|
Vector3 mostExtreme = Vector3::NaN;
|
|
projectionDistance = -INFINITY;
|
|
for(int i = 0; i < 8; ++i)
|
|
{
|
|
float d = Vector3::Dot(direction, corners[i]);
|
|
if (d > projectionDistance)
|
|
{
|
|
projectionDistance = d;
|
|
mostExtreme = corners[i];
|
|
}
|
|
}
|
|
return mostExtreme;
|
|
}
|
|
|
|
PBVolume<6> Frustum::ToPBVolume() const {
|
|
PBVolume<6> frustumVolume;
|
|
frustumVolume.p[0] = NearPlane();
|
|
frustumVolume.p[1] = LeftPlane();
|
|
frustumVolume.p[2] = RightPlane();
|
|
frustumVolume.p[3] = TopPlane();
|
|
frustumVolume.p[4] = BottomPlane();
|
|
frustumVolume.p[5] = FarPlane();
|
|
|
|
return frustumVolume;
|
|
}
|
|
|
|
AABB Frustum::MinimalEnclosingAABB() const {
|
|
AABB aabb;
|
|
aabb.SetNegativeInfinity();
|
|
for(int i = 0; i < 8; ++i)
|
|
aabb.Enclose(CornerPoint(i));
|
|
return aabb;
|
|
}
|
|
|
|
bool Frustum::IsFinite() const {
|
|
return pos.IsFinite() && front.IsFinite() && up.IsFinite() && Math::IsFinite(nearPlaneDistance)
|
|
&& Math::IsFinite(farPlaneDistance) && Math::IsFinite(horizontalFov) && Math::IsFinite(verticalFov);
|
|
}
|
|
|
|
OBB Frustum::MinimalEnclosingOBB(float expandGuardband) const {
|
|
assert(IsFinite());
|
|
assert(front.IsNormalized());
|
|
assert(up.IsNormalized());
|
|
|
|
OBB obb;
|
|
obb.pos = pos + (nearPlaneDistance + farPlaneDistance) * 0.5f * front;
|
|
obb.axis[1] = up;
|
|
obb.axis[2] = -front;
|
|
obb.axis[0] = Vector3::Cross(obb.axis[1], obb.axis[2]);
|
|
obb.r = Vector3::Zero;
|
|
|
|
for (int i = 0; i < 8; ++i)
|
|
obb.Enclose(CornerPoint(i));
|
|
|
|
// Expand the generated OBB very slightly to avoid numerical issues when
|
|
// testing whether this Frustum actually is contained inside the generated OBB.
|
|
obb.r.x += expandGuardband;
|
|
obb.r.y += expandGuardband;
|
|
obb.r.z += expandGuardband;
|
|
|
|
return obb;
|
|
}
|
|
|
|
void Frustum::SetKind(FrustumProjectiveSpace projectiveSpace, FrustumHandedness handedness) {
|
|
|
|
}
|
|
|
|
void Frustum::SetViewPlaneDistances(float n, float f) {
|
|
nearPlaneDistance = n;
|
|
farPlaneDistance = f;
|
|
ProjectionMatrixChanged();
|
|
|
|
}
|
|
|
|
void Frustum::SetFrame(const Vector3 &pos, const Vector3 &front, const Vector3 &up) {
|
|
this->pos = pos;
|
|
this->front = front;
|
|
this->up = up;
|
|
WorldMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetPos(const Vector3 &pos) {
|
|
this->pos = pos;
|
|
WorldMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetFront(const Vector3 &front) {
|
|
this->front = front;
|
|
WorldMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetUp(const Vector3 &up) {
|
|
this->up = up;
|
|
WorldMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetPerspective(float h, float v) {
|
|
type = FrustumType::Perspective;
|
|
this->horizontalFov = h;
|
|
this->verticalFov = v;
|
|
ProjectionMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetOrthographic(float w, float h) {
|
|
type = FrustumType::Orthographic;
|
|
orthographicWidth = w;
|
|
orthographicHeight = h;
|
|
ProjectionMatrixChanged();
|
|
}
|
|
|
|
float Frustum::AspectRatio() const {
|
|
return Math::Tan(horizontalFov* 0.5f) / Math::Tan(verticalFov*0.5f);
|
|
}
|
|
|
|
void Frustum::SetHorizontalFovAndAspectRatio(float hFov, float aspectRatio) {
|
|
type = FrustumType::Perspective;
|
|
horizontalFov = hFov;
|
|
verticalFov = 2.f * Math::Atan(Math::Tan(hFov * 0.5f) / aspectRatio);
|
|
ProjectionMatrixChanged();
|
|
}
|
|
|
|
void Frustum::SetVerticalFovAndAspectRatio(float vFov, float aspectRatio) {
|
|
type = FrustumType::Perspective;
|
|
verticalFov = vFov;
|
|
horizontalFov = 2.f * Math::Atan(Math::Tan(vFov * 0.5f) * aspectRatio);
|
|
return ProjectionMatrixChanged();
|
|
}
|
|
|
|
Ray Frustum::UnProject(float x, float y) const {
|
|
assert(x >= -1.f);
|
|
assert(x <= 1.f);
|
|
assert(y >= -1.f);
|
|
assert(y <= 1.f);
|
|
|
|
if (type == FrustumType::Perspective) {
|
|
Vector3 nearPlanePos = NearPlanePos(x, y);
|
|
return Ray(pos, (nearPlanePos - pos).Normalized());
|
|
} else
|
|
return UnProjectFromNearPlane(x, y);
|
|
}
|
|
|
|
Ray Frustum::UnProject(const Vector2 &xy) const {
|
|
return UnProject(xy.x, xy.y);
|
|
}
|
|
|
|
Ray Frustum::UnProjectFromNearPlane(float x, float y) const {
|
|
return UnProjectLineSegment(x, y).ToRay();
|
|
}
|
|
|
|
LineSegment Frustum::UnProjectLineSegment(float x, float y) const {
|
|
Vector3 nearPlanePos = NearPlanePos(x, y);
|
|
Vector3 farPlanePos = FarPlanePos(x, y);
|
|
return {nearPlanePos, farPlanePos};
|
|
}
|
|
|
|
Vector3 Frustum::PointInside(float x, float y, float z) const {
|
|
assert(z >= 0.f);
|
|
assert(z <= 1.f);
|
|
return UnProjectLineSegment(x, y).GetPoint(z);
|
|
}
|
|
|
|
Vector3 Frustum::PointInside(const Vector3 &xyz) const {
|
|
return PointInside(xyz.x, xyz.y, xyz.z);
|
|
}
|
|
|
|
Vector3 Frustum::CenterPoint() const {
|
|
return pos + (nearPlaneDistance + farPlaneDistance) * 0.5f * front;
|
|
}
|
|
|
|
Vector3 Frustum::CornerPoint(int cornerIndex) const {
|
|
assert(0 <= cornerIndex && cornerIndex <= 7);
|
|
switch(cornerIndex)
|
|
{
|
|
default: // For release builds where assume() is disabled, return always the first option if out-of-bounds.
|
|
case 0: return NearPlanePos(-1, -1);
|
|
case 1: return FarPlanePos(-1, -1);
|
|
case 2: return NearPlanePos(-1, 1);
|
|
case 3: return FarPlanePos(-1, 1);
|
|
case 4: return NearPlanePos(1, -1);
|
|
case 5: return FarPlanePos(1, -1);
|
|
case 6: return NearPlanePos(1, 1);
|
|
case 7: return FarPlanePos(1, 1);
|
|
}
|
|
}
|
|
|
|
Vector3 Frustum::NearPlanePos(float x, float y) const {
|
|
assert(type == FrustumType::Perspective || type == FrustumType::Orthographic);
|
|
|
|
if (type == FrustumType::Perspective)
|
|
{
|
|
float frontPlaneHalfWidth = std::tan(horizontalFov*0.5f)*nearPlaneDistance;
|
|
float frontPlaneHalfHeight = std::tan(verticalFov*0.5f)*nearPlaneDistance;
|
|
x = x * frontPlaneHalfWidth; // Map [-1,1] to [-width/2, width/2].
|
|
y = y * frontPlaneHalfHeight; // Map [-1,1] to [-height/2, height/2].
|
|
Vector3 right = WorldRight();
|
|
return pos + front * nearPlaneDistance + x * right + y * up;
|
|
}
|
|
else
|
|
{
|
|
Vector3 right = WorldRight();
|
|
return pos + front * nearPlaneDistance
|
|
+ x * orthographicWidth * 0.5f * right
|
|
+ y * orthographicHeight * 0.5f * up;
|
|
}
|
|
}
|
|
|
|
Vector3 Frustum::NearPlanePos(const Vector2 &point) const { return NearPlanePos(point.x, point.y); }
|
|
|
|
Vector3 Frustum::FarPlanePos(float x, float y) const {
|
|
assert(type == FrustumType::Perspective || type == FrustumType::Orthographic);
|
|
|
|
if (type == FrustumType::Perspective)
|
|
{
|
|
float farPlaneHalfWidth = std::tan(horizontalFov*0.5f)*farPlaneDistance;
|
|
float farPlaneHalfHeight = std::tan(verticalFov*0.5f)*farPlaneDistance;
|
|
|
|
x = x * farPlaneHalfWidth;
|
|
y = y * farPlaneHalfHeight;
|
|
Vector3 right = WorldRight();
|
|
return pos + front * farPlaneDistance + x * right + y * up;
|
|
} else {
|
|
Vector3 right = WorldRight();
|
|
return pos + front * farPlaneDistance
|
|
+ x * orthographicWidth * 0.5f * right
|
|
+ y * orthographicHeight * 0.5f * up;
|
|
}
|
|
}
|
|
|
|
Vector3 Frustum::FarPlanePos(const Vector2 &point) const {
|
|
return FarPlanePos(point.x, point.y);
|
|
}
|
|
|
|
Plane Frustum::TopPlane() const {
|
|
if (type == FrustumType::Perspective) {
|
|
Vector3 topSide = front + Math::Tan(verticalFov * 0.5f) * up;
|
|
Vector3 right = WorldRight();
|
|
Vector3 topSideNormal = ((handedness == FrustumHandedness::Right) ? Vector3::Cross(right, topSide) : Vector3::Cross(topSide, right)).Normalized();
|
|
return Plane(pos, topSideNormal);
|
|
} else
|
|
{
|
|
return Plane(NearPlanePos(0.f, 1.f), up);
|
|
}
|
|
}
|
|
|
|
Plane Frustum::BottomPlane() const {
|
|
if (type == FrustumType::Perspective) {
|
|
Vector3 bottomSide = front - Math::Tan(verticalFov * 0.5f) * up;
|
|
Vector3 left = -WorldRight();
|
|
Vector3 bottomSideNormal = ((handedness == FrustumHandedness::Right) ? Vector3::Cross(left, bottomSide) : Vector3::Cross(bottomSide, left)).Normalized();
|
|
return Plane(pos, bottomSideNormal);
|
|
} else {
|
|
return Plane(NearPlanePos(0.f, -1.f), -up);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Plane Frustum::RightPlane() const {
|
|
if (type == FrustumType::Perspective) {
|
|
Vector3 right = WorldRight();
|
|
right.ScaleToLength(Math::Tan(horizontalFov * 0.5f));
|
|
Vector3 rightSide = front + right;
|
|
Vector3 rightSideNormal = ((handedness == FrustumHandedness::Right) ? Vector3::Cross(rightSide, up) : Vector3::Cross(up, rightSide)).Normalized();
|
|
return Plane(pos, rightSideNormal);
|
|
} else {
|
|
Vector3 right = WorldRight();
|
|
return Plane(NearPlanePos(1.f,0.f), right.Normalized());
|
|
}
|
|
}
|
|
|
|
Plane Frustum::LeftPlane() const {
|
|
if (type == FrustumType::Perspective) {
|
|
Vector3 left = -WorldRight();
|
|
left.ScaleToLength(Math::Tan(horizontalFov*0.5f));
|
|
Vector3 leftSide = front + left;
|
|
Vector3 leftSideNormal = ((handedness == FrustumHandedness::Right) ? Vector3::Cross(up, leftSide) : Vector3::Cross(leftSide, up)).Normalized();
|
|
return Plane(pos, leftSideNormal);
|
|
} else {
|
|
Vector3 left = -WorldRight();
|
|
return Plane(NearPlanePos(-1.f, 0.f), left.Normalized());
|
|
}
|
|
}
|
|
|
|
Plane Frustum::FarPlane() const {
|
|
return Plane(pos + front * farPlaneDistance, front);
|
|
}
|
|
|
|
Plane Frustum::NearPlane() const {
|
|
return Plane(pos + front * nearPlaneDistance, -front);
|
|
}
|
|
|
|
float Frustum::NearPlaneWidth() const {
|
|
if (type == FrustumType::Perspective)
|
|
return Math::Tan(horizontalFov*0.5f)*2.f * nearPlaneDistance;
|
|
else
|
|
return orthographicWidth;
|
|
}
|
|
|
|
float Frustum::NearPlaneHeight() const {
|
|
if (type == FrustumType::Perspective)
|
|
return Math::Tan(verticalFov*0.5f) * 2.f * nearPlaneDistance;
|
|
else
|
|
return orthographicWidth;
|
|
}
|
|
|
|
Plane Frustum::GetPlane(int faceIndex) const {
|
|
assert(0 <= faceIndex && faceIndex <= 5);
|
|
switch(faceIndex)
|
|
{
|
|
default: // For release builds where assume() is disabled, always return the first option if out-of-bounds.
|
|
case 0: return NearPlane();
|
|
case 1: return FarPlane();
|
|
case 2: return LeftPlane();
|
|
case 3: return RightPlane();
|
|
case 4: return TopPlane();
|
|
case 5: return BottomPlane();
|
|
}
|
|
}
|
|
|
|
void Frustum::GetPlanes(Plane *outArray) const {
|
|
assert(outArray);
|
|
for (int i = 0; i < 6; ++i)
|
|
outArray[i] = GetPlane(i);
|
|
}
|
|
|
|
void Frustum::Translate(const Vector3 &offset) {
|
|
pos += offset;
|
|
}
|
|
|
|
|
|
void Frustum::Transform(const Matrix3x3& transform) {
|
|
assert(transform.HasUniformScale());
|
|
pos = transform * pos;
|
|
front = transform * front;
|
|
float scaleFactor = front.Normalize();
|
|
up = (transform * up).Normalized();
|
|
nearPlaneDistance *= scaleFactor;
|
|
farPlaneDistance *= scaleFactor;
|
|
|
|
if (type == FrustumType::Orthographic) {
|
|
orthographicWidth *= scaleFactor;
|
|
orthographicHeight *= scaleFactor;
|
|
}
|
|
}
|
|
|
|
void Frustum::Transform(const Matrix4x4& transform) {
|
|
assert(transform.Row(3).Equals(0,0,0,1));
|
|
|
|
assert(transform.HasUniformScale());
|
|
|
|
// TODO: This is incorrect. Technically we need only a mat3x4 to implement this operation.
|
|
// But we choose to not implement this in our code, instead opting for only 3x3 and 4x4.
|
|
// Care should be taken when using this, it may need to be edited for mathematical correctness
|
|
Transform(transform.GetRotatePart());
|
|
}
|
|
|
|
void Frustum::Transform(const Quaternion& transform) {
|
|
Transform(transform.ToMatrix3x3());
|
|
}
|
|
|
|
|
|
|
|
Polyhedron Frustum::ToPolyhedron() const {
|
|
// Note to maintainer: this function is an exact copy of AABB::ToPolyhedron() and OBB::ToPolyhedron().
|
|
|
|
Polyhedron p;
|
|
// Populate the corners of this Frustum.
|
|
// They will be in the order 0: ---, 1: --+, 2: -+-, 3: -++, 4: +--, 5: +-+, 6: ++-, 7: +++.
|
|
for (int i = 0; i < 8; ++i)
|
|
{
|
|
p.v.push_back(CornerPoint(i));
|
|
}
|
|
|
|
// generate the 6 faces of this Frustum. The function Frustum::GetPlane() has a convention of returning
|
|
// the planes in order near, far, left, right, top, bottom, so follow the same convention here.
|
|
const int faces[6][4] =
|
|
{
|
|
{ 0, 4, 6, 2 }, // Z-: near plane
|
|
{ 1, 3, 7, 5 }, // Z+: far plane
|
|
{ 0, 2, 3, 1 }, // X-: left plane
|
|
{ 4, 5, 7, 6 }, // X+: right plane
|
|
{ 7, 3, 2, 6 }, // Y+: top plane
|
|
{ 0, 1, 5, 4 }, // Y-: bottom plane
|
|
};
|
|
|
|
for (int f = 0; f < 6; ++f)
|
|
{
|
|
Polyhedron::Face face;
|
|
if (this->handedness == FrustumHandedness::Left)
|
|
{
|
|
for (int v = 0; v < 4; ++v)
|
|
face.v.push_back(faces[f][3-v]);
|
|
} else {
|
|
for (int v = 0; v < 4; ++v)
|
|
face.v.push_back(faces[f][v]);
|
|
}
|
|
p.f.push_back(face);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
Matrix4x4 Frustum::ViewProjMatrix() const { return viewProjectionMatrix; }
|
|
|
|
Matrix4x4 Frustum::ComputeViewProjMatrix() const { return ComputeProjectionMatrix() * ComputeViewMatrix();}
|
|
|
|
Vector3 Frustum::Project(const Vector3 &point) const {
|
|
Vector4 projectedPoint = ViewProjMatrix().Mul(Vector4(point, 1.f));
|
|
projectedPoint.NormalizeW();
|
|
return projectedPoint.XYZ();
|
|
}
|
|
|
|
Matrix4x4 Frustum::WorldMatrix() const { return worldMatrix;}
|
|
|
|
Matrix4x4 Frustum::ComputeWorldMatrix() const {
|
|
assert(pos.IsFinite());
|
|
//assert(up.IsNormalized(1e-3f));
|
|
assert(front.IsNormalized(1e-3f));
|
|
assert(up.IsPerpendicular(front));
|
|
|
|
Matrix4x4 m;
|
|
m.SetCol3(0, WorldRight().Normalized());
|
|
m.SetCol3(1, up);
|
|
if (handedness == FrustumHandedness::Right)
|
|
m.SetCol3(2, -front);
|
|
else
|
|
m.SetCol3(2, front);
|
|
m.SetCol3(3, pos);
|
|
assert(!m.HasNegativeScale());
|
|
return m;
|
|
}
|
|
|
|
Matrix4x4 Frustum::ViewMatrix() const { auto m = worldMatrix; m.InverseOrthonormal(); return m; }
|
|
|
|
Matrix4x4 Frustum::ComputeViewMatrix() const {
|
|
Matrix4x4 world = ComputeWorldMatrix();
|
|
world.InverseOrthonormal();
|
|
return world;
|
|
}
|
|
|
|
Matrix4x4 Frustum::ProjectionMatrix() const { return projectionMatrix;}
|
|
|
|
Matrix4x4 Frustum::ComputeProjectionMatrix() const {
|
|
if (type == FrustumType::Invalid || projectiveSpace == FrustumProjectiveSpace::Invalid)
|
|
return Matrix4x4::NaN;
|
|
|
|
//assert(type == FrustumType::Perspective || type == FrustumType::Orthographic);
|
|
//assert(projectiveSpace == FrustumProjectiveSpace::GL || projectiveSpace == FrustumProjectiveSpace::D3D);
|
|
//assert(handedness == FrustumHandedness::Left || handedness == FrustumHandedness::Right);
|
|
|
|
if (type == FrustumType::Perspective) {
|
|
if (projectiveSpace == FrustumProjectiveSpace::GL) {
|
|
if (handedness == FrustumHandedness::Right)
|
|
return Matrix4x4::OpenGLPerspProjRH(nearPlaneDistance, farPlaneDistance, NearPlaneWidth(), NearPlaneHeight());
|
|
else
|
|
return Matrix4x4::OpenGLPerspProjLH(nearPlaneDistance, farPlaneDistance, NearPlaneWidth(), NearPlaneHeight());
|
|
|
|
} else if (projectiveSpace == FrustumProjectiveSpace::D3D) {
|
|
if (handedness == FrustumHandedness::Right)
|
|
return Matrix4x4::D3DPerspProjRH(nearPlaneDistance, farPlaneDistance, NearPlaneWidth(), NearPlaneHeight());
|
|
else
|
|
return Matrix4x4::D3DPerspProjLH(nearPlaneDistance, farPlaneDistance, NearPlaneHeight(), NearPlaneHeight());
|
|
}
|
|
} else if (type == FrustumType::Orthographic) {
|
|
if (projectiveSpace == FrustumProjectiveSpace::GL) {
|
|
if (handedness == FrustumHandedness::Right)
|
|
return Matrix4x4::OpenGLOrthoProjRH(nearPlaneDistance, farPlaneDistance, orthographicWidth, orthographicHeight);
|
|
else if (handedness == FrustumHandedness::Left)
|
|
return Matrix4x4::OpenGLOrthoProjLH(nearPlaneDistance, farPlaneDistance, orthographicWidth, orthographicHeight);
|
|
|
|
} else if (projectiveSpace == FrustumProjectiveSpace::D3D) {
|
|
if (handedness == FrustumHandedness::Right)
|
|
return Matrix4x4::D3DOrthoProjRH(nearPlaneDistance, farPlaneDistance, orthographicWidth, orthographicHeight);
|
|
else
|
|
return Matrix4x4::D3DOrthoProjLH(nearPlaneDistance, farPlaneDistance, orthographicWidth, orthographicHeight);
|
|
}
|
|
}
|
|
//assert(false && "Not all values of Frustum were initialized properly! Please initialize correctly before calling Frustum::ProjectionMatrix()!");
|
|
return Matrix4x4::NaN;
|
|
}
|
|
|
|
bool Frustum::Contains(const Vector3 &point) const {
|
|
const float eps = 1e-3f;
|
|
const float position = 1.f + eps;
|
|
const float neg = -position;
|
|
Vector3 projected = Project(point);
|
|
|
|
if (projectiveSpace == FrustumProjectiveSpace::D3D) {
|
|
return neg <= projected.x && projected.x <= position &&
|
|
neg <= projected.y && projected.y <= position &&
|
|
-eps <= projected.z && projected.z <= position;
|
|
} else if (projectiveSpace == FrustumProjectiveSpace::GL) {
|
|
return neg <= projected.x && projected.x <= position &&
|
|
neg <= projected.y && projected.y <= position &&
|
|
neg <= projected.z && projected.z <= position;
|
|
} else {
|
|
// TODO: Make Frustum::Contains agnostic of the projection settings.
|
|
assert(false && "Not all values of Frustum were initialized properly! Please initialize correctly before calling Frustum::Contains()!");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Frustum::Contains(const LineSegment &lineSegment) const {
|
|
return Contains(lineSegment.A) && Contains(lineSegment.B);
|
|
}
|
|
|
|
bool Frustum::Contains(const Triangle &triangle) const {
|
|
return Contains(triangle.V0) && Contains(triangle.V1) && Contains(triangle.V2);
|
|
}
|
|
|
|
bool Frustum::Contains(const Polygon &polygon) const {
|
|
for (int i = 0; i < polygon.NumVertices(); ++i)
|
|
if (!Contains(polygon.Vertex(i)))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool Frustum::Contains(const AABB &aabb) const {
|
|
for (int i = 0; i < 8; ++i)
|
|
if (!Contains(aabb.CornerPoint(i)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Frustum::Contains(const OBB &obb) const {
|
|
for (int i = 0; i < 8; ++i)
|
|
if (!Contains(obb.CornerPoint(i)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Frustum::Contains(const Frustum &frustum) const {
|
|
for (int i = 0; i < 8; ++i)
|
|
if (!Contains(frustum.CornerPoint(i)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Frustum::Contains(const Polyhedron &polyhedron) const {
|
|
assert(polyhedron.IsClosed());
|
|
|
|
for (int i = 0; i < polyhedron.NumVertices(); ++i)
|
|
if (!Contains(polyhedron.Vertex(i)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
float Frustum::Distance(const Vector3 &point) const {
|
|
Vector3 pt = ClosestPoint(point);
|
|
return pt.Distance(point);
|
|
}
|
|
|
|
Vector3 Frustum::ClosestPoint(const Vector3 &point) const {
|
|
if (type == FrustumType::Orthographic) {
|
|
float frontHalfSize = (farPlaneDistance - nearPlaneDistance) * 0.5f;
|
|
float halfWidth = orthographicWidth * 0.5f;
|
|
float halfHeight = orthographicHeight * 0.5f;
|
|
|
|
Vector3 frustumCenter = pos + (frontHalfSize + nearPlaneDistance) * front;
|
|
Vector3 right = Vector3::Cross(front, up);
|
|
assert(right.IsNormalized());
|
|
Vector3 d = point - frustumCenter;
|
|
Vector3 closestPoint = frustumCenter;
|
|
|
|
closestPoint += Math::Clamp(Vector3::Dot(d, front), -frontHalfSize, frontHalfSize) * front;
|
|
closestPoint += Math::Clamp(Vector3::Dot(d, right), -halfWidth, halfWidth) * right;
|
|
closestPoint += Math::Clamp(Vector3::Dot(d, up), -halfHeight, halfHeight) * up;
|
|
|
|
return closestPoint;
|
|
} else {
|
|
return ToPolyhedron().ClosestPoint(point);
|
|
}
|
|
}
|
|
|
|
bool Frustum::Intersects(const Ray &ray) const {
|
|
return this->ToPolyhedron().Intersects(ray);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Line &line) const
|
|
{
|
|
///@todo This is a naive test. Implement a faster version.
|
|
return this->ToPolyhedron().Intersects(line);
|
|
}
|
|
|
|
bool Frustum::Intersects(const LineSegment &lineSegment) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, lineSegment);
|
|
}
|
|
|
|
bool Frustum::Intersects(const AABB &aabb) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, aabb);
|
|
}
|
|
|
|
bool Frustum::Intersects(const OBB &obb) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, obb);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Plane &plane) const
|
|
{
|
|
return plane.Intersects(*this);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Triangle &triangle) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, triangle);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Polygon &polygon) const
|
|
{
|
|
return polygon.Intersects(*this);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Sphere &sphere) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, sphere);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Capsule &capsule) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, capsule);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Frustum &frustum) const
|
|
{
|
|
return Algorithms::GJKIntersect(*this, frustum);
|
|
}
|
|
|
|
bool Frustum::Intersects(const Polyhedron &polyhedron) const
|
|
{
|
|
return this->ToPolyhedron().Intersects(polyhedron);
|
|
}
|
|
|
|
void Frustum::WorldMatrixChanged() {
|
|
worldMatrix = ComputeWorldMatrix();
|
|
Matrix4x4 viewMatrix = worldMatrix;
|
|
viewMatrix.InverseOrthonormal();
|
|
viewProjectionMatrix = projectionMatrix * viewMatrix;
|
|
}
|
|
|
|
void Frustum::ProjectionMatrixChanged() {
|
|
projectionMatrix = ComputeProjectionMatrix();
|
|
if (!Math::IsNotANumber(worldMatrix[0][0])) {
|
|
Matrix4x4 viewMatrix = worldMatrix;
|
|
viewMatrix.InverseOrthonormal();
|
|
viewProjectionMatrix = projectionMatrix * viewMatrix;
|
|
}
|
|
}
|
|
|
|
Frustum::Frustum()
|
|
: type(FrustumType::Invalid),
|
|
pos(Vector3::NaN),
|
|
front(Vector3::NaN),
|
|
up(Vector3::NaN),
|
|
nearPlaneDistance(NAN),
|
|
farPlaneDistance(NAN),
|
|
worldMatrix(Matrix4x4::NaN),
|
|
viewProjectionMatrix(Matrix4x4::NaN)
|
|
{
|
|
// For conveniency, allow automatic initialization of the graphics API and handedness in use.
|
|
// If neither of the #defines are set, user must specify per-instance.
|
|
}
|
|
|
|
Vector3 Frustum::WorldRight() const {
|
|
if (handedness == FrustumHandedness::Right)
|
|
return Vector3::Cross(front, up);
|
|
else
|
|
return Vector3::Cross(up, front);
|
|
}
|
|
|
|
|
|
}
|