Torque3D/Engine/source/math/util/frustum.cpp
DavidWyand-GG b32e7688c2 Side by side rendering
- Side by side rendering implemented throughout the graphics pipeline.
- New GuiTSCtrl renderStyle property is set to "stereo side by side" to
activate.
- You set an IDisplayDevice on the GameConnection to define any vertical
FOV, projection offset, and stereo eye offset properties required for
the stereo rendering (no display device included with this commit).
- Full and Empty templates updated with correct scripts and shaders.
2013-04-09 15:19:18 -04:00

494 lines
18 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "math/util/frustum.h"
#include "math/mMathFn.h"
#include "math/mathUtils.h"
#include "math/mSphere.h"
#include "platform/profiler.h"
//TODO: For OBB/frustum intersections and ortho frustums, we can resort to a much quicker AABB/OBB test
// Must be CW ordered for face[0] of each edge! Keep in mind that normals
// are pointing *inwards* and thus what appears CCW outside is CW inside.
FrustumData::EdgeListType FrustumData::smEdges
(
PolyhedronData::Edge( PlaneNear, PlaneTop, NearTopRight, NearTopLeft ),
PolyhedronData::Edge( PlaneNear, PlaneBottom, NearBottomLeft, NearBottomRight ),
PolyhedronData::Edge( PlaneNear, PlaneLeft, NearTopLeft, NearBottomLeft ),
PolyhedronData::Edge( PlaneNear, PlaneRight, NearTopRight, NearBottomRight ),
PolyhedronData::Edge( PlaneFar, PlaneTop, FarTopLeft, FarTopRight ),
PolyhedronData::Edge( PlaneFar, PlaneBottom, FarBottomRight, FarBottomLeft ),
PolyhedronData::Edge( PlaneFar, PlaneLeft, FarBottomLeft, FarTopLeft ),
PolyhedronData::Edge( PlaneFar, PlaneRight, FarTopRight, FarBottomRight ),
PolyhedronData::Edge( PlaneTop, PlaneLeft, FarTopLeft, NearTopLeft ),
PolyhedronData::Edge( PlaneTop, PlaneRight, NearTopRight, FarTopRight ),
PolyhedronData::Edge( PlaneBottom, PlaneLeft, NearBottomLeft, FarBottomLeft ),
PolyhedronData::Edge( PlaneBottom, PlaneRight, FarBottomRight, NearBottomRight )
);
//-----------------------------------------------------------------------------
Frustum::Frustum( bool isOrtho,
F32 nearLeft,
F32 nearRight,
F32 nearTop,
F32 nearBottom,
F32 nearDist,
F32 farDist,
const MatrixF &transform )
{
mTransform = transform;
mPosition = transform.getPosition();
mNearLeft = nearLeft;
mNearRight = nearRight;
mNearTop = nearTop;
mNearBottom = nearBottom;
mNearDist = nearDist;
mFarDist = farDist;
mIsOrtho = isOrtho;
mNumTiles = 1;
mCurrTile.set(0,0);
mTileOverlap.set(0.0f, 0.0f);
mProjectionOffset.zero();
mProjectionOffsetMatrix.identity();
}
//-----------------------------------------------------------------------------
void Frustum::set( bool isOrtho,
F32 fovYInRadians,
F32 aspectRatio,
F32 nearDist,
F32 farDist,
const MatrixF &transform )
{
F32 left, right, top, bottom;
MathUtils::makeFrustum( &left, &right, &top, &bottom, fovYInRadians, aspectRatio, nearDist );
tile( &left, &right, &top, &bottom, mNumTiles, mCurrTile, mTileOverlap );
set( isOrtho, left, right, top, bottom, nearDist, farDist, transform );
}
//-----------------------------------------------------------------------------
void Frustum::set( bool isOrtho,
F32 nearLeft,
F32 nearRight,
F32 nearTop,
F32 nearBottom,
F32 nearDist,
F32 farDist,
const MatrixF &transform )
{
mTransform = transform;
mPosition = mTransform.getPosition();
mNearLeft = nearLeft;
mNearRight = nearRight;
mNearTop = nearTop;
mNearBottom = nearBottom;
mNearDist = nearDist;
mFarDist = farDist;
mIsOrtho = isOrtho;
mDirty = true;
}
//-----------------------------------------------------------------------------
#if 0
void Frustum::set( const MatrixF &projMat, bool normalize )
{
// From "Fast Extraction of Viewing Frustum Planes from the World-View-Projection Matrix"
// by Gil Gribb and Klaus Hartmann.
//
// http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf
// Right clipping plane.
mPlanes[ PlaneRight ].set( projMat[3] - projMat[0],
projMat[7] - projMat[4],
projMat[11] - projMat[8],
projMat[15] - projMat[12] );
// Left clipping plane.
mPlanes[ PlaneLeft ].set( projMat[3] + projMat[0],
projMat[7] + projMat[4],
projMat[11] + projMat[8],
projMat[15] + projMat[12] );
// Bottom clipping plane.
mPlanes[ PlaneBottom ].set( projMat[3] + projMat[1],
projMat[7] + projMat[5],
projMat[11] + projMat[9],
projMat[15] + projMat[13] );
// Top clipping plane.
mPlanes[ PlaneTop ].set( projMat[3] - projMat[1],
projMat[7] - projMat[5],
projMat[11] - projMat[9],
projMat[15] - projMat[13] );
// Near clipping plane
mPlanes[ PlaneNear ].set( projMat[3] + projMat[2],
projMat[7] + projMat[6],
projMat[11] + projMat[10],
projMat[15] + projMat[14] );
// Far clipping plane.
mPlanes[ PlaneFar ].set( projMat[3] - projMat[2],
projMat[7] - projMat[6],
projMat[11] - projMat[10],
projMat[15] - projMat[14] );
if( normalize )
{
for( S32 i = 0; i < PlaneCount; ++ i )
mPlanes[ i ].normalize();
}
/* // Create the corner points via plane intersections.
mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneLeft ], &mPoints[ NearTopLeft ] );
mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneRight ], &mPoints[ NearTopRight ] );
mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneLeft ], &mPoints[ NearBottomLeft ] );
mPlanes[ PlaneNear ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneRight ], &mPoints[ NearBottomRight ] );
mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneLeft ], &mPoints[ FarTopLeft ] );
mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneTop ], mPlanes[ PlaneRight ], &mPoints[ FarTopRight ] );
mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneLeft ], &mPoints[ FarBottomLeft ] );
mPlanes[ PlaneFar ].intersect( mPlanes[ PlaneBottom ], mPlanes[ PlaneRight ], &mPoints[ FarBottomRight ] );
*/
// Update the axis aligned bounding box.
_updateBounds();
}
#endif
//-----------------------------------------------------------------------------
void Frustum::setNearDist( F32 nearDist )
{
setNearFarDist( nearDist, mFarDist );
}
//-----------------------------------------------------------------------------
void Frustum::setFarDist( F32 farDist )
{
setNearFarDist( mNearDist, farDist );
}
//-----------------------------------------------------------------------------
void Frustum::setNearFarDist( F32 nearDist, F32 farDist )
{
if( mNearDist == nearDist && mFarDist == farDist )
return;
// Recalculate the frustum.
MatrixF xfm( mTransform );
set( mIsOrtho, getFov(), getAspectRatio(), nearDist, farDist, xfm );
}
//-----------------------------------------------------------------------------
void Frustum::cropNearFar(F32 newNearDist, F32 newFarDist)
{
const F32 newOverOld = newNearDist / mNearDist;
set( mIsOrtho, mNearLeft * newOverOld, mNearRight * newOverOld, mNearTop * newOverOld, mNearBottom * newOverOld,
newNearDist, newFarDist, mTransform);
}
//-----------------------------------------------------------------------------
void FrustumData::_update() const
{
if( !mDirty )
return;
PROFILE_SCOPE( Frustum_update );
const Point3F& cameraPos = mPosition;
// Build the frustum points in camera space first.
if( mIsOrtho )
{
mPoints[ NearTopLeft ].set( mNearLeft, mNearDist, mNearTop );
mPoints[ NearTopRight ].set( mNearRight, mNearDist, mNearTop );
mPoints[ NearBottomLeft ].set( mNearLeft, mNearDist, mNearBottom );
mPoints[ NearBottomRight ].set( mNearRight, mNearDist, mNearBottom );
mPoints[ FarTopLeft ].set( mNearLeft, mFarDist, mNearTop );
mPoints[ FarTopRight ].set( mNearRight, mFarDist, mNearTop );
mPoints[ FarBottomLeft ].set( mNearLeft, mFarDist, mNearBottom );
mPoints[ FarBottomRight ].set( mNearRight, mFarDist, mNearBottom );
}
else
{
const F32 farOverNear = mFarDist / mNearDist;
mPoints[ NearTopLeft ].set( mNearLeft, mNearDist, mNearTop );
mPoints[ NearTopRight ].set( mNearRight, mNearDist, mNearTop );
mPoints[ NearBottomLeft ].set( mNearLeft, mNearDist, mNearBottom );
mPoints[ NearBottomRight ].set( mNearRight, mNearDist, mNearBottom );
mPoints[ FarTopLeft ].set( mNearLeft * farOverNear, mFarDist, mNearTop * farOverNear );
mPoints[ FarTopRight ].set( mNearRight * farOverNear, mFarDist, mNearTop * farOverNear );
mPoints[ FarBottomLeft ].set( mNearLeft * farOverNear, mFarDist, mNearBottom * farOverNear );
mPoints[ FarBottomRight ].set( mNearRight * farOverNear, mFarDist, mNearBottom * farOverNear );
}
// Transform the points into the desired culling space.
for( U32 i = 0; i < mPoints.size(); ++ i )
mTransform.mulP( mPoints[ i ] );
// Update the axis aligned bounding box from
// the newly transformed points.
mBounds = Box3F::aroundPoints( mPoints.address(), mPoints.size() );
// Finally build the planes.
if( mIsOrtho )
{
mPlanes[ PlaneLeft ].set( mPoints[ NearBottomLeft ],
mPoints[ FarTopLeft ],
mPoints[ FarBottomLeft ] );
mPlanes[ PlaneRight ].set( mPoints[ NearTopRight ],
mPoints[ FarBottomRight ],
mPoints[ FarTopRight ] );
mPlanes[ PlaneTop ].set( mPoints[ FarTopRight ],
mPoints[ NearTopLeft ],
mPoints[ NearTopRight ] );
mPlanes[ PlaneBottom ].set( mPoints[ NearBottomRight ],
mPoints[ FarBottomLeft ],
mPoints[ FarBottomRight ] );
mPlanes[ PlaneNear ].set( mPoints[ NearTopLeft ],
mPoints[ NearBottomLeft ],
mPoints[ NearTopRight ] );
mPlanes[ PlaneFar ].set( mPoints[ FarTopLeft ],
mPoints[ FarTopRight ],
mPoints[ FarBottomLeft ] );
}
else
{
mPlanes[ PlaneLeft ].set( cameraPos,
mPoints[ NearTopLeft ],
mPoints[ NearBottomLeft ] );
mPlanes[ PlaneRight ].set( cameraPos,
mPoints[ NearBottomRight ],
mPoints[ NearTopRight ] );
mPlanes[ PlaneTop ].set( cameraPos,
mPoints[ NearTopRight ],
mPoints[ NearTopLeft ] );
mPlanes[ PlaneBottom ].set( cameraPos,
mPoints[ NearBottomLeft ],
mPoints[ NearBottomRight ] );
mPlanes[ PlaneNear ].set( mPoints[ NearTopLeft ],
mPoints[ NearBottomLeft ],
mPoints[ NearTopRight ] );
mPlanes[ PlaneFar ].set( mPoints[ FarTopLeft ],
mPoints[ FarTopRight ],
mPoints[ FarBottomLeft ] );
}
// If the frustum plane orientation doesn't match mIsInverted
// now, invert all the plane normals.
//
// Note that if we have a transform matrix with a negative scale,
// then the initial planes we have computed will always be inverted.
const bool inverted = mPlanes[ PlaneNear ].whichSide( cameraPos ) == PlaneF::Front;
if( inverted != mIsInverted )
{
for( U32 i = 0; i < mPlanes.size(); ++ i )
mPlanes[ i ].invert();
}
AssertFatal( mPlanes[ PlaneNear ].whichSide( cameraPos ) != PlaneF::Front,
"Frustum::_update - Viewpoint lies on front side of near plane!" );
// And now the center points which are mostly just used in debug rendering.
mPlaneCenters[ PlaneLeftCenter ] = ( mPoints[ NearTopLeft ] +
mPoints[ NearBottomLeft ] +
mPoints[ FarTopLeft ] +
mPoints[ FarBottomLeft ] ) / 4.0f;
mPlaneCenters[ PlaneRightCenter ] = ( mPoints[ NearTopRight ] +
mPoints[ NearBottomRight ] +
mPoints[ FarTopRight ] +
mPoints[ FarBottomRight ] ) / 4.0f;
mPlaneCenters[ PlaneTopCenter ] = ( mPoints[ NearTopLeft ] +
mPoints[ NearTopRight ] +
mPoints[ FarTopLeft ] +
mPoints[ FarTopRight ] ) / 4.0f;
mPlaneCenters[ PlaneBottomCenter ] = ( mPoints[ NearBottomLeft ] +
mPoints[ NearBottomRight ] +
mPoints[ FarBottomLeft ] +
mPoints[ FarBottomRight ] ) / 4.0f;
mPlaneCenters[ PlaneNearCenter ] = ( mPoints[ NearTopLeft ] +
mPoints[ NearTopRight ] +
mPoints[ NearBottomLeft ] +
mPoints[ NearBottomRight ] ) / 4.0f;
mPlaneCenters[ PlaneFarCenter ] = ( mPoints[ FarTopLeft ] +
mPoints[ FarTopRight ] +
mPoints[ FarBottomLeft ] +
mPoints[ FarBottomRight ] ) / 4.0f;
// Done.
mDirty = false;
}
//-----------------------------------------------------------------------------
void Frustum::invert()
{
mIsInverted = !mIsInverted;
_update();
}
//-----------------------------------------------------------------------------
void Frustum::setTransform( const MatrixF &mat )
{
mTransform = mat;
mPosition = mTransform.getPosition();
mDirty = true;
}
//-----------------------------------------------------------------------------
void Frustum::scaleFromCenter( F32 scale )
{
// Extract the fov and aspect ratio.
F32 fovInRadians = mAtan2( (mNearTop - mNearBottom)*mNumTiles/2.0f, mNearDist ) * 2.0f;
F32 aspectRatio = (mNearRight - mNearLeft)/(mNearTop - mNearBottom);
// Now move the near and far planes out.
F32 halfDist = ( mFarDist - mNearDist ) / 2.0f;
mNearDist -= halfDist * ( scale - 1.0f );
mFarDist += halfDist * ( scale - 1.0f );
// Setup the new scaled frustum.
set( mIsOrtho, fovInRadians, aspectRatio, mNearDist, mFarDist, mTransform );
}
//-----------------------------------------------------------------------------
void Frustum::mul( const MatrixF& mat )
{
mTransform.mul( mat );
mDirty = true;
}
//-----------------------------------------------------------------------------
void Frustum::mulL( const MatrixF& mat )
{
MatrixF last( mTransform );
mTransform.mul( mat, last );
mDirty = true;
}
//-----------------------------------------------------------------------------
void Frustum::setProjectionOffset(const Point2F& offsetMat)
{
mProjectionOffset = offsetMat;
mProjectionOffsetMatrix.identity();
mProjectionOffsetMatrix.setPosition(Point3F(mProjectionOffset.x, mProjectionOffset.y, 0.0f));
}
//-----------------------------------------------------------------------------
void Frustum::getProjectionMatrix( MatrixF *proj, bool gfxRotate ) const
{
if (mIsOrtho)
{
MathUtils::makeOrthoProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate);
proj->mulL(mProjectionOffsetMatrix);
}
else
{
MathUtils::makeProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate);
proj->mulL(mProjectionOffsetMatrix);
}
}
//-----------------------------------------------------------------------------
void Frustum::tileFrustum(U32 numTiles, const Point2I& curTile, Point2F overlap)
{
//These will be stored to re-tile the frustum if needed
mNumTiles = numTiles;
mCurrTile = curTile;
mTileOverlap = overlap;
tile(&mNearLeft, &mNearRight, &mNearTop, &mNearBottom, mNumTiles, mCurrTile, mTileOverlap);
}
//-----------------------------------------------------------------------------
void Frustum::tile( F32 *left, F32 *right, F32 *top, F32 *bottom, U32 numTiles, const Point2I& curTile, Point2F overlap )
{
if (numTiles == 1)
return;
Point2F tileSize( ( *right - *left ) / (F32)numTiles,
( *top - *bottom ) / (F32)numTiles );
F32 leftOffset = tileSize.x*overlap.x;
F32 rightOffset = tileSize.x*overlap.x*2;
F32 bottomOffset = tileSize.y*overlap.y;
F32 topOffset = tileSize.y*overlap.y*2;
*left += tileSize.x * curTile.x - leftOffset;
*right = *left + tileSize.x + rightOffset;
*bottom += tileSize.y * curTile.y - bottomOffset;
*top = *bottom + tileSize.y + topOffset;
}