Frustum partial implementation.

This commit is contained in:
2024-07-10 14:17:20 -04:00
parent 62aeb36628
commit 6684c64ab7
2 changed files with 698 additions and 86 deletions

View File

@@ -142,175 +142,250 @@ namespace J3ML::Geometry
Matrix4x4 viewProjectionMatrix;
public:
/// The default constructor creates an uninitialized Frustum object.
/** This means that the values of the members type, projectiveSpace, handedness, pos, front, up, nearPlaneDistance, farPlaneDistance, horizontalFov/orthographicWidth and
verticalFov/orthographicHeight are all NaN after creating a new Frustum using this
default constructor. Remember to assign to them before use.
@note As an exception to other classes in MathGeoLib, this class initializes its members to NaNs, whereas the other classes leave the members uninitialized. This difference
is because the Frustum class implements a caching mechanism where world, projection and viewProj matrices are recomputed on demand, which does not work nicely together
if the defaults were uninitialized.
*/
/// The default constructor creates an uninitialized Frustum object.
/** This means that the values of the members type, projectiveSpace, handedness, pos, front, up, nearPlaneDistance, farPlaneDistance, horizontalFov/orthographicWidth and
verticalFov/orthographicHeight are all NaN after creating a new Frustum using this
default constructor. Remember to assign to them before use.
@note As an exception to other classes in MathGeoLib, this class initializes its members to NaNs, whereas the other classes leave the members uninitialized. This difference
is because the Frustum class implements a caching mechanism where world, projection and viewProj matrices are recomputed on demand, which does not work nicely together
if the defaults were uninitialized. */
Frustum();
/// Quickly returns an arbitrary point inside this Frustum. Used in GJK intersection test.
inline Vector3 AnyPointFast() const { return CornerPoint(0); }
static Frustum CreateFrustumFromCamera(const CoordinateFrame& cam, float aspect, float fovY, float zNear, float zFar);
public:
/// Quickly returns an arbitrary point inside this Frustum. Used in GJK intersection test.
[[nodiscard]] Vector3 AnyPointFast() const { return CornerPoint(0); }
/// Returns the tightest AABB that contains this Frustum.
/** This function computes the optimal minimum volume AABB that encloses this Frustum.
@note Since an AABB cannot generally represent a Frustum, this conversion is not exact, but the returned AABB
specifies a larger volume.
@see MinimalEnclosingOBB(), ToPolyhedron(). */
AABB MinimalEnclosingAABB() const;
[[nodiscard]] AABB MinimalEnclosingAABB() const;
[[nodiscard]] bool IsFinite() const;
/// Returns the tightest OBB that encloses this Frustum.
/** This function computes the optimal minimum volume OBB that encloses this Frustum.
@note If the type of this frustum is Perspective, this conversion is not exact, but the returned OBB specifies
a larger volume. If the type of this Frustum is orthographic, this conversion is exact, since the shape of an
orthographic Frustum is an OBB.
@see MinimalEnclosingAABB(), ToPolyhedron(). */
OBB MinimalEnclosingOBB() const;
[[nodiscard]] OBB MinimalEnclosingOBB(float expandGuardband = 1e-5f) const;
/// Sets the type of this Frustum.
/** @note Calling this function recomputes the cached view and projection matrices of this Frustum.
@see SetViewPlaneDistances(), SetFrame(), SetPos(), SetFront(), SetUp(), SetPerspective(), SetOrthographic(), ProjectiveSpace(), Handedness(). */
void SetKind(FrustumProjectiveSpace projectiveSpace, FrustumHandedness handedness);
/// Sets the depth clip distances of this Frustum.
/** @param nearPlaneDistance The z distance from the eye point to the position of the Frustum near clip plane. Always pass a positive value here.
@param farPlaneDistance The z distance from the eye point to the position of the Frustum far clip plane. Always pass a value that is larger than nearClipDistance.
/** @param n The z distance from the eye point to the position of the Frustum near clip plane. Always pass a positive value here.
@param f The z distance from the eye point to the position of the Frustum far clip plane. Always pass a value that is larger than nearClipDistance.
@note Calling this function recomputes the cached projection matrix of this Frustum.
@see SetKind(), SetFrame(), SetPos(), SetFront(), SetUp(), SetPerspective(), SetOrthographic(), NearPlaneDistance(), FarPlaneDistance(). */
void SetViewPlaneDistances(float nearPlaneDistance, float farPlaneDistance);
void SetViewPlaneDistances(float n, float f);
/// Specifies the full coordinate space of this Frustum in one call.
/** @note Calling this function recomputes the cached world matrix of this Frustum.
@note As a micro-optimization, prefer this function over the individual SetPos/SetFront/SetUp functions if you need to do a batch of two or more changes, to avoid
redundant recomputation of the world matrix.
@see SetKind(), SetViewPlaneDistances(), SetPos(), SetFront(), SetUp(), SetPerspective(), SetOrthographic(), Pos(), Front(), Up(). */
void SetFrame(const Vector3& pos, const Vector3& front, const Vector3& up);
/// Sets the world-space position of this Frustum.
/** @note Calling this function recomputes the cached world matrix of this Frustum.
@see SetKind(), SetViewPlaneDistances(), SetFrame(), SetFront(), SetUp(), SetPerspective(), SetOrthographic(), Pos(). */
void SetPos(const Vector3& pos);
/// Sets the world-space direction the Frustum eye is looking towards.
/** @note Calling this function recomputes the cached world matrix of this Frustum.
@see SetKind(), SetViewPlaneDistances(), SetFrame(), SetPos(), SetUp(), SetPerspective(), SetOrthographic(), Front(). */
void SetFront(const Vector3& front);
/// Sets the world-space camera up direction vector of this Frustum.
/** @note Calling this function recomputes the cached world matrix of this Frustum.
@see SetKind(), SetViewPlaneDistances(), SetFrame(), SetPos(), SetFront(), SetPerspective(), SetOrthographic(), Up(). */
void SetUp(const Vector3& up);
/// Makes this Frustum use a perspective projection formula with the given FOV parameters.
/** A Frustum that uses the perspective projection is shaped like a pyramid that is cut from the top, and has a
base with a rectangular area.
@note Calling this function recomputes the cached projection matrix of this Frustum.
@see SetKind(), SetViewPlaneDistances(), SetFrame(), SetPos(), SetFront(), SetUp(), SetOrthographic(), HorizontalFov(), VerticalFov(), SetHorizontalFovAndAspectRatio(), SetVerticalFovAndAspectRatio(). */
void SetPerspective(float horizontalFov, float verticalFov);
void SetPerspective(float h, float v);
/// Makes this Frustum use an orthographic projection formula with the given FOV parameters.
/** A Frustum that uses the orthographic projection is shaded like a cube (an OBB).
@note Calling this function recomputes the cached projection matrix of this Frustum.
@see SetKind(), SetViewPlaneDistances(), SetFrame(), SetPos(), SetFront(), SetUp(), SetOrthographic(), OrthographicWidth(), OrthographicHeight(). */
void SetOrthographic(float orthographicWidth, float orthographicHeight);
void SetOrthographic(float w, float h);
/// Returns the handedness of the projection formula used by this Frustum.
/** @see SetKind(), FrustumHandedness. */
FrustumHandedness Handedness() const { return handedness; }
[[nodiscard]] FrustumHandedness Handedness() const { return handedness; }
/// Returns the type of the projection formula used by this Frustum.
/** @see SetPerspective(), SetOrthographic(), FrustumType. */
FrustumType Type() const { return type; }
[[nodiscard]] FrustumType Type() const { return type; }
/// Returns the convention of the post-projective space used by this Frustum.
/** @see SetKind(), FrustumProjectiveSpace. */
FrustumProjectiveSpace ProjectiveSpace() const { return projectiveSpace;}
[[nodiscard]] FrustumProjectiveSpace ProjectiveSpace() const { return projectiveSpace;}
/// Returns the world-space position of this Frustum.
/** @see SetPos(), Front(), Up(). */
const Vector3 &Pos() const {return pos;}
[[nodiscard]] const Vector3 &Pos() const {return pos;}
/// Returns the world-space camera look-at direction of this Frustum.
/** @see Pos(), SetFront(), Up(). */
const Vector3 &Front() const { return front; }
[[nodiscard]] const Vector3 &Front() const { return front; }
/// Returns the world-space camera up direction of this Frustum.
/** @see Pos(), Front(), SetUp(). */
const Vector3 &Up() const { return up; }
[[nodiscard]] const Vector3 &Up() const { return up; }
/// Returns the distance from the Frustum eye to the near clip plane.
/** @see SetViewPlaneDistances(), FarPlaneDistance(). */
float NearPlaneDistance() const { return nearPlaneDistance; }
[[nodiscard]] float NearPlaneDistance() const { return nearPlaneDistance; }
/// Returns the distance from the Frustum eye to the far clip plane.
/** @see SetViewPlaneDistances(), NearPlaneDistance(). */
float FarPlaneDistance() const { return farPlaneDistance;}
[[nodiscard]] float FarPlaneDistance() const { return farPlaneDistance;}
/// Returns the horizontal field-of-view used by this Frustum, in radians.
/** @note Calling this function when the Frustum is not set to use perspective projection will return values that are meaningless.
@see SetPerspective(), Type(), VerticalFov(). */
float HorizontalFov() const { return horizontalFov;}
[[nodiscard]] float HorizontalFov() const { return horizontalFov;}
/// Returns the vertical field-of-view used by this Frustum, in radians.
/** @note Calling this function when the Frustum is not set to use perspective projection will return values that are meaningless.
@see SetPerspective(), Type(), HorizontalFov(). */
float VerticalFov() const { return verticalFov;}
[[nodiscard]] float VerticalFov() const { return verticalFov;}
/// Returns the world-space width of this Frustum.
/** @note Calling this function when the Frustum is not set to use orthographic projection will return values that are meaningless.
@see SetOrthographic(), Type(), OrthographicHeight(). */
float OrthographicWidth() const { return orthographicWidth; }
[[nodiscard]] float OrthographicWidth() const { return orthographicWidth; }
/// Returns the world-space height of this Frustum.
/** @note Calling this function when the Frustum is not set to use orthographic projection will return values that are meaningless.
@see SetOrthographic(), Type(), OrthographicWidth(). */
float OrthograhpicHeight() const { return orthographicHeight; }
[[nodiscard]] float OrthograhpicHeight() const { return orthographicHeight; }
/// Returns the number of line segment edges that this Frustum is made up of, which is always 12.
/** This function is used in template-based algorithms to provide an unified API for iterating over the features of a Polyhedron. */
int NumEdges() const { return 12; }
static int NumEdges() { return 12; }
/// Returns the aspect ratio of the view rectangle on the near plane.
/** The aspect ratio is the ratio of the width of the viewing rectangle to its height. This can also be computed by
the expression horizontalFov / verticalFov. To produce a proper non-stretched image when rendering, this
aspect ratio should match the aspect ratio of the actual render target (e.g. 4:3, 16:9 or 16:10 in full screen mode).
@see horizontalFov, verticalFov. */
float AspectRatio() const;
[[nodiscard]] float AspectRatio() const;
/// Makes this Frustum use a perspective projection formula with the given horizontal FOV parameter and aspect ratio.
/** Specifies the horizontal and vertical field-of-view values for this Frustum based on the given horizontal FOV
and the screen size aspect ratio.
@note Calling this function recomputes the cached projection matrix of this Frustum.
@see SetPerspective(), SetVerticalFovAndAspectRatio(). */
void SetHorizontalFovAndAspectRatio(float horizontalFov, float aspectRatio);
void SetHorizontalFovAndAspectRatio(float hFov, float aspectRatio);
/// Makes this Frustum use a perspective projection formula with the given vertical FOV parameter and aspect ratio.
/** Specifies the horizontal and vertical field-of-view values for this Frustum based on the given vertical FOV
and the screen size aspect ratio.
@note Calling this function recomputes the cached projection matrix of this Frustum.
@see SetPerspective(), SetHorizontalFovAndAspectRatio(). */
void SetVerticalFovAndAspectRatio(float verticalFov, float aspectRatio);
void SetVerticalFovAndAspectRatio(float vFov, float aspectRatio);
Vector3 CornerPoint(int cornerIndex) const;
/// Finds a ray in world space that originates at the eye point and looks in the given direction inside the frustum.
/** The (x,y) coordinate specifies the normalized viewport coordinate through which the ray passes.
Both x and y must be in the range [-1,1].
Specifying (-1, -1) returns the bottom-left corner of the near plane.
The point (1, 1) corresponds to the top-right corner of the near plane. */
Ray UnProject(float x, float y) const;
Vector3 NearPlanePos(float x, float y) const;
Vector3 FarPlanePos(float x, float y) const;
Ray UnProject(const Vector2& xy) const;
Ray UnProjectFromNearPlane(float x, float y) const;
[[nodiscard]] LineSegment UnProjectLineSegment(float x, float y) const;
Vector3 PointInside(float x, float y, float z) const;
Vector3 PointInside(const Vector3& xyz) const;
[[nodiscard]] Vector3 CenterPoint() const;
[[nodiscard]] Vector3 CornerPoint(int cornerIndex) const;
/// Returns a point on the near plane.
/** @param x A value in the range [-1, 1].
@param y A value in the range [-1, 1].
Specifying (-1, -1) returns the bottom-left corner of the near plane.
The point (1, 1) corresponds to the top-right corner of the near plane.
@note This coordinate space is called the normalized viewport coordinate space.
@see FarPlanePos(). */
[[nodiscard]] Vector3 NearPlanePos(float x, float y) const;
[[nodiscard]] Vector3 NearPlanePos(const Vector2& point) const;
/// Returns a point on the far plane.
/** @param x A value in the range [-1, 1].
@param y A value in the range [-1, 1].
Specifying (-1, -1) returns the bottom-left corner of the far plane.
The point (1, 1) corresponds to the top-right corner of the far plane.
@note This coordinate space is called the normalized viewport coordinate space.
@see NearPlanePos(). */
[[nodiscard]] Vector3 FarPlanePos(float x, float y) const;
[[nodiscard]] Vector3 FarPlanePos(const Vector2& point) const;
/// Computes the direction vector that points logically to the right-hand side of the Frustum.
/** This vector together with the member variables 'front' and 'up' form the orthonormal basis of the view frustum.
@see pos, front. */
Vector3 WorldRight() const;
[[nodiscard]] Vector3 WorldRight() const;
[[nodiscard]] Plane TopPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
[[nodiscard]] Plane BottomPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
[[nodiscard]] Plane RightPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
Plane TopPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
Plane BottomPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
Plane RightPlane() const; ///< [similarOverload: LeftPlane] [hideIndex]
/// Returns the plane equation of the specified side of this Frustum.
/** The normal vector of the returned plane points outwards from the volume inside the frustum.
This means the negative half-space of the Frustum is the space inside the Frustum.
[indexTitle: Left/Right/Top/BottomPlane]
@see NearPlane(), FarPlane(), GetPlane(), GetPlanes(). */
Plane LeftPlane() const;
[[nodiscard]] Plane LeftPlane() const;
/// Computes the plane equation of the far plane of this Frustum. [similarOverload: NearPlane]
/** The normal vector of the returned plane points outwards from the volume inside the frustum, i.e. away from the eye point.
(towards front). This means the negative half-space of the Frustum is the space inside the Frustum.
@see front, FarPlane(), LeftPlane(), RightPlane(), TopPlane(), BottomPlane(), GetPlane(), GetPlanes(). */
Plane FarPlane() const;
[[nodiscard]] Plane FarPlane() const;
/// Computes the plane equation of the near plane of this Frustum.
/** The normal vector of the returned plane points outwards from the volume inside the frustum, i.e. towards the eye point
(towards -front). This means the negative half-space of the Frustum is the space inside the Frustum.
@see front, FarPlane(), LeftPlane(), RightPlane(), TopPlane(), BottomPlane(), GetPlane(), GetPlanes(). */
Plane NearPlane() const;
[[nodiscard]] Plane NearPlane() const;
/// Computes the width of the near plane quad in world space units.
/** @see NearPlaneHeight(). */
float NearPlaneWidth() const;
[[nodiscard]] float NearPlaneWidth() const;
/// Computes the height of the near plane quad in world space units.
/** @see NearPlaneHeight(). */
float NearPlaneHeight() const;
[[nodiscard]] float NearPlaneHeight() const;
/// Returns the specified plane of this frustum.
/** The normal vector of the returned plane points outwards from the volume inside the frustum.
@param faceIndex A number in the range [0,5], which returns the plane at the selected index from
the array { near, far, left, right, top, bottom} */
Plane GetPlane(int faceIndex) const;
/// Returns all six planes of this Frustum.
/** The planes will be output in the order { near, far, left, right, top, bottom }.
@param outArray [out] A pointer to an array of at least 6 elements. This pointer will receive the planes of this Frustum.
This pointer may not be null.
@see GetPlane(), NearPlane(), FarPlane(), LeftPlane(), RightPlane(), TopPlane(), BottomPlane(). */
void GetPlanes(Plane *outArray) const;
/// Moves this Frustum by the given offset vector.
/** @note This function operates in-place.
@param offset The world space offset to apply to the position of this Frustum.
@see Transform(). */
void Translate(const Vector3& offset);
/// Applies a transformation to this Frustum.
/** @param transform The transformation to apply to this Frustum. This transformation must be
* affine, and must contain an orthogoal set of column vectors (may not contain shear or projection).
@@ -325,23 +400,56 @@ namespace J3ML::Geometry
/** This function returns a Polyhedron representation of this Frustum. This conversion is exact, meaning that the returned
Polyhedron represents exactly the same set of points that this Frustum does.
@see MinimalEnclosingAABB(), MinimalEnclosingOBB(). */
Polyhedron ToPolyhedron() const;
[[nodiscard]] Polyhedron ToPolyhedron() const;
/// Converts this Frustum to a PBVolume.
/** This function returns a plane-bounded volume representation of this Frustum. The conversion is exact, meaning that the
returned PBVolume<6> represents exactly the same set of points that this Frustum does.
@see ToPolyhedron(). */
//PBVolume<6> ToPBVolume() const;
[[nodiscard]] PBVolume<6> ToPBVolume() const;
Matrix4x4 ViewProjMatrix() const { return viewProjectionMatrix; }
Matrix4x4 ComputeViewProjMatrix() const;
[[nodiscard]] Vector3 Project(const Vector3& point) const;
/// Computes the matrix that transforms from the world (global) space to the projection space of this Frustum.
/** The matrix computed by this function is simply the concatenation ProjectionMatrix()*ViewMatrix(). This order
of concatenation follows the M*v convention of transforming vectors (as opposed to the v*M convention). This
multiplication order is used, since the matrices ProjectionMatrix() and ViewMatrix() also follow the M*v convention.
@return A matrix that performs the world->view->proj transformation. This matrix is neither invertible or
orthonormal. The returned matrix is built to use the convention Matrix * vector
to map a point between these spaces. (as opposed to the convention v*M).
@see WorldMatrix(), ViewMatrix(), ProjectionMatrix(). */
[[nodiscard]] Matrix4x4 ViewProjMatrix() const;
[[nodiscard]] Matrix4x4 ComputeViewProjMatrix() const;
/// Computes the matrix that transforms from the view space to the world (global) space of this Frustum.
/** @note The returned matrix is the inverse of the matrix returned by ViewMatrix().
@return An orthonormal affine matrix that performs the view->world transformation. The returned
matrix is built to use the convention Matrix * vector to map a point between these spaces.
(as opposed to the convention v*M).
@see ViewMatrix(), ProjectionMatrix(), ViewProjMatrix(). */
[[nodiscard]] Matrix4x4 WorldMatrix() const;
[[nodiscard]] Matrix4x4 ComputeWorldMatrix() const;
/// Computes the matrix that transforms from the world (global) space to the view space of this Frustum.
/** @note The returned matrix is the inverse of the matrix returned by WorldMatrix().
@return An orthonormal affine matrix that performs the world->view transformation. The returned
matrix is built to use the convention Matrix * vector to map a point between these spaces.
(as opposed to the convention v*M).
@see WorldMatrix(), ProjectionMatrix(), ViewProjMatrix(). */
[[nodiscard]] Matrix4x4 ViewMatrix() const;
[[nodiscard]] Matrix4x4 ComputeViewMatrix() const;
/// Computes the matrix that projects from the view space to the projection space of this Frustum.
/** @return A projection matrix that performs the view->proj transformation. This matrix is neither
invertible or orthonormal. The returned matrix is built to use the convention Matrix * vector
to map a point between these spaces. (as opposed to the convention v*M).
@see WorldMatrix(), ViewMatrix(), ViewProjMatrix(). */
[[nodiscard]] Matrix4x4 ProjectionMatrix() const;
[[nodiscard]] Matrix4x4 ComputeProjectionMatrix() const;
Vector3 Project(const Vector3& point) const {
Vector4 projectedPoint = ViewProjMatrix().Mul(Vector4(point, 1.f));
projectedPoint.NormalizeW();
return projectedPoint.XYZ();
}
/// Tests if the given object is fully contained inside this Frustum.
/** This function returns true if the given object lies inside this Frustum, and false otherwise.
@@ -349,26 +457,27 @@ namespace J3ML::Geometry
due to float inaccuracies, this cannot generally be relied upon.
@todo Add Contains(Circle/Disc/Sphere/Capsule).
@see Distance(), Intersects(), ClosestPoint(). */
bool Contains(const Vector3 &point) const {
const float eps = 1e-3f;
const float position = 1.f + eps;
const float neg = -position;
Vector3 projected = Project(point);
}
bool Contains(const LineSegment &lineSegment) const;
bool Contains(const Triangle &triangle) const;
bool Contains(const Polygon &polygon) const;
bool Contains(const AABB &aabb) const;
bool Contains(const OBB &obb) const;
bool Contains(const Frustum &frustum) const;
bool Contains(const Polyhedron &polyhedron) const;
[[nodiscard]] bool Contains(const Vector3 &point) const;
[[nodiscard]] bool Contains(const LineSegment &lineSegment) const;
[[nodiscard]] bool Contains(const Triangle &triangle) const;
[[nodiscard]] bool Contains(const Polygon &polygon) const;
[[nodiscard]] bool Contains(const AABB &aabb) const;
[[nodiscard]] bool Contains(const OBB &obb) const;
[[nodiscard]] bool Contains(const Frustum &frustum) const;
[[nodiscard]] bool Contains(const Polyhedron &polyhedron) const;
/// Computes the distance between this Frustum and the given object.
/** This function finds the nearest pair of points on this and the given object, and computes their distance.
If the two objects intersect, or one object is contained inside the other, the returned distance is zero.
@todo Add Frustum::Distance(Line/Ray/LineSegment/Plane/Triangle/Polygon/Circle/Disc/AABB/OBB/Capsule/Frustum/Polyhedron).
@see Contains(), Intersects(), ClosestPoint(). */
float Distance(const Vector3 &point) const;
[[nodiscard]] float Distance(const Vector3 &point) const;
/// Computes the closest point inside this frustum to the given point.
/** If the target point lies inside this Frustum, then that point is returned.
@see Distance(), Contains(), Intersects().
@todo Add ClosestPoint(Line/Ray/LineSegment/Plane/Triangle/Polygon/Circle/Disc/AABB/OBB/Capsule/Frustum/Polyhedron). */
[[nodiscard]] Vector3 ClosestPoint(const Vector3& point) const;
/// Tests whether this Frustum and the given object intersect.
/** Both objects are treated as "solid", meaning that if one of the objects is fully contained inside
@@ -377,18 +486,19 @@ namespace J3ML::Geometry
The first parameter of this function specifies the other object to test against.
@see Contains(), Distance(), ClosestPoint().
@todo Add Intersects(Circle/Disc). */
bool Intersects(const Ray& ray) const;
//bool Intersects(const Line& line) const;
bool Intersects(const LineSegment& lineSegment) const;
bool Intersects(const AABB& aabb) const;
bool Intersects(const OBB& obb) const;
bool Intersects(const Plane& plane) const;
bool Intersects(const Triangle& triangle) const;
bool Intersects(const Polygon& lineSegment) const;
bool Intersects(const Sphere& aabb) const;
bool Intersects(const Capsule& obb) const;
bool Intersects(const Frustum& plane) const;
bool Intersects(const Polyhedron& triangle) const;
[[nodiscard]] bool Intersects(const Ray& ray) const;
[[nodiscard]] bool Intersects(const Line &line) const;
[[nodiscard]] bool Intersects(const LineSegment& lineSegment) const;
[[nodiscard]] bool Intersects(const AABB& aabb) const;
[[nodiscard]] bool Intersects(const OBB& obb) const;
[[nodiscard]] bool Intersects(const Plane& plane) const;
[[nodiscard]] bool Intersects(const Triangle& triangle) const;
[[nodiscard]] bool Intersects(const Polygon& lineSegment) const;
[[nodiscard]] bool Intersects(const Sphere& aabb) const;
[[nodiscard]] bool Intersects(const Capsule& obb) const;
[[nodiscard]] bool Intersects(const Frustum& plane) const;
[[nodiscard]] bool Intersects(const Polyhedron& triangle) const;
/// Projects this Frustum onto the given 1D axis direction vector.
/** This function collapses this Frustum onto an 1D axis for the purposes of e.g. separate axis test computations.
The function returns a 1D range [outMin, outMax] denoting the interval of the projection.
@@ -402,9 +512,23 @@ namespace J3ML::Geometry
Vector3 ExtremePoint(const Vector3 &direction, float &projectionDistance) const;
LineSegment Edge(int edgeIndex) const;
[[nodiscard]] LineSegment Edge(int edgeIndex) const;
bool Intersects(const Line &line) const;
/// Maps a point from the normalized viewport space to the screen space
/** In normalized viewport space, top-left: (-1, 1), top-right: (1,1), bottom-left: (-1, -1), bottom-right: (-1, 1)
In screen space: top-left: (0, 0), top-right: (0, screenWidth-1), bottom-left: (0, screenHeight-1), bottom-right: (screenWidth-1, screenHeight-1).
This mapping is affine.
@see ScreenToViewportSpace(). */
static Vector2 ViewportToScreenSpace(float x, float y, int screenWidth, int screenHeight);
static Vector2 ViewportToScreenSpace(const Vector2& point, int screenWidth, int screenHeight);
/// Maps a point from screen space to normalized viewport space.
/** This function computes the inverse function of ViewportToScreenSpace(). This mapping is affine.
@see ViewportToScreenSpace(). */
static Vector2 ScreenToViewportSpace(float x, float y, int screenWidth, int screenHeight);
static Vector2 ScreenToViewportSpace(const Vector2& point, int screenWidth, int screenHeight);
};
Frustum operator * (const Matrix3x3& transform, const Frustum& frustum);

