├── README.md ├── mikktspace.c └── mikktspace.h /README.md: -------------------------------------------------------------------------------- 1 | # MikkTSpace 2 | A common standard for tangent space used in baking tools to produce normal maps. 3 | 4 | More information can be found at http://www.mikktspace.com/. 5 | -------------------------------------------------------------------------------- /mikktspace.c: -------------------------------------------------------------------------------- 1 | /** \file mikktspace/mikktspace.c 2 | * \ingroup mikktspace 3 | */ 4 | /** 5 | * Copyright (C) 2011 by Morten S. Mikkelsen 6 | * 7 | * This software is provided 'as-is', without any express or implied 8 | * warranty. In no event will the authors be held liable for any damages 9 | * arising from the use of this software. 10 | * 11 | * Permission is granted to anyone to use this software for any purpose, 12 | * including commercial applications, and to alter it and redistribute it 13 | * freely, subject to the following restrictions: 14 | * 15 | * 1. The origin of this software must not be misrepresented; you must not 16 | * claim that you wrote the original software. If you use this software 17 | * in a product, an acknowledgment in the product documentation would be 18 | * appreciated but is not required. 19 | * 2. Altered source versions must be plainly marked as such, and must not be 20 | * misrepresented as being the original software. 21 | * 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "mikktspace.h" 32 | 33 | #define TFALSE 0 34 | #define TTRUE 1 35 | 36 | #ifndef M_PI 37 | #define M_PI 3.1415926535897932384626433832795 38 | #endif 39 | 40 | #define INTERNAL_RND_SORT_SEED 39871946 41 | 42 | // internal structure 43 | typedef struct { 44 | float x, y, z; 45 | } SVec3; 46 | 47 | static tbool veq( const SVec3 v1, const SVec3 v2 ) 48 | { 49 | return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); 50 | } 51 | 52 | static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) 53 | { 54 | SVec3 vRes; 55 | 56 | vRes.x = v1.x + v2.x; 57 | vRes.y = v1.y + v2.y; 58 | vRes.z = v1.z + v2.z; 59 | 60 | return vRes; 61 | } 62 | 63 | 64 | static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) 65 | { 66 | SVec3 vRes; 67 | 68 | vRes.x = v1.x - v2.x; 69 | vRes.y = v1.y - v2.y; 70 | vRes.z = v1.z - v2.z; 71 | 72 | return vRes; 73 | } 74 | 75 | static SVec3 vscale(const float fS, const SVec3 v) 76 | { 77 | SVec3 vRes; 78 | 79 | vRes.x = fS * v.x; 80 | vRes.y = fS * v.y; 81 | vRes.z = fS * v.z; 82 | 83 | return vRes; 84 | } 85 | 86 | static float LengthSquared( const SVec3 v ) 87 | { 88 | return v.x*v.x + v.y*v.y + v.z*v.z; 89 | } 90 | 91 | static float Length( const SVec3 v ) 92 | { 93 | return sqrtf(LengthSquared(v)); 94 | } 95 | 96 | static SVec3 Normalize( const SVec3 v ) 97 | { 98 | return vscale(1 / Length(v), v); 99 | } 100 | 101 | static float vdot( const SVec3 v1, const SVec3 v2) 102 | { 103 | return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; 104 | } 105 | 106 | 107 | static tbool NotZero(const float fX) 108 | { 109 | // could possibly use FLT_EPSILON instead 110 | return fabsf(fX) > FLT_MIN; 111 | } 112 | 113 | static tbool VNotZero(const SVec3 v) 114 | { 115 | // might change this to an epsilon based test 116 | return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); 117 | } 118 | 119 | 120 | 121 | typedef struct { 122 | int iNrFaces; 123 | int * pTriMembers; 124 | } SSubGroup; 125 | 126 | typedef struct { 127 | int iNrFaces; 128 | int * pFaceIndices; 129 | int iVertexRepresentitive; 130 | tbool bOrientPreservering; 131 | } SGroup; 132 | 133 | // 134 | #define MARK_DEGENERATE 1 135 | #define QUAD_ONE_DEGEN_TRI 2 136 | #define GROUP_WITH_ANY 4 137 | #define ORIENT_PRESERVING 8 138 | 139 | 140 | 141 | typedef struct { 142 | int FaceNeighbors[3]; 143 | SGroup * AssignedGroup[3]; 144 | 145 | // normalized first order face derivatives 146 | SVec3 vOs, vOt; 147 | float fMagS, fMagT; // original magnitudes 148 | 149 | // determines if the current and the next triangle are a quad. 150 | int iOrgFaceNumber; 151 | int iFlag, iTSpacesOffs; 152 | unsigned char vert_num[4]; 153 | } STriInfo; 154 | 155 | typedef struct { 156 | SVec3 vOs; 157 | float fMagS; 158 | SVec3 vOt; 159 | float fMagT; 160 | int iCounter; // this is to average back into quads. 161 | tbool bOrient; 162 | } STSpace; 163 | 164 | static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 165 | static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 166 | static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 167 | static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); 168 | static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], 169 | const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, 170 | const SMikkTSpaceContext * pContext); 171 | 172 | static int MakeIndex(const int iFace, const int iVert) 173 | { 174 | assert(iVert>=0 && iVert<4 && iFace>=0); 175 | return (iFace<<2) | (iVert&0x3); 176 | } 177 | 178 | static void IndexToData(int * piFace, int * piVert, const int iIndexIn) 179 | { 180 | piVert[0] = iIndexIn&0x3; 181 | piFace[0] = iIndexIn>>2; 182 | } 183 | 184 | static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) 185 | { 186 | STSpace ts_res; 187 | 188 | // this if is important. Due to floating point precision 189 | // averaging when ts0==ts1 will cause a slight difference 190 | // which results in tangent space splits later on 191 | if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && 192 | veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) 193 | { 194 | ts_res.fMagS = pTS0->fMagS; 195 | ts_res.fMagT = pTS0->fMagT; 196 | ts_res.vOs = pTS0->vOs; 197 | ts_res.vOt = pTS0->vOt; 198 | } 199 | else 200 | { 201 | ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); 202 | ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); 203 | ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); 204 | ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); 205 | if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); 206 | if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); 207 | } 208 | 209 | return ts_res; 210 | } 211 | 212 | 213 | 214 | static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); 215 | static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); 216 | static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); 217 | 218 | 219 | // degen triangles 220 | static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); 221 | static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); 222 | 223 | 224 | tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) 225 | { 226 | return genTangSpace(pContext, 180.0f); 227 | } 228 | 229 | tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) 230 | { 231 | // count nr_triangles 232 | int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; 233 | STriInfo * pTriInfos = NULL; 234 | SGroup * pGroups = NULL; 235 | STSpace * psTspace = NULL; 236 | int iNrTrianglesIn = 0, f=0, t=0, i=0; 237 | int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; 238 | int iNrActiveGroups = 0, index = 0; 239 | const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); 240 | tbool bRes = TFALSE; 241 | const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); 242 | 243 | // verify all call-backs have been set 244 | if ( pContext->m_pInterface->m_getNumFaces==NULL || 245 | pContext->m_pInterface->m_getNumVerticesOfFace==NULL || 246 | pContext->m_pInterface->m_getPosition==NULL || 247 | pContext->m_pInterface->m_getNormal==NULL || 248 | pContext->m_pInterface->m_getTexCoord==NULL ) 249 | return TFALSE; 250 | 251 | // count triangles on supported faces 252 | for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); 255 | if (verts==3) ++iNrTrianglesIn; 256 | else if (verts==4) iNrTrianglesIn += 2; 257 | } 258 | if (iNrTrianglesIn<=0) return TFALSE; 259 | 260 | // allocate memory for an index list 261 | piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); 262 | pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); 263 | if (piTriListIn==NULL || pTriInfos==NULL) 264 | { 265 | if (piTriListIn!=NULL) free(piTriListIn); 266 | if (pTriInfos!=NULL) free(pTriInfos); 267 | return TFALSE; 268 | } 269 | 270 | // make an initial triangle --> face index list 271 | iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); 272 | 273 | // make a welded index list of identical positions and attributes (pos, norm, texc) 274 | //printf("gen welded index list begin\n"); 275 | GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); 276 | //printf("gen welded index list end\n"); 277 | 278 | // Mark all degenerate triangles 279 | iTotTris = iNrTrianglesIn; 280 | iDegenTriangles = 0; 281 | for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); 377 | if (verts!=3 && verts!=4) continue; 378 | 379 | 380 | // I've decided to let degenerate triangles and group-with-anythings 381 | // vary between left/right hand coordinate systems at the vertices. 382 | // All healthy triangles on the other hand are built to always be either or. 383 | 384 | /*// force the coordinate system orientation to be uniform for every face. 385 | // (this is already the case for good triangles but not for 386 | // degenerate ones and those with bGroupWithAnything==true) 387 | bool bOrient = psTspace[index].bOrient; 388 | if (psTspace[index].iCounter == 0) // tspace was not derived from a group 389 | { 390 | // look for a space created in GenerateTSpaces() by iCounter>0 391 | bool bNotFound = true; 392 | int i=1; 393 | while (i 0) bNotFound=false; 396 | else ++i; 397 | } 398 | if (!bNotFound) bOrient = psTspace[index+i].bOrient; 399 | }*/ 400 | 401 | // set data 402 | for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; 406 | float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; 407 | if (pContext->m_pInterface->m_setTSpace!=NULL) 408 | pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); 409 | if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) 410 | pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); 411 | 412 | ++index; 413 | } 414 | } 415 | 416 | free(psTspace); 417 | 418 | 419 | return TTRUE; 420 | } 421 | 422 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 423 | 424 | typedef struct { 425 | float vert[3]; 426 | int index; 427 | } STmpVert; 428 | 429 | static const int g_iCells = 2048; 430 | 431 | #ifdef _MSC_VER 432 | # define NOINLINE __declspec(noinline) 433 | #else 434 | # define NOINLINE __attribute__ ((noinline)) 435 | #endif 436 | 437 | // it is IMPORTANT that this function is called to evaluate the hash since 438 | // inlining could potentially reorder instructions and generate different 439 | // results for the same effective input value fVal. 440 | static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) 441 | { 442 | const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); 443 | const int iIndex = (int)fIndex; 444 | return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); 445 | } 446 | 447 | static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); 448 | static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); 449 | static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 450 | 451 | static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) 452 | { 453 | 454 | // Generate bounding box 455 | int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; 456 | STmpVert * pTmpVert = NULL; 457 | int i=0, iChannel=0, k=0, e=0; 458 | int iMaxCount=0; 459 | SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; 460 | float fMin, fMax; 461 | for (i=1; i<(iNrTrianglesIn*3); i++) 462 | { 463 | const int index = piTriList_in_and_out[i]; 464 | 465 | const SVec3 vP = GetPosition(pContext, index); 466 | if (vMin.x > vP.x) vMin.x = vP.x; 467 | else if (vMax.x < vP.x) vMax.x = vP.x; 468 | if (vMin.y > vP.y) vMin.y = vP.y; 469 | else if (vMax.y < vP.y) vMax.y = vP.y; 470 | if (vMin.z > vP.z) vMin.z = vP.z; 471 | else if (vMax.z < vP.z) vMax.z = vP.z; 472 | } 473 | 474 | vDim = vsub(vMax,vMin); 475 | iChannel = 0; 476 | fMin = vMin.x; fMax=vMax.x; 477 | if (vDim.y>vDim.x && vDim.y>vDim.z) 478 | { 479 | iChannel=1; 480 | fMin = vMin.y; 481 | fMax = vMax.y; 482 | } 483 | else if (vDim.z>vDim.x) 484 | { 485 | iChannel=2; 486 | fMin = vMin.z; 487 | fMax = vMax.z; 488 | } 489 | 490 | // make allocations 491 | piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); 492 | piHashCount = (int *) malloc(sizeof(int)*g_iCells); 493 | piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); 494 | piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); 495 | 496 | if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) 497 | { 498 | if (piHashTable!=NULL) free(piHashTable); 499 | if (piHashCount!=NULL) free(piHashCount); 500 | if (piHashOffsets!=NULL) free(piHashOffsets); 501 | if (piHashCount2!=NULL) free(piHashCount2); 502 | GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); 503 | return; 504 | } 505 | memset(piHashCount, 0, sizeof(int)*g_iCells); 506 | memset(piHashCount2, 0, sizeof(int)*g_iCells); 507 | 508 | // count amount of elements in each cell unit 509 | for (i=0; i<(iNrTrianglesIn*3); i++) 510 | { 511 | const int index = piTriList_in_and_out[i]; 512 | const SVec3 vP = GetPosition(pContext, index); 513 | const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); 514 | const int iCell = FindGridCell(fMin, fMax, fVal); 515 | ++piHashCount[iCell]; 516 | } 517 | 518 | // evaluate start index of each cell. 519 | piHashOffsets[0]=0; 520 | for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; 589 | if (fvMax[c]dx && dy>dz) channel=1; 599 | else if (dz>dx) channel=2; 600 | 601 | fSep = 0.5f*(fvMax[channel]+fvMin[channel]); 602 | 603 | // stop if all vertices are NaNs 604 | if (!isfinite(fSep)) 605 | return; 606 | 607 | // terminate recursion when the separation/average value 608 | // is no longer strictly between fMin and fMax values. 609 | if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) 610 | { 611 | // complete the weld 612 | for (l=iL_in; l<=iR_in; l++) 613 | { 614 | int i = pTmpVert[l].index; 615 | const int index = piTriList_in_and_out[i]; 616 | const SVec3 vP = GetPosition(pContext, index); 617 | const SVec3 vN = GetNormal(pContext, index); 618 | const SVec3 vT = GetTexCoord(pContext, index); 619 | 620 | tbool bNotFound = TTRUE; 621 | int l2=iL_in, i2rec=-1; 622 | while (l20); // at least 2 entries 649 | 650 | // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] 651 | while (iL < iR) 652 | { 653 | tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; 654 | while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); 657 | bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); 663 | bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) 777 | { 778 | const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); 779 | if (verts!=3 && verts!=4) continue; 780 | 781 | pTriInfos[iDstTriIndex].iOrgFaceNumber = f; 782 | pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; 783 | 784 | if (verts==3) 785 | { 786 | unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; 787 | pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; 788 | piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); 789 | piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); 790 | piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); 791 | ++iDstTriIndex; // next 792 | } 793 | else 794 | { 795 | { 796 | pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; 797 | pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; 798 | } 799 | 800 | { 801 | // need an order independent way to evaluate 802 | // tspace on quads. This is done by splitting 803 | // along the shortest diagonal. 804 | const int i0 = MakeIndex(f, 0); 805 | const int i1 = MakeIndex(f, 1); 806 | const int i2 = MakeIndex(f, 2); 807 | const int i3 = MakeIndex(f, 3); 808 | const SVec3 T0 = GetTexCoord(pContext, i0); 809 | const SVec3 T1 = GetTexCoord(pContext, i1); 810 | const SVec3 T2 = GetTexCoord(pContext, i2); 811 | const SVec3 T3 = GetTexCoord(pContext, i3); 812 | const float distSQ_02 = LengthSquared(vsub(T2,T0)); 813 | const float distSQ_13 = LengthSquared(vsub(T3,T1)); 814 | tbool bQuadDiagIs_02; 815 | if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); 889 | res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; 890 | return res; 891 | } 892 | 893 | static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) 894 | { 895 | int iF, iI; 896 | SVec3 res; float norm[3]; 897 | IndexToData(&iF, &iI, index); 898 | pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); 899 | res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; 900 | return res; 901 | } 902 | 903 | static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) 904 | { 905 | int iF, iI; 906 | SVec3 res; float texc[2]; 907 | IndexToData(&iF, &iI, index); 908 | pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); 909 | res.x=texc[0]; res.y=texc[1]; res.z=1.0f; 910 | return res; 911 | } 912 | 913 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 914 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 915 | 916 | typedef union { 917 | struct 918 | { 919 | int i0, i1, f; 920 | }; 921 | int array[3]; 922 | } SEdge; 923 | 924 | static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); 925 | static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); 926 | 927 | // returns the texture area times 2 928 | static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) 929 | { 930 | const SVec3 t1 = GetTexCoord(pContext, indices[0]); 931 | const SVec3 t2 = GetTexCoord(pContext, indices[1]); 932 | const SVec3 t3 = GetTexCoord(pContext, indices[2]); 933 | 934 | const float t21x = t2.x-t1.x; 935 | const float t21y = t2.y-t1.y; 936 | const float t31x = t3.x-t1.x; 937 | const float t31y = t3.y-t1.y; 938 | 939 | const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; 940 | 941 | return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; 942 | } 943 | 944 | static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) 945 | { 946 | int f=0, i=0, t=0; 947 | // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. 948 | 949 | // generate neighbor info list 950 | for (f=0; f0 ? ORIENT_PRESERVING : 0); 989 | 990 | if ( NotZero(fSignedAreaSTx2) ) 991 | { 992 | const float fAbsArea = fabsf(fSignedAreaSTx2); 993 | const float fLenOs = Length(vOs); 994 | const float fLenOt = Length(vOt); 995 | const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; 996 | if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); 997 | if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); 998 | 999 | // evaluate magnitudes prior to normalization of vOs and vOt 1000 | pTriInfos[f].fMagS = fLenOs / fAbsArea; 1001 | pTriInfos[f].fMagT = fLenOt / fAbsArea; 1002 | 1003 | // if this is a good triangle 1004 | if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) 1005 | pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); 1006 | } 1007 | } 1008 | 1009 | // force otherwise healthy quads to a fixed orientation 1010 | while (t<(iNrTrianglesIn-1)) 1011 | { 1012 | const int iFO_a = pTriInfos[t].iOrgFaceNumber; 1013 | const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; 1014 | if (iFO_a==iFO_b) // this is a quad 1015 | { 1016 | const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1017 | const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1018 | 1019 | // bad triangles should already have been removed by 1020 | // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false 1021 | if ((bIsDeg_a||bIsDeg_b)==TFALSE) 1022 | { 1023 | const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1024 | const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1025 | // if this happens the quad has extremely bad mapping!! 1026 | if (bOrientA!=bOrientB) 1027 | { 1028 | //printf("found quad with bad mapping\n"); 1029 | tbool bChooseOrientFirstTri = TFALSE; 1030 | if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; 1031 | else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) 1032 | bChooseOrientFirstTri = TTRUE; 1033 | 1034 | // force match 1035 | { 1036 | const int t0 = bChooseOrientFirstTri ? t : (t+1); 1037 | const int t1 = bChooseOrientFirstTri ? (t+1) : t; 1038 | pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first 1039 | pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit 1040 | } 1041 | } 1042 | } 1043 | t += 2; 1044 | } 1045 | else 1046 | ++t; 1047 | } 1048 | 1049 | // match up edge pairs 1050 | { 1051 | SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); 1052 | if (pEdges==NULL) 1053 | BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); 1054 | else 1055 | { 1056 | BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); 1057 | 1058 | free(pEdges); 1059 | } 1060 | } 1061 | } 1062 | 1063 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1064 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1065 | 1066 | static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); 1067 | static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); 1068 | 1069 | static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) 1070 | { 1071 | const int iNrMaxGroups = iNrTrianglesIn*3; 1072 | int iNrActiveGroups = 0; 1073 | int iOffset = 0, f=0, i=0; 1074 | (void)iNrMaxGroups; /* quiet warnings in non debug mode */ 1075 | for (f=0; fiVertexRepresentitive = vert_index; 1088 | pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; 1089 | pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; 1090 | pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; 1091 | ++iNrActiveGroups; 1092 | 1093 | AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); 1094 | bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1095 | neigh_indexL = pTriInfos[f].FaceNeighbors[i]; 1096 | neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; 1097 | if (neigh_indexL>=0) // neighbor 1098 | { 1099 | const tbool bAnswer = 1100 | AssignRecur(piTriListIn, pTriInfos, neigh_indexL, 1101 | pTriInfos[f].AssignedGroup[i] ); 1102 | 1103 | const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1104 | const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; 1105 | assert(bAnswer || bDiff); 1106 | (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ 1107 | } 1108 | if (neigh_indexR>=0) // neighbor 1109 | { 1110 | const tbool bAnswer = 1111 | AssignRecur(piTriListIn, pTriInfos, neigh_indexR, 1112 | pTriInfos[f].AssignedGroup[i] ); 1113 | 1114 | const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1115 | const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; 1116 | assert(bAnswer || bDiff); 1117 | (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ 1118 | } 1119 | 1120 | // update offset 1121 | iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; 1122 | // since the groups are disjoint a triangle can never 1123 | // belong to more than 3 groups. Subsequently something 1124 | // is completely screwed if this assertion ever hits. 1125 | assert(iOffset <= iNrMaxGroups); 1126 | } 1127 | } 1128 | } 1129 | 1130 | return iNrActiveGroups; 1131 | } 1132 | 1133 | static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) 1134 | { 1135 | pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; 1136 | ++pGroup->iNrFaces; 1137 | } 1138 | 1139 | static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], 1140 | const int iMyTriIndex, SGroup * pGroup) 1141 | { 1142 | STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; 1143 | 1144 | // track down vertex 1145 | const int iVertRep = pGroup->iVertexRepresentitive; 1146 | const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; 1147 | int i=-1; 1148 | if (pVerts[0]==iVertRep) i=0; 1149 | else if (pVerts[1]==iVertRep) i=1; 1150 | else if (pVerts[2]==iVertRep) i=2; 1151 | assert(i>=0 && i<3); 1152 | 1153 | // early out 1154 | if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; 1155 | else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; 1156 | if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) 1157 | { 1158 | // first to group with a group-with-anything triangle 1159 | // determines it's orientation. 1160 | // This is the only existing order dependency in the code!! 1161 | if ( pMyTriInfo->AssignedGroup[0] == NULL && 1162 | pMyTriInfo->AssignedGroup[1] == NULL && 1163 | pMyTriInfo->AssignedGroup[2] == NULL ) 1164 | { 1165 | pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); 1166 | pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); 1167 | } 1168 | } 1169 | { 1170 | const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1171 | if (bOrient != pGroup->bOrientPreservering) return TFALSE; 1172 | } 1173 | 1174 | AddTriToGroup(pGroup, iMyTriIndex); 1175 | pMyTriInfo->AssignedGroup[i] = pGroup; 1176 | 1177 | { 1178 | const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; 1179 | const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; 1180 | if (neigh_indexL>=0) 1181 | AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); 1182 | if (neigh_indexR>=0) 1183 | AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); 1184 | } 1185 | 1186 | 1187 | 1188 | return TTRUE; 1189 | } 1190 | 1191 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1192 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1193 | 1194 | static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); 1195 | static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); 1196 | static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); 1197 | 1198 | static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], 1199 | const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, 1200 | const SMikkTSpaceContext * pContext) 1201 | { 1202 | STSpace * pSubGroupTspace = NULL; 1203 | SSubGroup * pUniSubGroups = NULL; 1204 | int * pTmpMembers = NULL; 1205 | int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; 1206 | for (g=0; giNrFaces; i++) // triangles 1232 | { 1233 | const int f = pGroup->pFaceIndices[i]; // triangle number 1234 | int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; 1235 | SSubGroup tmp_group; 1236 | tbool bFound; 1237 | SVec3 n, vOs, vOt; 1238 | if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; 1239 | else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; 1240 | else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; 1241 | assert(index>=0 && index<3); 1242 | 1243 | iVertIndex = piTriListIn[f*3+index]; 1244 | assert(iVertIndex==pGroup->iVertexRepresentitive); 1245 | 1246 | // is normalized already 1247 | n = GetNormal(pContext, iVertIndex); 1248 | 1249 | // project 1250 | vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); 1251 | vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); 1252 | if ( VNotZero(vOs) ) vOs = Normalize(vOs); 1253 | if ( VNotZero(vOt) ) vOt = Normalize(vOt); 1254 | 1255 | // original face number 1256 | iOF_1 = pTriInfos[f].iOrgFaceNumber; 1257 | 1258 | iMembers = 0; 1259 | for (j=0; jiNrFaces; j++) 1260 | { 1261 | const int t = pGroup->pFaceIndices[j]; // triangle number 1262 | const int iOF_2 = pTriInfos[t].iOrgFaceNumber; 1263 | 1264 | // project 1265 | SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); 1266 | SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); 1267 | if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); 1268 | if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); 1269 | 1270 | { 1271 | const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; 1272 | // make sure triangles which belong to the same quad are joined. 1273 | const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; 1274 | 1275 | const float fCosS = vdot(vOs,vOs2); 1276 | const float fCosT = vdot(vOt,vOt2); 1277 | 1278 | assert(f!=t || bSameOrgFace); // sanity check 1279 | if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) 1280 | pTmpMembers[iMembers++] = t; 1281 | } 1282 | } 1283 | 1284 | // sort pTmpMembers 1285 | tmp_group.iNrFaces = iMembers; 1286 | tmp_group.pTriMembers = pTmpMembers; 1287 | if (iMembers>1) 1288 | { 1289 | unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? 1290 | QuickSort(pTmpMembers, 0, iMembers-1, uSeed); 1291 | } 1292 | 1293 | // look for an existing match 1294 | bFound = TFALSE; 1295 | l=0; 1296 | while (liVertexRepresentitive); 1327 | ++iUniqueSubGroups; 1328 | } 1329 | 1330 | // output tspace 1331 | { 1332 | const int iOffs = pTriInfos[f].iTSpacesOffs; 1333 | const int iVert = pTriInfos[f].vert_num[index]; 1334 | STSpace * pTS_out = &psTspace[iOffs+iVert]; 1335 | assert(pTS_out->iCounter<2); 1336 | assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); 1337 | if (pTS_out->iCounter==1) 1338 | { 1339 | *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); 1340 | pTS_out->iCounter = 2; // update counter 1341 | pTS_out->bOrient = pGroup->bOrientPreservering; 1342 | } 1343 | else 1344 | { 1345 | assert(pTS_out->iCounter==0); 1346 | *pTS_out = pSubGroupTspace[l]; 1347 | pTS_out->iCounter = 1; // update counter 1348 | pTS_out->bOrient = pGroup->bOrientPreservering; 1349 | } 1350 | } 1351 | } 1352 | 1353 | // clean up and offset iUniqueTspaces 1354 | for (s=0; s=0 && i<3); 1391 | 1392 | // project 1393 | index = piTriListIn[3*f+i]; 1394 | n = GetNormal(pContext, index); 1395 | vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); 1396 | vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); 1397 | if ( VNotZero(vOs) ) vOs = Normalize(vOs); 1398 | if ( VNotZero(vOt) ) vOt = Normalize(vOt); 1399 | 1400 | i2 = piTriListIn[3*f + (i<2?(i+1):0)]; 1401 | i1 = piTriListIn[3*f + i]; 1402 | i0 = piTriListIn[3*f + (i>0?(i-1):2)]; 1403 | 1404 | p0 = GetPosition(pContext, i0); 1405 | p1 = GetPosition(pContext, i1); 1406 | p2 = GetPosition(pContext, i2); 1407 | v1 = vsub(p0,p1); 1408 | v2 = vsub(p2,p1); 1409 | 1410 | // project 1411 | v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); 1412 | v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); 1413 | 1414 | // weight contribution by the angle 1415 | // between the two edge vectors 1416 | fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); 1417 | fAngle = (float) acos(fCos); 1418 | fMagS = pTriInfos[f].fMagS; 1419 | fMagT = pTriInfos[f].fMagT; 1420 | 1421 | res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); 1422 | res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); 1423 | res.fMagS+=(fAngle*fMagS); 1424 | res.fMagT+=(fAngle*fMagT); 1425 | fAngleSum += fAngle; 1426 | } 1427 | } 1428 | 1429 | // normalize 1430 | if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); 1431 | if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); 1432 | if (fAngleSum>0) 1433 | { 1434 | res.fMagS /= fAngleSum; 1435 | res.fMagT /= fAngleSum; 1436 | } 1437 | 1438 | return res; 1439 | } 1440 | 1441 | static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) 1442 | { 1443 | tbool bStillSame=TTRUE; 1444 | int i=0; 1445 | if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; 1446 | while (iiNrFaces && bStillSame) 1447 | { 1448 | bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; 1449 | if (bStillSame) ++i; 1450 | } 1451 | return bStillSame; 1452 | } 1453 | 1454 | static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) 1455 | { 1456 | int iL, iR, n, index, iMid, iTmp; 1457 | 1458 | // Random 1459 | unsigned int t=uSeed&31; 1460 | t=(uSeed<>(32-t)); 1461 | uSeed=uSeed+t+3; 1462 | // Random end 1463 | 1464 | iL=iLeft; iR=iRight; 1465 | n = (iR-iL)+1; 1466 | assert(n>=0); 1467 | index = (int) (uSeed%n); 1468 | 1469 | iMid=pSortBuffer[index + iL]; 1470 | 1471 | 1472 | do 1473 | { 1474 | while (pSortBuffer[iL] < iMid) 1475 | ++iL; 1476 | while (pSortBuffer[iR] > iMid) 1477 | --iR; 1478 | 1479 | if (iL <= iR) 1480 | { 1481 | iTmp = pSortBuffer[iL]; 1482 | pSortBuffer[iL] = pSortBuffer[iR]; 1483 | pSortBuffer[iR] = iTmp; 1484 | ++iL; --iR; 1485 | } 1486 | } 1487 | while (iL <= iR); 1488 | 1489 | if (iLeft < iR) 1490 | QuickSort(pSortBuffer, iLeft, iR, uSeed); 1491 | if (iL < iRight) 1492 | QuickSort(pSortBuffer, iL, iRight, uSeed); 1493 | } 1494 | 1495 | ///////////////////////////////////////////////////////////////////////////////////////////// 1496 | ///////////////////////////////////////////////////////////////////////////////////////////// 1497 | 1498 | static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); 1499 | static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); 1500 | 1501 | static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) 1502 | { 1503 | // build array of edges 1504 | unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? 1505 | int iEntries=0, iCurStartIndex=-1, f=0, i=0; 1506 | for (f=0; f pSortBuffer[iRight].array[channel]) 1657 | { 1658 | sTmp = pSortBuffer[iLeft]; 1659 | pSortBuffer[iLeft] = pSortBuffer[iRight]; 1660 | pSortBuffer[iRight] = sTmp; 1661 | } 1662 | return; 1663 | } 1664 | 1665 | // Random 1666 | t=uSeed&31; 1667 | t=(uSeed<>(32-t)); 1668 | uSeed=uSeed+t+3; 1669 | // Random end 1670 | 1671 | iL = iLeft; 1672 | iR = iRight; 1673 | n = (iR-iL)+1; 1674 | assert(n>=0); 1675 | index = (int) (uSeed%n); 1676 | 1677 | iMid=pSortBuffer[index + iL].array[channel]; 1678 | 1679 | do 1680 | { 1681 | while (pSortBuffer[iL].array[channel] < iMid) 1682 | ++iL; 1683 | while (pSortBuffer[iR].array[channel] > iMid) 1684 | --iR; 1685 | 1686 | if (iL <= iR) 1687 | { 1688 | sTmp = pSortBuffer[iL]; 1689 | pSortBuffer[iL] = pSortBuffer[iR]; 1690 | pSortBuffer[iR] = sTmp; 1691 | ++iL; --iR; 1692 | } 1693 | } 1694 | while (iL <= iR); 1695 | 1696 | if (iLeft < iR) 1697 | QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); 1698 | if (iL < iRight) 1699 | QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); 1700 | } 1701 | 1702 | // resolve ordering and edge number 1703 | static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) 1704 | { 1705 | *edgenum_out = -1; 1706 | 1707 | // test if first index is on the edge 1708 | if (indices[0]==i0_in || indices[0]==i1_in) 1709 | { 1710 | // test if second index is on the edge 1711 | if (indices[1]==i0_in || indices[1]==i1_in) 1712 | { 1713 | edgenum_out[0]=0; // first edge 1714 | i0_out[0]=indices[0]; 1715 | i1_out[0]=indices[1]; 1716 | } 1717 | else 1718 | { 1719 | edgenum_out[0]=2; // third edge 1720 | i0_out[0]=indices[2]; 1721 | i1_out[0]=indices[0]; 1722 | } 1723 | } 1724 | else 1725 | { 1726 | // only second and third index is on the edge 1727 | edgenum_out[0]=1; // second edge 1728 | i0_out[0]=indices[1]; 1729 | i1_out[0]=indices[2]; 1730 | } 1731 | } 1732 | 1733 | 1734 | ///////////////////////////////////////////////////////////////////////////////////////////// 1735 | /////////////////////////////////// Degenerate triangles //////////////////////////////////// 1736 | 1737 | static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) 1738 | { 1739 | int iNextGoodTriangleSearchIndex=-1; 1740 | tbool bStillFindingGoodOnes; 1741 | 1742 | // locate quads with only one good triangle 1743 | int t=0; 1744 | while (t<(iTotTris-1)) 1745 | { 1746 | const int iFO_a = pTriInfos[t].iOrgFaceNumber; 1747 | const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; 1748 | if (iFO_a==iFO_b) // this is a quad 1749 | { 1750 | const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1751 | const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1752 | if ((bIsDeg_a^bIsDeg_b)!=0) 1753 | { 1754 | pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; 1755 | pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; 1756 | } 1757 | t += 2; 1758 | } 1759 | else 1760 | ++t; 1761 | } 1762 | 1763 | // reorder list so all degen triangles are moved to the back 1764 | // without reordering the good triangles 1765 | iNextGoodTriangleSearchIndex = 1; 1766 | t=0; 1767 | bStillFindingGoodOnes = TTRUE; 1768 | while (t (t+1)); 1792 | 1793 | // swap triangle t0 and t1 1794 | if (!bJustADegenerate) 1795 | { 1796 | int i=0; 1797 | for (i=0; i<3; i++) 1798 | { 1799 | const int index = piTriList_out[t0*3+i]; 1800 | piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; 1801 | piTriList_out[t1*3+i] = index; 1802 | } 1803 | { 1804 | const STriInfo tri_info = pTriInfos[t0]; 1805 | pTriInfos[t0] = pTriInfos[t1]; 1806 | pTriInfos[t1] = tri_info; 1807 | } 1808 | } 1809 | else 1810 | bStillFindingGoodOnes = TFALSE; // this is not supposed to happen 1811 | } 1812 | 1813 | if (bStillFindingGoodOnes) ++t; 1814 | } 1815 | 1816 | assert(bStillFindingGoodOnes); // code will still work. 1817 | assert(iNrTrianglesIn == t); 1818 | } 1819 | 1820 | static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) 1821 | { 1822 | int t=0, i=0; 1823 | // deal with degenerate triangles 1824 | // punishment for degenerate triangles is O(N^2) 1825 | for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf 52 | * Note that though the tangent spaces at the vertices are generated in an order-independent way, 53 | * by this implementation, the interpolated tangent space is still affected by which diagonal is 54 | * chosen to split each quad. A sensible solution is to have your tools pipeline always 55 | * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. 56 | * If these have the same length then compare the diagonals defined by the texture coordinates. 57 | * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin 58 | * and also quad triangulator plugin. 59 | */ 60 | 61 | 62 | typedef int tbool; 63 | typedef struct SMikkTSpaceContext SMikkTSpaceContext; 64 | 65 | typedef struct { 66 | // Returns the number of faces (triangles/quads) on the mesh to be processed. 67 | int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); 68 | 69 | // Returns the number of vertices on face number iFace 70 | // iFace is a number in the range {0, 1, ..., getNumFaces()-1} 71 | int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); 72 | 73 | // returns the position/normal/texcoord of the referenced face of vertex number iVert. 74 | // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. 75 | void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); 76 | void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); 77 | void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); 78 | 79 | // either (or both) of the two setTSpace callbacks can be set. 80 | // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. 81 | 82 | // This function is used to return the tangent and fSign to the application. 83 | // fvTangent is a unit length vector. 84 | // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. 85 | // bitangent = fSign * cross(vN, tangent); 86 | // Note that the results are returned unindexed. It is possible to generate a new index list 87 | // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. 88 | // DO NOT! use an already existing index list. 89 | void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); 90 | 91 | // This function is used to return tangent space results to the application. 92 | // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their 93 | // true magnitudes which can be used for relief mapping effects. 94 | // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. 95 | // However, both are perpendicular to the vertex normal. 96 | // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. 97 | // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); 98 | // bitangent = fSign * cross(vN, tangent); 99 | // Note that the results are returned unindexed. It is possible to generate a new index list 100 | // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. 101 | // DO NOT! use an already existing index list. 102 | void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, 103 | const tbool bIsOrientationPreserving, const int iFace, const int iVert); 104 | } SMikkTSpaceInterface; 105 | 106 | struct SMikkTSpaceContext 107 | { 108 | SMikkTSpaceInterface * m_pInterface; // initialized with callback functions 109 | void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) 110 | }; 111 | 112 | // these are both thread safe! 113 | tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) 114 | tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); 115 | 116 | 117 | // To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the 118 | // normal map sampler must use the exact inverse of the pixel shader transformation. 119 | // The most efficient transformation we can possibly do in the pixel shader is 120 | // achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. 121 | // pixel shader (fast transform out) 122 | // vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); 123 | // where vNt is the tangent space normal. The normal map sampler must likewise use the 124 | // interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. 125 | // sampler does (exact inverse of pixel shader): 126 | // float3 row0 = cross(vB, vN); 127 | // float3 row1 = cross(vN, vT); 128 | // float3 row2 = cross(vT, vB); 129 | // float fSign = dot(vT, row0)<0 ? -1 : 1; 130 | // vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); 131 | // where vNout is the sampled normal in some chosen 3D space. 132 | // 133 | // Should you choose to reconstruct the bitangent in the pixel shader instead 134 | // of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. 135 | // Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of 136 | // quads as your renderer then problems will occur since the interpolated tangent spaces will differ 137 | // eventhough the vertex level tangent spaces match. This can be solved either by triangulating before 138 | // sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. 139 | // However, this must be used both by the sampler and your tools/rendering pipeline. 140 | 141 | #ifdef __cplusplus 142 | } 143 | #endif 144 | 145 | #endif 146 | --------------------------------------------------------------------------------