//========= Copyright © Valve Corporation, All rights reserved. ============// // // Purpose: // //==========================================================================// #include "cmatrixstack.h" // Define RUN_TEST_CODE to validate that CMatrixStack behaves the same as ID3DXMatrixStack //#define RUN_TEST_CODE #if defined( RUN_TEST_CODE ) #ifndef WIN32 #error sorry man #endif #ifdef _X360 #include "d3d9.h" #include "d3dx9.h" #else #include #include "../../dx9sdk/include/d3d9.h" #include "../../dx9sdk/include/d3dx9.h" #endif #else // RUN_TEST_CODE #if !defined( _X360 ) struct D3DXMATRIX : public VMatrix{}; struct D3DXVECTOR3 : public Vector{}; #endif // _X360 #endif // RUN_TEST_CODE // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" CMatrixStack::CMatrixStack( void ) { m_Stack.AddToTail(); m_Stack.Tail().Identity(); } CMatrixStack::~CMatrixStack( void ) { Assert( m_Stack.Count() == 1 ); // Catch push/pop mismatch Assert( m_Stack.Tail().IsIdentity() ); // Modifying the root matrix is probably unintentional } D3DXMATRIX *CMatrixStack::GetTop( void ) { return (D3DXMATRIX *)&m_Stack.Tail(); } void CMatrixStack::Push() { AssertMsg( m_Stack.Count() < 100, "CMatrixStack - Push/Pop mismatch!" ); // Catch push/pop mismatch // Duplicate the current 'top' matrix (NOTE: AddToTail can realloc!) m_Stack.AddToTail(); VMatrix *top = &m_Stack.Tail(); top[0] = top[-1]; } void CMatrixStack::Pop() { AssertMsg( m_Stack.Count() > 1, "CMatrixStack - Push/Pop mismatch!" ); // Catch push/pop mismatch m_Stack.RemoveMultipleFromTail( 1 ); } void CMatrixStack::LoadIdentity() { m_Stack.Tail().Identity(); } void CMatrixStack::LoadMatrix( const D3DXMATRIX *pMat ) { COMPILE_TIME_ASSERT( sizeof( VMatrix ) == sizeof( D3DXMATRIX ) ); memcpy( &m_Stack.Tail(), pMat, sizeof( VMatrix ) ); } void CMatrixStack::MultMatrix( const D3DXMATRIX *pMat ) { // Right-multiply VMatrix &top = m_Stack.Tail(); VMatrix result; MatrixMultiply( top, *(const VMatrix *)pMat, result ); top = result; } void CMatrixStack::MultMatrixLocal( const D3DXMATRIX *pMat ) { // Left-multiply VMatrix &top = m_Stack.Tail(); VMatrix result; MatrixMultiply( *(const VMatrix *)pMat, top, result ); top = result; } bool CMatrixStack::ScaleLocal( float x, float y, float z ) { VMatrix scale; MatrixBuildScale( scale, x, y, z ); // scale = scale.Transpose(); // A no-op, in this case :) MultMatrixLocal( (D3DXMATRIX *)&scale ); return true; } bool CMatrixStack::RotateAxisLocal( const D3DXVECTOR3 *pV, float angleInRadians ) { COMPILE_TIME_ASSERT( sizeof( Vector ) == sizeof( D3DXVECTOR3 ) ); const Vector &axis = *(const Vector *)pV; VMatrix rotate; MatrixBuildRotationAboutAxis( rotate, axis, angleInRadians * 180 / M_PI ); rotate = rotate.Transpose(); MultMatrixLocal( (D3DXMATRIX *)&rotate ); return true; } bool CMatrixStack::TranslateLocal( float x, float y, float z ) { VMatrix translate; MatrixBuildTranslation( translate, x, y, z ); translate = translate.Transpose(); MultMatrixLocal( (D3DXMATRIX *)&translate ); return true; } //==========================================================================// // // Test code to ensure this produces the same results as ID3DMATRIXStack // //==========================================================================// #if defined( RUN_TEST_CODE ) class CMatrixStack_Test { enum TestOps { LOAD_IDENT = 0, LOAD_MAT = 1, PUSH = 2, POP = 3, MULT = 4, MULT_LOCAL = 5, // Only these local/left-multiply variants are used by ShaderAPI (not the right-multiply ROTATE/TRANSLATE/SCALE versions) SCALE_LOCAL = 6, ROTATE_LOCAL = 7, TRANSLATE_LOCAL = 8, NUM_TEST_OPS }; public: CMatrixStack *m_pStack; ID3DXMatrixStack *m_pD3DStack; CMatrixStack_Test() { m_pStack = new CMatrixStack(); HRESULT result = D3DXCreateMatrixStack( 0, &m_pD3DStack ); if ( result == S_OK ) { VMatrix testMatrix; testMatrix.Identity(); MatrixTranslate( testMatrix, Vector( 1, 2, 3 ) ); MatrixRotate( testMatrix, Vector( 1, 0, 0 ), 90 ); MatrixTranslate( testMatrix, Vector( 4, 5, 6 ) ); // CMatrixStack mimics D3DX's transposed matrix style testMatrix = testMatrix.Transpose(); // Leave the top matrix unmodified m_pStack->Push(); m_pD3DStack->Push(); int depth = 2; Msg( "CMatrixStack test...\n" ); srand(1352469); for ( int i = 0; i < 1000; i++ ) { TestOps op = (TestOps)( rand() % NUM_TEST_OPS ); switch( op ) { case LOAD_IDENT: Msg( "LOAD_IDENT\n" ); m_pStack->LoadIdentity(); m_pD3DStack->LoadIdentity(); break; case LOAD_MAT: Msg( "LOAD_MAT\n" ); m_pStack->LoadMatrix( (D3DXMATRIX *) &testMatrix ); m_pD3DStack->LoadMatrix( (D3DXMATRIX *) &testMatrix ); break; case PUSH: Msg( "PUSH\n" ); m_pStack->Push(); m_pD3DStack->Push(); depth++; break; case POP: if ( depth > 2 ) // Leave the top matrix unmodified { Msg( "POP\n" ); m_pStack->Pop(); m_pD3DStack->Pop(); depth--; } break; case MULT: Msg( "MULT\n" ); m_pStack->MultMatrix( (D3DXMATRIX *) &testMatrix ); m_pD3DStack->MultMatrix( (D3DXMATRIX *) &testMatrix ); break; case MULT_LOCAL: Msg( "MULT_LOCAL\n" ); m_pStack->MultMatrixLocal( (D3DXMATRIX *) &testMatrix ); m_pD3DStack->MultMatrixLocal( (D3DXMATRIX *) &testMatrix ); break; case SCALE_LOCAL: Msg( "SCALE_LOCAL\n" ); if ( i & 1 ) { m_pStack->ScaleLocal( 2.0f, 2.0f, 2.0f ); m_pD3DStack->ScaleLocal( 2.0f, 2.0f, 2.0f ); } else { m_pStack->ScaleLocal( 0.5f, 0.5f, 0.5f ); m_pD3DStack->ScaleLocal( 0.5f, 0.5f, 0.5f ); } break; case ROTATE_LOCAL: { Msg( "ROTATE_LOCAL\n" ); float angleInRadians = ( (i&1)?+1:-1 )*0.5f*M_PI; D3DXVECTOR3 axis(1,0,0); if ( (i%3) == 1 ) axis = D3DXVECTOR3(0,1,0); if ( (i%3) == 2 ) axis = D3DXVECTOR3(0,0,1); m_pStack->RotateAxisLocal( &axis, angleInRadians ); m_pD3DStack->RotateAxisLocal( &axis, angleInRadians ); break; } case TRANSLATE_LOCAL: { Msg( "TRANSLATE_LOCAL\n" ); Vector delta = RandomVector( -10, +10 ); m_pStack->TranslateLocal( delta.x, delta.y, delta.z ); m_pD3DStack->TranslateLocal( delta.x, delta.y, delta.z ); break; } } CompareTopMatrices( op ); } while( depth > 1 ) { m_pStack->Pop(); m_pD3DStack->Pop(); depth--; CompareTopMatrices( POP ); } m_pD3DStack->Release(); } delete m_pStack; } void CompareTopMatrices( TestOps op ) { // Compare the top matrices float mat[4][4], d3dMat[4][4]; COMPILE_TIME_ASSERT( sizeof( D3DXMATRIX ) == 16*sizeof( float ) ); memcpy( mat, m_pStack->GetTop(), sizeof( D3DXMATRIX ) ); memcpy( d3dMat, m_pD3DStack->GetTop(), sizeof( D3DXMATRIX ) ); for ( int y = 0; y < 4; y++ ) { for ( int x = 0; x < 4; x++ ) { static float absEpsilon = 1.0e-5f, relEpsilon = 1.0e-4f; float absolute = fabsf( mat[y][x] - d3dMat[y][x] ); float relative = 0; if ( fabsf( d3dMat[y][x] ) > 0.00001f ) { relative = fabsf( ( mat[y][x] / d3dMat[y][x] ) - 1.0f ); } AssertMsg9( ( absolute <= absEpsilon ) && ( relative <= relEpsilon ), "DIFFERENCE! CMatrixStack[%d][%d] = %10f, ID3DXMatrixStack[%d][%d] = %10f (OP: %d, Absolute diff: %10e, Relative diff: %10e)\n", y, x, mat[y][x], y, x, d3dMat[y][x], op, absolute, relative ); } } // Copy the D3D version back onto ours, to negate accumulated numerical differences m_pStack->LoadMatrix( m_pD3DStack->GetTop() ); } }; static CMatrixStack_Test g_MatrixStackTest; #endif // defined( RUN_TEST_CODE )