/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined( INCLUDED_MATH_FRUSTUM_H ) #define INCLUDED_MATH_FRUSTUM_H /// \file /// \brief View-frustum data types and related operations. #include "generic/enumeration.h" #include "math/matrix.h" #include "math/plane.h" #include "math/aabb.h" #include "math/line.h" inline Matrix4 matrix4_frustum( float left, float right, float bottom, float top, float nearval, float farval ){ return Matrix4( static_cast( ( 2 * nearval ) / ( right - left ) ), 0, 0, 0, 0, static_cast( ( 2 * nearval ) / ( top - bottom ) ), 0, 0, static_cast( ( right + left ) / ( right - left ) ), static_cast( ( top + bottom ) / ( top - bottom ) ), static_cast( -( farval + nearval ) / ( farval - nearval ) ), -1, 0, 0, static_cast( -( 2 * farval * nearval ) / ( farval - nearval ) ), 0 ); } typedef unsigned char ClipResult; const ClipResult c_CLIP_PASS = 0x00; // 000000 const ClipResult c_CLIP_LT_X = 0x01; // 000001 const ClipResult c_CLIP_GT_X = 0x02; // 000010 const ClipResult c_CLIP_LT_Y = 0x04; // 000100 const ClipResult c_CLIP_GT_Y = 0x08; // 001000 const ClipResult c_CLIP_LT_Z = 0x10; // 010000 const ClipResult c_CLIP_GT_Z = 0x20; // 100000 const ClipResult c_CLIP_FAIL = 0x3F; // 111111 template class Vector4ClipLT { public: static bool compare( const Vector4& self ){ return self[Index::VALUE] < self[3]; } static double scale( const Vector4& self, const Vector4& other ){ return ( self[Index::VALUE] - self[3] ) / ( other[3] - other[Index::VALUE] ); } }; template class Vector4ClipGT { public: static bool compare( const Vector4& self ){ return self[Index::VALUE] > -self[3]; } static double scale( const Vector4& self, const Vector4& other ){ return ( self[Index::VALUE] + self[3] ) / ( -other[3] - other[Index::VALUE] ); } }; template class Vector4ClipPolygon { public: typedef Vector4* iterator; typedef const Vector4* const_iterator; static std::size_t apply( const_iterator first, const_iterator last, iterator out ){ const_iterator next = first, i = last - 1; iterator tmp( out ); bool b0 = ClipPlane::compare( *i ); while ( next != last ) { bool b1 = ClipPlane::compare( *next ); if ( b0 ^ b1 ) { *out = vector4_subtracted( *next, *i ); double scale = ClipPlane::scale( *i, *out ); ( *out )[0] = static_cast( ( *i )[0] + scale * ( ( *out )[0] ) ); ( *out )[1] = static_cast( ( *i )[1] + scale * ( ( *out )[1] ) ); ( *out )[2] = static_cast( ( *i )[2] + scale * ( ( *out )[2] ) ); ( *out )[3] = static_cast( ( *i )[3] + scale * ( ( *out )[3] ) ); ++out; } if ( b1 ) { *out = *next; ++out; } i = next; ++next; b0 = b1; } return out - tmp; } }; #define CLIP_X_LT_W( p ) ( Vector4ClipLT< IntegralConstant<0> >::compare( p ) ) #define CLIP_X_GT_W( p ) ( Vector4ClipGT< IntegralConstant<0> >::compare( p ) ) #define CLIP_Y_LT_W( p ) ( Vector4ClipLT< IntegralConstant<1> >::compare( p ) ) #define CLIP_Y_GT_W( p ) ( Vector4ClipGT< IntegralConstant<1> >::compare( p ) ) #define CLIP_Z_LT_W( p ) ( Vector4ClipLT< IntegralConstant<2> >::compare( p ) ) #define CLIP_Z_GT_W( p ) ( Vector4ClipGT< IntegralConstant<2> >::compare( p ) ) inline ClipResult homogenous_clip_point( const Vector4& clipped ){ ClipResult result = c_CLIP_FAIL; if ( CLIP_X_LT_W( clipped ) ) { result &= ~c_CLIP_LT_X; // X < W } if ( CLIP_X_GT_W( clipped ) ) { result &= ~c_CLIP_GT_X; // X > -W } if ( CLIP_Y_LT_W( clipped ) ) { result &= ~c_CLIP_LT_Y; // Y < W } if ( CLIP_Y_GT_W( clipped ) ) { result &= ~c_CLIP_GT_Y; // Y > -W } if ( CLIP_Z_LT_W( clipped ) ) { result &= ~c_CLIP_LT_Z; // Z < W } if ( CLIP_Z_GT_W( clipped ) ) { result &= ~c_CLIP_GT_Z; // Z > -W } return result; } /// \brief Clips \p point by canonical matrix \p self. /// Stores the result in \p clipped. /// Returns a bitmask indicating which clip-planes the point was outside. inline ClipResult matrix4_clip_point( const Matrix4& self, const Vector3& point, Vector4& clipped ){ clipped[0] = point[0]; clipped[1] = point[1]; clipped[2] = point[2]; clipped[3] = 1; matrix4_transform_vector4( self, clipped ); return homogenous_clip_point( clipped ); } inline std::size_t homogenous_clip_triangle( Vector4 clipped[9] ){ Vector4 buffer[9]; std::size_t count = 3; count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<0> > >::apply( clipped, clipped + count, buffer ); count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<0> > >::apply( buffer, buffer + count, clipped ); count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<1> > >::apply( clipped, clipped + count, buffer ); count = Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<1> > >::apply( buffer, buffer + count, clipped ); count = Vector4ClipPolygon< Vector4ClipLT< IntegralConstant<2> > >::apply( clipped, clipped + count, buffer ); return Vector4ClipPolygon< Vector4ClipGT< IntegralConstant<2> > >::apply( buffer, buffer + count, clipped ); } /// \brief Transforms and clips the triangle formed by \p p0, \p p1, \p p2 by the canonical matrix \p self. /// Stores the resulting polygon in \p clipped. /// Returns the number of points in the resulting polygon. inline std::size_t matrix4_clip_triangle( const Matrix4& self, const Vector3& p0, const Vector3& p1, const Vector3& p2, Vector4 clipped[9] ){ clipped[0][0] = p0[0]; clipped[0][1] = p0[1]; clipped[0][2] = p0[2]; clipped[0][3] = 1; clipped[1][0] = p1[0]; clipped[1][1] = p1[1]; clipped[1][2] = p1[2]; clipped[1][3] = 1; clipped[2][0] = p2[0]; clipped[2][1] = p2[1]; clipped[2][2] = p2[2]; clipped[2][3] = 1; matrix4_transform_vector4( self, clipped[0] ); matrix4_transform_vector4( self, clipped[1] ); matrix4_transform_vector4( self, clipped[2] ); return homogenous_clip_triangle( clipped ); } inline std::size_t homogenous_clip_line( Vector4 clipped[2] ){ const Vector4& p0 = clipped[0]; const Vector4& p1 = clipped[1]; // early out { ClipResult mask0 = homogenous_clip_point( clipped[0] ); ClipResult mask1 = homogenous_clip_point( clipped[1] ); if ( ( mask0 | mask1 ) == c_CLIP_PASS ) { // both points passed all planes return 2; } if ( mask0 & mask1 ) { // both points failed any one plane return 0; } } { const bool index = CLIP_X_LT_W( p0 ); if ( index ^ CLIP_X_LT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[0] - p0[3] ) / ( clip[3] - clip[0] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } { const bool index = CLIP_X_GT_W( p0 ); if ( index ^ CLIP_X_GT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[0] + p0[3] ) / ( -clip[3] - clip[0] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } { const bool index = CLIP_Y_LT_W( p0 ); if ( index ^ CLIP_Y_LT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[1] - p0[3] ) / ( clip[3] - clip[1] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } { const bool index = CLIP_Y_GT_W( p0 ); if ( index ^ CLIP_Y_GT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[1] + p0[3] ) / ( -clip[3] - clip[1] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } { const bool index = CLIP_Z_LT_W( p0 ); if ( index ^ CLIP_Z_LT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[2] - p0[3] ) / ( clip[3] - clip[2] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } { const bool index = CLIP_Z_GT_W( p0 ); if ( index ^ CLIP_Z_GT_W( p1 ) ) { Vector4 clip( vector4_subtracted( p1, p0 ) ); double scale = ( p0[2] + p0[3] ) / ( -clip[3] - clip[2] ); clip[0] = static_cast( p0[0] + scale * clip[0] ); clip[1] = static_cast( p0[1] + scale * clip[1] ); clip[2] = static_cast( p0[2] + scale * clip[2] ); clip[3] = static_cast( p0[3] + scale * clip[3] ); clipped[index] = clip; } else if ( index == 0 ) { return 0; } } return 2; } /// \brief Transforms and clips the line formed by \p p0, \p p1 by the canonical matrix \p self. /// Stores the resulting line in \p clipped. /// Returns the number of points in the resulting line. inline std::size_t matrix4_clip_line( const Matrix4& self, const Vector3& p0, const Vector3& p1, Vector4 clipped[2] ){ clipped[0][0] = p0[0]; clipped[0][1] = p0[1]; clipped[0][2] = p0[2]; clipped[0][3] = 1; clipped[1][0] = p1[0]; clipped[1][1] = p1[1]; clipped[1][2] = p1[2]; clipped[1][3] = 1; matrix4_transform_vector4( self, clipped[0] ); matrix4_transform_vector4( self, clipped[1] ); return homogenous_clip_line( clipped ); } struct Frustum { Plane3 right, left, bottom, top, back, front; Frustum(){ } Frustum( const Plane3& _right, const Plane3& _left, const Plane3& _bottom, const Plane3& _top, const Plane3& _back, const Plane3& _front ) : right( _right ), left( _left ), bottom( _bottom ), top( _top ), back( _back ), front( _front ){ } }; inline Frustum frustum_transformed( const Frustum& frustum, const Matrix4& transform ){ return Frustum( plane3_transformed( frustum.right, transform ), plane3_transformed( frustum.left, transform ), plane3_transformed( frustum.bottom, transform ), plane3_transformed( frustum.top, transform ), plane3_transformed( frustum.back, transform ), plane3_transformed( frustum.front, transform ) ); } inline Frustum frustum_inverse_transformed( const Frustum& frustum, const Matrix4& transform ){ return Frustum( plane3_inverse_transformed( frustum.right, transform ), plane3_inverse_transformed( frustum.left, transform ), plane3_inverse_transformed( frustum.bottom, transform ), plane3_inverse_transformed( frustum.top, transform ), plane3_inverse_transformed( frustum.back, transform ), plane3_inverse_transformed( frustum.front, transform ) ); } inline bool viewproj_test_point( const Matrix4& viewproj, const Vector3& point ){ Vector4 hpoint( matrix4_transformed_vector4( viewproj, Vector4( point, 1.0f ) ) ); if ( fabs( hpoint[0] ) < fabs( hpoint[3] ) && fabs( hpoint[1] ) < fabs( hpoint[3] ) && fabs( hpoint[2] ) < fabs( hpoint[3] ) ) { return true; } return false; } inline bool viewproj_test_transformed_point( const Matrix4& viewproj, const Vector3& point, const Matrix4& localToWorld ){ return viewproj_test_point( viewproj, matrix4_transformed_point( localToWorld, point ) ); } inline Frustum frustum_from_viewproj( const Matrix4& viewproj ){ return Frustum ( plane3_normalised( Plane3( viewproj[ 3] - viewproj[ 0], viewproj[ 7] - viewproj[ 4], viewproj[11] - viewproj[ 8], viewproj[15] - viewproj[12] ) ), plane3_normalised( Plane3( viewproj[ 3] + viewproj[ 0], viewproj[ 7] + viewproj[ 4], viewproj[11] + viewproj[ 8], viewproj[15] + viewproj[12] ) ), plane3_normalised( Plane3( viewproj[ 3] + viewproj[ 1], viewproj[ 7] + viewproj[ 5], viewproj[11] + viewproj[ 9], viewproj[15] + viewproj[13] ) ), plane3_normalised( Plane3( viewproj[ 3] - viewproj[ 1], viewproj[ 7] - viewproj[ 5], viewproj[11] - viewproj[ 9], viewproj[15] - viewproj[13] ) ), plane3_normalised( Plane3( viewproj[ 3] - viewproj[ 2], viewproj[ 7] - viewproj[ 6], viewproj[11] - viewproj[10], viewproj[15] - viewproj[14] ) ), plane3_normalised( Plane3( viewproj[ 3] + viewproj[ 2], viewproj[ 7] + viewproj[ 6], viewproj[11] + viewproj[10], viewproj[15] + viewproj[14] ) ) ); } struct VolumeIntersection { enum Value { OUTSIDE, INSIDE, PARTIAL }; }; typedef EnumeratedValue VolumeIntersectionValue; const VolumeIntersectionValue c_volumeOutside( VolumeIntersectionValue::OUTSIDE ); const VolumeIntersectionValue c_volumeInside( VolumeIntersectionValue::INSIDE ); const VolumeIntersectionValue c_volumePartial( VolumeIntersectionValue::PARTIAL ); inline VolumeIntersectionValue frustum_test_aabb( const Frustum& frustum, const AABB& aabb ){ VolumeIntersectionValue result = c_volumeInside; switch ( aabb_classify_plane( aabb, frustum.right ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } switch ( aabb_classify_plane( aabb, frustum.left ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } switch ( aabb_classify_plane( aabb, frustum.bottom ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } switch ( aabb_classify_plane( aabb, frustum.top ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } switch ( aabb_classify_plane( aabb, frustum.back ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } switch ( aabb_classify_plane( aabb, frustum.front ) ) { case 2: return c_volumeOutside; case 1: result = c_volumePartial; } return result; } inline double plane_distance_to_point( const Plane3& plane, const Vector3& point ){ return vector3_dot( plane.normal(), point ) + plane.d; } inline double plane_distance_to_oriented_extents( const Plane3& plane, const Vector3& extents, const Matrix4& orientation ){ return fabs( extents[0] * vector3_dot( plane.normal(), vector4_to_vector3( orientation.x() ) ) ) + fabs( extents[1] * vector3_dot( plane.normal(), vector4_to_vector3( orientation.y() ) ) ) + fabs( extents[2] * vector3_dot( plane.normal(), vector4_to_vector3( orientation.z() ) ) ); } /// \brief Return false if \p aabb with \p orientation is partially or completely outside \p plane. inline bool plane_contains_oriented_aabb( const Plane3& plane, const AABB& aabb, const Matrix4& orientation ){ double dot = plane_distance_to_point( plane, aabb.origin ); return !( dot > 0 || -dot < plane_distance_to_oriented_extents( plane, aabb.extents, orientation ) ); } inline VolumeIntersectionValue frustum_intersects_transformed_aabb( const Frustum& frustum, const AABB& aabb, const Matrix4& localToWorld ){ AABB aabb_world( aabb ); matrix4_transform_point( localToWorld, aabb_world.origin ); if ( plane_contains_oriented_aabb( frustum.right, aabb_world, localToWorld ) || plane_contains_oriented_aabb( frustum.left, aabb_world, localToWorld ) || plane_contains_oriented_aabb( frustum.bottom, aabb_world, localToWorld ) || plane_contains_oriented_aabb( frustum.top, aabb_world, localToWorld ) || plane_contains_oriented_aabb( frustum.back, aabb_world, localToWorld ) || plane_contains_oriented_aabb( frustum.front, aabb_world, localToWorld ) ) { return c_volumeOutside; } return c_volumeInside; } inline bool plane3_test_point( const Plane3& plane, const Vector3& point ){ return vector3_dot( point, plane.normal() ) + plane.dist() <= 0; } inline bool plane3_test_line( const Plane3& plane, const Segment& segment ){ return segment_classify_plane( segment, plane ) == 2; } inline bool frustum_test_point( const Frustum& frustum, const Vector3& point ){ return !plane3_test_point( frustum.right, point ) && !plane3_test_point( frustum.left, point ) && !plane3_test_point( frustum.bottom, point ) && !plane3_test_point( frustum.top, point ) && !plane3_test_point( frustum.back, point ) && !plane3_test_point( frustum.front, point ); } inline bool frustum_test_line( const Frustum& frustum, const Segment& segment ){ return !plane3_test_line( frustum.right, segment ) && !plane3_test_line( frustum.left, segment ) && !plane3_test_line( frustum.bottom, segment ) && !plane3_test_line( frustum.top, segment ) && !plane3_test_line( frustum.back, segment ) && !plane3_test_line( frustum.front, segment ); } inline bool viewer_test_plane( const Vector4& viewer, const Plane3& plane ){ return ( ( plane.a * viewer[0] ) + ( plane.b * viewer[1] ) + ( plane.c * viewer[2] ) + ( plane.d * viewer[3] ) ) > 0; } inline Vector3 triangle_cross( const Vector3& p0, const Vector3& p1, const Vector3& p2 ){ return vector3_cross( vector3_subtracted( p1, p0 ), vector3_subtracted( p1, p2 ) ); } inline bool viewer_test_triangle( const Vector4& viewer, const Vector3& p0, const Vector3& p1, const Vector3& p2 ){ Vector3 cross( triangle_cross( p0, p1, p2 ) ); return ( ( viewer[0] * cross[0] ) + ( viewer[1] * cross[1] ) + ( viewer[2] * cross[2] ) + ( viewer[3] * 0 ) ) > 0; } inline Vector4 viewer_from_transformed_viewer( const Vector4& viewer, const Matrix4& transform ){ if ( viewer[3] == 0 ) { return Vector4( matrix4_transformed_direction( transform, vector4_to_vector3( viewer ) ), 0 ); } else { return Vector4( matrix4_transformed_point( transform, vector4_to_vector3( viewer ) ), viewer[3] ); } } inline bool viewer_test_transformed_plane( const Vector4& viewer, const Plane3& plane, const Matrix4& localToWorld ){ #if 0 return viewer_test_plane( viewer_from_transformed_viewer( viewer, matrix4_affine_inverse( localToWorld ) ), plane ); #else return viewer_test_plane( viewer, plane3_transformed( plane, localToWorld ) ); #endif } inline Vector4 viewer_from_viewproj( const Matrix4& viewproj ){ // get viewer pos in object coords Vector4 viewer( matrix4_transformed_vector4( matrix4_full_inverse( viewproj ), Vector4( 0, 0, -1, 0 ) ) ); if ( viewer[3] != 0 ) { // non-affine matrix viewer[0] /= viewer[3]; viewer[1] /= viewer[3]; viewer[2] /= viewer[3]; viewer[3] /= viewer[3]; } return viewer; } #endif