View File

@@ -1,11 +1,11 @@
#include <J3ML/Geometry/Common.h>
#include <J3ML/Geometry/Frustum.h>
#include <cmath>
#include "J3ML/Geometry/AABB.h"
#include <J3ML/Geometry/AABB.hpp>
#include <J3ML/Geometry/Polyhedron.h>
#include <J3ML/Geometry/LineSegment.h>
#include <J3ML/Geometry/Polygon.h>
#include <J3ML/Algorithm/GJK.h>
#include <J3ML/Algorithm/GJK.hpp>
namespace J3ML::Geometry
@@ -96,6 +96,22 @@ namespace J3ML::Geometry
}
}
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];
@@ -130,6 +146,19 @@ namespace J3ML::Geometry
return frustum;
}
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();
@@ -138,6 +167,141 @@ namespace J3ML::Geometry
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)
@@ -175,6 +339,8 @@ namespace J3ML::Geometry
}
}
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);
@@ -195,6 +361,134 @@ namespace J3ML::Geometry
}
}
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();
}
}
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();
} 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();
} 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().
@@ -234,6 +528,182 @@ namespace J3ML::Geometry
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);
}
@@ -294,6 +764,22 @@ namespace J3ML::Geometry
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),
@@ -314,4 +800,6 @@ namespace J3ML::Geometry
else
return Vector3::Cross(up, front);
}
}
}