////////////////////////////////////////////////////////////////////// // // Crytek SuperFramework Source code // // (c) by Crytek GmbH 2003. All rights reserved. // Used with permission to distribute for non-commercial purposes. // // // File:TangentSpaceCalculation.h // Description: calculated the tangent space base vector for a given mesh // Dependencies: none // Documentation: "How to calculate tangent base vectors.doc" // // Usage: // implement the proxy class: CTriangleInputProxy // instance the proxy class: CTriangleInputProxy MyProxy(MyData); // instance the calculation helper: CTangentSpaceCalculation MyTangent; // do the calculation: MyTangent.CalculateTangentSpace(MyProxy); // get the data back: MyTangent.GetTriangleIndices(), MyTangent.GetBase() // // History: // - 12/07/2002: Created by Martin Mittring as part of CryEngine // - 08/18/2003: MM improved stability (no illegal floats) with bad input data // - 08/19/2003: MM added check for input data problems (DebugMesh() is deactivated by default) // - 10/02/2003: MM removed rundundant code // ////////////////////////////////////////////////////////////////////// #pragma once #include // STL vector<> #include // STL map<,,> multimap<> #define BASEMATRIXMERGEBIAS 0.9f /* // use this as reference class CTriangleInputProxy { public: //! /return 0.. DWORD GetTriangleCount( void ) const; //! /param indwTriNo 0.. //! /param outdwPos //! /param outdwNorm //! /param outdwUV void GetTriangleIndices( const DWORD indwTriNo, DWORD outdwPos[3], DWORD outdwNorm[3], DWORD outdwUV[3] ) const; //! /param indwPos 0.. //! /param outfPos void GetPos( const DWORD indwPos, float outfPos[3] ) const; //! /param indwPos 0.. //! /param outfUV void GetUV( const DWORD indwPos, float outfUV[2] ) const; }; */ // --------------------------------------------------------------------------------------------------------------- // InputProxy - use CTriangleInputProxy as reference template class CTangentSpaceCalculation { public: // IN ------------------------------------------------ //! /param inInput - the normals are only used as smoothing input - we calculate the normals ourself void CalculateTangentSpace( const InputProxy &inInput ); // OUT ----------------------------------------------- //! /return the number of base vectors that are stored 0.. size_t GetBaseCount( void ); //! //! /param indwTriNo 0.. //! /param outdwBase void GetTriangleBaseIndices( const DWORD indwTriNo, DWORD outdwBase[3] ); //! returns a orthogonal base (perpendicular and normalized) //! /param indwPos 0.. //! /param outU in object/worldspace //! /param outV in object/worldspace //! /param outN in object/worldspace void GetBase( const DWORD indwPos, float *outU, float *outV, float *outN ); private: // ----------------------------------------------------------------- class CVec2 { public: float x,y; CVec2(){} CVec2(float fXval, float fYval) { x=fXval; y=fYval; } friend CVec2 operator - (const CVec2 &vec1, const CVec2 &vec2) { return CVec2(vec1.x-vec2.x, vec1.y-vec2.y); } operator float * () { return((float *)this); } }; class CVec3 { public: float x,y,z; CVec3(){} CVec3(float fXval, float fYval, float fZval) { x=fXval; y=fYval; z=fZval; } friend CVec3 operator - (const CVec3 &vec1, const CVec3 &vec2) { return CVec3(vec1.x-vec2.x, vec1.y-vec2.y, vec1.z-vec2.z); } friend CVec3 operator - (const CVec3 &vec1) { return CVec3(-vec1.x, -vec1.y, -vec1.z); } friend CVec3 operator + (const CVec3 &vec1, const CVec3 &vec2) { return CVec3(vec1.x+vec2.x, vec1.y+vec2.y, vec1.z+vec2.z); } friend CVec3 operator * (const CVec3 &vec1, const float fVal) { return CVec3(vec1.x*fVal, vec1.y*fVal, vec1.z*fVal); } friend float operator * (const CVec3 &vec1, const CVec3 &vec2) { return( vec1.x*vec2.x + vec1.y*vec2.y + vec1.z*vec2.z); } operator float * () { return((float *)this); } void Negate() { x=-x;y=-y;z=-z; } friend CVec3 normalize( const CVec3 &vec ) { CVec3 ret; float fLen=length(vec); if(fLen<0.00001f)return(vec); fLen=1.0f/fLen;ret.x=vec.x*fLen;ret.y=vec.y*fLen;ret.z=vec.z*fLen;return(ret); } friend CVec3 cross( const CVec3 &vec1, const CVec3 &vec2 ) { return CVec3(vec1.y*vec2.z-vec1.z*vec2.y, vec1.z*vec2.x-vec1.x*vec2.z, vec1.x*vec2.y-vec1.y*vec2.x); } friend float length( const CVec3 &invA ) { return (float)sqrt(invA.x*invA.x+invA.y*invA.y+invA.z*invA.z); } friend float CalcAngleBetween( const CVec3 &invA, const CVec3 &invB ) { float LengthQ=length(invA)*length(invB); if(LengthQ<0.0001f)LengthQ=0.0001f; // to prevent division by zero float f=(invA*invB)/LengthQ; if(f>1.0f)f=1.0f; // acos need input in the range [-1..1] else if(f<-1.0f)f=-1.0f; // float fRet=(float)acos(f); // cosf is not avaiable on every plattform return(fRet); } friend bool IsZero( const CVec3 &invA ) { return(invA.x==0.0f && invA.y==0.0f && invA.z==0.0f); } friend bool IsNormalized( const CVec3 &invA ) { float f=length(invA);return(f>=0.95f && f<=1.05f); } }; class CBaseIndex { public: DWORD m_dwPosNo; //!< 0.. DWORD m_dwNormNo; //!< 0.. }; // helper to get order for CVertexLoadHelper struct CBaseIndexOrder: public std::binary_function< CBaseIndex, CBaseIndex, bool> { bool operator() ( const CBaseIndex &a, const CBaseIndex &b ) const { // first sort by position if(a.m_dwPosNob.m_dwPosNo)return(false); // then by normal if(a.m_dwNormNob.m_dwNormNo)return(false); return(false); } }; class CBase33 { public: CBase33() { } CBase33(CVec3 Uval, CVec3 Vval, CVec3 Nval) { u=Uval; v=Vval; n=Nval; } CVec3 u; //!< CVec3 v; //!< CVec3 n; //!< is part of the tangent base but can be used also as vertex normal }; class CTriBaseIndex { public: DWORD p[3]; //!< index in m_BaseVectors }; // output data ----------------------------------------------------------------------------------- std::vector m_TriBaseAssigment; //!< [0..dwTriangleCount] std::vector m_BaseVectors; //!< [0..] generated output data //! /param indwPosNo //! /param indwNormNo CBase33 &GetBase( std::multimap &inMap, const DWORD indwPosNo, const DWORD indwNormNo ); private: //! creates, copies or returns a reference //! /param inMap //! /param indwPosNo //! /param indwNormNo //! /param inU weighted //! /param inV weighted //! /param inN normalized DWORD AddUV2Base( std::multimap &inMap, const DWORD indwPosNo, const DWORD indwNormNo, const CVec3 &inU, const CVec3 &inV, const CVec3 &inNormN ); //! /param inMap //! /param indwPosNo //! /param indwNormNo //! /param inNormal weighted normal void AddNormal2Base( std::multimap &inMap, const DWORD indwPosNo, const DWORD indwNormNo, const CVec3 &inNormal ); //! this code was heavly tested with external test app by SergiyM and MartinM //! rotates the input vector with the rotation to-from //! /param vFrom has to be normalized //! /param vTo has to be normalized //! /param vInput static CVec3 Rotate( const CVec3 &vFrom, const CVec3 &vTo, const CVec3 &vInput ) { // no mesh is perfect // assert(IsNormalized(vFrom)); // no mesh is perfect // assert(IsNormalized(vTo)); CVec3 vRotAxis=cross(vFrom,vTo); // rotation axis float fSin=length(vRotAxis); float fCos=vFrom*vTo; if(fSin<0.00001f) // no rotation return(vInput); vRotAxis=vRotAxis*(1.0f/fSin); // normalize CVec3 vFrom90deg=normalize(cross(vRotAxis,vFrom)); // perpendicular to vRotAxis and vFrom90deg // Base is vFrom,vFrom90deg,vRotAxis float fXInPlane=vFrom*vInput; float fYInPlane=vFrom90deg*vInput; CVec3 a=vFrom*(fXInPlane*fCos-fYInPlane*fSin); CVec3 b=vFrom90deg*(fXInPlane*fSin+fYInPlane*fCos); CVec3 c=vRotAxis*(vRotAxis*vInput); return( a + b + c ); } void DebugMesh( const InputProxy &inInput ) const; }; // --------------------------------------------------------------------------------------------------------------- template void CTangentSpaceCalculation::DebugMesh( const InputProxy &inInput ) const { DWORD dwTriCount=inInput.GetTriangleCount(); // search for polygons that use the same indices (input data problems) for(DWORD a=0;a void CTangentSpaceCalculation::CalculateTangentSpace( const InputProxy &inInput ) { DWORD dwTriCount=inInput.GetTriangleCount(); // clear result m_BaseVectors.clear(); m_TriBaseAssigment.clear(); m_TriBaseAssigment.reserve(dwTriCount); assert(m_BaseVectors.size()==0); assert(m_TriBaseAssigment.size()==0); std::multimap mBaseMap; // second=index into m_BaseVectors, generated output data std::vector vTriangleBase; // base vectors per triangle // DebugMesh(inInput); // only for debugging - slow // calculate the base vectors per triangle ------------------------------------------- { for(DWORD i=0;i::iterator it; for(it=m_BaseVectors.begin();it!=m_BaseVectors.end();++it) { CBase33 &ref=(*it); // rotate u and v in n plane { // (N is dominating, U and V equal weighted) CVec3 vUout,vVout,vNout; vNout=normalize(ref.n); vUout = ref.u - vNout * (vNout*ref.u); // project u in n plane vVout = ref.v - vNout * (vNout*ref.v); // project v in n plane ref.u=normalize(vUout);ref.v=normalize(vVout);ref.n=vNout; //assert(ref.u.x>=-1 && ref.u.x<=1); //assert(ref.u.y>=-1 && ref.u.y<=1); //assert(ref.u.z>=-1 && ref.u.z<=1); //assert(ref.v.x>=-1 && ref.v.x<=1); //assert(ref.v.y>=-1 && ref.v.y<=1); //assert(ref.v.z>=-1 && ref.v.z<=1); //assert(ref.n.x>=-1 && ref.n.x<=1); //assert(ref.n.y>=-1 && ref.n.y<=1); //assert(ref.n.z>=-1 && ref.n.z<=1); } } } } template DWORD CTangentSpaceCalculation::AddUV2Base( std::multimap &inMap, const DWORD indwPosNo, const DWORD indwNormNo, const CVec3 &inU, const CVec3 &inV, const CVec3 &inNormN ) { // no mesh is perfect // assert(IsNormalized(inNormN)); CBaseIndex Indx; Indx.m_dwPosNo=indwPosNo; Indx.m_dwNormNo=indwNormNo; typename std::multimap::iterator iFind,iFindEnd; iFind = inMap.lower_bound(Indx); assert(iFind!=inMap.end()); CVec3 vNormal=m_BaseVectors[(*iFind).second].n; iFindEnd = inMap.upper_bound(Indx); DWORD dwBaseUVIndex=0xffffffff; // init with not found bool bParity=(cross(inU,inV)*inNormN>0.0f); for(;iFind!=iFindEnd;++iFind) { CBase33 &refFound=m_BaseVectors[(*iFind).second]; if(!IsZero(refFound.u)) { bool bParityRef=(cross(refFound.u,refFound.v)*refFound.n>0.0f); bool bParityCheck=(bParityRef==bParity); if(!bParityCheck)continue; // bool bHalfAngleCheck=normalize(inU+inV) * normalize(refFound.u+refFound.v) > 0.0f; CVec3 vRotHalf=Rotate(normalize(refFound.n),inNormN,normalize(refFound.u+refFound.v)); bool bHalfAngleCheck=normalize(inU+inV) * vRotHalf > 0.0f; // // bool bHalfAngleCheck=normalize(normalize(inU)+normalize(inV)) * normalize(normalize(refFound.u)+normalize(refFound.v)) > 0.0f; if(!bHalfAngleCheck)continue; } dwBaseUVIndex=(*iFind).second;break; } if(dwBaseUVIndex==0xffffffff) // not found { // otherwise create a new base CBase33 Base( CVec3(0,0,0), CVec3(0,0,0), vNormal ); dwBaseUVIndex = m_BaseVectors.size(); inMap.insert( std::pair(Indx,dwBaseUVIndex) ); m_BaseVectors.push_back(Base); } CBase33 &refBaseUV=m_BaseVectors[dwBaseUVIndex]; refBaseUV.u=refBaseUV.u+inU; refBaseUV.v=refBaseUV.v+inV; //no mesh is perfect if(inU.x!=0.0f || inU.y!=0.0f || inU.z!=0.0f) assert(refBaseUV.u.x!=0.0f || refBaseUV.u.y!=0.0f || refBaseUV.u.z!=0.0f); // no mesh is perfect if(inV.x!=0.0f || inV.y!=0.0f || inV.z!=0.0f) assert(refBaseUV.v.x!=0.0f || refBaseUV.v.y!=0.0f || refBaseUV.v.z!=0.0f); return(dwBaseUVIndex); } template void CTangentSpaceCalculation::AddNormal2Base( std::multimap &inMap, const DWORD indwPosNo, const DWORD indwNormNo, const CVec3 &inNormal ) { CBaseIndex Indx; Indx.m_dwPosNo=indwPosNo; Indx.m_dwNormNo=indwNormNo; typename std::multimap::iterator iFind = inMap.find(Indx); DWORD dwBaseNIndex; if(iFind!=inMap.end()) // found { // resuse the existing one dwBaseNIndex=(*iFind).second; } else { // otherwise create a new base CBase33 Base( CVec3(0,0,0), CVec3(0,0,0), CVec3(0,0,0) ); dwBaseNIndex=m_BaseVectors.size(); inMap.insert( std::pair(Indx,dwBaseNIndex) ); m_BaseVectors.push_back(Base); } CBase33 &refBaseN=m_BaseVectors[dwBaseNIndex]; refBaseN.n=refBaseN.n+inNormal; } template void CTangentSpaceCalculation::GetBase( const DWORD indwPos, float *outU, float *outV, float *outN ) { CBase33 &base=m_BaseVectors[indwPos]; outU[0]=base.u.x; outV[0]=base.v.x; outN[0]=base.n.x; outU[1]=base.u.y; outV[1]=base.v.y; outN[1]=base.n.y; outU[2]=base.u.z; outV[2]=base.v.z; outN[2]=base.n.z; } template void CTangentSpaceCalculation::GetTriangleBaseIndices( const DWORD indwTriNo, DWORD outdwBase[3] ) { assert(indwTriNo size_t CTangentSpaceCalculation::GetBaseCount( void ) { return(m_BaseVectors.size()); }