From f7ea63c522dae6998ae837029c8439e43e83fbb0 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Fri, 30 Jun 2017 15:44:31 +0200 Subject: [PATCH 01/87] cargo new --- .gitignore | 3 +++ Cargo.toml | 6 ++++++ src/lib.rs | 6 ++++++ 3 files changed, 15 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000..6aa106405a4b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000000..0b5568451a7f2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mikktspace" +version = "0.1.0" +authors = ["Benjamin Wasty "] + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000000..cdfbe1aa56211 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} From 91eb4123d26f7332f7b3bcc817e20a5be70e14a5 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Fri, 30 Jun 2017 15:46:33 +0200 Subject: [PATCH 02/87] add original MikktSpace C implentation source: https://svn.blender.org/svnroot/bf-blender/trunk/blender/intern/mikktspace/ Revision 61900 --- src/mikktspace.c | 1890 ++++++++++++++++++++++++++++++++++++++++++++++ src/mikktspace.h | 145 ++++ 2 files changed, 2035 insertions(+) create mode 100644 src/mikktspace.c create mode 100644 src/mikktspace.h diff --git a/src/mikktspace.c b/src/mikktspace.c new file mode 100644 index 0000000000000..62aa2da251740 --- /dev/null +++ b/src/mikktspace.c @@ -0,0 +1,1890 @@ +/** \file mikktspace/mikktspace.c + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +#define TFALSE 0 +#define TTRUE 1 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define INTERNAL_RND_SORT_SEED 39871946 + +// internal structure +typedef struct { + float x, y, z; +} SVec3; + +static tbool veq( const SVec3 v1, const SVec3 v2 ) +{ + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); +} + +static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x + v2.x; + vRes.y = v1.y + v2.y; + vRes.z = v1.z + v2.z; + + return vRes; +} + + +static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x - v2.x; + vRes.y = v1.y - v2.y; + vRes.z = v1.z - v2.z; + + return vRes; +} + +static SVec3 vscale(const float fS, const SVec3 v) +{ + SVec3 vRes; + + vRes.x = fS * v.x; + vRes.y = fS * v.y; + vRes.z = fS * v.z; + + return vRes; +} + +static float LengthSquared( const SVec3 v ) +{ + return v.x*v.x + v.y*v.y + v.z*v.z; +} + +static float Length( const SVec3 v ) +{ + return sqrtf(LengthSquared(v)); +} + +static SVec3 Normalize( const SVec3 v ) +{ + return vscale(1 / Length(v), v); +} + +static float vdot( const SVec3 v1, const SVec3 v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + + +static tbool NotZero(const float fX) +{ + // could possibly use FLT_EPSILON instead + return fabsf(fX) > FLT_MIN; +} + +static tbool VNotZero(const SVec3 v) +{ + // might change this to an epsilon based test + return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); +} + + + +typedef struct { + int iNrFaces; + int * pTriMembers; +} SSubGroup; + +typedef struct { + int iNrFaces; + int * pFaceIndices; + int iVertexRepresentitive; + tbool bOrientPreservering; +} SGroup; + +// +#define MARK_DEGENERATE 1 +#define QUAD_ONE_DEGEN_TRI 2 +#define GROUP_WITH_ANY 4 +#define ORIENT_PRESERVING 8 + + + +typedef struct { + int FaceNeighbors[3]; + SGroup * AssignedGroup[3]; + + // normalized first order face derivatives + SVec3 vOs, vOt; + float fMagS, fMagT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int iOrgFaceNumber; + int iFlag, iTSpacesOffs; + unsigned char vert_num[4]; +} STriInfo; + +typedef struct { + SVec3 vOs; + float fMagS; + SVec3 vOt; + float fMagT; + int iCounter; // this is to average back into quads. + tbool bOrient; +} STSpace; + +static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext); + +static int MakeIndex(const int iFace, const int iVert) +{ + assert(iVert>=0 && iVert<4 && iFace>=0); + return (iFace<<2) | (iVert&0x3); +} + +static void IndexToData(int * piFace, int * piVert, const int iIndexIn) +{ + piVert[0] = iIndexIn&0x3; + piFace[0] = iIndexIn>>2; +} + +static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) +{ + STSpace ts_res; + + // this if is important. Due to floating point precision + // averaging when ts0==ts1 will cause a slight difference + // which results in tangent space splits later on + if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && + veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) + { + ts_res.fMagS = pTS0->fMagS; + ts_res.fMagT = pTS0->fMagT; + ts_res.vOs = pTS0->vOs; + ts_res.vOt = pTS0->vOt; + } + else + { + ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); + ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); + ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); + ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); + if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); + if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); + } + + return ts_res; +} + + + +static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); + + +// degen triangles +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); + + +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) +{ + return genTangSpace(pContext, 180.0f); +} + +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) +{ + // count nr_triangles + int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; + STriInfo * pTriInfos = NULL; + SGroup * pGroups = NULL; + STSpace * psTspace = NULL; + int iNrTrianglesIn = 0, f=0, t=0, i=0; + int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; + int iNrActiveGroups = 0, index = 0; + const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); + tbool bRes = TFALSE; + const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); + + // verify all call-backs have been set + if ( pContext->m_pInterface->m_getNumFaces==NULL || + pContext->m_pInterface->m_getNumVerticesOfFace==NULL || + pContext->m_pInterface->m_getPosition==NULL || + pContext->m_pInterface->m_getNormal==NULL || + pContext->m_pInterface->m_getTexCoord==NULL ) + return TFALSE; + + // count triangles on supported faces + for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts==3) ++iNrTrianglesIn; + else if (verts==4) iNrTrianglesIn += 2; + } + if (iNrTrianglesIn<=0) return TFALSE; + + // allocate memory for an index list + piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); + pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); + if (piTriListIn==NULL || pTriInfos==NULL) + { + if (piTriListIn!=NULL) free(piTriListIn); + if (pTriInfos!=NULL) free(pTriInfos); + return TFALSE; + } + + // make an initial triangle --> face index list + iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + //printf("gen welded index list begin\n"); + GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); + //printf("gen welded index list end\n"); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + + // set data + for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + if (pContext->m_pInterface->m_setTSpace!=NULL) + pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); + if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) + pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); + + ++index; + } + } + + free(psTspace); + + + return TTRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + float vert[3]; + int index; +} STmpVert; + +static const int g_iCells = 2048; + +#ifdef _MSC_VER + #define NOINLINE __declspec(noinline) +#else + #define NOINLINE __attribute__ ((noinline)) +#endif + +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) +{ + const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); + const int iIndex = (int)fIndex; + return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); +} + +static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); +static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); +static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); + +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + + // Generate bounding box + int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; + STmpVert * pTmpVert = NULL; + int i=0, iChannel=0, k=0, e=0; + int iMaxCount=0; + SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; + float fMin, fMax; + for (i=1; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + + const SVec3 vP = GetPosition(pContext, index); + if (vMin.x > vP.x) vMin.x = vP.x; + else if (vMax.x < vP.x) vMax.x = vP.x; + if (vMin.y > vP.y) vMin.y = vP.y; + else if (vMax.y < vP.y) vMax.y = vP.y; + if (vMin.z > vP.z) vMin.z = vP.z; + else if (vMax.z < vP.z) vMax.z = vP.z; + } + + vDim = vsub(vMax,vMin); + iChannel = 0; + fMin = vMin.x; fMax=vMax.x; + if (vDim.y>vDim.x && vDim.y>vDim.z) + { + iChannel=1; + fMin = vMin.y, fMax=vMax.y; + } + else if (vDim.z>vDim.x) + { + iChannel=2; + fMin = vMin.z, fMax=vMax.z; + } + + // make allocations + piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); + piHashCount = (int *) malloc(sizeof(int)*g_iCells); + piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); + piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); + + if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) + { + if (piHashTable!=NULL) free(piHashTable); + if (piHashCount!=NULL) free(piHashCount); + if (piHashOffsets!=NULL) free(piHashOffsets); + if (piHashCount2!=NULL) free(piHashCount2); + GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); + return; + } + memset(piHashCount, 0, sizeof(int)*g_iCells); + memset(piHashCount2, 0, sizeof(int)*g_iCells); + + // count amount of elements in each cell unit + for (i=0; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); + const int iCell = FindGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0]=0; + for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; + else if (fvMax[c]dx && dy>dz) channel=1; + else if (dz>dx) channel=2; + + fSep = 0.5f*(fvMax[channel]+fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) + { + // complete the weld + for (l=iL_in; l<=iR_in; l++) + { + int i = pTmpVert[l].index; + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const SVec3 vN = GetNormal(pContext, index); + const SVec3 vT = GetTexCoord(pContext, index); + + tbool bNotFound = TTRUE; + int l2=iL_in, i2rec=-1; + while (l20); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) + { + tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; + while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) + { + const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; + + if (verts==3) + { + unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; + pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; + piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); + piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); + piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); + ++iDstTriIndex; // next + } + else + { + { + pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + const int i0 = MakeIndex(f, 0); + const int i1 = MakeIndex(f, 1); + const int i2 = MakeIndex(f, 2); + const int i3 = MakeIndex(f, 3); + const SVec3 T0 = GetTexCoord(pContext, i0); + const SVec3 T1 = GetTexCoord(pContext, i1); + const SVec3 T2 = GetTexCoord(pContext, i2); + const SVec3 T3 = GetTexCoord(pContext, i3); + const float distSQ_02 = LengthSquared(vsub(T2,T0)); + const float distSQ_13 = LengthSquared(vsub(T3,T1)); + tbool bQuadDiagIs_02; + if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); + res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; + return res; +} + +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float norm[3]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); + res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; + return res; +} + +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float texc[2]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); + res.x=texc[0]; res.y=texc[1]; res.z=1.0f; + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union { + struct + { + int i0, i1, f; + }; + int array[3]; +} SEdge; + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); +static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); + +// returns the texture area times 2 +static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) +{ + const SVec3 t1 = GetTexCoord(pContext, indices[0]); + const SVec3 t2 = GetTexCoord(pContext, indices[1]); + const SVec3 t3 = GetTexCoord(pContext, indices[2]); + + const float t21x = t2.x-t1.x; + const float t21y = t2.y-t1.y; + const float t31x = t3.x-t1.x; + const float t31y = t3.y-t1.y; + + const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; + + return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; +} + +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + int f=0, i=0, t=0; + // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + + // generate neighbor info list + for (f=0; f0 ? ORIENT_PRESERVING : 0); + + if ( NotZero(fSignedAreaSTx2) ) + { + const float fAbsArea = fabsf(fSignedAreaSTx2); + const float fLenOs = Length(vOs); + const float fLenOt = Length(vOt); + const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; + if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); + if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + + // if this is a good triangle + if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) + pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); + } + } + + // force otherwise healthy quads to a fixed orientation + while (t<(iNrTrianglesIn-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a||bIsDeg_b)==TFALSE) + { + const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + // if this happens the quad has extremely bad mapping!! + if (bOrientA!=bOrientB) + { + //printf("found quad with bad mapping\n"); + tbool bChooseOrientFirstTri = TFALSE; + if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; + else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) + bChooseOrientFirstTri = TTRUE; + + // force match + { + const int t0 = bChooseOrientFirstTri ? t : (t+1); + const int t1 = bChooseOrientFirstTri ? (t+1) : t; + pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } + else + ++t; + } + + // match up edge pairs + { + SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); + if (pEdges==NULL) + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + else + { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + free(pEdges); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); + +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) +{ + const int iNrMaxGroups = iNrTrianglesIn*3; + int iNrActiveGroups = 0; + int iOffset = 0, f=0, i=0; + (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (f=0; fiVertexRepresentitive = vert_index; + pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; + pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; + pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; + ++iNrActiveGroups; + + AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); + bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + neigh_indexL = pTriInfos[f].FaceNeighbors[i]; + neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + // update offset + iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert(iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; +} + +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) +{ + pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; + ++pGroup->iNrFaces; +} + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], + const int iMyTriIndex, SGroup * pGroup) +{ + STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; + + // track down vertex + const int iVertRep = pGroup->iVertexRepresentitive; + const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; + int i=-1; + if (pVerts[0]==iVertRep) i=0; + else if (pVerts[1]==iVertRep) i=1; + else if (pVerts[2]==iVertRep) i=2; + assert(i>=0 && i<3); + + // early out + if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; + else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; + if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) + { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if ( pMyTriInfo->AssignedGroup[0] == NULL && + pMyTriInfo->AssignedGroup[1] == NULL && + pMyTriInfo->AssignedGroup[2] == NULL ) + { + pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); + pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + if (bOrient != pGroup->bOrientPreservering) return TFALSE; + } + + AddTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo->AssignedGroup[i] = pGroup; + + { + const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; + const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + if (neigh_indexR>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + + + + return TTRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); +static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); + +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext) +{ + STSpace * pSubGroupTspace = NULL; + SSubGroup * pUniSubGroups = NULL; + int * pTmpMembers = NULL; + int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; + for (g=0; giNrFaces; i++) // triangles + { + const int f = pGroup->pFaceIndices[i]; // triangle number + int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; + SSubGroup tmp_group; + tbool bFound; + SVec3 n, vOs, vOt; + if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; + else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; + else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; + assert(index>=0 && index<3); + + iVertIndex = piTriListIn[f*3+index]; + assert(iVertIndex==pGroup->iVertexRepresentitive); + + // is normalized already + n = GetNormal(pContext, iVertIndex); + + // project + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + // original face number + iOF_1 = pTriInfos[f].iOrgFaceNumber; + + iMembers = 0; + for (j=0; jiNrFaces; j++) + { + const int t = pGroup->pFaceIndices[j]; // triangle number + const int iOF_2 = pTriInfos[t].iOrgFaceNumber; + + // project + SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); + SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); + if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); + if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); + + { + const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; + // make sure triangles which belong to the same quad are joined. + const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; + + const float fCosS = vdot(vOs,vOs2); + const float fCosT = vdot(vOt,vOt2); + + assert(f!=t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) + pTmpMembers[iMembers++] = t; + } + } + + // sort pTmpMembers + tmp_group.iNrFaces = iMembers; + tmp_group.pTriMembers = pTmpMembers; + if (iMembers>1) + { + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + QuickSort(pTmpMembers, 0, iMembers-1, uSeed); + } + + // look for an existing match + bFound = TFALSE; + l=0; + while (liVertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + const int iOffs = pTriInfos[f].iTSpacesOffs; + const int iVert = pTriInfos[f].vert_num[index]; + STSpace * pTS_out = &psTspace[iOffs+iVert]; + assert(pTS_out->iCounter<2); + assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + if (pTS_out->iCounter==1) + { + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); + pTS_out->iCounter = 2; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + else + { + assert(pTS_out->iCounter==0); + *pTS_out = pSubGroupTspace[l]; + pTS_out->iCounter = 1; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + } + } + + // clean up and offset iUniqueTspaces + for (s=0; s=0 && i<3); + + // project + index = piTriListIn[3*f+i]; + n = GetNormal(pContext, index); + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + i2 = piTriListIn[3*f + (i<2?(i+1):0)]; + i1 = piTriListIn[3*f + i]; + i0 = piTriListIn[3*f + (i>0?(i-1):2)]; + + p0 = GetPosition(pContext, i0); + p1 = GetPosition(pContext, i1); + p2 = GetPosition(pContext, i2); + v1 = vsub(p0,p1); + v2 = vsub(p2,p1); + + // project + v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); + v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); + + // weight contribution by the angle + // between the two edge vectors + fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); + fAngle = (float) acos(fCos); + fMagS = pTriInfos[f].fMagS; + fMagT = pTriInfos[f].fMagT; + + res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); + res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); + res.fMagS+=(fAngle*fMagS); + res.fMagT+=(fAngle*fMagT); + fAngleSum += fAngle; + } + } + + // normalize + if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); + if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); + if (fAngleSum>0) + { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum; + } + + return res; +} + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) +{ + tbool bStillSame=TTRUE; + int i=0; + if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; + while (iiNrFaces && bStillSame) + { + bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; + if (bStillSame) ++i; + } + return bStillSame; +} + +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) +{ + int iL, iR, n, index, iMid, iTmp; + + // Random + unsigned int t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft; iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL]; + + + do + { + while (pSortBuffer[iL] < iMid) + ++iL; + while (pSortBuffer[iR] > iMid) + --iR; + + if (iL <= iR) + { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSort(pSortBuffer, iLeft, iR, uSeed); + if (iL < iRight) + QuickSort(pSortBuffer, iL, iRight, uSeed); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +{ + // build array of edges + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + int iEntries=0, iCurStartIndex=-1, f=0, i=0; + for (f=0; f pSortBuffer[iRight].array[channel]) + { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft, iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL].array[channel]; + + do + { + while (pSortBuffer[iL].array[channel] < iMid) + ++iL; + while (pSortBuffer[iR].array[channel] > iMid) + --iR; + + if (iL <= iR) + { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + if (iL < iRight) + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); +} + +// resolve ordering and edge number +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +{ + *edgenum_out = -1; + + // test if first index is on the edge + if (indices[0]==i0_in || indices[0]==i1_in) + { + // test if second index is on the edge + if (indices[1]==i0_in || indices[1]==i1_in) + { + edgenum_out[0]=0; // first edge + i0_out[0]=indices[0]; + i1_out[0]=indices[1]; + } + else + { + edgenum_out[0]=2; // third edge + i0_out[0]=indices[2]; + i1_out[0]=indices[0]; + } + } + else + { + // only second and third index is on the edge + edgenum_out[0]=1; // second edge + i0_out[0]=indices[1]; + i1_out[0]=indices[2]; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Degenerate triangles //////////////////////////////////// + +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +{ + int iNextGoodTriangleSearchIndex=-1; + tbool bStillFindingGoodOnes; + + // locate quads with only one good triangle + int t=0; + while (t<(iTotTris-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + if ((bIsDeg_a^bIsDeg_b)!=0) + { + pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } + else + ++t; + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + iNextGoodTriangleSearchIndex = 1; + t=0; + bStillFindingGoodOnes = TTRUE; + while (t (t+1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) + { + int i=0; + for (i=0; i<3; i++) + { + const int index = piTriList_out[t0*3+i]; + piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; + piTriList_out[t1*3+i] = index; + } + { + const STriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } + else + bStillFindingGoodOnes = TFALSE; // this is not supposed to happen + } + + if (bStillFindingGoodOnes) ++t; + } + + assert(bStillFindingGoodOnes); // code will still work. + assert(iNrTrianglesIn == t); +} + +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +{ + int t=0, i=0; + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct { + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif From 329c76b7f6c352e3d6ef8745cb52fe30049d0cc0 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Sun, 2 Jul 2017 09:02:28 +0200 Subject: [PATCH 03/87] WIP make c code compatible with corrode --- src/mikktspace.c | 222 ++++++++++++++++++++++++----------------------- src/mikktspace.h | 10 +-- 2 files changed, 118 insertions(+), 114 deletions(-) diff --git a/src/mikktspace.c b/src/mikktspace.c index 62aa2da251740..87352629926e8 100644 --- a/src/mikktspace.c +++ b/src/mikktspace.c @@ -130,7 +130,7 @@ typedef struct { tbool bOrientPreservering; } SGroup; -// +// #define MARK_DEGENERATE 1 #define QUAD_ONE_DEGEN_TRI 2 #define GROUP_WITH_ANY 4 @@ -141,7 +141,7 @@ typedef struct { typedef struct { int FaceNeighbors[3]; SGroup * AssignedGroup[3]; - + // normalized first order face derivatives SVec3 vOs, vOt; float fMagS, fMagT; // original magnitudes @@ -161,17 +161,17 @@ typedef struct { tbool bOrient; } STSpace; -static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, +static int GenerateInitialVerticesIndexList(STriInfo* pTriInfos, int* piTriList_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo* pTriInfos, const int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo* pTriInfos, SGroup* pGroups, int* piGroupTrianglesBuffer, const int* piTriListIn, const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace* psTspace, const STriInfo* pTriInfos, const SGroup* pGroups, + const int iNrActiveGroups, const int* piTriListIn, const float fThresCos, const SMikkTSpaceContext * pContext); static int MakeIndex(const int iFace, const int iVert) { - assert(iVert>=0 && iVert<4 && iFace>=0); + // assert(iVert>=0 && iVert<4 && iFace>=0); return (iFace<<2) | (iVert&0x3); } @@ -217,8 +217,8 @@ static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); // degen triangles -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); +static void DegenPrologue(STriInfo* pTriInfos, int* piTriList_out, const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace* psTspace, STriInfo* pTriInfos, int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) @@ -301,13 +301,13 @@ tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThre // put the degenerate triangles last. DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris); - + // evaluate triangle level attributes and neighbor list //printf("gen neighbors list begin\n"); InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); //printf("gen neighbors list end\n"); - + // based on the 4 rules, identify groups based on connectivity iNrMaxGroups = iNrTrianglesIn*3; pGroups = (SGroup *) malloc(sizeof(SGroup)*iNrMaxGroups); @@ -349,7 +349,7 @@ tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThre //printf("gen tspaces begin\n"); bRes = GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext); //printf("gen tspaces end\n"); - + // clean up free(pGroups); free(piGroupTrianglesBuffer); @@ -375,7 +375,7 @@ tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThre { const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts!=3 && verts!=4) continue; - + // I've decided to let degenerate triangles and group-with-anythings // vary between left/right hand coordinate systems at the vertices. @@ -402,8 +402,8 @@ tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThre for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; - float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + float* tang = {pTSpace->vOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float* bitang = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; if (pContext->m_pInterface->m_setTSpace!=NULL) pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) @@ -415,7 +415,7 @@ tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThre free(psTspace); - + return TTRUE; } @@ -444,11 +444,11 @@ static NOINLINE int FindGridCell(const float fMin, const float fMax, const float return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); } -static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); -static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); -static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void MergeVertsFast(int* piTriList_in_and_out, STmpVert* pTmpVert, const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); +static void MergeVertsSlow(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int* pTable, const int iEntries); +static void GenerateSharedVerticesIndexListSlow(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +static void GenerateSharedVerticesIndexList(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) { // Generate bounding box @@ -527,13 +527,13 @@ static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SM const int iCell = FindGridCell(fMin, fMax, fVal); int * pTable = NULL; - assert(piHashCount2[iCell]0); // at least 2 entries + // assert((iR_in-iL_in)>0); // at least 2 entries // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] while (iL < iR) @@ -645,29 +645,29 @@ static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], cons tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + // assert(iL>=iL_in && iL<=iR_in); bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + // assert(iR>=iL_in && iR<=iR_in); bReadyRightSwap = pTmpVert[iR].vert[channel]iVertexRepresentitive = vert_index; pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; @@ -1091,10 +1091,10 @@ static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupT const tbool bAnswer = AssignRecur(piTriListIn, pTriInfos, neigh_indexL, pTriInfos[f].AssignedGroup[i] ); - + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); + // assert(bAnswer || bDiff); (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ } if (neigh_indexR>=0) // neighbor @@ -1105,7 +1105,7 @@ static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupT const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); + // assert(bAnswer || bDiff); (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ } @@ -1114,7 +1114,7 @@ static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupT // since the groups are disjoint a triangle can never // belong to more than 3 groups. Subsequently something // is completely screwed if this assertion ever hits. - assert(iOffset <= iNrMaxGroups); + // assert(iOffset <= iNrMaxGroups); } } } @@ -1128,7 +1128,7 @@ static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) ++pGroup->iNrFaces; } -static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], +static tbool AssignRecur(const int* piTriListIn, STriInfo* psTriInfos, const int iMyTriIndex, SGroup * pGroup) { STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; @@ -1140,7 +1140,7 @@ static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], if (pVerts[0]==iVertRep) i=0; else if (pVerts[1]==iVertRep) i=1; else if (pVerts[2]==iVertRep) i=2; - assert(i>=0 && i<3); + // assert(i>=0 && i<3); // early out if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; @@ -1185,10 +1185,10 @@ static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); -static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); +static STSpace EvalTspace(int* face_indices, const int iFaces, const int* piTriListIn, const STriInfo* pTriInfos, const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, +static tbool GenerateTSpaces(STSpace* psTspace, const STriInfo* pTriInfos, const SGroup* pGroups, + const int iNrActiveGroups, const int* piTriListIn, const float fThresCos, const SMikkTSpaceContext * pContext) { STSpace * pSubGroupTspace = NULL; @@ -1230,14 +1230,14 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; - assert(index>=0 && index<3); + // assert(index>=0 && index<3); iVertIndex = piTriListIn[f*3+index]; - assert(iVertIndex==pGroup->iVertexRepresentitive); + // assert(iVertIndex==pGroup->iVertexRepresentitive); // is normalized already n = GetNormal(pContext, iVertIndex); - + // project vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); @@ -1246,7 +1246,7 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con // original face number iOF_1 = pTriInfos[f].iOrgFaceNumber; - + iMembers = 0; for (j=0; jiNrFaces; j++) { @@ -1267,7 +1267,7 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con const float fCosS = vdot(vOs,vOs2); const float fCosT = vdot(vOt,vOt2); - assert(f!=t || bSameOrgFace); // sanity check + // assert(f!=t || bSameOrgFace); // sanity check if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) pTmpMembers[iMembers++] = t; } @@ -1290,9 +1290,9 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con bFound = CompareSubGroups(&tmp_group, &pUniSubGroups[l]); if (!bFound) ++l; } - + // assign tangent space index - assert(bFound || l==iUniqueSubGroups); + // assert(bFound || l==iUniqueSubGroups); //piTempTangIndices[f*3+index] = iUniqueTspaces+l; // if no match was found we allocate a new subgroup @@ -1324,8 +1324,8 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con const int iOffs = pTriInfos[f].iTSpacesOffs; const int iVert = pTriInfos[f].vert_num[index]; STSpace * pTS_out = &psTspace[iOffs+iVert]; - assert(pTS_out->iCounter<2); - assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + // assert(pTS_out->iCounter<2); + // assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); if (pTS_out->iCounter==1) { *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); @@ -1334,7 +1334,7 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con } else { - assert(pTS_out->iCounter==0); + // assert(pTS_out->iCounter==0); *pTS_out = pSubGroupTspace[l]; pTS_out->iCounter = 1; // update counter pTS_out->bOrient = pGroup->bOrientPreservering; @@ -1356,7 +1356,7 @@ static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], con return TTRUE; } -static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], +static STSpace EvalTspace(int* face_indices, const int iFaces, const int* piTriListIn, const STriInfo* pTriInfos, const SMikkTSpaceContext * pContext, const int iVertexRepresentitive) { STSpace res; @@ -1379,7 +1379,7 @@ static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriL if (piTriListIn[3*f+0]==iVertexRepresentitive) i=0; else if (piTriListIn[3*f+1]==iVertexRepresentitive) i=1; else if (piTriListIn[3*f+2]==iVertexRepresentitive) i=2; - assert(i>=0 && i<3); + // assert(i>=0 && i<3); // project index = piTriListIn[3*f+i]; @@ -1455,7 +1455,7 @@ static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSee iL=iLeft; iR=iRight; n = (iR-iL)+1; - assert(n>=0); + // assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL]; @@ -1488,9 +1488,9 @@ static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSee ///////////////////////////////////////////////////////////////////////////////////////////// static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int* indices, const int i0_in, const int i1_in); -static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +static void BuildNeighborsFast(STriInfo* pTriInfos, SEdge * pEdges, const int* piTriListIn, const int iNrTrianglesIn) { // build array of edges unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? @@ -1500,9 +1500,10 @@ static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int p { const int i0 = piTriListIn[f*3+i]; const int i1 = piTriListIn[f*3+(i<2?(i+1):0)]; + // TODO!!: not accepted by corrode pEdges[f*3+i].i0 = i0 < i1 ? i0 : i1; // put minimum index in i0 - pEdges[f*3+i].i1 = !(i0 < i1) ? i0 : i1; // put maximum index in i1 - pEdges[f*3+i].f = f; // record face number + // pEdges[f*3+i].i1 = !(i0 < i1) ? i0 : i1; // put maximum index in i1 + // pEdges[f*3+i].f = f; // record face number } // sort over all edges by i0, this is the pricy one. @@ -1515,14 +1516,15 @@ static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int p iCurStartIndex = 0; for (i=1; i=0); + // assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL].array[channel]; @@ -1691,10 +1695,10 @@ static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int } // resolve ordering and edge number -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int* indices, const int i0_in, const int i1_in) { *edgenum_out = -1; - + // test if first index is on the edge if (indices[0]==i0_in || indices[0]==i1_in) { @@ -1725,7 +1729,7 @@ static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int ind ///////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// Degenerate triangles //////////////////////////////////// -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +static void DegenPrologue(STriInfo* pTriInfos, int* piTriList_out, const int iNrTrianglesIn, const int iTotTris) { int iNextGoodTriangleSearchIndex=-1; tbool bStillFindingGoodOnes; @@ -1779,7 +1783,7 @@ static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int i t0 = t; t1 = iNextGoodTriangleSearchIndex; ++iNextGoodTriangleSearchIndex; - assert(iNextGoodTriangleSearchIndex > (t+1)); + // assert(iNextGoodTriangleSearchIndex > (t+1)); // swap triangle t0 and t1 if (!bJustADegenerate) @@ -1804,11 +1808,11 @@ static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int i if (bStillFindingGoodOnes) ++t; } - assert(bStillFindingGoodOnes); // code will still work. - assert(iNrTrianglesIn == t); + // assert(bStillFindingGoodOnes); // code will still work. + // assert(iNrTrianglesIn == t); } -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +static void DegenEpilogue(STSpace* psTspace, STriInfo* pTriInfos, int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) { int t=0, i=0; // deal with degenerate triangles @@ -1842,7 +1846,7 @@ static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriLis const int iSrcOffs=pTriInfos[iTri].iTSpacesOffs; const int iDstVert=pTriInfos[t].vert_num[i]; const int iDstOffs=pTriInfos[t].iTSpacesOffs; - + // copy tspace psTspace[iDstOffs+iDstVert] = psTspace[iSrcOffs+iSrcVert]; } @@ -1884,7 +1888,7 @@ static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriLis else ++i; } - assert(!bNotFound); + // assert(!bNotFound); } } } diff --git a/src/mikktspace.h b/src/mikktspace.h index 52c44a713c60e..63001666fb6db 100644 --- a/src/mikktspace.h +++ b/src/mikktspace.h @@ -72,9 +72,9 @@ typedef struct { // returns the position/normal/texcoord of the referenced face of vertex number iVert. // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. - void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); - void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); - void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float* fvPosOut, const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float* fvNormOut, const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float* fvTexcOut, const int iFace, const int iVert); // either (or both) of the two setTSpace callbacks can be set. // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. @@ -86,7 +86,7 @@ typedef struct { // Note that the results are returned unindexed. It is possible to generate a new index list // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. // DO NOT! use an already existing index list. - void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float* fvTangent, const float fSign, const int iFace, const int iVert); // This function is used to return tangent space results to the application. // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their @@ -99,7 +99,7 @@ typedef struct { // Note that the results are returned unindexed. It is possible to generate a new index list // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. // DO NOT! use an already existing index list. - void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float* fvTangent, const float* fvBiTangent, const float fMagS, const float fMagT, const tbool bIsOrientationPreserving, const int iFace, const int iVert); } SMikkTSpaceInterface; From 242434244fe8302bbf509f23003136d70ba15d2c Mon Sep 17 00:00:00 2001 From: alteous Date: Sat, 8 Jul 2017 19:02:23 +0100 Subject: [PATCH 04/87] Add reference implementation --- reference/mikktspace.c | 1890 ++++++++++++++++++++++++++++++++++++++++ reference/mikktspace.h | 145 +++ 2 files changed, 2035 insertions(+) create mode 100644 reference/mikktspace.c create mode 100644 reference/mikktspace.h diff --git a/reference/mikktspace.c b/reference/mikktspace.c new file mode 100644 index 0000000000000..62aa2da251740 --- /dev/null +++ b/reference/mikktspace.c @@ -0,0 +1,1890 @@ +/** \file mikktspace/mikktspace.c + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +#define TFALSE 0 +#define TTRUE 1 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define INTERNAL_RND_SORT_SEED 39871946 + +// internal structure +typedef struct { + float x, y, z; +} SVec3; + +static tbool veq( const SVec3 v1, const SVec3 v2 ) +{ + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); +} + +static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x + v2.x; + vRes.y = v1.y + v2.y; + vRes.z = v1.z + v2.z; + + return vRes; +} + + +static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x - v2.x; + vRes.y = v1.y - v2.y; + vRes.z = v1.z - v2.z; + + return vRes; +} + +static SVec3 vscale(const float fS, const SVec3 v) +{ + SVec3 vRes; + + vRes.x = fS * v.x; + vRes.y = fS * v.y; + vRes.z = fS * v.z; + + return vRes; +} + +static float LengthSquared( const SVec3 v ) +{ + return v.x*v.x + v.y*v.y + v.z*v.z; +} + +static float Length( const SVec3 v ) +{ + return sqrtf(LengthSquared(v)); +} + +static SVec3 Normalize( const SVec3 v ) +{ + return vscale(1 / Length(v), v); +} + +static float vdot( const SVec3 v1, const SVec3 v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + + +static tbool NotZero(const float fX) +{ + // could possibly use FLT_EPSILON instead + return fabsf(fX) > FLT_MIN; +} + +static tbool VNotZero(const SVec3 v) +{ + // might change this to an epsilon based test + return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); +} + + + +typedef struct { + int iNrFaces; + int * pTriMembers; +} SSubGroup; + +typedef struct { + int iNrFaces; + int * pFaceIndices; + int iVertexRepresentitive; + tbool bOrientPreservering; +} SGroup; + +// +#define MARK_DEGENERATE 1 +#define QUAD_ONE_DEGEN_TRI 2 +#define GROUP_WITH_ANY 4 +#define ORIENT_PRESERVING 8 + + + +typedef struct { + int FaceNeighbors[3]; + SGroup * AssignedGroup[3]; + + // normalized first order face derivatives + SVec3 vOs, vOt; + float fMagS, fMagT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int iOrgFaceNumber; + int iFlag, iTSpacesOffs; + unsigned char vert_num[4]; +} STriInfo; + +typedef struct { + SVec3 vOs; + float fMagS; + SVec3 vOt; + float fMagT; + int iCounter; // this is to average back into quads. + tbool bOrient; +} STSpace; + +static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext); + +static int MakeIndex(const int iFace, const int iVert) +{ + assert(iVert>=0 && iVert<4 && iFace>=0); + return (iFace<<2) | (iVert&0x3); +} + +static void IndexToData(int * piFace, int * piVert, const int iIndexIn) +{ + piVert[0] = iIndexIn&0x3; + piFace[0] = iIndexIn>>2; +} + +static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) +{ + STSpace ts_res; + + // this if is important. Due to floating point precision + // averaging when ts0==ts1 will cause a slight difference + // which results in tangent space splits later on + if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && + veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) + { + ts_res.fMagS = pTS0->fMagS; + ts_res.fMagT = pTS0->fMagT; + ts_res.vOs = pTS0->vOs; + ts_res.vOt = pTS0->vOt; + } + else + { + ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); + ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); + ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); + ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); + if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); + if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); + } + + return ts_res; +} + + + +static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); + + +// degen triangles +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); + + +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) +{ + return genTangSpace(pContext, 180.0f); +} + +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) +{ + // count nr_triangles + int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; + STriInfo * pTriInfos = NULL; + SGroup * pGroups = NULL; + STSpace * psTspace = NULL; + int iNrTrianglesIn = 0, f=0, t=0, i=0; + int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; + int iNrActiveGroups = 0, index = 0; + const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); + tbool bRes = TFALSE; + const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); + + // verify all call-backs have been set + if ( pContext->m_pInterface->m_getNumFaces==NULL || + pContext->m_pInterface->m_getNumVerticesOfFace==NULL || + pContext->m_pInterface->m_getPosition==NULL || + pContext->m_pInterface->m_getNormal==NULL || + pContext->m_pInterface->m_getTexCoord==NULL ) + return TFALSE; + + // count triangles on supported faces + for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts==3) ++iNrTrianglesIn; + else if (verts==4) iNrTrianglesIn += 2; + } + if (iNrTrianglesIn<=0) return TFALSE; + + // allocate memory for an index list + piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); + pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); + if (piTriListIn==NULL || pTriInfos==NULL) + { + if (piTriListIn!=NULL) free(piTriListIn); + if (pTriInfos!=NULL) free(pTriInfos); + return TFALSE; + } + + // make an initial triangle --> face index list + iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + //printf("gen welded index list begin\n"); + GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); + //printf("gen welded index list end\n"); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + + // set data + for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + if (pContext->m_pInterface->m_setTSpace!=NULL) + pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); + if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) + pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); + + ++index; + } + } + + free(psTspace); + + + return TTRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + float vert[3]; + int index; +} STmpVert; + +static const int g_iCells = 2048; + +#ifdef _MSC_VER + #define NOINLINE __declspec(noinline) +#else + #define NOINLINE __attribute__ ((noinline)) +#endif + +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) +{ + const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); + const int iIndex = (int)fIndex; + return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); +} + +static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); +static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); +static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); + +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + + // Generate bounding box + int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; + STmpVert * pTmpVert = NULL; + int i=0, iChannel=0, k=0, e=0; + int iMaxCount=0; + SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; + float fMin, fMax; + for (i=1; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + + const SVec3 vP = GetPosition(pContext, index); + if (vMin.x > vP.x) vMin.x = vP.x; + else if (vMax.x < vP.x) vMax.x = vP.x; + if (vMin.y > vP.y) vMin.y = vP.y; + else if (vMax.y < vP.y) vMax.y = vP.y; + if (vMin.z > vP.z) vMin.z = vP.z; + else if (vMax.z < vP.z) vMax.z = vP.z; + } + + vDim = vsub(vMax,vMin); + iChannel = 0; + fMin = vMin.x; fMax=vMax.x; + if (vDim.y>vDim.x && vDim.y>vDim.z) + { + iChannel=1; + fMin = vMin.y, fMax=vMax.y; + } + else if (vDim.z>vDim.x) + { + iChannel=2; + fMin = vMin.z, fMax=vMax.z; + } + + // make allocations + piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); + piHashCount = (int *) malloc(sizeof(int)*g_iCells); + piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); + piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); + + if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) + { + if (piHashTable!=NULL) free(piHashTable); + if (piHashCount!=NULL) free(piHashCount); + if (piHashOffsets!=NULL) free(piHashOffsets); + if (piHashCount2!=NULL) free(piHashCount2); + GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); + return; + } + memset(piHashCount, 0, sizeof(int)*g_iCells); + memset(piHashCount2, 0, sizeof(int)*g_iCells); + + // count amount of elements in each cell unit + for (i=0; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); + const int iCell = FindGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0]=0; + for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; + else if (fvMax[c]dx && dy>dz) channel=1; + else if (dz>dx) channel=2; + + fSep = 0.5f*(fvMax[channel]+fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) + { + // complete the weld + for (l=iL_in; l<=iR_in; l++) + { + int i = pTmpVert[l].index; + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const SVec3 vN = GetNormal(pContext, index); + const SVec3 vT = GetTexCoord(pContext, index); + + tbool bNotFound = TTRUE; + int l2=iL_in, i2rec=-1; + while (l20); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) + { + tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; + while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) + { + const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; + + if (verts==3) + { + unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; + pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; + piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); + piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); + piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); + ++iDstTriIndex; // next + } + else + { + { + pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + const int i0 = MakeIndex(f, 0); + const int i1 = MakeIndex(f, 1); + const int i2 = MakeIndex(f, 2); + const int i3 = MakeIndex(f, 3); + const SVec3 T0 = GetTexCoord(pContext, i0); + const SVec3 T1 = GetTexCoord(pContext, i1); + const SVec3 T2 = GetTexCoord(pContext, i2); + const SVec3 T3 = GetTexCoord(pContext, i3); + const float distSQ_02 = LengthSquared(vsub(T2,T0)); + const float distSQ_13 = LengthSquared(vsub(T3,T1)); + tbool bQuadDiagIs_02; + if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); + res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; + return res; +} + +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float norm[3]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); + res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; + return res; +} + +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float texc[2]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); + res.x=texc[0]; res.y=texc[1]; res.z=1.0f; + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union { + struct + { + int i0, i1, f; + }; + int array[3]; +} SEdge; + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); +static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); + +// returns the texture area times 2 +static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) +{ + const SVec3 t1 = GetTexCoord(pContext, indices[0]); + const SVec3 t2 = GetTexCoord(pContext, indices[1]); + const SVec3 t3 = GetTexCoord(pContext, indices[2]); + + const float t21x = t2.x-t1.x; + const float t21y = t2.y-t1.y; + const float t31x = t3.x-t1.x; + const float t31y = t3.y-t1.y; + + const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; + + return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; +} + +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + int f=0, i=0, t=0; + // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + + // generate neighbor info list + for (f=0; f0 ? ORIENT_PRESERVING : 0); + + if ( NotZero(fSignedAreaSTx2) ) + { + const float fAbsArea = fabsf(fSignedAreaSTx2); + const float fLenOs = Length(vOs); + const float fLenOt = Length(vOt); + const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; + if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); + if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + + // if this is a good triangle + if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) + pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); + } + } + + // force otherwise healthy quads to a fixed orientation + while (t<(iNrTrianglesIn-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a||bIsDeg_b)==TFALSE) + { + const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + // if this happens the quad has extremely bad mapping!! + if (bOrientA!=bOrientB) + { + //printf("found quad with bad mapping\n"); + tbool bChooseOrientFirstTri = TFALSE; + if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; + else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) + bChooseOrientFirstTri = TTRUE; + + // force match + { + const int t0 = bChooseOrientFirstTri ? t : (t+1); + const int t1 = bChooseOrientFirstTri ? (t+1) : t; + pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } + else + ++t; + } + + // match up edge pairs + { + SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); + if (pEdges==NULL) + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + else + { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + free(pEdges); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); + +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) +{ + const int iNrMaxGroups = iNrTrianglesIn*3; + int iNrActiveGroups = 0; + int iOffset = 0, f=0, i=0; + (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (f=0; fiVertexRepresentitive = vert_index; + pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; + pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; + pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; + ++iNrActiveGroups; + + AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); + bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + neigh_indexL = pTriInfos[f].FaceNeighbors[i]; + neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + // update offset + iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert(iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; +} + +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) +{ + pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; + ++pGroup->iNrFaces; +} + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], + const int iMyTriIndex, SGroup * pGroup) +{ + STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; + + // track down vertex + const int iVertRep = pGroup->iVertexRepresentitive; + const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; + int i=-1; + if (pVerts[0]==iVertRep) i=0; + else if (pVerts[1]==iVertRep) i=1; + else if (pVerts[2]==iVertRep) i=2; + assert(i>=0 && i<3); + + // early out + if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; + else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; + if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) + { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if ( pMyTriInfo->AssignedGroup[0] == NULL && + pMyTriInfo->AssignedGroup[1] == NULL && + pMyTriInfo->AssignedGroup[2] == NULL ) + { + pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); + pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + if (bOrient != pGroup->bOrientPreservering) return TFALSE; + } + + AddTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo->AssignedGroup[i] = pGroup; + + { + const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; + const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + if (neigh_indexR>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + + + + return TTRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); +static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); + +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext) +{ + STSpace * pSubGroupTspace = NULL; + SSubGroup * pUniSubGroups = NULL; + int * pTmpMembers = NULL; + int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; + for (g=0; giNrFaces; i++) // triangles + { + const int f = pGroup->pFaceIndices[i]; // triangle number + int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; + SSubGroup tmp_group; + tbool bFound; + SVec3 n, vOs, vOt; + if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; + else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; + else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; + assert(index>=0 && index<3); + + iVertIndex = piTriListIn[f*3+index]; + assert(iVertIndex==pGroup->iVertexRepresentitive); + + // is normalized already + n = GetNormal(pContext, iVertIndex); + + // project + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + // original face number + iOF_1 = pTriInfos[f].iOrgFaceNumber; + + iMembers = 0; + for (j=0; jiNrFaces; j++) + { + const int t = pGroup->pFaceIndices[j]; // triangle number + const int iOF_2 = pTriInfos[t].iOrgFaceNumber; + + // project + SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); + SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); + if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); + if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); + + { + const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; + // make sure triangles which belong to the same quad are joined. + const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; + + const float fCosS = vdot(vOs,vOs2); + const float fCosT = vdot(vOt,vOt2); + + assert(f!=t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) + pTmpMembers[iMembers++] = t; + } + } + + // sort pTmpMembers + tmp_group.iNrFaces = iMembers; + tmp_group.pTriMembers = pTmpMembers; + if (iMembers>1) + { + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + QuickSort(pTmpMembers, 0, iMembers-1, uSeed); + } + + // look for an existing match + bFound = TFALSE; + l=0; + while (liVertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + const int iOffs = pTriInfos[f].iTSpacesOffs; + const int iVert = pTriInfos[f].vert_num[index]; + STSpace * pTS_out = &psTspace[iOffs+iVert]; + assert(pTS_out->iCounter<2); + assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + if (pTS_out->iCounter==1) + { + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); + pTS_out->iCounter = 2; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + else + { + assert(pTS_out->iCounter==0); + *pTS_out = pSubGroupTspace[l]; + pTS_out->iCounter = 1; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + } + } + + // clean up and offset iUniqueTspaces + for (s=0; s=0 && i<3); + + // project + index = piTriListIn[3*f+i]; + n = GetNormal(pContext, index); + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + i2 = piTriListIn[3*f + (i<2?(i+1):0)]; + i1 = piTriListIn[3*f + i]; + i0 = piTriListIn[3*f + (i>0?(i-1):2)]; + + p0 = GetPosition(pContext, i0); + p1 = GetPosition(pContext, i1); + p2 = GetPosition(pContext, i2); + v1 = vsub(p0,p1); + v2 = vsub(p2,p1); + + // project + v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); + v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); + + // weight contribution by the angle + // between the two edge vectors + fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); + fAngle = (float) acos(fCos); + fMagS = pTriInfos[f].fMagS; + fMagT = pTriInfos[f].fMagT; + + res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); + res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); + res.fMagS+=(fAngle*fMagS); + res.fMagT+=(fAngle*fMagT); + fAngleSum += fAngle; + } + } + + // normalize + if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); + if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); + if (fAngleSum>0) + { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum; + } + + return res; +} + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) +{ + tbool bStillSame=TTRUE; + int i=0; + if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; + while (iiNrFaces && bStillSame) + { + bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; + if (bStillSame) ++i; + } + return bStillSame; +} + +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) +{ + int iL, iR, n, index, iMid, iTmp; + + // Random + unsigned int t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft; iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL]; + + + do + { + while (pSortBuffer[iL] < iMid) + ++iL; + while (pSortBuffer[iR] > iMid) + --iR; + + if (iL <= iR) + { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSort(pSortBuffer, iLeft, iR, uSeed); + if (iL < iRight) + QuickSort(pSortBuffer, iL, iRight, uSeed); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +{ + // build array of edges + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + int iEntries=0, iCurStartIndex=-1, f=0, i=0; + for (f=0; f pSortBuffer[iRight].array[channel]) + { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft, iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL].array[channel]; + + do + { + while (pSortBuffer[iL].array[channel] < iMid) + ++iL; + while (pSortBuffer[iR].array[channel] > iMid) + --iR; + + if (iL <= iR) + { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + if (iL < iRight) + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); +} + +// resolve ordering and edge number +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +{ + *edgenum_out = -1; + + // test if first index is on the edge + if (indices[0]==i0_in || indices[0]==i1_in) + { + // test if second index is on the edge + if (indices[1]==i0_in || indices[1]==i1_in) + { + edgenum_out[0]=0; // first edge + i0_out[0]=indices[0]; + i1_out[0]=indices[1]; + } + else + { + edgenum_out[0]=2; // third edge + i0_out[0]=indices[2]; + i1_out[0]=indices[0]; + } + } + else + { + // only second and third index is on the edge + edgenum_out[0]=1; // second edge + i0_out[0]=indices[1]; + i1_out[0]=indices[2]; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Degenerate triangles //////////////////////////////////// + +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +{ + int iNextGoodTriangleSearchIndex=-1; + tbool bStillFindingGoodOnes; + + // locate quads with only one good triangle + int t=0; + while (t<(iTotTris-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + if ((bIsDeg_a^bIsDeg_b)!=0) + { + pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } + else + ++t; + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + iNextGoodTriangleSearchIndex = 1; + t=0; + bStillFindingGoodOnes = TTRUE; + while (t (t+1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) + { + int i=0; + for (i=0; i<3; i++) + { + const int index = piTriList_out[t0*3+i]; + piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; + piTriList_out[t1*3+i] = index; + } + { + const STriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } + else + bStillFindingGoodOnes = TFALSE; // this is not supposed to happen + } + + if (bStillFindingGoodOnes) ++t; + } + + assert(bStillFindingGoodOnes); // code will still work. + assert(iNrTrianglesIn == t); +} + +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +{ + int t=0, i=0; + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct { + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif From 92fe7b77e65490591eb6ec107d6e1e4a14f850e3 Mon Sep 17 00:00:00 2001 From: alteous Date: Sat, 8 Jul 2017 19:06:37 +0100 Subject: [PATCH 05/87] Add CMake build script for reference implementation --- reference/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 reference/CMakeLists.txt diff --git a/reference/CMakeLists.txt b/reference/CMakeLists.txt new file mode 100644 index 0000000000000..7b57c07fb658e --- /dev/null +++ b/reference/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 2.8) +project(mikktspace) +set(PROJECT_VERSION_MAJOR "1") +set(PROJECT_VERSION_MINOR "0") +set(PROJECT_VERSION_PATCH "0") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") +set(SOURCES mikktspace.h mikktspace.c) +add_library(mikktspace SHARED ${SOURCES}) From 17630127326b0517a62edd96291e84bad47778e7 Mon Sep 17 00:00:00 2001 From: alteous Date: Sat, 8 Jul 2017 19:08:29 +0100 Subject: [PATCH 06/87] Add mikktspace-sys library --- Cargo.toml | 3 +++ mikktspace-sys/Cargo.toml | 6 ++++++ mikktspace-sys/src/lib.rs | 6 ++++++ 3 files changed, 15 insertions(+) create mode 100644 mikktspace-sys/Cargo.toml create mode 100644 mikktspace-sys/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 0b5568451a7f2..14ae2a507af47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,7 @@ name = "mikktspace" version = "0.1.0" authors = ["Benjamin Wasty "] +[dev-dependencies] +mikktspace-sys = { path = "./mikktspace-sys" } + [dependencies] diff --git a/mikktspace-sys/Cargo.toml b/mikktspace-sys/Cargo.toml new file mode 100644 index 0000000000000..717102a12feee --- /dev/null +++ b/mikktspace-sys/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mikktspace-sys" +version = "0.1.0" +authors = ["alteous "] + +[dependencies] diff --git a/mikktspace-sys/src/lib.rs b/mikktspace-sys/src/lib.rs new file mode 100644 index 0000000000000..cdfbe1aa56211 --- /dev/null +++ b/mikktspace-sys/src/lib.rs @@ -0,0 +1,6 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} From 7100ed91ec9b40927dbce8d6220e9a9b375b65b0 Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 9 Jul 2017 11:52:03 +0100 Subject: [PATCH 07/87] Added stub FFI Former-commit-id: 5d11cb5b8100a169c105405e75d58399caf6f57d --- .gitignore | 1 + Cargo.toml | 4 +- mikktspace-sys/Cargo.toml | 4 + mikktspace-sys/build.rs | 9 ++ .../libmikktspace}/CMakeLists.txt | 3 +- .../libmikktspace.a.REMOVED.git-id | 1 + .../libmikktspace}/mikktspace.c | 0 .../libmikktspace}/mikktspace.h | 0 mikktspace-sys/src/ffi.rs | 136 ++++++++++++++++++ mikktspace-sys/src/lib.rs | 123 ++++++++++++++++ 10 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 mikktspace-sys/build.rs rename {reference => mikktspace-sys/libmikktspace}/CMakeLists.txt (80%) create mode 100644 mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id rename {reference => mikktspace-sys/libmikktspace}/mikktspace.c (100%) rename {reference => mikktspace-sys/libmikktspace}/mikktspace.h (100%) create mode 100644 mikktspace-sys/src/ffi.rs diff --git a/.gitignore b/.gitignore index 6aa106405a4b4..ba7432bda89ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ +/mikktspace-sys/target/ **/*.rs.bk Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 14ae2a507af47..8802dbc6e5e7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,5 @@ name = "mikktspace" version = "0.1.0" authors = ["Benjamin Wasty "] -[dev-dependencies] -mikktspace-sys = { path = "./mikktspace-sys" } - [dependencies] +mikktspace-sys = { path = "./mikktspace-sys" } diff --git a/mikktspace-sys/Cargo.toml b/mikktspace-sys/Cargo.toml index 717102a12feee..8b54f66d5680f 100644 --- a/mikktspace-sys/Cargo.toml +++ b/mikktspace-sys/Cargo.toml @@ -2,5 +2,9 @@ name = "mikktspace-sys" version = "0.1.0" authors = ["alteous "] +build = "build.rs" + +[build-dependencies] +cmake = "0.1" [dependencies] diff --git a/mikktspace-sys/build.rs b/mikktspace-sys/build.rs new file mode 100644 index 0000000000000..155a9ed067b08 --- /dev/null +++ b/mikktspace-sys/build.rs @@ -0,0 +1,9 @@ + +extern crate cmake; + +fn main() { + let dst = cmake::build("libmikktspace"); + println!("cargo:rustc-link-search=native={}", dst.display()); + println!("cargo:rustc-link-lib=static=mikktspace"); +} + diff --git a/reference/CMakeLists.txt b/mikktspace-sys/libmikktspace/CMakeLists.txt similarity index 80% rename from reference/CMakeLists.txt rename to mikktspace-sys/libmikktspace/CMakeLists.txt index 7b57c07fb658e..376409d8c8580 100644 --- a/reference/CMakeLists.txt +++ b/mikktspace-sys/libmikktspace/CMakeLists.txt @@ -7,4 +7,5 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") set(SOURCES mikktspace.h mikktspace.c) -add_library(mikktspace SHARED ${SOURCES}) +add_library(mikktspace STATIC ${SOURCES}) +install(TARGETS mikktspace ARCHIVE DESTINATION ".") diff --git a/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id b/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id new file mode 100644 index 0000000000000..1ebb68ee379ae --- /dev/null +++ b/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id @@ -0,0 +1 @@ +e52ce628fdfe770b52733629d6c2ba6ae871c323 \ No newline at end of file diff --git a/reference/mikktspace.c b/mikktspace-sys/libmikktspace/mikktspace.c similarity index 100% rename from reference/mikktspace.c rename to mikktspace-sys/libmikktspace/mikktspace.c diff --git a/reference/mikktspace.h b/mikktspace-sys/libmikktspace/mikktspace.h similarity index 100% rename from reference/mikktspace.h rename to mikktspace-sys/libmikktspace/mikktspace.h diff --git a/mikktspace-sys/src/ffi.rs b/mikktspace-sys/src/ffi.rs new file mode 100644 index 0000000000000..b4e8fe9e727bd --- /dev/null +++ b/mikktspace-sys/src/ffi.rs @@ -0,0 +1,136 @@ + +#![allow(bad_style)] + +use std::os::raw::*; + +#[allow(dead_code)] +pub type tbool = c_int; + +#[allow(dead_code)] +const TFALSE: tbool = 0; + +#[allow(dead_code)] +const TTRUE: tbool = 1; + +#[repr(C)] +pub struct SMikkTSpaceInterface { + /// Returns the number of faces (triangles/quads) on the mesh to be processed. + pub m_getNumFaces: extern "C" fn(pContext: *const SMikkTSpaceContext) -> c_int, + + /// Returns the number of vertices on face number iFace + /// iFace is a number in the range {0, 1, ..., getNumFaces()-1} + pub m_getNumVerticesOfFace: extern "C" fn( + pContext: *const SMikkTSpaceContext, + iFace: c_int, + ) -> c_int, + + /// Returns the position of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getPosition: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvPosOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// Returns the normal of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getNormal: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvNormOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// Returns the texcoord of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getTexCoord: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvTexcOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// either (or both) of the two setTSpace callbacks can be set. + /// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + /// This function is used to return the tangent and fSign to the application. + /// fvTangent is a unit length vector. + /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + /// bitangent = fSign * cross(vN, tangent); + /// Note that the results are returned unindexed. It is possible to generate a new index list + /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + /// DO NOT! use an already existing index list. + pub m_setTSpaceBasic: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvTangent: *const c_float, + fSign: *const c_float, + iFace: c_int, + iVert: c_int, + ), + + /// This function is used to return tangent space results to the application. + /// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + /// true magnitudes which can be used for relief mapping effects. + /// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + /// However, both are perpendicular to the vertex normal. + /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + /// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + /// bitangent = fSign * cross(vN, tangent); + /// Note that the results are returned unindexed. It is possible to generate a new index list + /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + /// DO NOT! use an already existing index list. + pub m_setTSpace: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvTangent: *const c_float, + fvBiTangent: *const c_float, + fMagS: *const c_float, + fMagT: *const c_float, + bIsOrientationPreserving: tbool, + iFace: c_int, + iVert: c_int, + ), +} + +/// these are both thread safe! +/// Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +pub type genTangSpaceDefault = extern "system" fn( + pContext: *const SMikkTSpaceContext, +) -> tbool; + +pub type genTangSpace = extern "system" fn( + pContext: *const SMikkTSpaceContext, + fAngularThreshold: c_float, +) -> tbool; + +/// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +/// normal map sampler must use the exact inverse of the pixel shader transformation. +/// The most efficient transformation we can possibly do in the pixel shader is +/// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +/// pixel shader (fast transform out) +/// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +/// where vNt is the tangent space normal. The normal map sampler must likewise use the +/// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +/// sampler does (exact inverse of pixel shader): +/// float3 row0 = cross(vB, vN); +/// float3 row1 = cross(vN, vT); +/// float3 row2 = cross(vT, vB); +/// float fSign = dot(vT, row0)<0 ? -1 : 1; +/// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +/// where vNout is the sampled normal in some chosen 3D space. +/// +/// Should you choose to reconstruct the bitangent in the pixel shader instead +/// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +/// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +/// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +/// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +/// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +/// However, this must be used both by the sampler and your tools/rendering pipeline. + +#[repr(C)] +pub struct SMikkTSpaceContext { + /// initialized with callback functions + pub m_pInterface: *const SMikkTSpaceInterface, + /// pointer to client side mesh data etc. (passed as the first parameter with every interface call) + pub m_pUserData: *const c_void, +} diff --git a/mikktspace-sys/src/lib.rs b/mikktspace-sys/src/lib.rs index cdfbe1aa56211..f0644a98e57de 100644 --- a/mikktspace-sys/src/lib.rs +++ b/mikktspace-sys/src/lib.rs @@ -1,3 +1,126 @@ + +mod ffi; + +use std::os::raw::*; +use std::mem; + +const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { + m_getNumFaces: faces, + m_getNumVerticesOfFace: vertices, + m_getPosition: position, + m_getNormal: normal, + m_getTexCoord: tex_coord, + m_setTSpaceBasic: set_tspace_basic, + m_setTSpace: set_tspace, +}; + +pub struct Context { + faces: i32, +} + +/// Returns the number of faces (triangles/quads) on the mesh to be processed. +#[no_mangle] +extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { + unsafe { + let m: *const Context = mem::transmute(pContext); + (*m).faces as c_int + } +} + +/// Returns the number of vertices on face number iFace +/// iFace is a number in the range {0, 1, ..., getNumFaces()-1} +#[no_mangle] +extern "C" fn vertices( + pContext: *const ffi::SMikkTSpaceContext, + iFace: c_int, +) -> c_int { + unsafe { + let _: *const Context = mem::transmute(pContext); + unimplemented!() + } +} + +/// Returns the position of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +#[no_mangle] +extern "C" fn position( + pContext: *const ffi::SMikkTSpaceContext, + fvPosOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let _: *const Context = mem::transmute(pContext); + } +} + +/// Returns the normal of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +#[no_mangle] +extern "C" fn normal( + pContext: *const ffi::SMikkTSpaceContext, + fvPosOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let _: *const Context = mem::transmute(pContext); + } +} + +/// Returns the texcoord of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +#[no_mangle] +extern "C" fn tex_coord( + pContext: *const ffi::SMikkTSpaceContext, + fvTexcOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let _: *const Context = mem::transmute(pContext); + } +} + +/// Returns the tangent and its sign to the application. +#[no_mangle] +extern "C" fn set_tspace_basic( + pContext: *const ffi::SMikkTSpaceContext, + fvTangent: *const c_float, + fSign: *const c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let _: *const Context = mem::transmute(pContext); + } +} + +/// Returns tangent space results to the application. +#[no_mangle] +extern "C" fn set_tspace( + pContext: *const ffi::SMikkTSpaceContext, + fvTangent: *const c_float, + fvBiTangent: *const c_float, + fMagS: *const c_float, + fMagT: *const c_float, + bIsOrientationPreserving: ffi::tbool, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let _: *const Context = mem::transmute(pContext); + } +} + +impl Context { + pub fn new() -> Self { + Context { + faces: 3, + } + } +} + #[cfg(test)] mod tests { #[test] From b450533bbecd4cc6235d75dc00a22ddd32851e89 Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 9 Jul 2017 11:53:43 +0100 Subject: [PATCH 08/87] Add libmikktspace.a to .gitignore Former-commit-id: 0289b4d33cb63b4a89994291bed38e5a4e604712 --- .gitignore | 1 + mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id diff --git a/.gitignore b/.gitignore index ba7432bda89ed..5d6a2baca9fd4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target/ /mikktspace-sys/target/ +/mikktspace-sys/libmikktspace/libmikktspace.a **/*.rs.bk Cargo.lock diff --git a/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id b/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id deleted file mode 100644 index 1ebb68ee379ae..0000000000000 --- a/mikktspace-sys/libmikktspace/libmikktspace.a.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -e52ce628fdfe770b52733629d6c2ba6ae871c323 \ No newline at end of file From 9bbd34b6f6a7b1413b6503c5867b4a45325ed026 Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 9 Jul 2017 12:42:53 +0100 Subject: [PATCH 09/87] Add Context abstraction Former-commit-id: 7bd77bfa85539326f688ead84edaf086c5c88985 --- mikktspace-sys/src/lib.rs | 47 +++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/mikktspace-sys/src/lib.rs b/mikktspace-sys/src/lib.rs index f0644a98e57de..84f7a6876bf2a 100644 --- a/mikktspace-sys/src/lib.rs +++ b/mikktspace-sys/src/lib.rs @@ -4,6 +4,7 @@ mod ffi; use std::os::raw::*; use std::mem; +/// Rust FFI for the MikkTSpace implementation. const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { m_getNumFaces: faces, m_getNumVerticesOfFace: vertices, @@ -15,7 +16,26 @@ const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { }; pub struct Context { - faces: i32, + faces: Box>, + positions: Box>, + tex_coords: Box>, + normals: Box>, +} + +/// Specifies whether a face is a triangle or a quad. +pub enum Face { + Triangle, + Quad, +} + +impl Face { + /// Returns the number of vertices bound by this face. + pub fn vertices(&self) -> i32 { + match self { + &Face::Triangle { .. } => 3, + &Face::Quad { .. } => 4, + } + } } /// Returns the number of faces (triangles/quads) on the mesh to be processed. @@ -23,7 +43,7 @@ pub struct Context { extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { unsafe { let m: *const Context = mem::transmute(pContext); - (*m).faces as c_int + (*m).faces.len() as c_int } } @@ -36,7 +56,7 @@ extern "C" fn vertices( ) -> c_int { unsafe { let _: *const Context = mem::transmute(pContext); - unimplemented!() + 3 } } @@ -114,16 +134,19 @@ extern "C" fn set_tspace( } impl Context { - pub fn new() -> Self { + /// Constructor for a MikkTSpace `Context`. + pub fn new(faces: F, positions: P, tex_coords: T, normals: N) -> Self + where + F: 'static + ExactSizeIterator, + P: 'static + ExactSizeIterator, + T: 'static + ExactSizeIterator, + N: 'static + ExactSizeIterator, + { Context { - faces: 3, + faces: Box::new(faces), + positions: Box::new(positions), + tex_coords: Box::new(tex_coords), + normals: Box::new(normals), } } } - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} From dc8fe97715d47f5de0239ea73a0f700195011dd2 Mon Sep 17 00:00:00 2001 From: alteous Date: Tue, 29 Aug 2017 19:09:12 +0100 Subject: [PATCH 10/87] Add closures interface Former-commit-id: 76f4a864fa3b1fbde9d5ac2eab7c5d0639d147ef --- Cargo.toml | 13 +- build.rs | 9 + examples/generate.rs | 45 + libmikktspace/CMakeLists.txt | 11 + libmikktspace/mikktspace.c | 1890 ++++++++++++++++ libmikktspace/mikktspace.h | 145 ++ mikktspace-sys/src/lib.rs | 47 +- src/ffi.rs | 129 ++ src/lib.rs | 170 +- test-data/Avocado.bin | Bin 0 -> 23580 bytes test-data/Avocado.gltf | 185 ++ test-data/Avocado.json.REMOVED.git-id | 1 + test-data/Avocado.obj | 1900 +++++++++++++++++ test-data/AvocadoOut.obj.REMOVED.git-id | 1 + .../Avocado_baseColor.png.REMOVED.git-id | 1 + test-data/Avocado_normal.png.REMOVED.git-id | 1 + ...ocado_roughnessMetallic.png.REMOVED.git-id | 1 + test-data/gen | Bin 0 -> 56872 bytes test-data/gen2 | Bin 0 -> 62688 bytes test-data/generate-obj.rs | 45 + test-data/generate-test-data | Bin 0 -> 57056 bytes test-data/generate-test-data.c | 245 +++ test-data/generate-test-obj.c | 250 +++ test-data/libmikktspace.a | Bin 0 -> 48966 bytes test-data/mikktspace.h | 145 ++ 25 files changed, 5192 insertions(+), 42 deletions(-) create mode 100644 build.rs create mode 100644 examples/generate.rs create mode 100644 libmikktspace/CMakeLists.txt create mode 100644 libmikktspace/mikktspace.c create mode 100644 libmikktspace/mikktspace.h create mode 100644 src/ffi.rs create mode 100644 test-data/Avocado.bin create mode 100644 test-data/Avocado.gltf create mode 100644 test-data/Avocado.json.REMOVED.git-id create mode 100644 test-data/Avocado.obj create mode 100644 test-data/AvocadoOut.obj.REMOVED.git-id create mode 100644 test-data/Avocado_baseColor.png.REMOVED.git-id create mode 100644 test-data/Avocado_normal.png.REMOVED.git-id create mode 100644 test-data/Avocado_roughnessMetallic.png.REMOVED.git-id create mode 100755 test-data/gen create mode 100755 test-data/gen2 create mode 100644 test-data/generate-obj.rs create mode 100755 test-data/generate-test-data create mode 100644 test-data/generate-test-data.c create mode 100644 test-data/generate-test-obj.c create mode 100644 test-data/libmikktspace.a create mode 100644 test-data/mikktspace.h diff --git a/Cargo.toml b/Cargo.toml index 8802dbc6e5e7e..16924db39035f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,14 @@ [package] -name = "mikktspace" +name = "mikktspace-sys" version = "0.1.0" -authors = ["Benjamin Wasty "] +authors = ["alteous "] +build = "build.rs" + +[build-dependencies] +cmake = "0.1" [dependencies] -mikktspace-sys = { path = "./mikktspace-sys" } +gltf = "0.7" + +[[example]] +name = "generate" \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000000..155a9ed067b08 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ + +extern crate cmake; + +fn main() { + let dst = cmake::build("libmikktspace"); + println!("cargo:rustc-link-search=native={}", dst.display()); + println!("cargo:rustc-link-lib=static=mikktspace"); +} + diff --git a/examples/generate.rs b/examples/generate.rs new file mode 100644 index 0000000000000..ed4444a406b63 --- /dev/null +++ b/examples/generate.rs @@ -0,0 +1,45 @@ + +extern crate gltf; + +use std::io::Write; + +fn main() { + let path = "test-data/Avocado.gltf"; + let gltf = gltf::Import::from_path(path).sync().unwrap(); + let mesh = gltf.meshes().nth(0).unwrap(); + let primitive = mesh.primitives().nth(0).unwrap(); + let positions: Vec<[f32; 3]> = primitive.positions().unwrap().collect(); + let normals: Vec<[f32; 3]> = primitive.normals().unwrap().collect(); + let mut tex_coords: Vec<[f32; 2]> = vec![]; + let mut indices: Vec = vec![]; + match primitive.tex_coords(0).unwrap() { + gltf::mesh::TexCoords::F32(iter) => tex_coords.extend(iter), + _ => unreachable!(), + } + match primitive.indices().unwrap() { + gltf::mesh::Indices::U16(iter) => indices.extend(iter), + _ => unreachable!(), + } + + let file = std::fs::File::create("Avocado.obj").unwrap(); + let mut writer = std::io::BufWriter::new(file); + for position in &positions { + writeln!(writer, "v {} {} {}", position[0], position[1], position[2]); + } + for normal in &normals { + writeln!(writer, "vn {} {} {}", normal[0], normal[1], normal[2]); + } + for tex_coord in &tex_coords { + writeln!(writer, "vt {} {}", tex_coord[0], tex_coord[1]); + } + let mut i = indices.iter(); + while let (Some(v0), Some(v1), Some(v2)) = (i.next(), i.next(), i.next()) { + writeln!( + writer, + "f {}/{}/{} {}/{}/{} {}/{}/{}", + 1 + v0, 1 + v0, 1 + v0, + 1 + v1, 1 + v1, 1 + v1, + 1 + v2, 1 + v2, 1 + v2, + ); + } +} diff --git a/libmikktspace/CMakeLists.txt b/libmikktspace/CMakeLists.txt new file mode 100644 index 0000000000000..376409d8c8580 --- /dev/null +++ b/libmikktspace/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.8) +project(mikktspace) +set(PROJECT_VERSION_MAJOR "1") +set(PROJECT_VERSION_MINOR "0") +set(PROJECT_VERSION_PATCH "0") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") +set(SOURCES mikktspace.h mikktspace.c) +add_library(mikktspace STATIC ${SOURCES}) +install(TARGETS mikktspace ARCHIVE DESTINATION ".") diff --git a/libmikktspace/mikktspace.c b/libmikktspace/mikktspace.c new file mode 100644 index 0000000000000..62aa2da251740 --- /dev/null +++ b/libmikktspace/mikktspace.c @@ -0,0 +1,1890 @@ +/** \file mikktspace/mikktspace.c + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +#define TFALSE 0 +#define TTRUE 1 + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#define INTERNAL_RND_SORT_SEED 39871946 + +// internal structure +typedef struct { + float x, y, z; +} SVec3; + +static tbool veq( const SVec3 v1, const SVec3 v2 ) +{ + return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); +} + +static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x + v2.x; + vRes.y = v1.y + v2.y; + vRes.z = v1.z + v2.z; + + return vRes; +} + + +static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) +{ + SVec3 vRes; + + vRes.x = v1.x - v2.x; + vRes.y = v1.y - v2.y; + vRes.z = v1.z - v2.z; + + return vRes; +} + +static SVec3 vscale(const float fS, const SVec3 v) +{ + SVec3 vRes; + + vRes.x = fS * v.x; + vRes.y = fS * v.y; + vRes.z = fS * v.z; + + return vRes; +} + +static float LengthSquared( const SVec3 v ) +{ + return v.x*v.x + v.y*v.y + v.z*v.z; +} + +static float Length( const SVec3 v ) +{ + return sqrtf(LengthSquared(v)); +} + +static SVec3 Normalize( const SVec3 v ) +{ + return vscale(1 / Length(v), v); +} + +static float vdot( const SVec3 v1, const SVec3 v2) +{ + return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; +} + + +static tbool NotZero(const float fX) +{ + // could possibly use FLT_EPSILON instead + return fabsf(fX) > FLT_MIN; +} + +static tbool VNotZero(const SVec3 v) +{ + // might change this to an epsilon based test + return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); +} + + + +typedef struct { + int iNrFaces; + int * pTriMembers; +} SSubGroup; + +typedef struct { + int iNrFaces; + int * pFaceIndices; + int iVertexRepresentitive; + tbool bOrientPreservering; +} SGroup; + +// +#define MARK_DEGENERATE 1 +#define QUAD_ONE_DEGEN_TRI 2 +#define GROUP_WITH_ANY 4 +#define ORIENT_PRESERVING 8 + + + +typedef struct { + int FaceNeighbors[3]; + SGroup * AssignedGroup[3]; + + // normalized first order face derivatives + SVec3 vOs, vOt; + float fMagS, fMagT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int iOrgFaceNumber; + int iFlag, iTSpacesOffs; + unsigned char vert_num[4]; +} STriInfo; + +typedef struct { + SVec3 vOs; + float fMagS; + SVec3 vOt; + float fMagT; + int iCounter; // this is to average back into quads. + tbool bOrient; +} STSpace; + +static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext); + +static int MakeIndex(const int iFace, const int iVert) +{ + assert(iVert>=0 && iVert<4 && iFace>=0); + return (iFace<<2) | (iVert&0x3); +} + +static void IndexToData(int * piFace, int * piVert, const int iIndexIn) +{ + piVert[0] = iIndexIn&0x3; + piFace[0] = iIndexIn>>2; +} + +static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) +{ + STSpace ts_res; + + // this if is important. Due to floating point precision + // averaging when ts0==ts1 will cause a slight difference + // which results in tangent space splits later on + if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && + veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) + { + ts_res.fMagS = pTS0->fMagS; + ts_res.fMagT = pTS0->fMagT; + ts_res.vOs = pTS0->vOs; + ts_res.vOt = pTS0->vOt; + } + else + { + ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); + ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); + ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); + ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); + if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); + if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); + } + + return ts_res; +} + + + +static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); + + +// degen triangles +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); + + +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) +{ + return genTangSpace(pContext, 180.0f); +} + +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) +{ + // count nr_triangles + int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; + STriInfo * pTriInfos = NULL; + SGroup * pGroups = NULL; + STSpace * psTspace = NULL; + int iNrTrianglesIn = 0, f=0, t=0, i=0; + int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; + int iNrActiveGroups = 0, index = 0; + const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); + tbool bRes = TFALSE; + const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); + + // verify all call-backs have been set + if ( pContext->m_pInterface->m_getNumFaces==NULL || + pContext->m_pInterface->m_getNumVerticesOfFace==NULL || + pContext->m_pInterface->m_getPosition==NULL || + pContext->m_pInterface->m_getNormal==NULL || + pContext->m_pInterface->m_getTexCoord==NULL ) + return TFALSE; + + // count triangles on supported faces + for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts==3) ++iNrTrianglesIn; + else if (verts==4) iNrTrianglesIn += 2; + } + if (iNrTrianglesIn<=0) return TFALSE; + + // allocate memory for an index list + piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); + pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); + if (piTriListIn==NULL || pTriInfos==NULL) + { + if (piTriListIn!=NULL) free(piTriListIn); + if (pTriInfos!=NULL) free(pTriInfos); + return TFALSE; + } + + // make an initial triangle --> face index list + iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + //printf("gen welded index list begin\n"); + GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); + //printf("gen welded index list end\n"); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + + // set data + for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; + float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; + if (pContext->m_pInterface->m_setTSpace!=NULL) + pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); + if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) + pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); + + ++index; + } + } + + free(psTspace); + + + return TTRUE; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + float vert[3]; + int index; +} STmpVert; + +static const int g_iCells = 2048; + +#ifdef _MSC_VER + #define NOINLINE __declspec(noinline) +#else + #define NOINLINE __attribute__ ((noinline)) +#endif + +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) +{ + const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); + const int iIndex = (int)fIndex; + return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); +} + +static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); +static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); +static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); + +static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + + // Generate bounding box + int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; + STmpVert * pTmpVert = NULL; + int i=0, iChannel=0, k=0, e=0; + int iMaxCount=0; + SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; + float fMin, fMax; + for (i=1; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + + const SVec3 vP = GetPosition(pContext, index); + if (vMin.x > vP.x) vMin.x = vP.x; + else if (vMax.x < vP.x) vMax.x = vP.x; + if (vMin.y > vP.y) vMin.y = vP.y; + else if (vMax.y < vP.y) vMax.y = vP.y; + if (vMin.z > vP.z) vMin.z = vP.z; + else if (vMax.z < vP.z) vMax.z = vP.z; + } + + vDim = vsub(vMax,vMin); + iChannel = 0; + fMin = vMin.x; fMax=vMax.x; + if (vDim.y>vDim.x && vDim.y>vDim.z) + { + iChannel=1; + fMin = vMin.y, fMax=vMax.y; + } + else if (vDim.z>vDim.x) + { + iChannel=2; + fMin = vMin.z, fMax=vMax.z; + } + + // make allocations + piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); + piHashCount = (int *) malloc(sizeof(int)*g_iCells); + piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); + piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); + + if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) + { + if (piHashTable!=NULL) free(piHashTable); + if (piHashCount!=NULL) free(piHashCount); + if (piHashOffsets!=NULL) free(piHashOffsets); + if (piHashCount2!=NULL) free(piHashCount2); + GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); + return; + } + memset(piHashCount, 0, sizeof(int)*g_iCells); + memset(piHashCount2, 0, sizeof(int)*g_iCells); + + // count amount of elements in each cell unit + for (i=0; i<(iNrTrianglesIn*3); i++) + { + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); + const int iCell = FindGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0]=0; + for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; + else if (fvMax[c]dx && dy>dz) channel=1; + else if (dz>dx) channel=2; + + fSep = 0.5f*(fvMax[channel]+fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) + { + // complete the weld + for (l=iL_in; l<=iR_in; l++) + { + int i = pTmpVert[l].index; + const int index = piTriList_in_and_out[i]; + const SVec3 vP = GetPosition(pContext, index); + const SVec3 vN = GetNormal(pContext, index); + const SVec3 vT = GetTexCoord(pContext, index); + + tbool bNotFound = TTRUE; + int l2=iL_in, i2rec=-1; + while (l20); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) + { + tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; + while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) + { + const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); + if (verts!=3 && verts!=4) continue; + + pTriInfos[iDstTriIndex].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; + + if (verts==3) + { + unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; + pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; + piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); + piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); + piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); + ++iDstTriIndex; // next + } + else + { + { + pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; + pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + const int i0 = MakeIndex(f, 0); + const int i1 = MakeIndex(f, 1); + const int i2 = MakeIndex(f, 2); + const int i3 = MakeIndex(f, 3); + const SVec3 T0 = GetTexCoord(pContext, i0); + const SVec3 T1 = GetTexCoord(pContext, i1); + const SVec3 T2 = GetTexCoord(pContext, i2); + const SVec3 T3 = GetTexCoord(pContext, i3); + const float distSQ_02 = LengthSquared(vsub(T2,T0)); + const float distSQ_13 = LengthSquared(vsub(T3,T1)); + tbool bQuadDiagIs_02; + if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); + res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; + return res; +} + +static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float norm[3]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); + res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; + return res; +} + +static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) +{ + int iF, iI; + SVec3 res; float texc[2]; + IndexToData(&iF, &iI, index); + pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); + res.x=texc[0]; res.y=texc[1]; res.z=1.0f; + return res; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union { + struct + { + int i0, i1, f; + }; + int array[3]; +} SEdge; + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); +static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); + +// returns the texture area times 2 +static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) +{ + const SVec3 t1 = GetTexCoord(pContext, indices[0]); + const SVec3 t2 = GetTexCoord(pContext, indices[1]); + const SVec3 t3 = GetTexCoord(pContext, indices[2]); + + const float t21x = t2.x-t1.x; + const float t21y = t2.y-t1.y; + const float t31x = t3.x-t1.x; + const float t31y = t3.y-t1.y; + + const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; + + return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; +} + +static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) +{ + int f=0, i=0, t=0; + // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + + // generate neighbor info list + for (f=0; f0 ? ORIENT_PRESERVING : 0); + + if ( NotZero(fSignedAreaSTx2) ) + { + const float fAbsArea = fabsf(fSignedAreaSTx2); + const float fLenOs = Length(vOs); + const float fLenOt = Length(vOt); + const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; + if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); + if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].fMagS = fLenOs / fAbsArea; + pTriInfos[f].fMagT = fLenOt / fAbsArea; + + // if this is a good triangle + if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) + pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); + } + } + + // force otherwise healthy quads to a fixed orientation + while (t<(iNrTrianglesIn-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a||bIsDeg_b)==TFALSE) + { + const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + // if this happens the quad has extremely bad mapping!! + if (bOrientA!=bOrientB) + { + //printf("found quad with bad mapping\n"); + tbool bChooseOrientFirstTri = TFALSE; + if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; + else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) + bChooseOrientFirstTri = TTRUE; + + // force match + { + const int t0 = bChooseOrientFirstTri ? t : (t+1); + const int t1 = bChooseOrientFirstTri ? (t+1) : t; + pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } + else + ++t; + } + + // match up edge pairs + { + SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); + if (pEdges==NULL) + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + else + { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + free(pEdges); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); + +static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) +{ + const int iNrMaxGroups = iNrTrianglesIn*3; + int iNrActiveGroups = 0; + int iOffset = 0, f=0, i=0; + (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (f=0; fiVertexRepresentitive = vert_index; + pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; + pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; + pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; + ++iNrActiveGroups; + + AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); + bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + neigh_indexL = pTriInfos[f].FaceNeighbors[i]; + neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR>=0) // neighbor + { + const tbool bAnswer = + AssignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].AssignedGroup[i] ); + + const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; + assert(bAnswer || bDiff); + (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + // update offset + iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert(iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; +} + +static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) +{ + pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; + ++pGroup->iNrFaces; +} + +static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], + const int iMyTriIndex, SGroup * pGroup) +{ + STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; + + // track down vertex + const int iVertRep = pGroup->iVertexRepresentitive; + const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; + int i=-1; + if (pVerts[0]==iVertRep) i=0; + else if (pVerts[1]==iVertRep) i=1; + else if (pVerts[2]==iVertRep) i=2; + assert(i>=0 && i<3); + + // early out + if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; + else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; + if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) + { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if ( pMyTriInfo->AssignedGroup[0] == NULL && + pMyTriInfo->AssignedGroup[1] == NULL && + pMyTriInfo->AssignedGroup[2] == NULL ) + { + pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); + pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; + if (bOrient != pGroup->bOrientPreservering) return TFALSE; + } + + AddTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo->AssignedGroup[i] = pGroup; + + { + const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; + const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; + if (neigh_indexL>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + if (neigh_indexR>=0) + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + + + + return TTRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); +static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); + +static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], + const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, + const SMikkTSpaceContext * pContext) +{ + STSpace * pSubGroupTspace = NULL; + SSubGroup * pUniSubGroups = NULL; + int * pTmpMembers = NULL; + int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; + for (g=0; giNrFaces; i++) // triangles + { + const int f = pGroup->pFaceIndices[i]; // triangle number + int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; + SSubGroup tmp_group; + tbool bFound; + SVec3 n, vOs, vOt; + if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; + else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; + else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; + assert(index>=0 && index<3); + + iVertIndex = piTriListIn[f*3+index]; + assert(iVertIndex==pGroup->iVertexRepresentitive); + + // is normalized already + n = GetNormal(pContext, iVertIndex); + + // project + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + // original face number + iOF_1 = pTriInfos[f].iOrgFaceNumber; + + iMembers = 0; + for (j=0; jiNrFaces; j++) + { + const int t = pGroup->pFaceIndices[j]; // triangle number + const int iOF_2 = pTriInfos[t].iOrgFaceNumber; + + // project + SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); + SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); + if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); + if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); + + { + const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; + // make sure triangles which belong to the same quad are joined. + const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; + + const float fCosS = vdot(vOs,vOs2); + const float fCosT = vdot(vOt,vOt2); + + assert(f!=t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) + pTmpMembers[iMembers++] = t; + } + } + + // sort pTmpMembers + tmp_group.iNrFaces = iMembers; + tmp_group.pTriMembers = pTmpMembers; + if (iMembers>1) + { + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + QuickSort(pTmpMembers, 0, iMembers-1, uSeed); + } + + // look for an existing match + bFound = TFALSE; + l=0; + while (liVertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + const int iOffs = pTriInfos[f].iTSpacesOffs; + const int iVert = pTriInfos[f].vert_num[index]; + STSpace * pTS_out = &psTspace[iOffs+iVert]; + assert(pTS_out->iCounter<2); + assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); + if (pTS_out->iCounter==1) + { + *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); + pTS_out->iCounter = 2; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + else + { + assert(pTS_out->iCounter==0); + *pTS_out = pSubGroupTspace[l]; + pTS_out->iCounter = 1; // update counter + pTS_out->bOrient = pGroup->bOrientPreservering; + } + } + } + + // clean up and offset iUniqueTspaces + for (s=0; s=0 && i<3); + + // project + index = piTriListIn[3*f+i]; + n = GetNormal(pContext, index); + vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); + vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); + if ( VNotZero(vOs) ) vOs = Normalize(vOs); + if ( VNotZero(vOt) ) vOt = Normalize(vOt); + + i2 = piTriListIn[3*f + (i<2?(i+1):0)]; + i1 = piTriListIn[3*f + i]; + i0 = piTriListIn[3*f + (i>0?(i-1):2)]; + + p0 = GetPosition(pContext, i0); + p1 = GetPosition(pContext, i1); + p2 = GetPosition(pContext, i2); + v1 = vsub(p0,p1); + v2 = vsub(p2,p1); + + // project + v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); + v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); + + // weight contribution by the angle + // between the two edge vectors + fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); + fAngle = (float) acos(fCos); + fMagS = pTriInfos[f].fMagS; + fMagT = pTriInfos[f].fMagT; + + res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); + res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); + res.fMagS+=(fAngle*fMagS); + res.fMagT+=(fAngle*fMagT); + fAngleSum += fAngle; + } + } + + // normalize + if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); + if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); + if (fAngleSum>0) + { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum; + } + + return res; +} + +static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) +{ + tbool bStillSame=TTRUE; + int i=0; + if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; + while (iiNrFaces && bStillSame) + { + bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; + if (bStillSame) ++i; + } + return bStillSame; +} + +static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) +{ + int iL, iR, n, index, iMid, iTmp; + + // Random + unsigned int t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft; iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL]; + + + do + { + while (pSortBuffer[iL] < iMid) + ++iL; + while (pSortBuffer[iR] > iMid) + --iR; + + if (iL <= iR) + { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSort(pSortBuffer, iLeft, iR, uSeed); + if (iL < iRight) + QuickSort(pSortBuffer, iL, iRight, uSeed); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// + +static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); + +static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) +{ + // build array of edges + unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + int iEntries=0, iCurStartIndex=-1, f=0, i=0; + for (f=0; f pSortBuffer[iRight].array[channel]) + { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + t=uSeed&31; + t=(uSeed<>(32-t)); + uSeed=uSeed+t+3; + // Random end + + iL=iLeft, iR=iRight; + n = (iR-iL)+1; + assert(n>=0); + index = (int) (uSeed%n); + + iMid=pSortBuffer[index + iL].array[channel]; + + do + { + while (pSortBuffer[iL].array[channel] < iMid) + ++iL; + while (pSortBuffer[iR].array[channel] > iMid) + --iR; + + if (iL <= iR) + { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; --iR; + } + } + while (iL <= iR); + + if (iLeft < iR) + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + if (iL < iRight) + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); +} + +// resolve ordering and edge number +static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) +{ + *edgenum_out = -1; + + // test if first index is on the edge + if (indices[0]==i0_in || indices[0]==i1_in) + { + // test if second index is on the edge + if (indices[1]==i0_in || indices[1]==i1_in) + { + edgenum_out[0]=0; // first edge + i0_out[0]=indices[0]; + i1_out[0]=indices[1]; + } + else + { + edgenum_out[0]=2; // third edge + i0_out[0]=indices[2]; + i1_out[0]=indices[0]; + } + } + else + { + // only second and third index is on the edge + edgenum_out[0]=1; // second edge + i0_out[0]=indices[1]; + i1_out[0]=indices[2]; + } +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////// Degenerate triangles //////////////////////////////////// + +static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) +{ + int iNextGoodTriangleSearchIndex=-1; + tbool bStillFindingGoodOnes; + + // locate quads with only one good triangle + int t=0; + while (t<(iTotTris-1)) + { + const int iFO_a = pTriInfos[t].iOrgFaceNumber; + const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; + if (iFO_a==iFO_b) // this is a quad + { + const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; + if ((bIsDeg_a^bIsDeg_b)!=0) + { + pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } + else + ++t; + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + iNextGoodTriangleSearchIndex = 1; + t=0; + bStillFindingGoodOnes = TTRUE; + while (t (t+1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) + { + int i=0; + for (i=0; i<3; i++) + { + const int index = piTriList_out[t0*3+i]; + piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; + piTriList_out[t1*3+i] = index; + } + { + const STriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } + else + bStillFindingGoodOnes = TFALSE; // this is not supposed to happen + } + + if (bStillFindingGoodOnes) ++t; + } + + assert(bStillFindingGoodOnes); // code will still work. + assert(iNrTrianglesIn == t); +} + +static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) +{ + int t=0, i=0; + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct { + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mikktspace-sys/src/lib.rs b/mikktspace-sys/src/lib.rs index 84f7a6876bf2a..f0644a98e57de 100644 --- a/mikktspace-sys/src/lib.rs +++ b/mikktspace-sys/src/lib.rs @@ -4,7 +4,6 @@ mod ffi; use std::os::raw::*; use std::mem; -/// Rust FFI for the MikkTSpace implementation. const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { m_getNumFaces: faces, m_getNumVerticesOfFace: vertices, @@ -16,26 +15,7 @@ const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { }; pub struct Context { - faces: Box>, - positions: Box>, - tex_coords: Box>, - normals: Box>, -} - -/// Specifies whether a face is a triangle or a quad. -pub enum Face { - Triangle, - Quad, -} - -impl Face { - /// Returns the number of vertices bound by this face. - pub fn vertices(&self) -> i32 { - match self { - &Face::Triangle { .. } => 3, - &Face::Quad { .. } => 4, - } - } + faces: i32, } /// Returns the number of faces (triangles/quads) on the mesh to be processed. @@ -43,7 +23,7 @@ impl Face { extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { unsafe { let m: *const Context = mem::transmute(pContext); - (*m).faces.len() as c_int + (*m).faces as c_int } } @@ -56,7 +36,7 @@ extern "C" fn vertices( ) -> c_int { unsafe { let _: *const Context = mem::transmute(pContext); - 3 + unimplemented!() } } @@ -134,19 +114,16 @@ extern "C" fn set_tspace( } impl Context { - /// Constructor for a MikkTSpace `Context`. - pub fn new(faces: F, positions: P, tex_coords: T, normals: N) -> Self - where - F: 'static + ExactSizeIterator, - P: 'static + ExactSizeIterator, - T: 'static + ExactSizeIterator, - N: 'static + ExactSizeIterator, - { + pub fn new() -> Self { Context { - faces: Box::new(faces), - positions: Box::new(positions), - tex_coords: Box::new(tex_coords), - normals: Box::new(normals), + faces: 3, } } } + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000000000..05e700bf30192 --- /dev/null +++ b/src/ffi.rs @@ -0,0 +1,129 @@ + +#![allow(bad_style)] +#![allow(dead_code)] + +use std::os::raw::*; + +pub type tbool = c_int; +pub const TFALSE: tbool = 0; +pub const TTRUE: tbool = 1; + +#[repr(C)] +pub struct SMikkTSpaceInterface { + /// Returns the number of faces (triangles/quads) on the mesh to be processed. + pub m_getNumFaces: extern "C" fn(pContext: *const SMikkTSpaceContext) -> c_int, + + /// Returns the number of vertices on face number iFace + /// iFace is a number in the range {0, 1, ..., getNumFaces()-1} + pub m_getNumVerticesOfFace: extern "C" fn( + pContext: *const SMikkTSpaceContext, + iFace: c_int, + ) -> c_int, + + /// Returns the position of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getPosition: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvPosOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// Returns the normal of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getNormal: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvNormOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// Returns the texcoord of the referenced face of vertex number + /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. + pub m_getTexCoord: extern "C" fn( + pContext: *const SMikkTSpaceContext, + fvTexcOut: *mut c_float, + iFace: c_int, + iVert: c_int, + ), + + /// either (or both) of the two setTSpace callbacks can be set. + /// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + /// This function is used to return the tangent and fSign to the application. + /// fvTangent is a unit length vector. + /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + /// bitangent = fSign * cross(vN, tangent); + /// Note that the results are returned unindexed. It is possible to generate a new index list + /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + /// DO NOT! use an already existing index list. + pub m_setTSpaceBasic: extern "C" fn( + pContext: *mut SMikkTSpaceContext, + fvTangent: *const c_float, + fSign: *const c_float, + iFace: c_int, + iVert: c_int, + ), + + /// This function is used to return tangent space results to the application. + /// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + /// true magnitudes which can be used for relief mapping effects. + /// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + /// However, both are perpendicular to the vertex normal. + /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + /// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + /// bitangent = fSign * cross(vN, tangent); + /// Note that the results are returned unindexed. It is possible to generate a new index list + /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + /// DO NOT! use an already existing index list. + pub m_setTSpace: extern "C" fn( + pContext: *mut SMikkTSpaceContext, + fvTangent: *const c_float, + fvBiTangent: *const c_float, + fMagS: *const c_float, + fMagT: *const c_float, + bIsOrientationPreserving: tbool, + iFace: c_int, + iVert: c_int, + ), +} + +/// these are both thread safe! +/// Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +extern "system" { + pub fn genTangSpaceDefault(pContext: *const SMikkTSpaceContext) -> tbool; + #[allow(dead_code)] + pub fn genTangSpace(pContext: *const SMikkTSpaceContext, fAngularThreshold: c_float) -> tbool; +} + +/// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +/// normal map sampler must use the exact inverse of the pixel shader transformation. +/// The most efficient transformation we can possibly do in the pixel shader is +/// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +/// pixel shader (fast transform out) +/// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +/// where vNt is the tangent space normal. The normal map sampler must likewise use the +/// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +/// sampler does (exact inverse of pixel shader): +/// float3 row0 = cross(vB, vN); +/// float3 row1 = cross(vN, vT); +/// float3 row2 = cross(vT, vB); +/// float fSign = dot(vT, row0)<0 ? -1 : 1; +/// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +/// where vNout is the sampled normal in some chosen 3D space. +/// +/// Should you choose to reconstruct the bitangent in the pixel shader instead +/// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +/// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +/// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +/// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +/// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +/// However, this must be used both by the sampler and your tools/rendering pipeline. + +#[repr(C)] +pub struct SMikkTSpaceContext { + /// initialized with callback functions + pub m_pInterface: *const SMikkTSpaceInterface, + /// pointer to client side mesh data etc. (passed as the first parameter with every interface call) + pub m_pUserData: *mut c_void, +} diff --git a/src/lib.rs b/src/lib.rs index cdfbe1aa56211..5f91648b90a03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,168 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { +#![allow(bad_style)] + +mod ffi; + +use std::os::raw::*; +use std::mem; +use std::ptr; + +/// Rust FFI for the MikkTSpace implementation. +const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { + m_getNumFaces: faces, + m_getNumVerticesOfFace: vertices, + m_getPosition: position, + m_getNormal: normal, + m_getTexCoord: tex_coord, + m_setTSpaceBasic: set_tspace_basic, + m_setTSpace: set_tspace, +}; + +struct Closures<'a> { + pub vertices_per_face: &'a Fn() -> usize, + + /// Returns the number of faces. + pub face_count: &'a Fn() -> usize, + + /// Returns the positions of the indexed face. + pub position: &'a Fn(usize, usize) -> &'a [f32; 3], + + /// Returns the normals of the indexed face. + pub normal: &'a Fn(usize, usize) -> &'a [f32; 3], + + /// Returns the texture co-ordinates of the indexed face. + pub tex_coord: &'a Fn(usize, usize) -> &'a [f32; 2], + + /// Sets the generated tangent for the indexed face. + pub set_tangent: &'a mut FnMut(usize, usize, [f32; 4]), +} + +/// Returns the number of faces (triangles/quads) on the mesh to be processed. +extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { + unsafe { + let x = (*pContext).m_pUserData as *const Closures; + ((*x).face_count)() as c_int + } +} + +/// Returns the number of vertices on face number iFace +/// iFace is a number in the range {0, 1, ..., getNumFaces()-1} +extern "C" fn vertices( + pContext: *const ffi::SMikkTSpaceContext, + _iFace: c_int, +) -> c_int { + unsafe { + let x = (*pContext).m_pUserData as *const Closures; + ((*x).vertices_per_face)() as c_int + } +} + +/// Returns the position of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +extern "C" fn position( + pContext: *const ffi::SMikkTSpaceContext, + fvPosOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let x = (*pContext).m_pUserData as *const Closures; + let slice = ((*x).position)(iFace as usize, iVert as usize); + let src = slice.as_ptr() as *const c_float; + ptr::copy_nonoverlapping::(src, fvPosOut, 3); + } +} + +/// Returns the normal of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +extern "C" fn normal( + pContext: *const ffi::SMikkTSpaceContext, + fvNormOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let x = (*pContext).m_pUserData as *const Closures; + let slice = ((*x).normal)(iFace as usize, iVert as usize); + let src = slice.as_ptr() as *const c_float; + ptr::copy_nonoverlapping::(src, fvNormOut, 3); } } + +/// Returns the texcoord of the referenced face of vertex number +/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. +extern "C" fn tex_coord( + pContext: *const ffi::SMikkTSpaceContext, + fvTexcOut: *mut c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let x = (*pContext).m_pUserData as *const Closures; + let slice = ((*x).tex_coord)(iFace as usize, iVert as usize); + let src = slice.as_ptr() as *const c_float; + ptr::copy_nonoverlapping::(src, fvTexcOut, 2); + } +} + +/// Returns the tangent and its sign to the application. +extern "C" fn set_tspace_basic( + pContext: *mut ffi::SMikkTSpaceContext, + fvTangent: *const c_float, + fSign: *const c_float, + iFace: c_int, + iVert: c_int, +) { + unsafe { + let x = (*pContext).m_pUserData as *mut Closures; + let mut tangent: [f32; 4] = mem::uninitialized(); + let dst: *mut c_float = tangent.as_mut_ptr(); + ptr::copy_nonoverlapping::(fvTangent, dst, 3); + tangent[3] = *fSign; + ((*x).set_tangent)(iFace as usize, iVert as usize, tangent); + } +} + +/// Returns tangent space results to the application. +extern "C" fn set_tspace( + _pContext: *mut ffi::SMikkTSpaceContext, + _fvTangent: *const c_float, + _fvBiTangent: *const c_float, + _fMagS: *const c_float, + _fMagT: *const c_float, + _bIsOrientationPreserving: ffi::tbool, + _iFace: c_int, + _iVert: c_int, +) { + unimplemented!() +} + +impl<'a> Closures<'a> { + pub fn generate(mut self) -> bool { + let ctx = ffi::SMikkTSpaceContext { + m_pInterface: &INTERFACE, + m_pUserData: &mut self as *mut Closures as *mut c_void, + }; + unsafe { + ffi::genTangSpaceDefault(&ctx) == ffi::TTRUE + } + } +} + +pub fn generate<'a>( + vertices_per_face: &'a Fn() -> usize, + face_count: &'a Fn() -> usize, + position: &'a Fn(usize, usize) -> &'a [f32; 3], + normal: &'a Fn(usize, usize) -> &'a [f32; 3], + tex_coord: &'a Fn(usize, usize) -> &'a [f32; 2], + set_tangent: &'a mut FnMut(usize, usize, [f32; 4]), +) -> bool { + let closures = Closures { + vertices_per_face, + face_count, + position, + normal, + tex_coord, + set_tangent, + }; + closures.generate() +} diff --git a/test-data/Avocado.bin b/test-data/Avocado.bin new file mode 100644 index 0000000000000000000000000000000000000000..79c28dec5fca03ae13abeda5b06f4c253ac96678 GIT binary patch literal 23580 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⪻TYc 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 HsrmnZ(R`E+ literal 0 HcmV?d00001 diff --git a/test-data/generate-obj.rs b/test-data/generate-obj.rs new file mode 100644 index 0000000000000..f226a2b9ef68b --- /dev/null +++ b/test-data/generate-obj.rs @@ -0,0 +1,45 @@ + +extern crate gltf; + +use std::io::Write; + +fn main() { + let path = "test-data/Avocado.gltf"; + let gltf = gltf::Import::from_path(path).sync().unwrap(); + let mesh = gltf.meshes().nth(0).unwrap(); + let primitive = mesh.primitives().nth(0).unwrap(); + let positions: Vec<[f32; 3]> = primitive.positions().unwrap().collect(); + let normals: Vec<[f32; 3]> = primitive.normals().unwrap().collect(); + let mut tex_coords: Vec<[f32; 2]> = vec![]; + let mut indices: Vec = vec![]; + match primitive.tex_coords(0).unwrap() { + gltf::mesh::TexCoords::F32(iter) => tex_coords.extend(iter), + _ => unreachable!(), + } + match primitive.indices().unwrap() { + gltf::mesh::Indices::U16(iter) => indices.extend(iter), + _ => unreachable!(), + } + + let file = std::fs::File::create("Avocado.obj").unwrap(); + let mut writer = std::io::BufWriter::new(file); + for position in &positions { + writeln!(writer, "v {} {} {}", position[0], position[1], position[2]); + } + for normal in &normals { + writeln!(writer, "vn {} {} {}", normal[0], normal[1], normal[2]); + } + for tex_coord in &tex_coords { + writeln!(writer, "vt {} {}", tex_coord[0], tex_coord[1]); + } + let mut i = indices.iter(); + while let (Some(v0), Some(v1), Some(v2)) = (i.next(), i.next(), i.next()) { + writeln!( + writer, + "f {}/{}/{} {}/{}/{} {}/{}/{}", + v0, v0, v0, + v1, v1, v1, + v2, v2, v2, + ); + } +} diff --git a/test-data/generate-test-data b/test-data/generate-test-data new file mode 100755 index 0000000000000000000000000000000000000000..b383add654c23d9bd041048b6afa9a78aaf2c91a GIT binary patch literal 57056 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 +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +struct tangent { + // Vector. + float v[3]; + + // Sign. + float s; +}; + +struct vertex { + // Borrows from `input.positions`. + float (*position)[3]; + + // Borrows from `input.positions`. + float (*normal)[3]; + + // Borrows from `input.positions`. + float (*tex_coord)[2]; +}; + +struct face { + // Borrows from `positions`, `normals`, and `tex_coords`. + struct vertex vertices[3]; +}; + +struct input { + // Owned. + float (*positions)[3]; + + // Owned. + float (*normals)[3]; + + // Owned. + float (*tex_coords)[2]; + + // Borrows from `positions`, `normals`, and `tex_coords`. + struct face *faces; + + // Number of entries in `positions`, `normals`, and `tex_coords`. + size_t nr_vertices; + + // Number of entries in `faces`. + size_t nr_faces; +} input; + +struct output { + // Borrows from `positions`, `normals`, and `tex_coords`. + struct vertex *vertices; + + // Owned. + struct tangent *tangents; + + // Number of entries in `vertices` and `tangents`. + // Equal to `3 * input.nr_faces`. + size_t nr_vertices; +} output; + +void print_vec2(float (*t)[2]) { + printf("[%f, %f]", (*t)[0], (*t)[1]); +} + +void print_vec3(float (*t)[3]) { + printf("[%f, %f, %f]", (*t)[0], (*t)[1], (*t)[2]); +} + +void print_tangent(const struct tangent *t) { + printf("[%f, %f, %f, %f]", t->v[0], t->v[1], t->v[2], t->s); +} + +int get_num_faces(const SMikkTSpaceContext *x) { + return input.nr_faces; +} + +int get_num_vertices_of_face(const SMikkTSpaceContext *x, int f) { + return 3; +} + +void get_position(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[3] = input.faces[f].vertices[v].position; + memcpy(dst, src, sizeof(*src)); +} + +void get_normal(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[3] = input.faces[f].vertices[v].normal; + memcpy(dst, src, sizeof(*src)); +} + +void get_tex_coord(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[2] = input.faces[f].vertices[v].tex_coord; + memcpy(dst, src, sizeof(*src)); +} + +void set_tspace_basic( + const SMikkTSpaceContext *x, + const float *t, + float s, + int f, + int v +) { + // The index of the last output (vertex, tangent) pair. + static int i = 0; + + struct vertex *in = &input.faces[f].vertices[v]; + + output.vertices[i].position = in->position; + output.vertices[i].normal = in->normal; + output.vertices[i].tex_coord = in->tex_coord; + memcpy(output.tangents[i].v, t, 3 * sizeof(float)); + output.tangents[i].s = s; + + ++i; +} + +void set_tspace( + const SMikkTSpaceContext *x, + const float *t, + const float *b, + float mag_s, + float mag_t, + tbool op, + int f, + int v +) { + assert(!"unreachable"); +} + +int main() { + input.nr_vertices = 406; + input.nr_faces = 682; + output.nr_vertices = 3 * input.nr_faces; + + input.positions = calloc(input.nr_vertices, sizeof(*input.positions)); + input.normals = calloc(input.nr_vertices, sizeof(*input.normals)); + input.tex_coords = calloc(input.nr_vertices, sizeof(*input.tex_coords)); + input.faces = calloc(input.nr_faces, sizeof(*input.faces)); + output.vertices = calloc(output.nr_vertices, sizeof(*output.vertices)); + output.tangents = calloc(output.nr_vertices, sizeof(*output.tangents)); + + FILE *fi = fopen("Avocado.obj", "rb"); + assert(fi); + char buffer[1024]; + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "v %f %f %f", + &input.positions[i][0], + &input.positions[i][1], + &input.positions[i][2] + ); + } + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "vn %f %f %f", + &input.normals[i][0], + &input.normals[i][1], + &input.normals[i][2] + ); + } + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "vt %f %f", + &input.tex_coords[i][0], + &input.tex_coords[i][1] + ); + } + + for (size_t i = 0; i < input.nr_faces; ++i) { + fgets(buffer, sizeof(buffer), fi); + int v[3]; + sscanf( + buffer, + "f %d/%d/%d %d/%d/%d %d/%d/%d", + &v[0], &v[0], &v[0], + &v[1], &v[1], &v[1], + &v[2], &v[2], &v[2] + ); + for (size_t j = 0; j < 3; ++j) { + input.faces[i].vertices[j].position = &input.positions[v[j] - 1]; + input.faces[i].vertices[j].normal = &input.normals[v[j] - 1]; + input.faces[i].vertices[j].tex_coord = &input.tex_coords[v[j] - 1]; + } + } + + SMikkTSpaceInterface interface = { + .m_getNumFaces = get_num_faces, + .m_getNumVerticesOfFace = get_num_vertices_of_face, + .m_getPosition = get_position, + .m_getNormal = get_normal, + .m_getTexCoord = get_tex_coord, + .m_setTSpaceBasic = set_tspace_basic, + .m_setTSpace = NULL, + }; + SMikkTSpaceContext context = { + .m_pInterface = &interface, + .m_pUserData = NULL, + }; + + genTangSpaceDefault(&context); + + printf("{\n \"vlist\": [\n"); + for (size_t i = 0; i < output.nr_vertices; ++i) { + printf(" {\"v\": "); + print_vec3(output.vertices[i].position); + printf(", \"vn\": "); + print_vec3(output.vertices[i].normal); + printf(", \"vt\": "); + print_vec2(output.vertices[i].tex_coord); + printf(", \"vx\": "); + print_tangent(&output.tangents[i]); + if (i == output.nr_vertices - 1) { + printf("}\n"); + } else { + printf("},\n"); + } + } + printf(" ]\n}"); + + fclose(fi); + free(input.positions); + free(input.normals); + free(input.tex_coords); + free(input.faces); + free(output.vertices); + free(output.tangents); + + return 0; +} + diff --git a/test-data/generate-test-obj.c b/test-data/generate-test-obj.c new file mode 100644 index 0000000000000..6d4a8ea9216ee --- /dev/null +++ b/test-data/generate-test-obj.c @@ -0,0 +1,250 @@ + +#include +#include +#include +#include +#include +#include + +#include "mikktspace.h" + +struct tangent { + // Vector. + float v[3]; + + // Sign. + float s; +}; + +struct vertex { + // Borrows from `input.positions`. + float (*position)[3]; + + // Borrows from `input.positions`. + float (*normal)[3]; + + // Borrows from `input.positions`. + float (*tex_coord)[2]; +}; + +struct face { + // Borrows from `positions`, `normals`, and `tex_coords`. + struct vertex vertices[3]; +}; + +struct input { + // Owned. + float (*positions)[3]; + + // Owned. + float (*normals)[3]; + + // Owned. + float (*tex_coords)[2]; + + // Borrows from `positions`, `normals`, and `tex_coords`. + struct face *faces; + + // Number of entries in `positions`, `normals`, and `tex_coords`. + size_t nr_vertices; + + // Number of entries in `faces`. + size_t nr_faces; +} input; + +struct output { + // Borrows from `positions`, `normals`, and `tex_coords`. + struct vertex *vertices; + + // Owned. + struct tangent *tangents; + + // Number of entries in `vertices` and `tangents`. + // Equal to `3 * input.nr_faces`. + size_t nr_vertices; +} output; + +void print_vec2(float (*t)[2]) { + printf("%f %f", (*t)[0], (*t)[1]); +} + +void print_vec3(float (*t)[3]) { + printf("%f %f %f", (*t)[0], (*t)[1], (*t)[2]); +} + +void print_tangent(const struct tangent *t) { + printf("%f %f %f %f", t->v[0], t->v[1], t->v[2], t->s); +} + +int get_num_faces(const SMikkTSpaceContext *x) { + return input.nr_faces; +} + +int get_num_vertices_of_face(const SMikkTSpaceContext *x, int f) { + return 3; +} + +void get_position(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[3] = input.faces[f].vertices[v].position; + memcpy(dst, src, sizeof(*src)); +} + +void get_normal(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[3] = input.faces[f].vertices[v].normal; + memcpy(dst, src, sizeof(*src)); +} + +void get_tex_coord(const SMikkTSpaceContext *x, float *dst, int f, int v) { + float (*src)[2] = input.faces[f].vertices[v].tex_coord; + memcpy(dst, src, sizeof(*src)); +} + +void set_tspace_basic( + const SMikkTSpaceContext *x, + const float *t, + float s, + int f, + int v +) { + // The index of the last output (vertex, tangent) pair. + static int i = 0; + + struct vertex *in = &input.faces[f].vertices[v]; + + output.vertices[i].position = in->position; + output.vertices[i].normal = in->normal; + output.vertices[i].tex_coord = in->tex_coord; + memcpy(output.tangents[i].v, t, 3 * sizeof(float)); + output.tangents[i].s = s; + + ++i; +} + +void set_tspace( + const SMikkTSpaceContext *x, + const float *t, + const float *b, + float mag_s, + float mag_t, + tbool op, + int f, + int v +) { + assert(!"unreachable"); +} + +int main() { + input.nr_vertices = 406; + input.nr_faces = 682; + output.nr_vertices = 3 * input.nr_faces; + + input.positions = calloc(input.nr_vertices, sizeof(*input.positions)); + input.normals = calloc(input.nr_vertices, sizeof(*input.normals)); + input.tex_coords = calloc(input.nr_vertices, sizeof(*input.tex_coords)); + input.faces = calloc(input.nr_faces, sizeof(*input.faces)); + output.vertices = calloc(output.nr_vertices, sizeof(*output.vertices)); + output.tangents = calloc(output.nr_vertices, sizeof(*output.tangents)); + + { + FILE *fi = fopen("Avocado.obj", "rb"); + assert(fi); + char buffer[1024]; + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "v %f %f %f", + &input.positions[i][0], + &input.positions[i][1], + &input.positions[i][2] + ); + } + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "vn %f %f %f", + &input.normals[i][0], + &input.normals[i][1], + &input.normals[i][2] + ); + } + + for (size_t i = 0; i < input.nr_vertices; ++i) { + fgets(buffer, sizeof(buffer), fi); + sscanf( + buffer, + "vt %f %f", + &input.tex_coords[i][0], + &input.tex_coords[i][1] + ); + } + + for (size_t i = 0; i < input.nr_faces; ++i) { + fgets(buffer, sizeof(buffer), fi); + int v[3]; + sscanf( + buffer, + "f %d/%d/%d %d/%d/%d %d/%d/%d", + &v[0], &v[0], &v[0], + &v[1], &v[1], &v[1], + &v[2], &v[2], &v[2] + ); + for (size_t j = 0; j < 3; ++j) { + input.faces[i].vertices[j].position = &input.positions[v[j] - 1]; + input.faces[i].vertices[j].normal = &input.normals[v[j] - 1]; + input.faces[i].vertices[j].tex_coord = &input.tex_coords[v[j] - 1]; + } + } + + fclose(fi); + } + + SMikkTSpaceInterface interface = { + .m_getNumFaces = get_num_faces, + .m_getNumVerticesOfFace = get_num_vertices_of_face, + .m_getPosition = get_position, + .m_getNormal = get_normal, + .m_getTexCoord = get_tex_coord, + .m_setTSpaceBasic = set_tspace_basic, + .m_setTSpace = NULL, + }; + SMikkTSpaceContext context = { + .m_pInterface = &interface, + .m_pUserData = NULL, + }; + + genTangSpaceDefault(&context); + + for (size_t i = 0; i < output.nr_vertices; ++i) { + printf("v "); + print_vec3(output.vertices[i].position); + printf("\n"); + } + + for (size_t i = 0; i < output.nr_vertices; ++i) { + printf("vn "); + print_vec3(output.vertices[i].normal); + printf("\n"); + } + + int k = 1; + for (size_t i = 0; i < output.nr_vertices / 3; ++i) { + int v0 = k++; + int v1 = k++; + int v2 = k++; + printf("f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2); + } + + free(input.positions); + free(input.normals); + free(input.tex_coords); + free(input.faces); + free(output.vertices); + free(output.tangents); + + return 0; +} + diff --git a/test-data/libmikktspace.a b/test-data/libmikktspace.a new file mode 100644 index 0000000000000000000000000000000000000000..01df84eade6ff3622b01a389c92403b3e6a73055 GIT binary patch literal 48966 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 H;r#zEDFCW8 literal 0 HcmV?d00001 diff --git a/test-data/mikktspace.h b/test-data/mikktspace.h new file mode 100644 index 0000000000000..52c44a713c60e --- /dev/null +++ b/test-data/mikktspace.h @@ -0,0 +1,145 @@ +/** \file mikktspace/mikktspace.h + * \ingroup mikktspace + */ +/** + * Copyright (C) 2011 by Morten S. Mikkelsen + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef __MIKKTSPACE_H__ +#define __MIKKTSPACE_H__ + + +#ifdef __cplusplus +extern "C" { +#endif + +/* Author: Morten S. Mikkelsen + * Version: 1.0 + * + * The files mikktspace.h and mikktspace.c are designed to be + * stand-alone files and it is important that they are kept this way. + * Not having dependencies on structures/classes/libraries specific + * to the program, in which they are used, allows them to be copied + * and used as is into any tool, program or plugin. + * The code is designed to consistently generate the same + * tangent spaces, for a given mesh, in any tool in which it is used. + * This is done by performing an internal welding step and subsequently an order-independent evaluation + * of tangent space for meshes consisting of triangles and quads. + * This means faces can be received in any order and the same is true for + * the order of vertices of each face. The generated result will not be affected + * by such reordering. Additionally, whether degenerate (vertices or texture coordinates) + * primitives are present or not will not affect the generated results either. + * Once tangent space calculation is done the vertices of degenerate primitives will simply + * inherit tangent space from neighboring non degenerate primitives. + * The analysis behind this implementation can be found in my master's thesis + * which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf + * Note that though the tangent spaces at the vertices are generated in an order-independent way, + * by this implementation, the interpolated tangent space is still affected by which diagonal is + * chosen to split each quad. A sensible solution is to have your tools pipeline always + * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. + * If these have the same length then compare the diagonals defined by the texture coordinates. + * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin + * and also quad triangulator plugin. + */ + + +typedef int tbool; +typedef struct SMikkTSpaceContext SMikkTSpaceContext; + +typedef struct { + // Returns the number of faces (triangles/quads) on the mesh to be processed. + int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); + + // Returns the number of vertices on face number iFace + // iFace is a number in the range {0, 1, ..., getNumFaces()-1} + int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); + + // returns the position/normal/texcoord of the referenced face of vertex number iVert. + // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. + void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); + void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); + void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); + + // either (or both) of the two setTSpace callbacks can be set. + // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. + + // This function is used to return the tangent and fSign to the application. + // fvTangent is a unit length vector. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); + + // This function is used to return tangent space results to the application. + // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their + // true magnitudes which can be used for relief mapping effects. + // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. + // However, both are perpendicular to the vertex normal. + // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. + // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); + // bitangent = fSign * cross(vN, tangent); + // Note that the results are returned unindexed. It is possible to generate a new index list + // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. + // DO NOT! use an already existing index list. + void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, + const tbool bIsOrientationPreserving, const int iFace, const int iVert); +} SMikkTSpaceInterface; + +struct SMikkTSpaceContext +{ + SMikkTSpaceInterface * m_pInterface; // initialized with callback functions + void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) +}; + +// these are both thread safe! +tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) +tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); + + +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. + +#ifdef __cplusplus +} +#endif + +#endif From 4f7c63a94523a5955da15c5d9f6201da150eef12 Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 30 Aug 2017 17:36:25 +0100 Subject: [PATCH 11/87] Add generation example Former-commit-id: fb3b6b7f4a340a1e1afe156697cc60d7d7b34117 --- Cargo.toml | 4 +- examples/generate.rs | 203 +- mikktspace-sys/Cargo.toml | 10 - mikktspace-sys/build.rs | 9 - mikktspace-sys/libmikktspace/CMakeLists.txt | 11 - mikktspace-sys/libmikktspace/mikktspace.c | 1890 ------------------- mikktspace-sys/libmikktspace/mikktspace.h | 145 -- mikktspace-sys/src/ffi.rs | 136 -- mikktspace-sys/src/lib.rs | 129 -- src/lib.rs | 19 +- 10 files changed, 180 insertions(+), 2376 deletions(-) delete mode 100644 mikktspace-sys/Cargo.toml delete mode 100644 mikktspace-sys/build.rs delete mode 100644 mikktspace-sys/libmikktspace/CMakeLists.txt delete mode 100644 mikktspace-sys/libmikktspace/mikktspace.c delete mode 100644 mikktspace-sys/libmikktspace/mikktspace.h delete mode 100644 mikktspace-sys/src/ffi.rs delete mode 100644 mikktspace-sys/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 16924db39035f..6993f9085cda1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mikktspace-sys" +name = "mikktspace" version = "0.1.0" authors = ["alteous "] build = "build.rs" @@ -8,7 +8,7 @@ build = "build.rs" cmake = "0.1" [dependencies] -gltf = "0.7" +cgmath = "0.15" [[example]] name = "generate" \ No newline at end of file diff --git a/examples/generate.rs b/examples/generate.rs index ed4444a406b63..9c71d5b40e7b6 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -1,45 +1,172 @@ +extern crate cgmath; +extern crate mikktspace; -extern crate gltf; +use cgmath::prelude::*; -use std::io::Write; +pub type Face = [u32; 3]; +pub type Vec2 = [f32; 2]; +pub type Vec3 = [f32; 3]; +pub type Vec4 = [f32; 4]; + +#[derive(Debug)] +struct Vertex { + position: Vec3, + normal: Vec3, + tex_coord: Vec2, +} + +#[derive(Debug)] +struct NewVertex { + position: Vec3, + normal: Vec3, + tex_coord: Vec2, + tangent: Vec4, +} + +fn make_cube() -> (Vec, Vec) { + struct ControlPoint { + uv: Vec2, + dir: Vec3, + } + let mut faces = Vec::new(); + let mut ctl_pts = Vec::new(); + let mut vertices = Vec::new(); + + // +x plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [1.0, 0.0, 0.0] }); + } + + // -x plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [-1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [-1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [-1.0, 0.0, 0.0] }); + } + + // +y plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.5], dir: [0.0, 1.0, 0.0] }); + } + + // -y plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.5], dir: [0.0, -1.0, 0.0] }); + } + + // +z plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, -1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, 1.0] }); + ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [0.0, 0.0, 1.0] }); + } + + // -z plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, -1.0] }); + ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [0.0, 0.0, -1.0] }); + } + + for pt in ctl_pts { + let p: cgmath::Vector3 = pt.dir.into(); + let n: cgmath::Vector3 = p.normalize(); + let t: cgmath::Vector2 = pt.uv.into(); + vertices.push(Vertex { + position: (p / 2.0).into(), + normal: n.into(), + tex_coord: t.into(), + }); + } + + (faces, vertices) +} fn main() { - let path = "test-data/Avocado.gltf"; - let gltf = gltf::Import::from_path(path).sync().unwrap(); - let mesh = gltf.meshes().nth(0).unwrap(); - let primitive = mesh.primitives().nth(0).unwrap(); - let positions: Vec<[f32; 3]> = primitive.positions().unwrap().collect(); - let normals: Vec<[f32; 3]> = primitive.normals().unwrap().collect(); - let mut tex_coords: Vec<[f32; 2]> = vec![]; - let mut indices: Vec = vec![]; - match primitive.tex_coords(0).unwrap() { - gltf::mesh::TexCoords::F32(iter) => tex_coords.extend(iter), - _ => unreachable!(), - } - match primitive.indices().unwrap() { - gltf::mesh::Indices::U16(iter) => indices.extend(iter), - _ => unreachable!(), - } - - let file = std::fs::File::create("Avocado.obj").unwrap(); - let mut writer = std::io::BufWriter::new(file); - for position in &positions { - writeln!(writer, "v {} {} {}", position[0], position[1], position[2]); - } - for normal in &normals { - writeln!(writer, "vn {} {} {}", normal[0], normal[1], normal[2]); - } - for tex_coord in &tex_coords { - writeln!(writer, "vt {} {}", tex_coord[0], tex_coord[1]); - } - let mut i = indices.iter(); - while let (Some(v0), Some(v1), Some(v2)) = (i.next(), i.next(), i.next()) { - writeln!( - writer, - "f {}/{}/{} {}/{}/{} {}/{}/{}", - 1 + v0, 1 + v0, 1 + v0, - 1 + v1, 1 + v1, 1 + v1, - 1 + v2, 1 + v2, 1 + v2, + let (faces, vertices) = make_cube(); + //println!("{:#?}", faces); + //println!("{:#?}", vertices); + + let vertex = |face, vert| { + let vs: &[u32; 3] = &faces[face % faces.len()]; + println!("reading {}, {}", face, vert); + &vertices[vs[vert] as usize % vertices.len()] + }; + let vertices_per_face = || 3; + let face_count = || faces.len(); + let position = |face, vert| &vertex(face, vert).position; + let normal = |face, vert| &vertex(face, vert).normal; + let tex_coord = |face, vert| &vertex(face, vert).tex_coord; + + let mut new_vertices = Vec::new(); + + { + let mut set_tangent = |face, vert, tangent| { + println!("setting {}, {}", face, vert); + new_vertices.push(NewVertex { + position: *position(face, vert), + normal: *normal(face, vert), + tex_coord: *tex_coord(face, vert), + tangent: tangent, + }); + }; + let ret = mikktspace::generate( + &vertices_per_face, + &face_count, + &position, + &normal, + &tex_coord, + &mut set_tangent, ); + assert_eq!(true, ret); } + println!("{:#?}", new_vertices); } diff --git a/mikktspace-sys/Cargo.toml b/mikktspace-sys/Cargo.toml deleted file mode 100644 index 8b54f66d5680f..0000000000000 --- a/mikktspace-sys/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "mikktspace-sys" -version = "0.1.0" -authors = ["alteous "] -build = "build.rs" - -[build-dependencies] -cmake = "0.1" - -[dependencies] diff --git a/mikktspace-sys/build.rs b/mikktspace-sys/build.rs deleted file mode 100644 index 155a9ed067b08..0000000000000 --- a/mikktspace-sys/build.rs +++ /dev/null @@ -1,9 +0,0 @@ - -extern crate cmake; - -fn main() { - let dst = cmake::build("libmikktspace"); - println!("cargo:rustc-link-search=native={}", dst.display()); - println!("cargo:rustc-link-lib=static=mikktspace"); -} - diff --git a/mikktspace-sys/libmikktspace/CMakeLists.txt b/mikktspace-sys/libmikktspace/CMakeLists.txt deleted file mode 100644 index 376409d8c8580..0000000000000 --- a/mikktspace-sys/libmikktspace/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -project(mikktspace) -set(PROJECT_VERSION_MAJOR "1") -set(PROJECT_VERSION_MINOR "0") -set(PROJECT_VERSION_PATCH "0") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") -set(SOURCES mikktspace.h mikktspace.c) -add_library(mikktspace STATIC ${SOURCES}) -install(TARGETS mikktspace ARCHIVE DESTINATION ".") diff --git a/mikktspace-sys/libmikktspace/mikktspace.c b/mikktspace-sys/libmikktspace/mikktspace.c deleted file mode 100644 index 62aa2da251740..0000000000000 --- a/mikktspace-sys/libmikktspace/mikktspace.c +++ /dev/null @@ -1,1890 +0,0 @@ -/** \file mikktspace/mikktspace.c - * \ingroup mikktspace - */ -/** - * Copyright (C) 2011 by Morten S. Mikkelsen - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#include -#include -#include -#include -#include -#include - -#include "mikktspace.h" - -#define TFALSE 0 -#define TTRUE 1 - -#ifndef M_PI -#define M_PI 3.1415926535897932384626433832795 -#endif - -#define INTERNAL_RND_SORT_SEED 39871946 - -// internal structure -typedef struct { - float x, y, z; -} SVec3; - -static tbool veq( const SVec3 v1, const SVec3 v2 ) -{ - return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); -} - -static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x + v2.x; - vRes.y = v1.y + v2.y; - vRes.z = v1.z + v2.z; - - return vRes; -} - - -static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x - v2.x; - vRes.y = v1.y - v2.y; - vRes.z = v1.z - v2.z; - - return vRes; -} - -static SVec3 vscale(const float fS, const SVec3 v) -{ - SVec3 vRes; - - vRes.x = fS * v.x; - vRes.y = fS * v.y; - vRes.z = fS * v.z; - - return vRes; -} - -static float LengthSquared( const SVec3 v ) -{ - return v.x*v.x + v.y*v.y + v.z*v.z; -} - -static float Length( const SVec3 v ) -{ - return sqrtf(LengthSquared(v)); -} - -static SVec3 Normalize( const SVec3 v ) -{ - return vscale(1 / Length(v), v); -} - -static float vdot( const SVec3 v1, const SVec3 v2) -{ - return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; -} - - -static tbool NotZero(const float fX) -{ - // could possibly use FLT_EPSILON instead - return fabsf(fX) > FLT_MIN; -} - -static tbool VNotZero(const SVec3 v) -{ - // might change this to an epsilon based test - return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); -} - - - -typedef struct { - int iNrFaces; - int * pTriMembers; -} SSubGroup; - -typedef struct { - int iNrFaces; - int * pFaceIndices; - int iVertexRepresentitive; - tbool bOrientPreservering; -} SGroup; - -// -#define MARK_DEGENERATE 1 -#define QUAD_ONE_DEGEN_TRI 2 -#define GROUP_WITH_ANY 4 -#define ORIENT_PRESERVING 8 - - - -typedef struct { - int FaceNeighbors[3]; - SGroup * AssignedGroup[3]; - - // normalized first order face derivatives - SVec3 vOs, vOt; - float fMagS, fMagT; // original magnitudes - - // determines if the current and the next triangle are a quad. - int iOrgFaceNumber; - int iFlag, iTSpacesOffs; - unsigned char vert_num[4]; -} STriInfo; - -typedef struct { - SVec3 vOs; - float fMagS; - SVec3 vOt; - float fMagT; - int iCounter; // this is to average back into quads. - tbool bOrient; -} STSpace; - -static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, - const SMikkTSpaceContext * pContext); - -static int MakeIndex(const int iFace, const int iVert) -{ - assert(iVert>=0 && iVert<4 && iFace>=0); - return (iFace<<2) | (iVert&0x3); -} - -static void IndexToData(int * piFace, int * piVert, const int iIndexIn) -{ - piVert[0] = iIndexIn&0x3; - piFace[0] = iIndexIn>>2; -} - -static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) -{ - STSpace ts_res; - - // this if is important. Due to floating point precision - // averaging when ts0==ts1 will cause a slight difference - // which results in tangent space splits later on - if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && - veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) - { - ts_res.fMagS = pTS0->fMagS; - ts_res.fMagT = pTS0->fMagT; - ts_res.vOs = pTS0->vOs; - ts_res.vOt = pTS0->vOt; - } - else - { - ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); - ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); - ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); - ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); - if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); - if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); - } - - return ts_res; -} - - - -static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); - - -// degen triangles -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); - - -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) -{ - return genTangSpace(pContext, 180.0f); -} - -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) -{ - // count nr_triangles - int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; - STriInfo * pTriInfos = NULL; - SGroup * pGroups = NULL; - STSpace * psTspace = NULL; - int iNrTrianglesIn = 0, f=0, t=0, i=0; - int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; - int iNrActiveGroups = 0, index = 0; - const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); - tbool bRes = TFALSE; - const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); - - // verify all call-backs have been set - if ( pContext->m_pInterface->m_getNumFaces==NULL || - pContext->m_pInterface->m_getNumVerticesOfFace==NULL || - pContext->m_pInterface->m_getPosition==NULL || - pContext->m_pInterface->m_getNormal==NULL || - pContext->m_pInterface->m_getTexCoord==NULL ) - return TFALSE; - - // count triangles on supported faces - for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts==3) ++iNrTrianglesIn; - else if (verts==4) iNrTrianglesIn += 2; - } - if (iNrTrianglesIn<=0) return TFALSE; - - // allocate memory for an index list - piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); - pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); - if (piTriListIn==NULL || pTriInfos==NULL) - { - if (piTriListIn!=NULL) free(piTriListIn); - if (pTriInfos!=NULL) free(pTriInfos); - return TFALSE; - } - - // make an initial triangle --> face index list - iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); - - // make a welded index list of identical positions and attributes (pos, norm, texc) - //printf("gen welded index list begin\n"); - GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); - //printf("gen welded index list end\n"); - - // Mark all degenerate triangles - iTotTris = iNrTrianglesIn; - iDegenTriangles = 0; - for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - - // I've decided to let degenerate triangles and group-with-anythings - // vary between left/right hand coordinate systems at the vertices. - // All healthy triangles on the other hand are built to always be either or. - - /*// force the coordinate system orientation to be uniform for every face. - // (this is already the case for good triangles but not for - // degenerate ones and those with bGroupWithAnything==true) - bool bOrient = psTspace[index].bOrient; - if (psTspace[index].iCounter == 0) // tspace was not derived from a group - { - // look for a space created in GenerateTSpaces() by iCounter>0 - bool bNotFound = true; - int i=1; - while (i 0) bNotFound=false; - else ++i; - } - if (!bNotFound) bOrient = psTspace[index+i].bOrient; - }*/ - - // set data - for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; - float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; - if (pContext->m_pInterface->m_setTSpace!=NULL) - pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); - if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) - pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); - - ++index; - } - } - - free(psTspace); - - - return TTRUE; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef struct { - float vert[3]; - int index; -} STmpVert; - -static const int g_iCells = 2048; - -#ifdef _MSC_VER - #define NOINLINE __declspec(noinline) -#else - #define NOINLINE __attribute__ ((noinline)) -#endif - -// it is IMPORTANT that this function is called to evaluate the hash since -// inlining could potentially reorder instructions and generate different -// results for the same effective input value fVal. -static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) -{ - const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); - const int iIndex = (int)fIndex; - return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); -} - -static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); -static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); -static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); - -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - - // Generate bounding box - int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; - STmpVert * pTmpVert = NULL; - int i=0, iChannel=0, k=0, e=0; - int iMaxCount=0; - SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; - float fMin, fMax; - for (i=1; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - - const SVec3 vP = GetPosition(pContext, index); - if (vMin.x > vP.x) vMin.x = vP.x; - else if (vMax.x < vP.x) vMax.x = vP.x; - if (vMin.y > vP.y) vMin.y = vP.y; - else if (vMax.y < vP.y) vMax.y = vP.y; - if (vMin.z > vP.z) vMin.z = vP.z; - else if (vMax.z < vP.z) vMax.z = vP.z; - } - - vDim = vsub(vMax,vMin); - iChannel = 0; - fMin = vMin.x; fMax=vMax.x; - if (vDim.y>vDim.x && vDim.y>vDim.z) - { - iChannel=1; - fMin = vMin.y, fMax=vMax.y; - } - else if (vDim.z>vDim.x) - { - iChannel=2; - fMin = vMin.z, fMax=vMax.z; - } - - // make allocations - piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); - piHashCount = (int *) malloc(sizeof(int)*g_iCells); - piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); - piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); - - if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) - { - if (piHashTable!=NULL) free(piHashTable); - if (piHashCount!=NULL) free(piHashCount); - if (piHashOffsets!=NULL) free(piHashOffsets); - if (piHashCount2!=NULL) free(piHashCount2); - GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); - return; - } - memset(piHashCount, 0, sizeof(int)*g_iCells); - memset(piHashCount2, 0, sizeof(int)*g_iCells); - - // count amount of elements in each cell unit - for (i=0; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); - const int iCell = FindGridCell(fMin, fMax, fVal); - ++piHashCount[iCell]; - } - - // evaluate start index of each cell. - piHashOffsets[0]=0; - for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; - else if (fvMax[c]dx && dy>dz) channel=1; - else if (dz>dx) channel=2; - - fSep = 0.5f*(fvMax[channel]+fvMin[channel]); - - // terminate recursion when the separation/average value - // is no longer strictly between fMin and fMax values. - if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) - { - // complete the weld - for (l=iL_in; l<=iR_in; l++) - { - int i = pTmpVert[l].index; - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const SVec3 vN = GetNormal(pContext, index); - const SVec3 vT = GetTexCoord(pContext, index); - - tbool bNotFound = TTRUE; - int l2=iL_in, i2rec=-1; - while (l20); // at least 2 entries - - // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] - while (iL < iR) - { - tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; - while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); - bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); - bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) - { - const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - pTriInfos[iDstTriIndex].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; - - if (verts==3) - { - unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; - pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; - piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); - piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); - piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); - ++iDstTriIndex; // next - } - else - { - { - pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; - } - - { - // need an order independent way to evaluate - // tspace on quads. This is done by splitting - // along the shortest diagonal. - const int i0 = MakeIndex(f, 0); - const int i1 = MakeIndex(f, 1); - const int i2 = MakeIndex(f, 2); - const int i3 = MakeIndex(f, 3); - const SVec3 T0 = GetTexCoord(pContext, i0); - const SVec3 T1 = GetTexCoord(pContext, i1); - const SVec3 T2 = GetTexCoord(pContext, i2); - const SVec3 T3 = GetTexCoord(pContext, i3); - const float distSQ_02 = LengthSquared(vsub(T2,T0)); - const float distSQ_13 = LengthSquared(vsub(T3,T1)); - tbool bQuadDiagIs_02; - if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); - res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; - return res; -} - -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float norm[3]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); - res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; - return res; -} - -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float texc[2]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); - res.x=texc[0]; res.y=texc[1]; res.z=1.0f; - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef union { - struct - { - int i0, i1, f; - }; - int array[3]; -} SEdge; - -static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); -static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); - -// returns the texture area times 2 -static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) -{ - const SVec3 t1 = GetTexCoord(pContext, indices[0]); - const SVec3 t2 = GetTexCoord(pContext, indices[1]); - const SVec3 t3 = GetTexCoord(pContext, indices[2]); - - const float t21x = t2.x-t1.x; - const float t21y = t2.y-t1.y; - const float t31x = t3.x-t1.x; - const float t31y = t3.y-t1.y; - - const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; - - return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; -} - -static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - int f=0, i=0, t=0; - // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. - - // generate neighbor info list - for (f=0; f0 ? ORIENT_PRESERVING : 0); - - if ( NotZero(fSignedAreaSTx2) ) - { - const float fAbsArea = fabsf(fSignedAreaSTx2); - const float fLenOs = Length(vOs); - const float fLenOt = Length(vOt); - const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; - if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); - if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); - - // evaluate magnitudes prior to normalization of vOs and vOt - pTriInfos[f].fMagS = fLenOs / fAbsArea; - pTriInfos[f].fMagT = fLenOt / fAbsArea; - - // if this is a good triangle - if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) - pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); - } - } - - // force otherwise healthy quads to a fixed orientation - while (t<(iNrTrianglesIn-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - - // bad triangles should already have been removed by - // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false - if ((bIsDeg_a||bIsDeg_b)==TFALSE) - { - const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - // if this happens the quad has extremely bad mapping!! - if (bOrientA!=bOrientB) - { - //printf("found quad with bad mapping\n"); - tbool bChooseOrientFirstTri = TFALSE; - if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; - else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) - bChooseOrientFirstTri = TTRUE; - - // force match - { - const int t0 = bChooseOrientFirstTri ? t : (t+1); - const int t1 = bChooseOrientFirstTri ? (t+1) : t; - pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first - pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit - } - } - } - t += 2; - } - else - ++t; - } - - // match up edge pairs - { - SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); - if (pEdges==NULL) - BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); - else - { - BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); - - free(pEdges); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); - -static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) -{ - const int iNrMaxGroups = iNrTrianglesIn*3; - int iNrActiveGroups = 0; - int iOffset = 0, f=0, i=0; - (void)iNrMaxGroups; /* quiet warnings in non debug mode */ - for (f=0; fiVertexRepresentitive = vert_index; - pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; - pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; - pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; - ++iNrActiveGroups; - - AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); - bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - neigh_indexL = pTriInfos[f].FaceNeighbors[i]; - neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexL, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - if (neigh_indexR>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexR, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - - // update offset - iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; - // since the groups are disjoint a triangle can never - // belong to more than 3 groups. Subsequently something - // is completely screwed if this assertion ever hits. - assert(iOffset <= iNrMaxGroups); - } - } - } - - return iNrActiveGroups; -} - -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) -{ - pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; - ++pGroup->iNrFaces; -} - -static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], - const int iMyTriIndex, SGroup * pGroup) -{ - STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; - - // track down vertex - const int iVertRep = pGroup->iVertexRepresentitive; - const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; - int i=-1; - if (pVerts[0]==iVertRep) i=0; - else if (pVerts[1]==iVertRep) i=1; - else if (pVerts[2]==iVertRep) i=2; - assert(i>=0 && i<3); - - // early out - if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; - else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; - if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) - { - // first to group with a group-with-anything triangle - // determines it's orientation. - // This is the only existing order dependency in the code!! - if ( pMyTriInfo->AssignedGroup[0] == NULL && - pMyTriInfo->AssignedGroup[1] == NULL && - pMyTriInfo->AssignedGroup[2] == NULL ) - { - pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); - pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); - } - } - { - const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - if (bOrient != pGroup->bOrientPreservering) return TFALSE; - } - - AddTriToGroup(pGroup, iMyTriIndex); - pMyTriInfo->AssignedGroup[i] = pGroup; - - { - const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; - const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); - if (neigh_indexR>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); - } - - - - return TTRUE; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); -static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); - -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, - const SMikkTSpaceContext * pContext) -{ - STSpace * pSubGroupTspace = NULL; - SSubGroup * pUniSubGroups = NULL; - int * pTmpMembers = NULL; - int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; - for (g=0; giNrFaces; i++) // triangles - { - const int f = pGroup->pFaceIndices[i]; // triangle number - int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; - SSubGroup tmp_group; - tbool bFound; - SVec3 n, vOs, vOt; - if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; - else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; - else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; - assert(index>=0 && index<3); - - iVertIndex = piTriListIn[f*3+index]; - assert(iVertIndex==pGroup->iVertexRepresentitive); - - // is normalized already - n = GetNormal(pContext, iVertIndex); - - // project - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - // original face number - iOF_1 = pTriInfos[f].iOrgFaceNumber; - - iMembers = 0; - for (j=0; jiNrFaces; j++) - { - const int t = pGroup->pFaceIndices[j]; // triangle number - const int iOF_2 = pTriInfos[t].iOrgFaceNumber; - - // project - SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); - SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); - if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); - if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); - - { - const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; - // make sure triangles which belong to the same quad are joined. - const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; - - const float fCosS = vdot(vOs,vOs2); - const float fCosT = vdot(vOt,vOt2); - - assert(f!=t || bSameOrgFace); // sanity check - if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) - pTmpMembers[iMembers++] = t; - } - } - - // sort pTmpMembers - tmp_group.iNrFaces = iMembers; - tmp_group.pTriMembers = pTmpMembers; - if (iMembers>1) - { - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - QuickSort(pTmpMembers, 0, iMembers-1, uSeed); - } - - // look for an existing match - bFound = TFALSE; - l=0; - while (liVertexRepresentitive); - ++iUniqueSubGroups; - } - - // output tspace - { - const int iOffs = pTriInfos[f].iTSpacesOffs; - const int iVert = pTriInfos[f].vert_num[index]; - STSpace * pTS_out = &psTspace[iOffs+iVert]; - assert(pTS_out->iCounter<2); - assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); - if (pTS_out->iCounter==1) - { - *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); - pTS_out->iCounter = 2; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - else - { - assert(pTS_out->iCounter==0); - *pTS_out = pSubGroupTspace[l]; - pTS_out->iCounter = 1; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - } - } - - // clean up and offset iUniqueTspaces - for (s=0; s=0 && i<3); - - // project - index = piTriListIn[3*f+i]; - n = GetNormal(pContext, index); - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - i2 = piTriListIn[3*f + (i<2?(i+1):0)]; - i1 = piTriListIn[3*f + i]; - i0 = piTriListIn[3*f + (i>0?(i-1):2)]; - - p0 = GetPosition(pContext, i0); - p1 = GetPosition(pContext, i1); - p2 = GetPosition(pContext, i2); - v1 = vsub(p0,p1); - v2 = vsub(p2,p1); - - // project - v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); - v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); - - // weight contribution by the angle - // between the two edge vectors - fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); - fAngle = (float) acos(fCos); - fMagS = pTriInfos[f].fMagS; - fMagT = pTriInfos[f].fMagT; - - res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); - res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); - res.fMagS+=(fAngle*fMagS); - res.fMagT+=(fAngle*fMagT); - fAngleSum += fAngle; - } - } - - // normalize - if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); - if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); - if (fAngleSum>0) - { - res.fMagS /= fAngleSum; - res.fMagT /= fAngleSum; - } - - return res; -} - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) -{ - tbool bStillSame=TTRUE; - int i=0; - if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; - while (iiNrFaces && bStillSame) - { - bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; - if (bStillSame) ++i; - } - return bStillSame; -} - -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) -{ - int iL, iR, n, index, iMid, iTmp; - - // Random - unsigned int t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft; iR=iRight; - n = (iR-iL)+1; - assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL]; - - - do - { - while (pSortBuffer[iL] < iMid) - ++iL; - while (pSortBuffer[iR] > iMid) - --iR; - - if (iL <= iR) - { - iTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = iTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSort(pSortBuffer, iLeft, iR, uSeed); - if (iL < iRight) - QuickSort(pSortBuffer, iL, iRight, uSeed); -} - -///////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// - -static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); - -static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) -{ - // build array of edges - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - int iEntries=0, iCurStartIndex=-1, f=0, i=0; - for (f=0; f pSortBuffer[iRight].array[channel]) - { - sTmp = pSortBuffer[iLeft]; - pSortBuffer[iLeft] = pSortBuffer[iRight]; - pSortBuffer[iRight] = sTmp; - } - return; - } - - // Random - t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft, iR=iRight; - n = (iR-iL)+1; - assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL].array[channel]; - - do - { - while (pSortBuffer[iL].array[channel] < iMid) - ++iL; - while (pSortBuffer[iR].array[channel] > iMid) - --iR; - - if (iL <= iR) - { - sTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = sTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); - if (iL < iRight) - QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); -} - -// resolve ordering and edge number -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) -{ - *edgenum_out = -1; - - // test if first index is on the edge - if (indices[0]==i0_in || indices[0]==i1_in) - { - // test if second index is on the edge - if (indices[1]==i0_in || indices[1]==i1_in) - { - edgenum_out[0]=0; // first edge - i0_out[0]=indices[0]; - i1_out[0]=indices[1]; - } - else - { - edgenum_out[0]=2; // third edge - i0_out[0]=indices[2]; - i1_out[0]=indices[0]; - } - } - else - { - // only second and third index is on the edge - edgenum_out[0]=1; // second edge - i0_out[0]=indices[1]; - i1_out[0]=indices[2]; - } -} - - -///////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////// Degenerate triangles //////////////////////////////////// - -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) -{ - int iNextGoodTriangleSearchIndex=-1; - tbool bStillFindingGoodOnes; - - // locate quads with only one good triangle - int t=0; - while (t<(iTotTris-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - if ((bIsDeg_a^bIsDeg_b)!=0) - { - pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; - pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; - } - t += 2; - } - else - ++t; - } - - // reorder list so all degen triangles are moved to the back - // without reordering the good triangles - iNextGoodTriangleSearchIndex = 1; - t=0; - bStillFindingGoodOnes = TTRUE; - while (t (t+1)); - - // swap triangle t0 and t1 - if (!bJustADegenerate) - { - int i=0; - for (i=0; i<3; i++) - { - const int index = piTriList_out[t0*3+i]; - piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; - piTriList_out[t1*3+i] = index; - } - { - const STriInfo tri_info = pTriInfos[t0]; - pTriInfos[t0] = pTriInfos[t1]; - pTriInfos[t1] = tri_info; - } - } - else - bStillFindingGoodOnes = TFALSE; // this is not supposed to happen - } - - if (bStillFindingGoodOnes) ++t; - } - - assert(bStillFindingGoodOnes); // code will still work. - assert(iNrTrianglesIn == t); -} - -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) -{ - int t=0, i=0; - // deal with degenerate triangles - // punishment for degenerate triangles is O(N^2) - for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf - * Note that though the tangent spaces at the vertices are generated in an order-independent way, - * by this implementation, the interpolated tangent space is still affected by which diagonal is - * chosen to split each quad. A sensible solution is to have your tools pipeline always - * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. - * If these have the same length then compare the diagonals defined by the texture coordinates. - * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin - * and also quad triangulator plugin. - */ - - -typedef int tbool; -typedef struct SMikkTSpaceContext SMikkTSpaceContext; - -typedef struct { - // Returns the number of faces (triangles/quads) on the mesh to be processed. - int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); - - // Returns the number of vertices on face number iFace - // iFace is a number in the range {0, 1, ..., getNumFaces()-1} - int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); - - // returns the position/normal/texcoord of the referenced face of vertex number iVert. - // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. - void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); - void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); - void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); - - // either (or both) of the two setTSpace callbacks can be set. - // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - // This function is used to return the tangent and fSign to the application. - // fvTangent is a unit length vector. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); - - // This function is used to return tangent space results to the application. - // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - // true magnitudes which can be used for relief mapping effects. - // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - // However, both are perpendicular to the vertex normal. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert); -} SMikkTSpaceInterface; - -struct SMikkTSpaceContext -{ - SMikkTSpaceInterface * m_pInterface; // initialized with callback functions - void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) -}; - -// these are both thread safe! -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); - - -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/mikktspace-sys/src/ffi.rs b/mikktspace-sys/src/ffi.rs deleted file mode 100644 index b4e8fe9e727bd..0000000000000 --- a/mikktspace-sys/src/ffi.rs +++ /dev/null @@ -1,136 +0,0 @@ - -#![allow(bad_style)] - -use std::os::raw::*; - -#[allow(dead_code)] -pub type tbool = c_int; - -#[allow(dead_code)] -const TFALSE: tbool = 0; - -#[allow(dead_code)] -const TTRUE: tbool = 1; - -#[repr(C)] -pub struct SMikkTSpaceInterface { - /// Returns the number of faces (triangles/quads) on the mesh to be processed. - pub m_getNumFaces: extern "C" fn(pContext: *const SMikkTSpaceContext) -> c_int, - - /// Returns the number of vertices on face number iFace - /// iFace is a number in the range {0, 1, ..., getNumFaces()-1} - pub m_getNumVerticesOfFace: extern "C" fn( - pContext: *const SMikkTSpaceContext, - iFace: c_int, - ) -> c_int, - - /// Returns the position of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getPosition: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvPosOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// Returns the normal of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getNormal: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvNormOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// Returns the texcoord of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getTexCoord: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvTexcOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// either (or both) of the two setTSpace callbacks can be set. - /// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - /// This function is used to return the tangent and fSign to the application. - /// fvTangent is a unit length vector. - /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - /// bitangent = fSign * cross(vN, tangent); - /// Note that the results are returned unindexed. It is possible to generate a new index list - /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - /// DO NOT! use an already existing index list. - pub m_setTSpaceBasic: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvTangent: *const c_float, - fSign: *const c_float, - iFace: c_int, - iVert: c_int, - ), - - /// This function is used to return tangent space results to the application. - /// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - /// true magnitudes which can be used for relief mapping effects. - /// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - /// However, both are perpendicular to the vertex normal. - /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - /// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - /// bitangent = fSign * cross(vN, tangent); - /// Note that the results are returned unindexed. It is possible to generate a new index list - /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - /// DO NOT! use an already existing index list. - pub m_setTSpace: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvTangent: *const c_float, - fvBiTangent: *const c_float, - fMagS: *const c_float, - fMagT: *const c_float, - bIsOrientationPreserving: tbool, - iFace: c_int, - iVert: c_int, - ), -} - -/// these are both thread safe! -/// Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -pub type genTangSpaceDefault = extern "system" fn( - pContext: *const SMikkTSpaceContext, -) -> tbool; - -pub type genTangSpace = extern "system" fn( - pContext: *const SMikkTSpaceContext, - fAngularThreshold: c_float, -) -> tbool; - -/// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -/// normal map sampler must use the exact inverse of the pixel shader transformation. -/// The most efficient transformation we can possibly do in the pixel shader is -/// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -/// pixel shader (fast transform out) -/// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -/// where vNt is the tangent space normal. The normal map sampler must likewise use the -/// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -/// sampler does (exact inverse of pixel shader): -/// float3 row0 = cross(vB, vN); -/// float3 row1 = cross(vN, vT); -/// float3 row2 = cross(vT, vB); -/// float fSign = dot(vT, row0)<0 ? -1 : 1; -/// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -/// where vNout is the sampled normal in some chosen 3D space. -/// -/// Should you choose to reconstruct the bitangent in the pixel shader instead -/// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -/// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -/// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -/// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -/// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -/// However, this must be used both by the sampler and your tools/rendering pipeline. - -#[repr(C)] -pub struct SMikkTSpaceContext { - /// initialized with callback functions - pub m_pInterface: *const SMikkTSpaceInterface, - /// pointer to client side mesh data etc. (passed as the first parameter with every interface call) - pub m_pUserData: *const c_void, -} diff --git a/mikktspace-sys/src/lib.rs b/mikktspace-sys/src/lib.rs deleted file mode 100644 index f0644a98e57de..0000000000000 --- a/mikktspace-sys/src/lib.rs +++ /dev/null @@ -1,129 +0,0 @@ - -mod ffi; - -use std::os::raw::*; -use std::mem; - -const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { - m_getNumFaces: faces, - m_getNumVerticesOfFace: vertices, - m_getPosition: position, - m_getNormal: normal, - m_getTexCoord: tex_coord, - m_setTSpaceBasic: set_tspace_basic, - m_setTSpace: set_tspace, -}; - -pub struct Context { - faces: i32, -} - -/// Returns the number of faces (triangles/quads) on the mesh to be processed. -#[no_mangle] -extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { - unsafe { - let m: *const Context = mem::transmute(pContext); - (*m).faces as c_int - } -} - -/// Returns the number of vertices on face number iFace -/// iFace is a number in the range {0, 1, ..., getNumFaces()-1} -#[no_mangle] -extern "C" fn vertices( - pContext: *const ffi::SMikkTSpaceContext, - iFace: c_int, -) -> c_int { - unsafe { - let _: *const Context = mem::transmute(pContext); - unimplemented!() - } -} - -/// Returns the position of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -#[no_mangle] -extern "C" fn position( - pContext: *const ffi::SMikkTSpaceContext, - fvPosOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let _: *const Context = mem::transmute(pContext); - } -} - -/// Returns the normal of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -#[no_mangle] -extern "C" fn normal( - pContext: *const ffi::SMikkTSpaceContext, - fvPosOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let _: *const Context = mem::transmute(pContext); - } -} - -/// Returns the texcoord of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -#[no_mangle] -extern "C" fn tex_coord( - pContext: *const ffi::SMikkTSpaceContext, - fvTexcOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let _: *const Context = mem::transmute(pContext); - } -} - -/// Returns the tangent and its sign to the application. -#[no_mangle] -extern "C" fn set_tspace_basic( - pContext: *const ffi::SMikkTSpaceContext, - fvTangent: *const c_float, - fSign: *const c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let _: *const Context = mem::transmute(pContext); - } -} - -/// Returns tangent space results to the application. -#[no_mangle] -extern "C" fn set_tspace( - pContext: *const ffi::SMikkTSpaceContext, - fvTangent: *const c_float, - fvBiTangent: *const c_float, - fMagS: *const c_float, - fMagT: *const c_float, - bIsOrientationPreserving: ffi::tbool, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let _: *const Context = mem::transmute(pContext); - } -} - -impl Context { - pub fn new() -> Self { - Context { - faces: 3, - } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} diff --git a/src/lib.rs b/src/lib.rs index 5f91648b90a03..ea550beeb57f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,9 @@ const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { m_setTSpace: set_tspace, }; +/// Rust front-end API for tangent generation. struct Closures<'a> { + /// Returns the number of vertices per face. pub vertices_per_face: &'a Fn() -> usize, /// Returns the number of faces. @@ -124,19 +126,23 @@ extern "C" fn set_tspace_basic( /// Returns tangent space results to the application. extern "C" fn set_tspace( - _pContext: *mut ffi::SMikkTSpaceContext, - _fvTangent: *const c_float, + pContext: *mut ffi::SMikkTSpaceContext, + fvTangent: *const c_float, _fvBiTangent: *const c_float, _fMagS: *const c_float, _fMagT: *const c_float, - _bIsOrientationPreserving: ffi::tbool, - _iFace: c_int, - _iVert: c_int, + bIsOrientationPreserving: ffi::tbool, + iFace: c_int, + iVert: c_int, ) { - unimplemented!() + const POSITIVE: f32 = 1.0; + const NEGATIVE: f32 = -1.0; + let fSign = if bIsOrientationPreserving != 0 { &POSITIVE } else { &NEGATIVE }; + set_tspace_basic(pContext, fvTangent, fSign, iFace, iVert); } impl<'a> Closures<'a> { + /// Generates tangents. pub fn generate(mut self) -> bool { let ctx = ffi::SMikkTSpaceContext { m_pInterface: &INTERFACE, @@ -148,6 +154,7 @@ impl<'a> Closures<'a> { } } +/// Generates tangents. pub fn generate<'a>( vertices_per_face: &'a Fn() -> usize, face_count: &'a Fn() -> usize, From 94705b0ac1aefbd73e1a776398a5fe82e387ede3 Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 30 Aug 2017 20:02:20 +0100 Subject: [PATCH 12/87] Fixed incorrect foreign function signature Former-commit-id: 909221683bc0bf7a15458b710e98c4e1565b71fb --- examples/generate.rs | 5 ----- src/ffi.rs | 6 +++--- src/lib.rs | 12 +++++------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index 9c71d5b40e7b6..82e87f5e6f62e 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -132,12 +132,9 @@ fn make_cube() -> (Vec, Vec) { fn main() { let (faces, vertices) = make_cube(); - //println!("{:#?}", faces); - //println!("{:#?}", vertices); let vertex = |face, vert| { let vs: &[u32; 3] = &faces[face % faces.len()]; - println!("reading {}, {}", face, vert); &vertices[vs[vert] as usize % vertices.len()] }; let vertices_per_face = || 3; @@ -147,10 +144,8 @@ fn main() { let tex_coord = |face, vert| &vertex(face, vert).tex_coord; let mut new_vertices = Vec::new(); - { let mut set_tangent = |face, vert, tangent| { - println!("setting {}, {}", face, vert); new_vertices.push(NewVertex { position: *position(face, vert), normal: *normal(face, vert), diff --git a/src/ffi.rs b/src/ffi.rs index 05e700bf30192..385fd822cfa2a 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -60,7 +60,7 @@ pub struct SMikkTSpaceInterface { pub m_setTSpaceBasic: extern "C" fn( pContext: *mut SMikkTSpaceContext, fvTangent: *const c_float, - fSign: *const c_float, + fSign: c_float, iFace: c_int, iVert: c_int, ), @@ -80,8 +80,8 @@ pub struct SMikkTSpaceInterface { pContext: *mut SMikkTSpaceContext, fvTangent: *const c_float, fvBiTangent: *const c_float, - fMagS: *const c_float, - fMagT: *const c_float, + fMagS: c_float, + fMagT: c_float, bIsOrientationPreserving: tbool, iFace: c_int, iVert: c_int, diff --git a/src/lib.rs b/src/lib.rs index ea550beeb57f8..c9e41e3d737f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ extern "C" fn tex_coord( extern "C" fn set_tspace_basic( pContext: *mut ffi::SMikkTSpaceContext, fvTangent: *const c_float, - fSign: *const c_float, + fSign: c_float, iFace: c_int, iVert: c_int, ) { @@ -119,7 +119,7 @@ extern "C" fn set_tspace_basic( let mut tangent: [f32; 4] = mem::uninitialized(); let dst: *mut c_float = tangent.as_mut_ptr(); ptr::copy_nonoverlapping::(fvTangent, dst, 3); - tangent[3] = *fSign; + tangent[3] = fSign; ((*x).set_tangent)(iFace as usize, iVert as usize, tangent); } } @@ -129,15 +129,13 @@ extern "C" fn set_tspace( pContext: *mut ffi::SMikkTSpaceContext, fvTangent: *const c_float, _fvBiTangent: *const c_float, - _fMagS: *const c_float, - _fMagT: *const c_float, + _fMagS: c_float, + _fMagT: c_float, bIsOrientationPreserving: ffi::tbool, iFace: c_int, iVert: c_int, ) { - const POSITIVE: f32 = 1.0; - const NEGATIVE: f32 = -1.0; - let fSign = if bIsOrientationPreserving != 0 { &POSITIVE } else { &NEGATIVE }; + let fSign = if bIsOrientationPreserving != 0 { 1.0 } else { -1.0 }; set_tspace_basic(pContext, fvTangent, fSign, iFace, iVert); } From 99918f6aad6bb74ff8a09abb59729c5c77375fdd Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 30 Aug 2017 20:41:10 +0100 Subject: [PATCH 13/87] Remove debug moduli Former-commit-id: ba8b4c7769097e5b50fc3e12a85fc71d06b5a2ae --- examples/generate.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index 82e87f5e6f62e..a01735d72d329 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -134,8 +134,8 @@ fn main() { let (faces, vertices) = make_cube(); let vertex = |face, vert| { - let vs: &[u32; 3] = &faces[face % faces.len()]; - &vertices[vs[vert] as usize % vertices.len()] + let vs: &[u32; 3] = &faces[face]; + &vertices[vs[vert] as usize] }; let vertices_per_face = || 3; let face_count = || faces.len(); From 3d19ed611130ff6c8db16f3602b209e824f8843c Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 30 Aug 2017 22:07:47 +0100 Subject: [PATCH 14/87] Make @bwasty the author Former-commit-id: 89f4dc6126d72e931954bdedb319ee44376656cc --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6993f9085cda1..453dd0bc557f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mikktspace" version = "0.1.0" -authors = ["alteous "] +authors = ["Benjamin Wasty "] build = "build.rs" [build-dependencies] @@ -11,4 +11,4 @@ cmake = "0.1" cgmath = "0.15" [[example]] -name = "generate" \ No newline at end of file +name = "generate" From 5a51803e6619a0b0b64fc07c7ecd77beb9c9fb85 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Wed, 30 Aug 2017 23:50:39 +0200 Subject: [PATCH 15/87] add travis config + readme Former-commit-id: a9a4929c42f353430eaf80f5c49526a4b7111b4b --- .travis.yml | 12 ++++++++++++ README.md | 5 +++++ 2 files changed, 17 insertions(+) create mode 100644 .travis.yml create mode 100644 README.md diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000000..058887cc14ea9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +dist: trusty +language: rust +rust: + - stable + - beta + - nightly +matrix: + allow_failures: + - rust: nightly +os: + - linux + - osx diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..aaf116a48c3c2 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# mikktspace-rs +[![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) +[![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/bwasty/mikktspace-rs) + +Rust MikkTSpace bindings From 254366314f29717f02639ed836195cc18db7a09a Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 3 Sep 2017 10:37:25 +0100 Subject: [PATCH 16/87] Test the interface Former-commit-id: 1d6faf7c33382998c671c6ecc0d8d9f963660522 --- examples/generate.rs | 2 +- test-data/Avocado.bin | Bin 23580 -> 0 bytes test-data/Avocado.gltf | 185 -- test-data/Avocado.json.REMOVED.git-id | 1 - test-data/Avocado.obj | 1900 ----------------- test-data/AvocadoOut.obj.REMOVED.git-id | 1 - .../Avocado_baseColor.png.REMOVED.git-id | 1 - test-data/Avocado_normal.png.REMOVED.git-id | 1 - ...ocado_roughnessMetallic.png.REMOVED.git-id | 1 - test-data/compare-with-generate-example | 144 ++ test-data/cube.obj | 114 + test-data/gen | Bin 56872 -> 0 bytes test-data/gen2 | Bin 62688 -> 0 bytes test-data/generate-obj.rs | 45 - test-data/generate-test-data | Bin 57056 -> 0 bytes test-data/generate-test-data.c | 66 +- test-data/generate-test-obj.c | 250 --- test-data/libmikktspace.a | Bin 48966 -> 0 bytes 18 files changed, 275 insertions(+), 2436 deletions(-) delete mode 100644 test-data/Avocado.bin delete mode 100644 test-data/Avocado.gltf delete mode 100644 test-data/Avocado.json.REMOVED.git-id delete mode 100644 test-data/Avocado.obj delete mode 100644 test-data/AvocadoOut.obj.REMOVED.git-id delete mode 100644 test-data/Avocado_baseColor.png.REMOVED.git-id delete mode 100644 test-data/Avocado_normal.png.REMOVED.git-id delete mode 100644 test-data/Avocado_roughnessMetallic.png.REMOVED.git-id create mode 100644 test-data/compare-with-generate-example create mode 100644 test-data/cube.obj delete mode 100755 test-data/gen delete mode 100755 test-data/gen2 delete mode 100644 test-data/generate-obj.rs delete mode 100755 test-data/generate-test-data delete mode 100644 test-data/generate-test-obj.c delete mode 100644 test-data/libmikktspace.a diff --git a/examples/generate.rs b/examples/generate.rs index a01735d72d329..20cc0c0fd4d13 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -132,7 +132,6 @@ fn make_cube() -> (Vec, Vec) { fn main() { let (faces, vertices) = make_cube(); - let vertex = |face, vert| { let vs: &[u32; 3] = &faces[face]; &vertices[vs[vert] as usize] @@ -163,5 +162,6 @@ fn main() { ); assert_eq!(true, ret); } + println!("{:#?}", new_vertices); } diff --git a/test-data/Avocado.bin b/test-data/Avocado.bin deleted file mode 100644 index 79c28dec5fca03ae13abeda5b06f4c253ac96678..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23580 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⪻TYc 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 HsrmnZ(R`E+ diff --git a/test-data/generate-obj.rs b/test-data/generate-obj.rs deleted file mode 100644 index f226a2b9ef68b..0000000000000 --- a/test-data/generate-obj.rs +++ /dev/null @@ -1,45 +0,0 @@ - -extern crate gltf; - -use std::io::Write; - -fn main() { - let path = "test-data/Avocado.gltf"; - let gltf = gltf::Import::from_path(path).sync().unwrap(); - let mesh = gltf.meshes().nth(0).unwrap(); - let primitive = mesh.primitives().nth(0).unwrap(); - let positions: Vec<[f32; 3]> = primitive.positions().unwrap().collect(); - let normals: Vec<[f32; 3]> = primitive.normals().unwrap().collect(); - let mut tex_coords: Vec<[f32; 2]> = vec![]; - let mut indices: Vec = vec![]; - match primitive.tex_coords(0).unwrap() { - gltf::mesh::TexCoords::F32(iter) => tex_coords.extend(iter), - _ => unreachable!(), - } - match primitive.indices().unwrap() { - gltf::mesh::Indices::U16(iter) => indices.extend(iter), - _ => unreachable!(), - } - - let file = std::fs::File::create("Avocado.obj").unwrap(); - let mut writer = std::io::BufWriter::new(file); - for position in &positions { - writeln!(writer, "v {} {} {}", position[0], position[1], position[2]); - } - for normal in &normals { - writeln!(writer, "vn {} {} {}", normal[0], normal[1], normal[2]); - } - for tex_coord in &tex_coords { - writeln!(writer, "vt {} {}", tex_coord[0], tex_coord[1]); - } - let mut i = indices.iter(); - while let (Some(v0), Some(v1), Some(v2)) = (i.next(), i.next(), i.next()) { - writeln!( - writer, - "f {}/{}/{} {}/{}/{} {}/{}/{}", - v0, v0, v0, - v1, v1, v1, - v2, v2, v2, - ); - } -} diff --git a/test-data/generate-test-data b/test-data/generate-test-data deleted file mode 100755 index b383add654c23d9bd041048b6afa9a78aaf2c91a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57056 zcmeIbe|(%pwLkvk7X)a#TPRutWx=9BptPlh)=*5_ENrpj+KNEZoA<(H`dx#}~lNt>`(!wV1=;%|Pw0;Go*Xgm`d4jHn(Y@cI$ zRE`NP%O?PqBd_!KOA?_^A|^bd`It_|WBrZRdzX;Vc-ujFrM}<3eeI_*JZb3Qnnd`@^5tr-wvdN`S^KU|3y>(?%{IBK}_&qB31>T(n{{1ZQ zd$Pb^&I12v7B~f!iT@X};D0jO+5f#076 zel_4e{H1@60g#EG8v*yFBP0C|{w~e{x1jIqsoqyxHA|~1YU+b+!L^O;p#tQ?qt`OLI+osJ1OsQ)AWKxS?jb##_=<+uj~*SH&|HZojuJ)L_-twY0Og zI#pXkOKY$hbQ{}S>J}`hX>YHqZEipsRn*;6Q@8G(nuglOCICnsl}f5%Z7|es)zvmN zwbWVbgX`OaAqI8pTdjuHa7bWvt@m3EbxkepL2JE2Y`CwjF%$$wTVpdAsi~>kSX=` zLVWYj{Nv2d^lA7rxOo=;Sr+FLAO6DcX*`27$aw_flZSW%%&&=;hn=%z9wv@vWRV@F~;tzJZr%cyW(U@(tXi=RdFDoRd+N z{V8+6*-G;XINR0n z;LdrW&jCNjf#2_dbImfJBM$hv28eLL0e_bRo^-(9?SP+fz^6Ol!w&d)4tOAPyf_dq z`rUVYRv_9F${*HOBEwTQoC~ou1CL@kW7Wt&gW#`vky2=w>A7&5$Oz@nlk)SYiEv%((?L>7LHv1-r@O$x*p zK8;lJ+>w!yWB^~QUMlJaAn%>g{2cgSU^?;R~5 z55zVPBfl2;t6nsKSoq{#P8=E%IX(+$$v`YT5UV~Kt2h#fPcA`$xc_LNu0Ig#59IX( z^7;b@28sfSX?bz~kwBLu0E=6Jm>)G%;Lks>kHa<)`(Yr~n|#+_M@BkcR&{2o;iZ4g zt%gVslsIcGyZ@4^CA|&xIoIH|YhYWD@(%=J-j31odB-!=e>7HoBvx@4LMo67j${_n z;gpafJ<}Z(c)nZa-PYsTc961)i||~6+ka|gB-#^x5A)vfc*`bvX8Mmla2CJAMT5_x z)H|L#HXjuxAsnC(a6+dGbvntkjt|(RfOy0tQ1T9RpsEDL?&#AAlIw|AA4W&VUcw+~ z>cSk!2|xMUKaY&Wi;BPPvm!ltv7!-Z5%2+aDi9=F!HKBzL&tjx zEvr5dFTA9LXwb&lJ%Qe$PlLD5U^PW_HKI>r0mV3Y2+`-1lJ+L+K_d_=ssIdG4bH5k zD(k~4>z|lK0Z^}Js;nzj)+}aC(^==JtVt^CSG$q5T4xRaN$PqPFtp@}&w^HBVJCv* z6e$$l${@0zffc?GEnkXe?rUc8#5}*kznAzMNU1j(AR<14UnNSsQ_3fzCjfp9c8;+6 zF`~vNH?h)qCrda7bABMNFOb-X(KY$Tu`mLKf%v?VtQhFN7Xqr{rLkc!`@_#j?R&{x zvV<%|gxfp_Igex(NYeB0^_Zj3lFA%f^1uElEqVG|(h`=)(vt0CVMt3FvSN&CiO1QJ zyU9XCxMfSOA(;h|v|F-Lw`3@sr9<8xW;2T3`KC03=z#-o6>x4%R6@!{|1B8_mGLGr z`T`t*(yM{ktAW^WA>U}H#okr&?IXoVlqJa37fE1&Bs~w`r{ud@b2Yd`#z00*xrJmB z(E*??OOzD`65nAokVvSYQw53-S`~@?0*Uk%-!^`{(1BBUTSe@^Qmmhec`rTgXon^3 zI5-RXu+H$inZq|e`SbsRHpM$vlOgw7G#5>t{D@>rsrWW&LE3zrABZF;ap4NY1xqbm z=xogMlgq{?zTj^q&Q(h)`1vzgxX(Kfh@U$sxi03pv=)Hn;y~UDE{^*Gkshxi`pIlW z6C#rMXsT)*KQMqn&xM>anU+B&T&cizL^#UBe8kSk_k28z#T-!f0oCkmR+ za0dH#Dt6vPklb++g`ocvB`rpSTInaR{#y!b%V6^R|CYkql9>F3s z^NJ`8%yd}JtmJd=KpK%KB}v6(2*rGeim4044(#JL`8o6mRp6}-l-jFGv0tdTx09&_ z(!-vIFH&W*{_n9>f`~HJXZ zB(X<8D%!K28(JQ?*fX%0`c&0ec-COV$Fn#lJ_A)SH}9gH}|?k+MbdFlnkqp{y|K7^mf$et!6i}FK>SV zvJ?oBh$)Z*_Fly|idZ176a%_KHQC}%CO-kwAi3#;Q7w21${fYemry7alTO9PkWJp= z?@K;`V(2PVT;#!|^^WImEMkIHZLli(lFwmWIjE@?X(mipD}uQyc7@0S+&E+bVW-Ig zZF9Fm;(Kr_%A~+Wa4o@ir=i3Sg zw2DcpnE2Q{lUi{gR-H`ztmP zC|6EYmYKw^R1!N#zHXW)zc$cizQYyd=DO*KioSVFp zYtakPAel@I@qlT38ER2zA9kRz)(N!#%V>u~)B4Fc`2>mFg>ko=$N-6Otb!V5Ta4A+ zO4t{{EQ8QWGGv{B-FYTHKnu%4io_e@i4YR$Sude09VBcn`U3Ilelb$3!Am8SqIv)! zOl{5(mD3TPKS4jq|axJSjTB0#}qnF#US$1r$9it;nl_^Iz15CywwU{Z38@L8ncM~yCg#9 z(>04i0KEl}63KyLFqI&mrbpx7k%AiYq4!%>EJ~$`cPgxeQUXn0T%-AR%VJS#Rc~}Y zzLg%$(y_sFF#zIGCPz1)8$+ozuRo6JRs$&us`wS0xb@0PNm8do2TRzxn)TLXzn z`8naCYs;2J`to9nmPJHA@*<)Q7&KIhQqdgGj{m|mAD8%Lf)$ey6HidDj0(Cm^!f1E z8vOm4$QRv>_2>2EVYUj8lzgXYq71Izk%Nk<-4lvkZh07du|M(bLQFXtp;==oWiQ621 z6G_EZ0{hpjHBl)#p5xdjYhOg6Ug=*#1{<*KWl|P;h+zsdv65x@%Oq z6UoSaB3R+sMv=BE98p>jxmS|VKE#8FD@7{Dl2EP&yRGKarJ_!y?TROV?10wv=8aG* zt*Y;H*Wf|2H)?E^)G9yttQg5D%|%+Efa=3}r4$o|mZslQ)g2V)zk3!(9w< z6oxV?w#<%I+OaA-wo=E?H>}2e+%|`#L1H%D;jZQcs}Y%@LT|+h?5Q_kFDfpdss5g!^9beS?w71H z?JR*EWbR433>-sK{RdTER_ z43rAU^XQ*Ntx{GNdE(}?(}xnY&=@KUO+%s#lNTS)ux;#>wU}}w7ZiV#`v)dKcdD4T zOhEaRcXPu$J4q+zh7TwT>>g%7HYo~dxuP(174x9Bn#K%O2ve#Qf^brWAP!YXPXX>* z5MtEa@H6VGf4k_j9Q6x$mI8OAfzMOmt~7AI0(UDb=v=WiLQ@cQS+Ymzw!*C{&$f_m zJBoT~lgREw7Km~MLK$Lpam|>mK-~(|P0;%Q((_C_L9H}OtFCK2jhjq^ji=EcjWLe~ z*{ba@Px3)ybbhxzlHU3$7Zo!e2VaG3#~v!{z=ypfNDY-MFLI5@6X*^&zxK=U4xi3` zJTY{xO6RHc$)O^GskkyLB9Y+ih){2j?n25_@+#)+d(mLdNR>!a8;5R=JP2v&0~|hs#-`6Z1pWvr9b4cb6p458ThD@OOZVJ`e~-X7f*b{| z!|??EX`H0TqMOJ=uYWfs7FQ{!;JZcesf^8xSOK`7bncG*J<~Te-KR9*pDN8L))~H}-BE$|?cJu#1x>jAnW~ZMg%Jr_vF& zedw+XI$~NcT7nuzwLZkw|I__w{dCp(3pYsXyAdQ;9D!*zT2np?6_u&r9ONV3!NqDN zy1}8aH)LQ2634n$DyUm7%!;6BWvKU=xD=I1vXE2JS*#z&Jt-j!eoZvjG}o-En9NwU zYxw96+nb#a|5kW!@;s0H6KG0&~#nvsBEs&+?Hj_=hOyUJX8J);5W8-Qbh7# z9Fjz-f2(K5bVOkOCj#j1&RJ}3JiJTK`-@_I|3>{|hqO}Gz2JPS$p84=}ms;$E-(feZH>ZvP^wm#bteZ3~}4J>t#QwjT9_E zi)_X^he0@&IEnA_So8#fG=FceIDy%KUNQ4k>;e>KOm7CoXg2XO(6M|)B!uPcQ-nC>i=7unAse@nNb&kJ}>EzgtV>2}AVH!eL>y1cC`*PrYXuYGlWzqiJXt27uxuK@Wj~0jL5ii;n z5lKLE+0atyZqFl0^a96?*p{#gx8PB@}-*@!;kjMem1h zyxqjZ>O}sOGnT!*8YzSo9q4k;Cl90Hj6{Ec2(#;T_T!TM9h3cx&SpojbeCj*#$+dS zw(0?IMK`h^_!z#z8%Q*yOYJ1Mp5PjS*-s?4i{J`^14y!CycN3%TR_-c!nP5%hpwFZ8 zh)c%;um3_S;FVA-e72Dop`IP9@a=?bnyniZD$ymx)ZN+)Hb14ufn}x8sl;!YBAbw^bRvd^gM{SkG+Ce>Pkp@>hEQgUPXXVAFla>6OJbHHA z{TDJq5yFrt6>SJz;Y1Os-3Z0o_s^;se{GazoXcj+v71pUG{V@Y{*7I|j4Y^~c?jpd zqL!El49^@Ss*cT<$M>_2xEOVi4VNF-nj)gO3~}wX{>AT%kk$C!JTtzZ`f49IXD#^+ zlpL};V14oqoq8ewcj&JAfy3J5h7;E2Mz8{FvsD5)lZ+)e3-|iB0>RS3;6o4|9q5i- zo-B)cr_h02lq*lj%3AAxJYM}c79Q51Hrcm(T?>z1P|}e8+l{1GlQg6|r@AQhaLMWG z^>5>Xs@ znU3^1H_{0r4TF`Ke+RT5gkj2|7a<~b@VgRCp<9umaw}?xEVId=q4yHW8pcO)dl;v9qa^qz1xUpikk!LoqsE+Z7mfO!Ly?k(H2Y6LN{Pf{0Pdc{dW#*AXyfWaQP6{lZ0%rr%PPOTVR7 zu_EuE>{1VO9t|c@-4xQF+(_slT1#n2ADiN$gsxEft=hp=4UBhz*^$AApx@ASf(GxT zu2*k2E!Y9ij#ITAyld4?v9*7at43&`t=cK1>)gCU&1}_9AwBO#f<8gDGb(i@_@`1c zR7$NO;<`x|A09`k$0^_Unp^K%Ea!cgfQ`JP8QjY^BUOOA(1 zFWTjKTIbT7b^fOAb z?$c0nTS6V`M}=Ry8%Q~}81>~|ge{6KuGDpVqEk5}axIVNgL|rQsj3(vMXd~8lJ*J! zhQ>=4F43#(NKgJtR=$h0X$B5LUI3cNJ8=PU*bX@(fOqIu(CS!0mU8O0Qd&VOAPVjo zyCAHq^ctaYEch`g7!e`l4Nk)8hOQlRLuDSH*2s8OCtEklPJ2F(WqlQEoCXZl?mSTh}J=PJ&k}p^&udqMcPv74bY@>#S$4=Euwa)^} ziZgFQI2*U_yWDlF<;h_(xa&UOuKScU$s1(|KMz^i;OUJHAfz4)zJhN^Kr)a|G?R~P zYw&60XXx)doZ6*CAqFR<%n3C^f4^4b#Mb_p3_bk~H(mz?3^&QBK90EYx`_uLv)o8w zW5@<%gYxiSjNh*o_MNH5$N{jJg8eLy6K1z;pC`8Tt0mP`Zv`5xU5Dv7jvjSj1L>%5 zfb7$BL`3dFyX(8zuCEaF0Yx_O=qn~2n{{?+WIw-g`0$_Xwu#QcUTII1ftrH_V9&ui zmD3!|r6dVfcx2^RNmA3bW7;Jyr-(6d?U|OioFM3wAzduS4)SA7s%D{?t{TZeHBtc5 z$b5`SokAnCEf_I-p_ynS72__BodR(+k~(iUQbwt(TrMs3sg^QOEfs*>ojUdZq@@$- zPFG7O+{hBuNCwJM5P;pCI+b%H$6*})6}950^w7m|ZrvG=(m2Z#Q&1HzjqU+tiw~CT zNAH5=T8lL|keK(}N(KwxxgD14Z}(vr7LgU11`(HDF$ZBAiXA9-;Y%go^XMTsC3>Sr zfWt|A2%b;IbFcvWd&`!wNJI)?bg`V`ZqvTFa2$B(AdTER&67+Ax8hNXU9s@aw9~S9 z_0B+|;@Lm~YoxmGu@^oOOmn-G8S9N#Jj@-yL>R;`ZwDL6Um*+vt3+H|9{Tq84fOww?dYCW8pocWVr{j zJOo+7J0SP@n_dxF!u)^&l;IrpP`nziR=}Gr;+yv>S)QQWL`pp8mD8)DLw9hwS9cnD z2VsCO+^`oPe^QAP$BkR$exysBtM{hFd3-fH9glW(bKR5Im+Vp=r>ig$9g7?4SGD27 zQBR>0@k}?O^gLHVA{ILl--HS}C`z+%GA+s?*VWWxmd|Rfkw-!mNE8Y94lL0q$isov z$ATz*J0Cfer#o-APx<_ZczlcZpd8w8HZu?5tEd;_(R14Yb^Yg%I#cG}ZzF~U_I_Ob zvG?niptB1>Eb%zLdu5PwboI*cRk3H0qhg!_7#r+@C7wbV3?mtrvSSWlsh0E7UeF12 z^%g!10mJ4e*&GVuevlM|D>?E)exSFopS zMoO&*pOB5Nqq@|3C{~jU3Dkq*hG-WEs2SrsxSN$4-(r4Z(hy6`)yxzzWjto4w$@FJ zRt~Ar5Xef6mRDwK=eeoP($wZ>r#41Ir@J1b)E5Z!w%6I8xi$0^H*1xewUyafOYqsq zEDhb{rnXvBTPLAcyn~qf5@nKB*CGz02LY3-Kg=t4wlil3( zlL&9pXo-1?)-0Dr77D96(5oJ+GV<9XbdxW+c^M!X7!j%C96)4GJzmd#Y`?2f-6XWj zHDP4la_wScCpyc5Co$Bq&b*8Z4I571hlvv}!`R0q_l$nbz}gY(R))_(W;}Ab47&r0 ziVB6W==574swzH2uFU+D=R=p@qLk?-3QIf}vS&^?EZk@xw9y7oL9Q}T%f~=5ALS)e zpQ=QQiWysp7))UpX! z4J=OfoW*mtAlR>Era4+vQ!!Ymh+mqJg%~fxwMy6Q_Akez z4CF!NoqUIgx|}*eF5Co8f}x1qaIo;m{C+0!_=h~HrQgYe%A(weOhi7Y1LNN?IXrPu zxe1&S0whza{MXw~mz_}$xfRuuuFJI_K)evz2JbfsV6SEk@(kD2z?A9bu;b|Pqq!PT z_UfNba41a`RaS-JZqf>rOZk0L-uo2}1<%ZZHeB;G3`O=!+rnqrbn(!hC}>Q)zB)gY zDWC4(xHyH!S7uqTSYUiqJ&^t&i&AbyCPuv#i=8zQR*KI!A)}g|pU(?`j9_>_jsQ>j zl}g(4==)Dm9t7Hrl-C||(unmY|MWF>s6JSfCu*(mMe$C-k0-iWEG~|C?QrRth4pMs zTz2v>z!buFipjXa>rLMJA$S1vX(j4-AL{6Uzl@Fn+h3-9JaC=t#&s~P6cycg_b4w1 zsK?!6{=>cggH-Qcf1e5tsX#X5y?#jzPR0>PuU|le@9-9)|B&5GdEuVXME%sZJZFIY z9fyD?xxOLJ%mgM|zIaA{unRrJufd{dkJ2X*7pS|!iM;Njga_>9o2(+-SJ4(z!a6~~)( zEAa-S9K`1+Hwh1E=1_tHPtAyn@am=<3OTAN>{pL>O}revf#|u!B)qwkItfk7dxbXv zBmRT9@-R_0*yD20`&BLpxfFLd-G4NPLb!8*u8bExEPlyxS$rX~x!>ASj6xXoShc_z z9@~-P$0C~tEYJ4u0g^+#E{#CX;zndV%!A!-P87ew;N^WyJo)WZ+5;@r-+sI% z>u9O}xLy$bCnB4ZR``<0<`e3jkl!lDxLB&j#cO3UE*3AwxH$dGn8-FC`#TzN%-Mj; zr2!}7cvyX+4Z!_4G+@{?;8bMuNol|-{ZdN|=6zMF0cWcQJhTW6xc*DI4D3-O(aQ7p z;n`U%yS;+ae8$uB}Y{(jsI>W6Qt0FP2e{D-L1rT(WpJN^%xESN9! zlRkK38QwgOt{e`;>HWZsKR((eh9lJNWGr97cq;>$8DCx&tA3X62#fol9aDZinEBvQ zhXWo@_LX@>hfN8iE9#mGocMw|-0MGz^@H06uvO|G2;=h8QRu9B!!AZd|Hsa-ONApE;&CsHK_bJIeaGgkw-IM%gw^Vi_UX8b`D?cx%^-}+C zync!F2XDbQsz}Fx*`(i)G|M39wIozabR0qORZrn6o#0EJ!Z+xIyx!@EjBSdK6Uos;c(?}ya1hg* zd=*thHXpY$k2^E>AXB88Iw9Ca>BYpYiW7p}G&0o9k626XiKLdNTLfgBMx7mPDjUm9g2H+|YzxmxFz<7TlwR&~u!^2V9o%u(?c6QbH2)=Oz?6a5m;y%Vj?ej8<2=ATy5v_)jz~zzXm@q#S}6#7-PmV zl_6#2WkFH_GOj|#UP^T^;$;_%7X07`kdcGv&{jGlGv5?6eK&Xy?nQ$yaa-bvf_Nu$ zIH>t>x0qmZPeL66gB<8_lc!?bQmNQc#FQ=!*s)_erfyn{HRnY|gE5zJ_?HSZVQjCC zXaa-VKzc${rW-1F#D`QkCk-u{5DA|lfo&B@$n>~hSlWo4u7G5ctX+sn@>MlSRxf}w z6ASbbiGV`-kGsC+CViP(PgKIi{9jw;c`h?~o8D^U|Kw=vZB*%c&8Nb;9Y zE3jQoJwS~otQA&5mFvfgfRmM9cd}|8XPW?%6>u7R=`O7zISJch@dw zmF~v{Cs1)EpD`=DcEZx>LyIZ2T%sy_8H225XCV^jr`Qx%MbHkiEy zxetHwwGBhgz2Yu)Pi%uf=1_D_nVj<6Q|2i^;^J${rKhAi3$o6=h}QVOVSHW>0p+TJ zyT9U82Ypd>B24m4y7C^xoX6+0J6%<7rP6PoNR@x<$eal$LauV@ax>XOCi^DBBwzh4 z?|964WbVXXE;TY!cS|84Z^icX!7_l3!)WC1+hj&~!QBENLCnC$`I)+^>Kc2GrO0JQ zXm^tX5+6ayc|MH?;Jf3P$!)YOJEKqepR^adP=*f2YpJ@^`nd9L7XxmeCt}2|mg&sC zDXp8BZ6SLv+a#x4lN22`o%eaurYW<&WTW$A_MFi13ZI<6l^;5c$3)4a+613B56k@T z)iOVMwk-1}=3tp$^l6yC``Jb-e3>b5F?flRFWlel2Eh&262J-!0M<2)GHgg=os761 zCz=IBc^wRe&g-F^!mx7{(~Iv*VcPTPyRau#WbigIlLk_3nvRf~#?! zBDJ-`wCmIm;erd~PtP!p=L4~MQ(7a7IqRxch=b~1iMB!POBSu-7_ z)v-xre-gFx>0EXHQI#&0GxNq@}ng%AaCTjY@3>rhE!5Q!f>TxgS6~El>0lXicx2#;9iTjE1oxTt{MjbhBvZ z7IPPHJu4I>>}-gunB4Rd#8*No0_S4j*rM-Eo}-dxgHed2+Ah$2qEGN0Pv;61Ugl!& z@M3g-MvkyWmZv5HvBkx9qwN}QVz z64BMD9WSTskU&}e@kAF=aiyd9eV*Ic=r7__3qSaWVp1d!D?n@=S&uC#MzmE$nZHp* zrz5&mMST(B4I({3fQv=rK+#*$-9*4Hr>s)Fs3PYgkmwPX?ecuLJikiL(k@_22Chw&*#gi zw5po}ak=E9Pd%>c7P9RB33jGtrnk(e+$pLZmD4mlRykv^8w{p+#=6HR_g;a>_AO8i zeC{%7;D_13-zF$U&Inn$$8L@89`*S9jTo@_ZGiZVUWCxQ8>b_L(k=JF8SYCl$(<%r zqV`TEBT;Uwol`8+o=3Nypp|(TaTJs}gYhxujI+VNzP_z4cbGXtjmSLl7-Vplcy{#t zTKd3DEHC3BYjCRO^xot?m6XxB}Namu) zF*WRKP@`yLiHssI&L)%3d=gC(Uy*TcunZ}Nyc(GCP^QQy_? zv}$E;gXt&_k-6HC`GSV-8s}b!vm-2_#4u~JZ?a{(;rHRsVF=1@T$fXi*qPfU>go?J z)6rM4v&+S5EBchIZcOjnaafgid(&29L8sqhyKwHfjA(9}$^C%j&d<84w{-Qz6RbY9 zJDQlJyiepVsRvxFny2(~9%*oobI-!Tk?K9HsBsdYqJ7pnrj`nGXT)o7lhC_%8QoNJZ zy5@#o;`y1>jyLU5U^dEy&1Zrf*kzh_fL%W7aa~ojH^+Oxmp<+yU8za0%t%^Q?8R#f zscU8oXPbb2h`pVI>(!d{Iz?L5D`-tsxv7!C2*g1D_9Cks@niJy! z9yJ=>V`>=NBuA^<0!yRiE?EMm^=Ncg=|(FYvqzK^z4}pYy{P26BzzF0b6pZ1bG1`D zXd$+4iftFfra3W>W{)Gb1K7(s#O79pG+J(X{09A}#pbTkjrJcde$$+<@>?lj+=$IB z|M3+01WAjNV*n$QWOO+BOEYk(7klp1XMfl4WI_XOa^xR?cF_WvY?aVUfFHj7`0Nu!nb+yXp6KI6KsS&Odci z-Z<07lkNww^Gj)l?fi2+-jE|tKXa4nK?`vN?fB7|0W^|7q%|H-pTb7b!4rz%#yra& zrIgr3coI%zuHGw&Up-?kUb~;lrD#pAWb`P2Erct1oY*!C3_^jdhJ*FVfnK(GH4w-69PQl5bv;CrV- zH;S)l2tOvlryI(OO2w)AI2}*#qdO_|4?M$Lj2Yse&3H5zz)3jLJTix zVjPgX{zy+j%#Y~dnEx2MOfE?!FY_ORb%T2_yyhf7H8|aey~wxKUgY@svKQI>KI}z& zTQDLkPM{Wf?%@x=A@P&(YTi_G+bHnf%8|Bzn6J7zhPyt;oEsLMjf+Z~-UPz>BoLCB z5gvRKWlR0Z2OBcez|Tlz$iPh@p=Vx}#R?%`_~k~|1Kd-5anP|m#RzNjJaQvvAOB&* zH=ird>L%mi!{`dUU7|PnAclsp)vq)A<6&NKk<2y7Ouc=pgSrvhyd4^|6=QuP$i@;O zd^<0R;MeGT{m;_7+w1Q^0Vvfjpx`}oUH6-a9E9H^asC;H$YkrA(cM(+*%yWQQO z_CBg74%%pu&Aam+EXJ*ytVS)l)E(Go7efJ1SB@$7z8bH-gXhzKaDl(ia}$0FBR}^t zeGB{tJr(|gvBibC7kUQH(+IdhLvWw$FOYY{{|r>>qi7y$Gfsw}kLi zI64Tw?xbF5ZTrgq74JoHqQ?B_id|ajvdP~Vhl!D`+5K?}a*S+i!GRFo=(bD7-EQ1_ zl%EUPjTf8CbLvo>LxwNbM(y%}B{mO)OZ`2Ywz~Nle3o7Qcd`^NADcp;aO_r<723@@ z3`}VjE)d(zI-&-Ph+DjgXx36pD5++7cFY$A7tJ304UopJ5jQ$#}SfqgYc1>T&9 z8?KSz5i_;p@>&uveEAc)o1){?yQkN$Zu9*`7~7MPF^)1g(9r};?_;2p$MJ^vu@K_( zu^42m!frbn{S+GAjYjWGHTn&X6}#wIjeaV<(YT-3f$O;oUI?Fu_xQ#AI}!76!3p68 zrCKYoQ0#%u?}5&5UNLq9uo@l>UpY2TSt0Ld;!SjWadfncbA-eqIZ1fjySLlr5kl$Q z>XWf)58-C2mS{Gy$gOzFrTQ4~odcl&*A6={3AoFPrxI2ha+v z0dn4Z~_)ocq;k{RtK-`9^-UN>*P4rkz*D$M?-22f)vjvbr6`z`(t3@Nf>&9k%(mTEBe+ zzn&bcc%~eyN}5@L8Rjzo9+_kgMK&M84_?p(jaa@X`WxwlEHepi$3sb;0Yj4>&N}a= zkP07OaKQ73de$I2#r^z@!e7t-pUhxbbn2Cq?1fUW6mC#8(KgP!G z-h7ck*&PobbfEk@P=*RShVnW9bj`PO?{&<#!?4y*Kv9!;j9>tx>S%e4NAvzTwdu%QO|)`?mEz1W^|UR1 zn@91RYvu0J+wn^|rT*=nZ7-ntxwf41{RD)B_qOEA1DdT66_>fOUNR~|N>z{xHbtuI zhzMKq+$Af6e}^t}dG3;F&!ZP&XsMp3Ql)ynSAHaou)!B`O=M!tlkU*Yj2(C#uob#g z5x{eQrJ`n@M^@QX0gMT7a4BHDvW;WKLshtqLJ5sw&!jX@ccZSgL;V1rA_iLW(-S0y z*W_>mZiVne2TZHqE8Ntn_>1A4>;lFQjz%N`iBV8k0Tko%h~zX67J%QY1)%RtjIqSR zTD;Ubd8!GRioPe>hN0|y%{KSdB(2r(-p4-@_xjpDG$7b z>qxKSm`X+z0Qx5vvfVkk5j+NbxPt;710@K-V_+6S@EDkn5IhFT5Q4`*0C!ZtV`U}x zd^zYVY?JN)CwE8()(OJh2$|lCa5};sgs2_Ti9t$y3N{8QDPJXOr)dKz#kovDN_Z_j z!pb4sG=L6@o~d8l#d`wO9H14sRDMd6zqulKp6JC`6?G!a8$I(ULAO2F;lj8%F2Ta5 zq2fv3M>(x?^(d7QA=iJ-BC=N@Kd+EKP2_hPGwf=92%vqRg+l zx5M)RmfYSBzpK~ZA@imGaWXVCo2rSa*}jw^^A;+F=i(XIgQfmwxb5f=WSDr5!@$Q6 zbWT-279=CLA8$E-a4*4_I><=HB$4Ev=AVtw!? zq)g(Bn^Cn)(Td#TxK@W3?Az{@v5LtZKL4}1i5zv{M+@w$d;Xr>im5u_v4jbZX1V*; zzTPK_gJ$5ZlyILI3siXLegQ)r`vv9FFk6N|99PR?818XgEsNobSsYi(Vz^?4TY5KUJ27A7{{twucn;&VA6~?pUGXQBV9DL(%(7J735k>2RPJ>Pt6(L7)z^ue8*Wq4< zi`nyC3%4NmQT4ESd5a)qey_J?}?-M|G0I5NPPpl)`v z^w10e;jIG#0?XDAw#~r8bJAfvzuJuzI#~%N+&$$n#CpeQcQFS$G z?Kd)u_xpo}`I1z7p?-h*x7b*gd`y1FQdkMv1k9 zll#anX!BE62)>Oj==47N5}4*BUkBS|UyYi#xiN+T^Xo#)+okmm#pnDBp939KFTd!y zu-vj1-L~k~MX`}Z)r+c@B`&%C-9D>2khp^3ilwpNFOB_p*`>b-;C6A~z+VcIU#P>> z9{p9wH~Xia3%5aXmbEPQ_OjUT%Ryme+R;Gd#k|0RpM~GX73AVOS1r10(W*svFRFR5 zqhY4Of9FN8MtZT1M5s{ZLm4mRvQXl5el}4uBfjK)y}N5F1k3}+!m~@TUWcLDR{Bf zw#Hhtp{1_2zGY_1nvYryjn)R=B@ODowV~M&4{0DW>#vmmqa)URDRQjTYG@H*;j5vx zF4%r&)L7fp_&~7U8rAGl5y7g(6x z^^NW8YeRMGg6$$QEYkS22SYWX_Eywlt*>otwys>)vOaiaZBr=N5^le8ed9g%sN5^s z?{B}dsc}sT;>d)28gB)E*Dajo`@jc$62Eq?M6U;BfGz%DG(WA4f!g+UOIpIsp*eRp zE(ta@t-96$)1WlGdsg`tF7!Fk?pbA(G%l}cY`&s#8NM&QZkE-!?7D@Lkb0M0yHN72 z#^p}%asw`DTy|~a@=JZ2Hu)|pS+hJ?TYvwuU_)rdeYLGDuOQ1C*RB)LrJxRUd@o#x zdM}$T1wmsWYPr-}b3NKqZ#9;;hpO5dmp0c2H=>@K+K@m`H3i$3Hd{z8stZB&ZfI)> zx3*u4bVcn(m9W+eXg zk-c!RaAB)ZhnBE!f*Y3yTib%|&>QH?hM?7O(ZY~axT1D_@aDF)>?z$6f}7B^>gL9K z!@(8dHJZ)VsueXYVd|Z9V6g34JU630B_*xW^oEx9I~!KbY`ngycI^jlUcS_SQ&mmn za{mhd@>`eQbi<_=Eu3|!^rdEP&CP9%;HZ*}wL!%jo7Y;SYFP;VX%;0y`)&$u4BgPu zQg39tB3Rp2w@zB@yUtemaV z+EGz^Pf&=NhNXo!=*eNly4tp2Jq61_DJ7OQV&ql?+tvmNZoj@3$x)wi%CBf@xo=b% zC%}|nsz&~32As%<}UfEXK5MI&lBMIY<0w~yz>i6cfvY=>RYQ@ycU#4cmu*Ngm1z3xEEm| zjEo})TQ;LS!k;6YUSwHsegfqY-i*6nD-oWL%X=FUF6csegje9fw;qJAAsj$>KOU&F zu)JT4rwMtOJP%J?mLXjA-I0-X2!HVXk&*2P3!fbs*@f`7=SD{Q5FYD?{17(3F*3p( z-7vyg2%i`j8L340#xF-kS`q#s33~wHO~*$@b|XCRS0f|+2=}~$@(5e;YFaPm`vHV2 z^}Ih#cUs;9%dNbP-n{cConG9Lce`!KV%bEcU;;*|Cbr5@s_xdT* zZ}gmXU-4G!Bkw8w&{Z=oCYU(o`13(0rV_AdiudFBOD3PT3>72E{CfONMgG|c?fi;V z{x*OMkiW*6pT9~FSbit|{K#)`<=5Kz2LX>Gf2Avbg`Iy4@E4I^4PCYCFR08T0_hh5 zJ`7zBILkAR5A$ar-v?bDVuMWk3VvB^%9kU5Xzj?zdKNJG`RnZZ>ybYn+ti0#`J3$g zZODIOjQpL*{}b|?T;_&-twxt4Yw2OeMG@>K#1pZQ<3 z+cBvSdOSw{<;cJHpGQW%?kd02F24-AoKQ^t&g%*W<+d7UYFF*2XJnEFu!~_aMLYJ0l}_H%Tf#q)0ITF!HOAzrrQYn?#;7kbe^S zpZFdgCFX?W&TIApQ6XYY?&d-ku4f1gp@_Uf~ zMVCCDvge&m$Y1ndBO`vS1$Oy@veOLxr;y+9^O2GBT&3IkuffYvZgc6ya?uOc ze-!zjIW{u#1H^6m`7hc0onrmJz_@YM{~5df3jlw1Xk_Gjz_ZI2d|UdV9H48Fzw$RD zBSF$%JjMHH{^BXqA1Uxp@g)iuPbt|^6qqvWZ>X|PX*Te{XU7|aSr)#%gE1rRA80qeA;LHnEc=UbwsM7 zpM)QpT>O;i2uvjT(0{@YO?`gKHN&Pk(-qjly*by1CL%w+=Y0~weIOsNju<|=HGSPo z%g{5SE@t6HI>Ilim!f~w>2A&Fh)x@u?u1S^>3p~>q`cwF(EXnZE47^er|A9vl}>Sw zPguKQ$&wHIN~+hu-yil}J#)^?Sy#*pOKSFKWkg*D&-A0ey@Y8I4cf(J&jNZ86#g@@GH~b9C=(QVure(&j z8-AA6Zxr5s+s;bt~W6~R1V z6L3F7keas1_3qvihw2xcCf_cZG&m>4ok#jenJfe?!AdG<=SRzoy}J8eXd5SL+2-e1gdH15Ua1JgpE- z_usAYV;Wz4gh2VIhJR1P#Wx6eNW;HQ#lS~=W(Yeq{N@h{*b-kJqTkW*&)=-zW*8sP z@YoXyEjBW)N9}J>r zVxFZOrtea4xU%GP4&bc!=a;Iq_$m?gX?T2*f{PCk(Q-{^K+DsovaMAb|CYH5AU;b( zn>76S>l9pkm56>!!nhuN3~=&qeM12dlh4x{?$huY8vde&uh#I3 zG@NgDXL~<&ivozx5z(ZE-}Gq(7vCA8f7b9b5(+LpH$=eH{af2uXGM-$LNslZ3C3(U?mPu>803-hzFubJX! zrO+9@-qmV+W3MZ0%nt$|y)J)3=!{+;KM(lX(3487_)s(X{ELPk&>bRYU&wh$;2Oyh z+>eC-tG+e8$0yKYoyT)Z1FfJ@HJ>GE^gpQSyr$c`M$?%qaKl|H3~2n&`xH#hUJ(77@HyI!s?I{^ zUQK7WuGfs;2LY!XW~K3Y8{}sCBE=2P12q2pH2jV%^?oA@{Ix9b=d-|nlm&h)3%no; z|L@KM|4v;0 z*vU`>`FuSK{<8v~Yz=%x;RQARp)B|VS>P*Bkr(Z))a}(|tUqh|@91$MXUE7a*KlM1 zU#1I8!akaG(#Okmz&XA;^th8VWR!U?;FNP9P0p7JzTqMjUJJM{m9%4(n$9#$$H?vO zEOgq4kA6w#b4wQbT|#HFHBZZ-LD&3c4PUOu@5LJaJ-|)eW)hK?L~a@OUv;>`6ACvp z;BH$@&5cWz*DPDQqN=9Gst>jW*W#v6u&pMvzNW6Jg;$GeYU*2R);6`QscovM54E(l z*VKkLT6Hb!TbqKok~s64dGqFHMyhGR9ln~{wzk^)Yj6>&?S8AF4fnBX>ci{T-;W}W z6z;h-hFqw)YBTz#Vh!(GRaM*4%k=-&tB)Bi9t$ah0LD{)V>3dR|(zdCkI#+~dmF{Hw0II{lJZ>du%HLo1YM zQAwyOxN%8KOIv;B-kUq?sw^4T%*vcGZ}x0!No`Xd=q|#2Rway^g1G3o25Kwy%at)3 zB-hw7w&d!$^Q=YnyoguTBG>sW=cTgDMXt%BhS$uQXW3WDtR)yY(6rRWL&q(&jC8M= ze@*7*&Yyi%imtk%2B9*N8Gpf?tFvTWowOyK#G00d8eSa6ToMeHmEEvx>Eb0dbKrcNXVomN zs;EiLVO14NID=L3Rx|Gi%XMStrC^fA-O-vlrG!CVhqhYvAZwSJP0DJ(Y}lSwAEu}( z+<}$bgy0(ut-GhDZrweosj(@=+FZL0ii2jETiPvs)0s1DZ9A@rhSJKhN$Z2_aaA@& zi?D{)aH!4=s;z5jcSE!oMff$m{p`*L-MaOyZde24kwyg*WK&C8U04qwC$<*dYKyNO zY~hA$sKHIMre^K>mS%-4>Qmp|QnRkMxgK3Y9$N2D;m@9n9=sX2xY=40ZpUQYes9}o zg0^~4$8fPb6NioMEp-bP)U;!UZ+38WjidK#*3`B)a{XJ=-mV)Xm&BncM%8dty)~F7 zxSAU`)GXJFeSli z@XR&5MSDeKy)~1mb-2N9&8)w_8T3>bYEwCoN;?-HLDPvE-@YQ37BGbdE>V?q0fnoxUm}zcs3%=^2Wb! z!Wjlphw0_Z0slSDcntqtI$}b@KejpYG5cMev~EI#TRKyIua20oRi|NAikwV&?cTI* z1spC4ySzC^God**1B!go<##urJi{{eoAU<~YWJ9ZPG|5;cn`|+Ow6Ra$tXT1H2yh) ziDURD4nGgzk1{po&3T3iyLCpo{7n0|p)A{^tF_NP%=rg)ey&e?|IzQ{EN{+7OlbUA zhKNa<@X@sL<{ZU@-KL-p)7wusl(T-b-!kEko^%s1^&03ur!} z-|S0G=rw}X75a2&0=mD*zbS9dc}%#InT}6-{riyTEN{+(Ovrt%^OG*WgUIBTlVgA% zcU!~LWP1=P0K%=kElsA60vK-4FMOpH1%A50@z&PbmL_Tlgn@u$3&3RR&F5j=^ zXT)ImHhMt!ud{x0?(Nf0ISe4*`7z~9_% zj4~D2-|6LlhaA>F%f?U #include #include @@ -52,18 +51,6 @@ struct input { size_t nr_faces; } input; -struct output { - // Borrows from `positions`, `normals`, and `tex_coords`. - struct vertex *vertices; - - // Owned. - struct tangent *tangents; - - // Number of entries in `vertices` and `tangents`. - // Equal to `3 * input.nr_faces`. - size_t nr_vertices; -} output; - void print_vec2(float (*t)[2]) { printf("[%f, %f]", (*t)[0], (*t)[1]); } @@ -86,17 +73,17 @@ int get_num_vertices_of_face(const SMikkTSpaceContext *x, int f) { void get_position(const SMikkTSpaceContext *x, float *dst, int f, int v) { float (*src)[3] = input.faces[f].vertices[v].position; - memcpy(dst, src, sizeof(*src)); + memcpy(dst, *src, 3 * sizeof(float)); } void get_normal(const SMikkTSpaceContext *x, float *dst, int f, int v) { float (*src)[3] = input.faces[f].vertices[v].normal; - memcpy(dst, src, sizeof(*src)); + memcpy(dst, *src, 3 * sizeof(float)); } void get_tex_coord(const SMikkTSpaceContext *x, float *dst, int f, int v) { float (*src)[2] = input.faces[f].vertices[v].tex_coord; - memcpy(dst, src, sizeof(*src)); + memcpy(dst, *src, 2 * sizeof(float)); } void set_tspace_basic( @@ -111,11 +98,14 @@ void set_tspace_basic( struct vertex *in = &input.faces[f].vertices[v]; - output.vertices[i].position = in->position; - output.vertices[i].normal = in->normal; - output.vertices[i].tex_coord = in->tex_coord; - memcpy(output.tangents[i].v, t, 3 * sizeof(float)); - output.tangents[i].s = s; + printf("%d: { v: ", i); + print_vec3(in->position); + printf(", vn: "); + print_vec3(in->normal); + printf(", vt: "); + print_vec2(in->tex_coord); + printf(", vx: [%f, %f, %f, %f] }", t[0], t[1], t[2], s); + puts(""); ++i; } @@ -130,22 +120,19 @@ void set_tspace( int f, int v ) { - assert(!"unreachable"); + set_tspace_basic(x, t, op != 0 ? 1.0f : -1.0f, f, v); } int main() { - input.nr_vertices = 406; - input.nr_faces = 682; - output.nr_vertices = 3 * input.nr_faces; + input.nr_vertices = 30; + input.nr_faces = 24; input.positions = calloc(input.nr_vertices, sizeof(*input.positions)); input.normals = calloc(input.nr_vertices, sizeof(*input.normals)); input.tex_coords = calloc(input.nr_vertices, sizeof(*input.tex_coords)); input.faces = calloc(input.nr_faces, sizeof(*input.faces)); - output.vertices = calloc(output.nr_vertices, sizeof(*output.vertices)); - output.tangents = calloc(output.nr_vertices, sizeof(*output.tangents)); - FILE *fi = fopen("Avocado.obj", "rb"); + FILE *fi = fopen("cube.obj", "rb"); assert(fi); char buffer[1024]; @@ -205,7 +192,7 @@ int main() { .m_getNormal = get_normal, .m_getTexCoord = get_tex_coord, .m_setTSpaceBasic = set_tspace_basic, - .m_setTSpace = NULL, + .m_setTSpace = set_tspace, }; SMikkTSpaceContext context = { .m_pInterface = &interface, @@ -214,32 +201,11 @@ int main() { genTangSpaceDefault(&context); - printf("{\n \"vlist\": [\n"); - for (size_t i = 0; i < output.nr_vertices; ++i) { - printf(" {\"v\": "); - print_vec3(output.vertices[i].position); - printf(", \"vn\": "); - print_vec3(output.vertices[i].normal); - printf(", \"vt\": "); - print_vec2(output.vertices[i].tex_coord); - printf(", \"vx\": "); - print_tangent(&output.tangents[i]); - if (i == output.nr_vertices - 1) { - printf("}\n"); - } else { - printf("},\n"); - } - } - printf(" ]\n}"); - fclose(fi); free(input.positions); free(input.normals); free(input.tex_coords); free(input.faces); - free(output.vertices); - free(output.tangents); return 0; } - diff --git a/test-data/generate-test-obj.c b/test-data/generate-test-obj.c deleted file mode 100644 index 6d4a8ea9216ee..0000000000000 --- a/test-data/generate-test-obj.c +++ /dev/null @@ -1,250 +0,0 @@ - -#include -#include -#include -#include -#include -#include - -#include "mikktspace.h" - -struct tangent { - // Vector. - float v[3]; - - // Sign. - float s; -}; - -struct vertex { - // Borrows from `input.positions`. - float (*position)[3]; - - // Borrows from `input.positions`. - float (*normal)[3]; - - // Borrows from `input.positions`. - float (*tex_coord)[2]; -}; - -struct face { - // Borrows from `positions`, `normals`, and `tex_coords`. - struct vertex vertices[3]; -}; - -struct input { - // Owned. - float (*positions)[3]; - - // Owned. - float (*normals)[3]; - - // Owned. - float (*tex_coords)[2]; - - // Borrows from `positions`, `normals`, and `tex_coords`. - struct face *faces; - - // Number of entries in `positions`, `normals`, and `tex_coords`. - size_t nr_vertices; - - // Number of entries in `faces`. - size_t nr_faces; -} input; - -struct output { - // Borrows from `positions`, `normals`, and `tex_coords`. - struct vertex *vertices; - - // Owned. - struct tangent *tangents; - - // Number of entries in `vertices` and `tangents`. - // Equal to `3 * input.nr_faces`. - size_t nr_vertices; -} output; - -void print_vec2(float (*t)[2]) { - printf("%f %f", (*t)[0], (*t)[1]); -} - -void print_vec3(float (*t)[3]) { - printf("%f %f %f", (*t)[0], (*t)[1], (*t)[2]); -} - -void print_tangent(const struct tangent *t) { - printf("%f %f %f %f", t->v[0], t->v[1], t->v[2], t->s); -} - -int get_num_faces(const SMikkTSpaceContext *x) { - return input.nr_faces; -} - -int get_num_vertices_of_face(const SMikkTSpaceContext *x, int f) { - return 3; -} - -void get_position(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[3] = input.faces[f].vertices[v].position; - memcpy(dst, src, sizeof(*src)); -} - -void get_normal(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[3] = input.faces[f].vertices[v].normal; - memcpy(dst, src, sizeof(*src)); -} - -void get_tex_coord(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[2] = input.faces[f].vertices[v].tex_coord; - memcpy(dst, src, sizeof(*src)); -} - -void set_tspace_basic( - const SMikkTSpaceContext *x, - const float *t, - float s, - int f, - int v -) { - // The index of the last output (vertex, tangent) pair. - static int i = 0; - - struct vertex *in = &input.faces[f].vertices[v]; - - output.vertices[i].position = in->position; - output.vertices[i].normal = in->normal; - output.vertices[i].tex_coord = in->tex_coord; - memcpy(output.tangents[i].v, t, 3 * sizeof(float)); - output.tangents[i].s = s; - - ++i; -} - -void set_tspace( - const SMikkTSpaceContext *x, - const float *t, - const float *b, - float mag_s, - float mag_t, - tbool op, - int f, - int v -) { - assert(!"unreachable"); -} - -int main() { - input.nr_vertices = 406; - input.nr_faces = 682; - output.nr_vertices = 3 * input.nr_faces; - - input.positions = calloc(input.nr_vertices, sizeof(*input.positions)); - input.normals = calloc(input.nr_vertices, sizeof(*input.normals)); - input.tex_coords = calloc(input.nr_vertices, sizeof(*input.tex_coords)); - input.faces = calloc(input.nr_faces, sizeof(*input.faces)); - output.vertices = calloc(output.nr_vertices, sizeof(*output.vertices)); - output.tangents = calloc(output.nr_vertices, sizeof(*output.tangents)); - - { - FILE *fi = fopen("Avocado.obj", "rb"); - assert(fi); - char buffer[1024]; - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "v %f %f %f", - &input.positions[i][0], - &input.positions[i][1], - &input.positions[i][2] - ); - } - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "vn %f %f %f", - &input.normals[i][0], - &input.normals[i][1], - &input.normals[i][2] - ); - } - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "vt %f %f", - &input.tex_coords[i][0], - &input.tex_coords[i][1] - ); - } - - for (size_t i = 0; i < input.nr_faces; ++i) { - fgets(buffer, sizeof(buffer), fi); - int v[3]; - sscanf( - buffer, - "f %d/%d/%d %d/%d/%d %d/%d/%d", - &v[0], &v[0], &v[0], - &v[1], &v[1], &v[1], - &v[2], &v[2], &v[2] - ); - for (size_t j = 0; j < 3; ++j) { - input.faces[i].vertices[j].position = &input.positions[v[j] - 1]; - input.faces[i].vertices[j].normal = &input.normals[v[j] - 1]; - input.faces[i].vertices[j].tex_coord = &input.tex_coords[v[j] - 1]; - } - } - - fclose(fi); - } - - SMikkTSpaceInterface interface = { - .m_getNumFaces = get_num_faces, - .m_getNumVerticesOfFace = get_num_vertices_of_face, - .m_getPosition = get_position, - .m_getNormal = get_normal, - .m_getTexCoord = get_tex_coord, - .m_setTSpaceBasic = set_tspace_basic, - .m_setTSpace = NULL, - }; - SMikkTSpaceContext context = { - .m_pInterface = &interface, - .m_pUserData = NULL, - }; - - genTangSpaceDefault(&context); - - for (size_t i = 0; i < output.nr_vertices; ++i) { - printf("v "); - print_vec3(output.vertices[i].position); - printf("\n"); - } - - for (size_t i = 0; i < output.nr_vertices; ++i) { - printf("vn "); - print_vec3(output.vertices[i].normal); - printf("\n"); - } - - int k = 1; - for (size_t i = 0; i < output.nr_vertices / 3; ++i) { - int v0 = k++; - int v1 = k++; - int v2 = k++; - printf("f %d//%d %d//%d %d//%d\n", v0, v0, v1, v1, v2, v2); - } - - free(input.positions); - free(input.normals); - free(input.tex_coords); - free(input.faces); - free(output.vertices); - free(output.tangents); - - return 0; -} - diff --git a/test-data/libmikktspace.a b/test-data/libmikktspace.a deleted file mode 100644 index 01df84eade6ff3622b01a389c92403b3e6a73055..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48966 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 H;r#zEDFCW8 From 7aeab71bc1dd4d0d86d181f3c6e24ea2f0af82f3 Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 3 Sep 2017 10:44:31 +0100 Subject: [PATCH 17/87] Cleanup Former-commit-id: b63a3688c8593f06f2a7870308def5c13845003e --- {test-data => examples}/cube.obj | 0 .../generate.c | 0 test-data/compare-with-generate-example | 144 ----------------- test-data/mikktspace.h | 145 ------------------ 4 files changed, 289 deletions(-) rename {test-data => examples}/cube.obj (100%) rename test-data/generate-test-data.c => examples/generate.c (100%) delete mode 100644 test-data/compare-with-generate-example delete mode 100644 test-data/mikktspace.h diff --git a/test-data/cube.obj b/examples/cube.obj similarity index 100% rename from test-data/cube.obj rename to examples/cube.obj diff --git a/test-data/generate-test-data.c b/examples/generate.c similarity index 100% rename from test-data/generate-test-data.c rename to examples/generate.c diff --git a/test-data/compare-with-generate-example b/test-data/compare-with-generate-example deleted file mode 100644 index 93f4f0b585390..0000000000000 --- a/test-data/compare-with-generate-example +++ /dev/null @@ -1,144 +0,0 @@ -0: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -1: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -2: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -3: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -4: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -5: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -6: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -7: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -8: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [-0.408248, 0.816497, 0.408248, -1.000000] } -9: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [-0.408248, 0.816497, 0.408248, -1.000000] } -10: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -11: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -12: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [-0.408248, 0.816497, 0.408248, -1.000000] } -13: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [-0.408248, 0.816497, 0.408248, -1.000000] } -14: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [-0.408248, 0.816497, -0.408248, -1.000000] } -15: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [-0.408248, 0.816497, -0.408248, -1.000000] } -16: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -17: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -18: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [-0.408248, 0.816497, -0.408248, -1.000000] } -19: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [-0.408248, 0.816497, -0.408248, -1.000000] } -20: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -21: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -22: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -23: { v: [0.500000, 0.000000, 0.000000], vn: [1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, -1.000000] } -24: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.408248, 0.816497, -0.408248, 1.000000] } -25: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.408248, 0.816497, -0.408248, 1.000000] } -26: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.408248, 0.816497, 0.408248, 1.000000] } -27: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.408248, 0.816497, 0.408248, 1.000000] } -28: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -29: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -30: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.408248, 0.816497, 0.408248, 1.000000] } -31: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.408248, 0.816497, 0.408248, 1.000000] } -32: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -33: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -34: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -35: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -36: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -37: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -38: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -39: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -40: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -41: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -42: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -43: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -44: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.408248, 0.816497, -0.408248, 1.000000] } -45: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.408248, 0.816497, -0.408248, 1.000000] } -46: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -47: { v: [-0.500000, 0.000000, 0.000000], vn: [-1.000000, 0.000000, 0.000000], vt: [0.500000, 0.500000], vx: [0.000000, 1.000000, 0.000000, 1.000000] } -48: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -49: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -50: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -51: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -52: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -53: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -54: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -55: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -56: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -57: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -58: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -59: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -60: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -61: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -62: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -63: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -64: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -65: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -66: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -67: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -68: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -69: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -70: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -71: { v: [0.000000, 0.500000, 0.000000], vn: [0.000000, 1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -72: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -73: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [-0.408248, 0.816497, 0.408248, 1.000000] } -74: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -75: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [-0.408248, 0.816497, -0.408248, 1.000000] } -76: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -77: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -78: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -79: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -80: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -81: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -82: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -83: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -84: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -85: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.408248, 0.816497, -0.408248, -1.000000] } -86: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -87: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -88: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -89: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -90: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -91: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.408248, 0.816497, 0.408248, -1.000000] } -92: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -93: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -94: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -95: { v: [0.000000, -0.500000, 0.000000], vn: [0.000000, -1.000000, 0.000000], vt: [0.000000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -96: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, 0.408248, -1.000000] } -97: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, 0.408248, -1.000000] } -98: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, 0.408248, -1.000000] } -99: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, 0.408248, -1.000000] } -100: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -101: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -102: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, 0.408248, -1.000000] } -103: { v: [-0.500000, -0.500000, 0.500000], vn: [-0.577350, -0.577350, 0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, 0.408248, -1.000000] } -104: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, -0.408248, -1.000000] } -105: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, -0.408248, -1.000000] } -106: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -107: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -108: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, -0.408248, -1.000000] } -109: { v: [0.500000, -0.500000, 0.500000], vn: [0.577350, -0.577350, 0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, -0.408248, -1.000000] } -110: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, -0.408248, -1.000000] } -111: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, -0.408248, -1.000000] } -112: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -113: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -114: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, -0.408248, -1.000000] } -115: { v: [0.500000, 0.500000, 0.500000], vn: [0.577350, 0.577350, 0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, -0.408248, -1.000000] } -116: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, 0.408248, -1.000000] } -117: { v: [-0.500000, 0.500000, 0.500000], vn: [-0.577350, 0.577350, 0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, 0.408248, -1.000000] } -118: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -119: { v: [0.000000, 0.000000, 0.500000], vn: [0.000000, 0.000000, 1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, -1.000000] } -120: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, 0.408248, 1.000000] } -121: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, 0.408248, 1.000000] } -122: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, 0.408248, 1.000000] } -123: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, 0.408248, 1.000000] } -124: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -125: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -126: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, 0.408248, 1.000000] } -127: { v: [0.500000, -0.500000, -0.500000], vn: [0.577350, -0.577350, -0.577350], vt: [1.000000, 1.000000], vx: [0.816497, 0.408248, 0.408248, 1.000000] } -128: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, -0.408248, 1.000000] } -129: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, -0.408248, 1.000000] } -130: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -131: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -132: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, -0.408248, 1.000000] } -133: { v: [-0.500000, -0.500000, -0.500000], vn: [-0.577350, -0.577350, -0.577350], vt: [0.000000, 1.000000], vx: [0.816497, -0.408248, -0.408248, 1.000000] } -134: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, -0.408248, 1.000000] } -135: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, -0.408248, 1.000000] } -136: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -137: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -138: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, -0.408248, 1.000000] } -139: { v: [-0.500000, 0.500000, -0.500000], vn: [-0.577350, 0.577350, -0.577350], vt: [0.000000, 0.000000], vx: [0.816497, 0.408248, -0.408248, 1.000000] } -140: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, 0.408248, 1.000000] } -141: { v: [0.500000, 0.500000, -0.500000], vn: [0.577350, 0.577350, -0.577350], vt: [1.000000, 0.000000], vx: [0.816497, -0.408248, 0.408248, 1.000000] } -142: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } -143: { v: [0.000000, 0.000000, -0.500000], vn: [0.000000, 0.000000, -1.000000], vt: [0.500000, 0.500000], vx: [1.000000, 0.000000, 0.000000, 1.000000] } diff --git a/test-data/mikktspace.h b/test-data/mikktspace.h deleted file mode 100644 index 52c44a713c60e..0000000000000 --- a/test-data/mikktspace.h +++ /dev/null @@ -1,145 +0,0 @@ -/** \file mikktspace/mikktspace.h - * \ingroup mikktspace - */ -/** - * Copyright (C) 2011 by Morten S. Mikkelsen - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#ifndef __MIKKTSPACE_H__ -#define __MIKKTSPACE_H__ - - -#ifdef __cplusplus -extern "C" { -#endif - -/* Author: Morten S. Mikkelsen - * Version: 1.0 - * - * The files mikktspace.h and mikktspace.c are designed to be - * stand-alone files and it is important that they are kept this way. - * Not having dependencies on structures/classes/libraries specific - * to the program, in which they are used, allows them to be copied - * and used as is into any tool, program or plugin. - * The code is designed to consistently generate the same - * tangent spaces, for a given mesh, in any tool in which it is used. - * This is done by performing an internal welding step and subsequently an order-independent evaluation - * of tangent space for meshes consisting of triangles and quads. - * This means faces can be received in any order and the same is true for - * the order of vertices of each face. The generated result will not be affected - * by such reordering. Additionally, whether degenerate (vertices or texture coordinates) - * primitives are present or not will not affect the generated results either. - * Once tangent space calculation is done the vertices of degenerate primitives will simply - * inherit tangent space from neighboring non degenerate primitives. - * The analysis behind this implementation can be found in my master's thesis - * which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf - * Note that though the tangent spaces at the vertices are generated in an order-independent way, - * by this implementation, the interpolated tangent space is still affected by which diagonal is - * chosen to split each quad. A sensible solution is to have your tools pipeline always - * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. - * If these have the same length then compare the diagonals defined by the texture coordinates. - * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin - * and also quad triangulator plugin. - */ - - -typedef int tbool; -typedef struct SMikkTSpaceContext SMikkTSpaceContext; - -typedef struct { - // Returns the number of faces (triangles/quads) on the mesh to be processed. - int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); - - // Returns the number of vertices on face number iFace - // iFace is a number in the range {0, 1, ..., getNumFaces()-1} - int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); - - // returns the position/normal/texcoord of the referenced face of vertex number iVert. - // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. - void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); - void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); - void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); - - // either (or both) of the two setTSpace callbacks can be set. - // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - // This function is used to return the tangent and fSign to the application. - // fvTangent is a unit length vector. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); - - // This function is used to return tangent space results to the application. - // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - // true magnitudes which can be used for relief mapping effects. - // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - // However, both are perpendicular to the vertex normal. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert); -} SMikkTSpaceInterface; - -struct SMikkTSpaceContext -{ - SMikkTSpaceInterface * m_pInterface; // initialized with callback functions - void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) -}; - -// these are both thread safe! -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); - - -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. - -#ifdef __cplusplus -} -#endif - -#endif From 7f0b70df425259535ce8a4a5f41cf2f3cc7564a1 Mon Sep 17 00:00:00 2001 From: alteous Date: Sun, 3 Sep 2017 10:59:08 +0100 Subject: [PATCH 18/87] Set libmikktspace version to crate version Former-commit-id: 95ceac69a9a32ab8c9eea29045efd02a068c6db0 --- libmikktspace/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libmikktspace/CMakeLists.txt b/libmikktspace/CMakeLists.txt index 376409d8c8580..7c0e879e53f6a 100644 --- a/libmikktspace/CMakeLists.txt +++ b/libmikktspace/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 2.8) project(mikktspace) -set(PROJECT_VERSION_MAJOR "1") -set(PROJECT_VERSION_MINOR "0") +set(PROJECT_VERSION_MAJOR "0") +set(PROJECT_VERSION_MINOR "1") set(PROJECT_VERSION_PATCH "0") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") From 6265eb4369f3cc27b51363ef9a7e6c49345272f2 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Wed, 6 Sep 2017 09:57:02 +0200 Subject: [PATCH 19/87] remove original corrode experiment Former-commit-id: 426d7fb539a3c54c70d0cf376e4f3a058bfebd82 --- src/mikktspace.c | 1894 ---------------------------------------------- src/mikktspace.h | 145 ---- 2 files changed, 2039 deletions(-) delete mode 100644 src/mikktspace.c delete mode 100644 src/mikktspace.h diff --git a/src/mikktspace.c b/src/mikktspace.c deleted file mode 100644 index 87352629926e8..0000000000000 --- a/src/mikktspace.c +++ /dev/null @@ -1,1894 +0,0 @@ -/** \file mikktspace/mikktspace.c - * \ingroup mikktspace - */ -/** - * Copyright (C) 2011 by Morten S. Mikkelsen - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#include -#include -#include -#include -#include -#include - -#include "mikktspace.h" - -#define TFALSE 0 -#define TTRUE 1 - -#ifndef M_PI -#define M_PI 3.1415926535897932384626433832795 -#endif - -#define INTERNAL_RND_SORT_SEED 39871946 - -// internal structure -typedef struct { - float x, y, z; -} SVec3; - -static tbool veq( const SVec3 v1, const SVec3 v2 ) -{ - return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); -} - -static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x + v2.x; - vRes.y = v1.y + v2.y; - vRes.z = v1.z + v2.z; - - return vRes; -} - - -static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x - v2.x; - vRes.y = v1.y - v2.y; - vRes.z = v1.z - v2.z; - - return vRes; -} - -static SVec3 vscale(const float fS, const SVec3 v) -{ - SVec3 vRes; - - vRes.x = fS * v.x; - vRes.y = fS * v.y; - vRes.z = fS * v.z; - - return vRes; -} - -static float LengthSquared( const SVec3 v ) -{ - return v.x*v.x + v.y*v.y + v.z*v.z; -} - -static float Length( const SVec3 v ) -{ - return sqrtf(LengthSquared(v)); -} - -static SVec3 Normalize( const SVec3 v ) -{ - return vscale(1 / Length(v), v); -} - -static float vdot( const SVec3 v1, const SVec3 v2) -{ - return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; -} - - -static tbool NotZero(const float fX) -{ - // could possibly use FLT_EPSILON instead - return fabsf(fX) > FLT_MIN; -} - -static tbool VNotZero(const SVec3 v) -{ - // might change this to an epsilon based test - return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); -} - - - -typedef struct { - int iNrFaces; - int * pTriMembers; -} SSubGroup; - -typedef struct { - int iNrFaces; - int * pFaceIndices; - int iVertexRepresentitive; - tbool bOrientPreservering; -} SGroup; - -// -#define MARK_DEGENERATE 1 -#define QUAD_ONE_DEGEN_TRI 2 -#define GROUP_WITH_ANY 4 -#define ORIENT_PRESERVING 8 - - - -typedef struct { - int FaceNeighbors[3]; - SGroup * AssignedGroup[3]; - - // normalized first order face derivatives - SVec3 vOs, vOt; - float fMagS, fMagT; // original magnitudes - - // determines if the current and the next triangle are a quad. - int iOrgFaceNumber; - int iFlag, iTSpacesOffs; - unsigned char vert_num[4]; -} STriInfo; - -typedef struct { - SVec3 vOs; - float fMagS; - SVec3 vOt; - float fMagT; - int iCounter; // this is to average back into quads. - tbool bOrient; -} STSpace; - -static int GenerateInitialVerticesIndexList(STriInfo* pTriInfos, int* piTriList_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void GenerateSharedVerticesIndexList(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void InitTriInfo(STriInfo* pTriInfos, const int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static int Build4RuleGroups(STriInfo* pTriInfos, SGroup* pGroups, int* piGroupTrianglesBuffer, const int* piTriListIn, const int iNrTrianglesIn); -static tbool GenerateTSpaces(STSpace* psTspace, const STriInfo* pTriInfos, const SGroup* pGroups, - const int iNrActiveGroups, const int* piTriListIn, const float fThresCos, - const SMikkTSpaceContext * pContext); - -static int MakeIndex(const int iFace, const int iVert) -{ - // assert(iVert>=0 && iVert<4 && iFace>=0); - return (iFace<<2) | (iVert&0x3); -} - -static void IndexToData(int * piFace, int * piVert, const int iIndexIn) -{ - piVert[0] = iIndexIn&0x3; - piFace[0] = iIndexIn>>2; -} - -static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) -{ - STSpace ts_res; - - // this if is important. Due to floating point precision - // averaging when ts0==ts1 will cause a slight difference - // which results in tangent space splits later on - if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && - veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) - { - ts_res.fMagS = pTS0->fMagS; - ts_res.fMagT = pTS0->fMagT; - ts_res.vOs = pTS0->vOs; - ts_res.vOt = pTS0->vOt; - } - else - { - ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); - ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); - ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); - ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); - if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); - if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); - } - - return ts_res; -} - - - -static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); - - -// degen triangles -static void DegenPrologue(STriInfo* pTriInfos, int* piTriList_out, const int iNrTrianglesIn, const int iTotTris); -static void DegenEpilogue(STSpace* psTspace, STriInfo* pTriInfos, int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); - - -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) -{ - return genTangSpace(pContext, 180.0f); -} - -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) -{ - // count nr_triangles - int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; - STriInfo * pTriInfos = NULL; - SGroup * pGroups = NULL; - STSpace * psTspace = NULL; - int iNrTrianglesIn = 0, f=0, t=0, i=0; - int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; - int iNrActiveGroups = 0, index = 0; - const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); - tbool bRes = TFALSE; - const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); - - // verify all call-backs have been set - if ( pContext->m_pInterface->m_getNumFaces==NULL || - pContext->m_pInterface->m_getNumVerticesOfFace==NULL || - pContext->m_pInterface->m_getPosition==NULL || - pContext->m_pInterface->m_getNormal==NULL || - pContext->m_pInterface->m_getTexCoord==NULL ) - return TFALSE; - - // count triangles on supported faces - for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts==3) ++iNrTrianglesIn; - else if (verts==4) iNrTrianglesIn += 2; - } - if (iNrTrianglesIn<=0) return TFALSE; - - // allocate memory for an index list - piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); - pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); - if (piTriListIn==NULL || pTriInfos==NULL) - { - if (piTriListIn!=NULL) free(piTriListIn); - if (pTriInfos!=NULL) free(pTriInfos); - return TFALSE; - } - - // make an initial triangle --> face index list - iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); - - // make a welded index list of identical positions and attributes (pos, norm, texc) - //printf("gen welded index list begin\n"); - GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); - //printf("gen welded index list end\n"); - - // Mark all degenerate triangles - iTotTris = iNrTrianglesIn; - iDegenTriangles = 0; - for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - - // I've decided to let degenerate triangles and group-with-anythings - // vary between left/right hand coordinate systems at the vertices. - // All healthy triangles on the other hand are built to always be either or. - - /*// force the coordinate system orientation to be uniform for every face. - // (this is already the case for good triangles but not for - // degenerate ones and those with bGroupWithAnything==true) - bool bOrient = psTspace[index].bOrient; - if (psTspace[index].iCounter == 0) // tspace was not derived from a group - { - // look for a space created in GenerateTSpaces() by iCounter>0 - bool bNotFound = true; - int i=1; - while (i 0) bNotFound=false; - else ++i; - } - if (!bNotFound) bOrient = psTspace[index+i].bOrient; - }*/ - - // set data - for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; - float* bitang = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; - if (pContext->m_pInterface->m_setTSpace!=NULL) - pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); - if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) - pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); - - ++index; - } - } - - free(psTspace); - - - return TTRUE; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef struct { - float vert[3]; - int index; -} STmpVert; - -static const int g_iCells = 2048; - -#ifdef _MSC_VER - #define NOINLINE __declspec(noinline) -#else - #define NOINLINE __attribute__ ((noinline)) -#endif - -// it is IMPORTANT that this function is called to evaluate the hash since -// inlining could potentially reorder instructions and generate different -// results for the same effective input value fVal. -static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) -{ - const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); - const int iIndex = (int)fIndex; - return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); -} - -static void MergeVertsFast(int* piTriList_in_and_out, STmpVert* pTmpVert, const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); -static void MergeVertsSlow(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int* pTable, const int iEntries); -static void GenerateSharedVerticesIndexListSlow(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); - -static void GenerateSharedVerticesIndexList(int* piTriList_in_and_out, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - - // Generate bounding box - int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; - STmpVert * pTmpVert = NULL; - int i=0, iChannel=0, k=0, e=0; - int iMaxCount=0; - SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; - float fMin, fMax; - for (i=1; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - - const SVec3 vP = GetPosition(pContext, index); - if (vMin.x > vP.x) vMin.x = vP.x; - else if (vMax.x < vP.x) vMax.x = vP.x; - if (vMin.y > vP.y) vMin.y = vP.y; - else if (vMax.y < vP.y) vMax.y = vP.y; - if (vMin.z > vP.z) vMin.z = vP.z; - else if (vMax.z < vP.z) vMax.z = vP.z; - } - - vDim = vsub(vMax,vMin); - iChannel = 0; - fMin = vMin.x; fMax=vMax.x; - if (vDim.y>vDim.x && vDim.y>vDim.z) - { - iChannel=1; - fMin = vMin.y, fMax=vMax.y; - } - else if (vDim.z>vDim.x) - { - iChannel=2; - fMin = vMin.z, fMax=vMax.z; - } - - // make allocations - piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); - piHashCount = (int *) malloc(sizeof(int)*g_iCells); - piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); - piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); - - if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) - { - if (piHashTable!=NULL) free(piHashTable); - if (piHashCount!=NULL) free(piHashCount); - if (piHashOffsets!=NULL) free(piHashOffsets); - if (piHashCount2!=NULL) free(piHashCount2); - GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); - return; - } - memset(piHashCount, 0, sizeof(int)*g_iCells); - memset(piHashCount2, 0, sizeof(int)*g_iCells); - - // count amount of elements in each cell unit - for (i=0; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); - const int iCell = FindGridCell(fMin, fMax, fVal); - ++piHashCount[iCell]; - } - - // evaluate start index of each cell. - piHashOffsets[0]=0; - for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; - else if (fvMax[c]dx && dy>dz) channel=1; - else if (dz>dx) channel=2; - - fSep = 0.5f*(fvMax[channel]+fvMin[channel]); - - // terminate recursion when the separation/average value - // is no longer strictly between fMin and fMax values. - if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) - { - // complete the weld - for (l=iL_in; l<=iR_in; l++) - { - int i = pTmpVert[l].index; - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const SVec3 vN = GetNormal(pContext, index); - const SVec3 vT = GetTexCoord(pContext, index); - - tbool bNotFound = TTRUE; - int l2=iL_in, i2rec=-1; - while (l20); // at least 2 entries - - // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] - while (iL < iR) - { - tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; - while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); - bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); - bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) - { - const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - pTriInfos[iDstTriIndex].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; - - if (verts==3) - { - unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; - pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; - piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); - piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); - piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); - ++iDstTriIndex; // next - } - else - { - { - pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; - } - - { - // need an order independent way to evaluate - // tspace on quads. This is done by splitting - // along the shortest diagonal. - const int i0 = MakeIndex(f, 0); - const int i1 = MakeIndex(f, 1); - const int i2 = MakeIndex(f, 2); - const int i3 = MakeIndex(f, 3); - const SVec3 T0 = GetTexCoord(pContext, i0); - const SVec3 T1 = GetTexCoord(pContext, i1); - const SVec3 T2 = GetTexCoord(pContext, i2); - const SVec3 T3 = GetTexCoord(pContext, i3); - const float distSQ_02 = LengthSquared(vsub(T2,T0)); - const float distSQ_13 = LengthSquared(vsub(T3,T1)); - tbool bQuadDiagIs_02; - if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); - res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; - return res; -} - -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float norm[3]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); - res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; - return res; -} - -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float texc[2]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); - res.x=texc[0]; res.y=texc[1]; res.z=1.0f; - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef union { - struct - { - int i0, i1, f; - }; - int array[3]; -} SEdge; - -static void BuildNeighborsFast(STriInfo* pTriInfos, SEdge * pEdges, const int* piTriListIn, const int iNrTrianglesIn); -static void BuildNeighborsSlow(STriInfo* pTriInfos, const int* piTriListIn, const int iNrTrianglesIn); - -// returns the texture area times 2 -static float CalcTexArea(const SMikkTSpaceContext * pContext, const int* indices) -{ - const SVec3 t1 = GetTexCoord(pContext, indices[0]); - const SVec3 t2 = GetTexCoord(pContext, indices[1]); - const SVec3 t3 = GetTexCoord(pContext, indices[2]); - - const float t21x = t2.x-t1.x; - const float t21y = t2.y-t1.y; - const float t31x = t3.x-t1.x; - const float t31y = t3.y-t1.y; - - const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; - - return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; -} - -static void InitTriInfo(STriInfo* pTriInfos, const int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - int f=0, i=0, t=0; - // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. - - // generate neighbor info list - for (f=0; f0 ? ORIENT_PRESERVING : 0); - - if ( NotZero(fSignedAreaSTx2) ) - { - const float fAbsArea = fabsf(fSignedAreaSTx2); - const float fLenOs = Length(vOs); - const float fLenOt = Length(vOt); - const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; - if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); - if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); - - // evaluate magnitudes prior to normalization of vOs and vOt - pTriInfos[f].fMagS = fLenOs / fAbsArea; - pTriInfos[f].fMagT = fLenOt / fAbsArea; - - // if this is a good triangle - if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) - pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); - } - } - - // force otherwise healthy quads to a fixed orientation - while (t<(iNrTrianglesIn-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - - // bad triangles should already have been removed by - // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false - if ((bIsDeg_a||bIsDeg_b)==TFALSE) - { - const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - // if this happens the quad has extremely bad mapping!! - if (bOrientA!=bOrientB) - { - //printf("found quad with bad mapping\n"); - tbool bChooseOrientFirstTri = TFALSE; - if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; - else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) - bChooseOrientFirstTri = TTRUE; - - // force match - { - const int t0 = bChooseOrientFirstTri ? t : (t+1); - const int t1 = bChooseOrientFirstTri ? (t+1) : t; - pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first - pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit - } - } - } - t += 2; - } - else - ++t; - } - - // match up edge pairs - { - SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); - if (pEdges==NULL) - BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); - else - { - BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); - - free(pEdges); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool AssignRecur(const int* piTriListIn, STriInfo* psTriInfos, const int iMyTriIndex, SGroup * pGroup); -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); - -static int Build4RuleGroups(STriInfo* pTriInfos, SGroup* pGroups, int* piGroupTrianglesBuffer, const int* piTriListIn, const int iNrTrianglesIn) -{ - const int iNrMaxGroups = iNrTrianglesIn*3; - int iNrActiveGroups = 0; - int iOffset = 0, f=0, i=0; - (void)iNrMaxGroups; /* quiet warnings in non debug mode */ - for (f=0; fiVertexRepresentitive = vert_index; - pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; - pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; - pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; - ++iNrActiveGroups; - - AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); - bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - neigh_indexL = pTriInfos[f].FaceNeighbors[i]; - neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexL, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - // assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - if (neigh_indexR>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexR, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - // assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - - // update offset - iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; - // since the groups are disjoint a triangle can never - // belong to more than 3 groups. Subsequently something - // is completely screwed if this assertion ever hits. - // assert(iOffset <= iNrMaxGroups); - } - } - } - - return iNrActiveGroups; -} - -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) -{ - pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; - ++pGroup->iNrFaces; -} - -static tbool AssignRecur(const int* piTriListIn, STriInfo* psTriInfos, - const int iMyTriIndex, SGroup * pGroup) -{ - STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; - - // track down vertex - const int iVertRep = pGroup->iVertexRepresentitive; - const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; - int i=-1; - if (pVerts[0]==iVertRep) i=0; - else if (pVerts[1]==iVertRep) i=1; - else if (pVerts[2]==iVertRep) i=2; - // assert(i>=0 && i<3); - - // early out - if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; - else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; - if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) - { - // first to group with a group-with-anything triangle - // determines it's orientation. - // This is the only existing order dependency in the code!! - if ( pMyTriInfo->AssignedGroup[0] == NULL && - pMyTriInfo->AssignedGroup[1] == NULL && - pMyTriInfo->AssignedGroup[2] == NULL ) - { - pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); - pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); - } - } - { - const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - if (bOrient != pGroup->bOrientPreservering) return TFALSE; - } - - AddTriToGroup(pGroup, iMyTriIndex); - pMyTriInfo->AssignedGroup[i] = pGroup; - - { - const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; - const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); - if (neigh_indexR>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); - } - - - - return TTRUE; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); -static STSpace EvalTspace(int* face_indices, const int iFaces, const int* piTriListIn, const STriInfo* pTriInfos, const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); - -static tbool GenerateTSpaces(STSpace* psTspace, const STriInfo* pTriInfos, const SGroup* pGroups, - const int iNrActiveGroups, const int* piTriListIn, const float fThresCos, - const SMikkTSpaceContext * pContext) -{ - STSpace * pSubGroupTspace = NULL; - SSubGroup * pUniSubGroups = NULL; - int * pTmpMembers = NULL; - int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; - for (g=0; giNrFaces; i++) // triangles - { - const int f = pGroup->pFaceIndices[i]; // triangle number - int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; - SSubGroup tmp_group; - tbool bFound; - SVec3 n, vOs, vOt; - if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; - else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; - else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; - // assert(index>=0 && index<3); - - iVertIndex = piTriListIn[f*3+index]; - // assert(iVertIndex==pGroup->iVertexRepresentitive); - - // is normalized already - n = GetNormal(pContext, iVertIndex); - - // project - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - // original face number - iOF_1 = pTriInfos[f].iOrgFaceNumber; - - iMembers = 0; - for (j=0; jiNrFaces; j++) - { - const int t = pGroup->pFaceIndices[j]; // triangle number - const int iOF_2 = pTriInfos[t].iOrgFaceNumber; - - // project - SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); - SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); - if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); - if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); - - { - const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; - // make sure triangles which belong to the same quad are joined. - const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; - - const float fCosS = vdot(vOs,vOs2); - const float fCosT = vdot(vOt,vOt2); - - // assert(f!=t || bSameOrgFace); // sanity check - if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) - pTmpMembers[iMembers++] = t; - } - } - - // sort pTmpMembers - tmp_group.iNrFaces = iMembers; - tmp_group.pTriMembers = pTmpMembers; - if (iMembers>1) - { - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - QuickSort(pTmpMembers, 0, iMembers-1, uSeed); - } - - // look for an existing match - bFound = TFALSE; - l=0; - while (liVertexRepresentitive); - ++iUniqueSubGroups; - } - - // output tspace - { - const int iOffs = pTriInfos[f].iTSpacesOffs; - const int iVert = pTriInfos[f].vert_num[index]; - STSpace * pTS_out = &psTspace[iOffs+iVert]; - // assert(pTS_out->iCounter<2); - // assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); - if (pTS_out->iCounter==1) - { - *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); - pTS_out->iCounter = 2; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - else - { - // assert(pTS_out->iCounter==0); - *pTS_out = pSubGroupTspace[l]; - pTS_out->iCounter = 1; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - } - } - - // clean up and offset iUniqueTspaces - for (s=0; s=0 && i<3); - - // project - index = piTriListIn[3*f+i]; - n = GetNormal(pContext, index); - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - i2 = piTriListIn[3*f + (i<2?(i+1):0)]; - i1 = piTriListIn[3*f + i]; - i0 = piTriListIn[3*f + (i>0?(i-1):2)]; - - p0 = GetPosition(pContext, i0); - p1 = GetPosition(pContext, i1); - p2 = GetPosition(pContext, i2); - v1 = vsub(p0,p1); - v2 = vsub(p2,p1); - - // project - v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); - v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); - - // weight contribution by the angle - // between the two edge vectors - fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); - fAngle = (float) acos(fCos); - fMagS = pTriInfos[f].fMagS; - fMagT = pTriInfos[f].fMagT; - - res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); - res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); - res.fMagS+=(fAngle*fMagS); - res.fMagT+=(fAngle*fMagT); - fAngleSum += fAngle; - } - } - - // normalize - if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); - if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); - if (fAngleSum>0) - { - res.fMagS /= fAngleSum; - res.fMagT /= fAngleSum; - } - - return res; -} - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) -{ - tbool bStillSame=TTRUE; - int i=0; - if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; - while (iiNrFaces && bStillSame) - { - bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; - if (bStillSame) ++i; - } - return bStillSame; -} - -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) -{ - int iL, iR, n, index, iMid, iTmp; - - // Random - unsigned int t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft; iR=iRight; - n = (iR-iL)+1; - // assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL]; - - - do - { - while (pSortBuffer[iL] < iMid) - ++iL; - while (pSortBuffer[iR] > iMid) - --iR; - - if (iL <= iR) - { - iTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = iTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSort(pSortBuffer, iLeft, iR, uSeed); - if (iL < iRight) - QuickSort(pSortBuffer, iL, iRight, uSeed); -} - -///////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// - -static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int* indices, const int i0_in, const int i1_in); - -static void BuildNeighborsFast(STriInfo* pTriInfos, SEdge * pEdges, const int* piTriListIn, const int iNrTrianglesIn) -{ - // build array of edges - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - int iEntries=0, iCurStartIndex=-1, f=0, i=0; - for (f=0; f pSortBuffer[iRight].array[channel]) - { - sTmp = pSortBuffer[iLeft]; - pSortBuffer[iLeft] = pSortBuffer[iRight]; - pSortBuffer[iRight] = sTmp; - } - return; - } - - // Random - t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft, iR=iRight; - n = (iR-iL)+1; - // assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL].array[channel]; - - do - { - while (pSortBuffer[iL].array[channel] < iMid) - ++iL; - while (pSortBuffer[iR].array[channel] > iMid) - --iR; - - if (iL <= iR) - { - sTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = sTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); - if (iL < iRight) - QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); -} - -// resolve ordering and edge number -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int* indices, const int i0_in, const int i1_in) -{ - *edgenum_out = -1; - - // test if first index is on the edge - if (indices[0]==i0_in || indices[0]==i1_in) - { - // test if second index is on the edge - if (indices[1]==i0_in || indices[1]==i1_in) - { - edgenum_out[0]=0; // first edge - i0_out[0]=indices[0]; - i1_out[0]=indices[1]; - } - else - { - edgenum_out[0]=2; // third edge - i0_out[0]=indices[2]; - i1_out[0]=indices[0]; - } - } - else - { - // only second and third index is on the edge - edgenum_out[0]=1; // second edge - i0_out[0]=indices[1]; - i1_out[0]=indices[2]; - } -} - - -///////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////// Degenerate triangles //////////////////////////////////// - -static void DegenPrologue(STriInfo* pTriInfos, int* piTriList_out, const int iNrTrianglesIn, const int iTotTris) -{ - int iNextGoodTriangleSearchIndex=-1; - tbool bStillFindingGoodOnes; - - // locate quads with only one good triangle - int t=0; - while (t<(iTotTris-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - if ((bIsDeg_a^bIsDeg_b)!=0) - { - pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; - pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; - } - t += 2; - } - else - ++t; - } - - // reorder list so all degen triangles are moved to the back - // without reordering the good triangles - iNextGoodTriangleSearchIndex = 1; - t=0; - bStillFindingGoodOnes = TTRUE; - while (t (t+1)); - - // swap triangle t0 and t1 - if (!bJustADegenerate) - { - int i=0; - for (i=0; i<3; i++) - { - const int index = piTriList_out[t0*3+i]; - piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; - piTriList_out[t1*3+i] = index; - } - { - const STriInfo tri_info = pTriInfos[t0]; - pTriInfos[t0] = pTriInfos[t1]; - pTriInfos[t1] = tri_info; - } - } - else - bStillFindingGoodOnes = TFALSE; // this is not supposed to happen - } - - if (bStillFindingGoodOnes) ++t; - } - - // assert(bStillFindingGoodOnes); // code will still work. - // assert(iNrTrianglesIn == t); -} - -static void DegenEpilogue(STSpace* psTspace, STriInfo* pTriInfos, int* piTriListIn, const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) -{ - int t=0, i=0; - // deal with degenerate triangles - // punishment for degenerate triangles is O(N^2) - for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf - * Note that though the tangent spaces at the vertices are generated in an order-independent way, - * by this implementation, the interpolated tangent space is still affected by which diagonal is - * chosen to split each quad. A sensible solution is to have your tools pipeline always - * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. - * If these have the same length then compare the diagonals defined by the texture coordinates. - * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin - * and also quad triangulator plugin. - */ - - -typedef int tbool; -typedef struct SMikkTSpaceContext SMikkTSpaceContext; - -typedef struct { - // Returns the number of faces (triangles/quads) on the mesh to be processed. - int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); - - // Returns the number of vertices on face number iFace - // iFace is a number in the range {0, 1, ..., getNumFaces()-1} - int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); - - // returns the position/normal/texcoord of the referenced face of vertex number iVert. - // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. - void (*m_getPosition)(const SMikkTSpaceContext * pContext, float* fvPosOut, const int iFace, const int iVert); - void (*m_getNormal)(const SMikkTSpaceContext * pContext, float* fvNormOut, const int iFace, const int iVert); - void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float* fvTexcOut, const int iFace, const int iVert); - - // either (or both) of the two setTSpace callbacks can be set. - // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - // This function is used to return the tangent and fSign to the application. - // fvTangent is a unit length vector. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float* fvTangent, const float fSign, const int iFace, const int iVert); - - // This function is used to return tangent space results to the application. - // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - // true magnitudes which can be used for relief mapping effects. - // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - // However, both are perpendicular to the vertex normal. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float* fvTangent, const float* fvBiTangent, const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert); -} SMikkTSpaceInterface; - -struct SMikkTSpaceContext -{ - SMikkTSpaceInterface * m_pInterface; // initialized with callback functions - void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) -}; - -// these are both thread safe! -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); - - -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. - -#ifdef __cplusplus -} -#endif - -#endif From 76ea78a2c99fc4d2fd86f169e616d55620cbd00d Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:50:36 +0100 Subject: [PATCH 20/87] Move cgmath to dev-dependencies Former-commit-id: d78472e2d7724f28ec29bb1a1c1727eca27c11c1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 453dd0bc557f0..f104a48d2f243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ build = "build.rs" [build-dependencies] cmake = "0.1" -[dependencies] +[dev-dependencies] cgmath = "0.15" [[example]] From 04d7bdd4ed0d6139c5e34c47e8871e8abe98d0cd Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:52:51 +0100 Subject: [PATCH 21/87] Add description field Former-commit-id: 8654d95d71ffefc456d185362c4b8e139b35cb52 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index f104a48d2f243..01660de63782c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "mikktspace" version = "0.1.0" authors = ["Benjamin Wasty "] +description = "Mikkelsen tangent space algorithm" build = "build.rs" [build-dependencies] From e0bacd90812b8c54608620408c6df6f385ddfdeb Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:54:15 +0100 Subject: [PATCH 22/87] Set documentation, repository, and homepage fields Former-commit-id: 24b6ba657455a761ccfeee98bf26d3da663dfc72 --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 01660de63782c..db7ec80bc8c2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "mikktspace" version = "0.1.0" authors = ["Benjamin Wasty "] description = "Mikkelsen tangent space algorithm" +documentation = "https://docs.rs/mikktspace" +repository = "https://github.com/bwasty/mikktspace-rs" +homepage = "https://github.com/bwasty/mikktspace-rs" build = "build.rs" [build-dependencies] From 41adcc20d77ec9d8a427e1fd0984d2acbe13baee Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:54:42 +0100 Subject: [PATCH 23/87] Add readme field Former-commit-id: 54f656f9532c4a62be935438ba4bdab189d860b5 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index db7ec80bc8c2d..ddfcbc6f459f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" repository = "https://github.com/bwasty/mikktspace-rs" homepage = "https://github.com/bwasty/mikktspace-rs" +readme = "README.md" build = "build.rs" [build-dependencies] From 40142d06286440a6249f714b4f749090b2cbb0ce Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:55:45 +0100 Subject: [PATCH 24/87] Add some keywords Former-commit-id: 260211540cf844e558139f1157a5c6f42587dd67 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index ddfcbc6f459f8..99ccefd19f912 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ documentation = "https://docs.rs/mikktspace" repository = "https://github.com/bwasty/mikktspace-rs" homepage = "https://github.com/bwasty/mikktspace-rs" readme = "README.md" +keywords = ["3D", "graphics", "algorithm", "tangent"] build = "build.rs" [build-dependencies] From 0048ac14bdbcb937c5f17f224efe6ff6ea023e1f Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:58:04 +0100 Subject: [PATCH 25/87] Add dual Apache-2.0 / MIT licence Former-commit-id: 4cb2812da7e4230d7fe0bc6620afa697cb6b23ee --- Cargo.toml | 1 + LICENSE-APACHE | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 26 +++++++ README.md | 15 ++++ 4 files changed, 244 insertions(+) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT diff --git a/Cargo.toml b/Cargo.toml index 99ccefd19f912..8b3134ea39f4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/bwasty/mikktspace-rs" homepage = "https://github.com/bwasty/mikktspace-rs" readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] +license = "MIT/Apache-2.0" build = "build.rs" [build-dependencies] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000000000..1e5006dc141e2 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000000000..16b0f84d02846 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2017 The mikktspace Library Developers + +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. + diff --git a/README.md b/README.md index aaf116a48c3c2..ad2a7206d2ef5 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,18 @@ [![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/bwasty/mikktspace-rs) Rust MikkTSpace bindings + +## License agreement + +Licensed under either of + + * Apache License, Version 2.0 + ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license + ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. \ No newline at end of file From 80329ed035b336c7d9e24dc28f806106603ee41a Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 10:59:28 +0100 Subject: [PATCH 26/87] Exclude C example This closes issue #5 Former-commit-id: 8fe70edc1d9b9b3fb6899307e5ef241ebb1a2f0d --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 8b3134ea39f4c..d7e9aad272680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] license = "MIT/Apache-2.0" build = "build.rs" +exclude = ["examples/cube.obj", "examples/generate.c"] [build-dependencies] cmake = "0.1" From 96fea5b5efadac0fb5ee3cbfa21575e139c0f992 Mon Sep 17 00:00:00 2001 From: alteous Date: Wed, 6 Sep 2017 11:01:30 +0100 Subject: [PATCH 27/87] Add Travis CI badge Former-commit-id: 3700dcc0bc70d6df135447116b57c3117ae23a13 --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d7e9aad272680..ba6edaee09bbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ license = "MIT/Apache-2.0" build = "build.rs" exclude = ["examples/cube.obj", "examples/generate.c"] +[badges] +travis-ci = { repository = "bwasty/mikktspace-rs" } + [build-dependencies] cmake = "0.1" From 9b44031cb8f26e47a4d2fffb588d61ea226d9459 Mon Sep 17 00:00:00 2001 From: alteous Date: Thu, 7 Sep 2017 14:07:29 +0100 Subject: [PATCH 28/87] Add @alteous to authors list Former-commit-id: 06a7f6ea755b4075c164050509db950866f8fc68 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ba6edaee09bbc..b9387c7430fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mikktspace" version = "0.1.0" -authors = ["Benjamin Wasty "] +authors = ["Benjamin Wasty ", "David Harvey-Macaulay "] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" repository = "https://github.com/bwasty/mikktspace-rs" From ec5aa738a3dadd226611acde5e7b9e0d25c7c8fc Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 12 Sep 2017 10:42:37 +0100 Subject: [PATCH 29/87] generate -> generate_tangents Former-commit-id: ffb682e8a1d22c92eb7619fb4679c2bb7de47cf2 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c9e41e3d737f8..488b98f554918 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,7 +153,7 @@ impl<'a> Closures<'a> { } /// Generates tangents. -pub fn generate<'a>( +pub fn generate_tangents<'a>( vertices_per_face: &'a Fn() -> usize, face_count: &'a Fn() -> usize, position: &'a Fn(usize, usize) -> &'a [f32; 3], From a4b7821128c0366547c800ff6f62651d49335971 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 12 Sep 2017 10:44:39 +0100 Subject: [PATCH 30/87] Exclude everything in the examples directory Former-commit-id: ebdf1856372bd5eae9e2c0ff126b7a9ee662a38e --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b9387c7430fbc..9d622024615a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,16 +4,16 @@ version = "0.1.0" authors = ["Benjamin Wasty ", "David Harvey-Macaulay "] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" -repository = "https://github.com/bwasty/mikktspace-rs" -homepage = "https://github.com/bwasty/mikktspace-rs" +repository = "https://github.com/gltf-rs/mikktspace" +homepage = "https://github.com/gltf-rs/mikktspace" readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] license = "MIT/Apache-2.0" build = "build.rs" -exclude = ["examples/cube.obj", "examples/generate.c"] +exclude = ["examples/**"] [badges] -travis-ci = { repository = "bwasty/mikktspace-rs" } +travis-ci = { repository = "gltf-rs/mikktspace" } [build-dependencies] cmake = "0.1" From 18fab51991bfbdb810d2aba233754c590675d5e1 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 12 Sep 2017 10:58:26 +0100 Subject: [PATCH 31/87] Update README Former-commit-id: c5cf7e1494f3f9f288ae88d6fd21c66e03cd15d4 --- README.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ad2a7206d2ef5..dd9a9cc1b798f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,30 @@ # mikktspace-rs [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) -[![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/bwasty/mikktspace-rs) +[![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) -Rust MikkTSpace bindings +Bindings to the [Mikkelsen Tangent Space Algorithm] reference implementation. -## License agreement +### Examples + +#### generate + +Demonstrates generating tangents for a cube with 4 triangular faces per side. + +```sh +cargo run --example generate +``` + +There is also an equivalent C example to check the correctness of the Rust bindings. + +```sh +cd examples +cmake ../libmikktspace +make +cc generate.c libmikktspace.a -I../libmikktspace -lm -o generate +./generate +``` + +### License agreement Licensed under either of @@ -17,4 +37,6 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. \ No newline at end of file +dual licensed as above, without any additional terms or conditions. + +[Mikkelsen Tangent Space Algorithm]: (https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) From e9573cc6f30655082626588aa00b8a73492e31e1 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 12 Sep 2017 11:11:05 +0100 Subject: [PATCH 32/87] Update Rust generate example Former-commit-id: 83df1a3f0c9771c6e61056160ebf7d5a6a3c15dd --- examples/generate.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index 20cc0c0fd4d13..10f912a997498 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -6,7 +6,6 @@ use cgmath::prelude::*; pub type Face = [u32; 3]; pub type Vec2 = [f32; 2]; pub type Vec3 = [f32; 3]; -pub type Vec4 = [f32; 4]; #[derive(Debug)] struct Vertex { @@ -15,14 +14,6 @@ struct Vertex { tex_coord: Vec2, } -#[derive(Debug)] -struct NewVertex { - position: Vec3, - normal: Vec3, - tex_coord: Vec2, - tangent: Vec4, -} - fn make_cube() -> (Vec, Vec) { struct ControlPoint { uv: Vec2, @@ -142,17 +133,20 @@ fn main() { let normal = |face, vert| &vertex(face, vert).normal; let tex_coord = |face, vert| &vertex(face, vert).tex_coord; - let mut new_vertices = Vec::new(); { + let mut i = 0; let mut set_tangent = |face, vert, tangent| { - new_vertices.push(NewVertex { - position: *position(face, vert), - normal: *normal(face, vert), - tex_coord: *tex_coord(face, vert), - tangent: tangent, - }); + println!( + "{index}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", + index = i, + v = position(face, vert), + vn = normal(face, vert), + vt = tex_coord(face, vert), + vx = tangent, + ); + i += 1; }; - let ret = mikktspace::generate( + let ret = mikktspace::generate_tangents( &vertices_per_face, &face_count, &position, @@ -162,6 +156,4 @@ fn main() { ); assert_eq!(true, ret); } - - println!("{:#?}", new_vertices); } From 7feb1892d8dbe98984e1191a6e373c77588087db Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Tue, 12 Sep 2017 11:16:03 +0100 Subject: [PATCH 33/87] Fix README Former-commit-id: ed17d10e44d351b855fc531f4e295acaaa64be61 --- README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dd9a9cc1b798f..8f52d1741e67c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# mikktspace-rs +# mikktspace + [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) [![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) -Bindings to the [Mikkelsen Tangent Space Algorithm] reference implementation. +Bindings to the [Mikkelsen Tangent Space Algorithm](https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. -### Examples +## Examples -#### generate +### generate Demonstrates generating tangents for a cube with 4 triangular faces per side. @@ -24,7 +25,7 @@ cc generate.c libmikktspace.a -I../libmikktspace -lm -o generate ./generate ``` -### License agreement +## License agreement Licensed under either of @@ -38,5 +39,3 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. - -[Mikkelsen Tangent Space Algorithm]: (https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) From bd430829082eaab6bd5f0afbfe8adc1566f7fbd9 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Tue, 12 Sep 2017 22:15:54 +0200 Subject: [PATCH 34/87] Fix travis badge in readme Former-commit-id: 74a228d1b89ab973e80292faaca8e273f67bec03 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8f52d1741e67c..aee0c31285065 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # mikktspace [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) -[![Build Status](https://travis-ci.org/bwasty/mikktspace-rs.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) +[![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) Bindings to the [Mikkelsen Tangent Space Algorithm](https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. From ede02d90034aee71edd46c56bbc3b0fb0d2576e9 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Sun, 24 Sep 2017 22:24:23 +0200 Subject: [PATCH 35/87] CMake: replace -c11 with -c1x for GCC 4.6 From https://stackoverflow.com/questions/16256586/how-to-enable-c11-on-later-versions-of-gcc --- libmikktspace/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libmikktspace/CMakeLists.txt b/libmikktspace/CMakeLists.txt index 7c0e879e53f6a..2ca07ddf4df9e 100644 --- a/libmikktspace/CMakeLists.txt +++ b/libmikktspace/CMakeLists.txt @@ -3,7 +3,7 @@ project(mikktspace) set(PROJECT_VERSION_MAJOR "0") set(PROJECT_VERSION_MINOR "1") set(PROJECT_VERSION_PATCH "0") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c11") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c1x") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") set(SOURCES mikktspace.h mikktspace.c) From 37763b07ff89b7a3d2bbaa1b7e51a5ed01458403 Mon Sep 17 00:00:00 2001 From: David LeGare Date: Wed, 18 Jul 2018 08:30:46 -0500 Subject: [PATCH 36/87] Build mikktspace using cc instead of cmake --- .gitignore | 2 -- Cargo.toml | 2 +- build.rs | 10 +++++----- libmikktspace/CMakeLists.txt | 11 ----------- 4 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 libmikktspace/CMakeLists.txt diff --git a/.gitignore b/.gitignore index 5d6a2baca9fd4..6aa106405a4b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ /target/ -/mikktspace-sys/target/ -/mikktspace-sys/libmikktspace/libmikktspace.a **/*.rs.bk Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 9d622024615a0..95781948438a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["examples/**"] travis-ci = { repository = "gltf-rs/mikktspace" } [build-dependencies] -cmake = "0.1" +cc = "1.0" [dev-dependencies] cgmath = "0.15" diff --git a/build.rs b/build.rs index 155a9ed067b08..0040ba9733d74 100644 --- a/build.rs +++ b/build.rs @@ -1,9 +1,9 @@ - -extern crate cmake; +extern crate cc; fn main() { - let dst = cmake::build("libmikktspace"); - println!("cargo:rustc-link-search=native={}", dst.display()); + cc::Build::new() + .file("libmikktspace/mikktspace.c") + .file("libmikktspace/mikktspace.c") + .compile("mikktspace"); println!("cargo:rustc-link-lib=static=mikktspace"); } - diff --git a/libmikktspace/CMakeLists.txt b/libmikktspace/CMakeLists.txt deleted file mode 100644 index 2ca07ddf4df9e..0000000000000 --- a/libmikktspace/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 2.8) -project(mikktspace) -set(PROJECT_VERSION_MAJOR "0") -set(PROJECT_VERSION_MINOR "1") -set(PROJECT_VERSION_PATCH "0") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c1x") -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG} -ggdb -DDEBUG") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE} -O2") -set(SOURCES mikktspace.h mikktspace.c) -add_library(mikktspace STATIC ${SOURCES}) -install(TARGETS mikktspace ARCHIVE DESTINATION ".") From b3ae27e22eeecfd4bd5bf7739b94789d56c9854a Mon Sep 17 00:00:00 2001 From: David LeGare Date: Wed, 18 Jul 2018 10:00:30 -0500 Subject: [PATCH 37/87] Remove redundant rustc link directive --- build.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/build.rs b/build.rs index 0040ba9733d74..98a50ad9e5663 100644 --- a/build.rs +++ b/build.rs @@ -5,5 +5,4 @@ fn main() { .file("libmikktspace/mikktspace.c") .file("libmikktspace/mikktspace.c") .compile("mikktspace"); - println!("cargo:rustc-link-lib=static=mikktspace"); } From 0c37e5d5064eb081a2501e5f6913399e60a9581b Mon Sep 17 00:00:00 2001 From: David LeGare Date: Wed, 18 Jul 2018 16:57:55 -0500 Subject: [PATCH 38/87] Fix typo in build.rs --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 98a50ad9e5663..efd6729168ef8 100644 --- a/build.rs +++ b/build.rs @@ -2,7 +2,7 @@ extern crate cc; fn main() { cc::Build::new() - .file("libmikktspace/mikktspace.c") + .file("libmikktspace/mikktspace.h") .file("libmikktspace/mikktspace.c") .compile("mikktspace"); } From 64352c21ce86bfb4533296e54188947f4a28f231 Mon Sep 17 00:00:00 2001 From: David LeGare Date: Thu, 19 Jul 2018 07:54:37 -0500 Subject: [PATCH 39/87] Fix build.rs to work on Linux --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index efd6729168ef8..2994458387e00 100644 --- a/build.rs +++ b/build.rs @@ -2,7 +2,7 @@ extern crate cc; fn main() { cc::Build::new() - .file("libmikktspace/mikktspace.h") .file("libmikktspace/mikktspace.c") + .include("libmikktspace") .compile("mikktspace"); } From 2f79db35cc33d466928baca15e77857788110352 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sun, 29 Jul 2018 13:58:45 +0100 Subject: [PATCH 40/87] Version 0.1.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 95781948438a7..139b7c75eaf7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mikktspace" -version = "0.1.0" +version = "0.1.1" authors = ["Benjamin Wasty ", "David Harvey-Macaulay "] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" From dffcb0aefa9be2a18eec1d18f3ffac64169c1552 Mon Sep 17 00:00:00 2001 From: Benjamin Wasty Date: Mon, 14 Jan 2019 00:21:29 +0100 Subject: [PATCH 41/87] fix link to blender wiki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aee0c31285065..61cdd10385f41 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) [![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) -Bindings to the [Mikkelsen Tangent Space Algorithm](https://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. +Bindings to the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. ## Examples From 43ec58f3219881c7c8fa361a738700abadf658d0 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 16:56:34 +0200 Subject: [PATCH 42/87] Replace bindings with Rust equivalent --- Cargo.toml | 12 +- README.md | 14 +- build.rs | 8 - examples/generate.c | 211 ---- examples/generate.rs | 111 +- libmikktspace/mikktspace.c | 1890 --------------------------------- libmikktspace/mikktspace.h | 145 --- src/ffi.rs | 129 --- src/generated.rs | 2036 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 210 +--- 10 files changed, 2160 insertions(+), 2606 deletions(-) delete mode 100644 build.rs delete mode 100644 examples/generate.c delete mode 100644 libmikktspace/mikktspace.c delete mode 100644 libmikktspace/mikktspace.h delete mode 100644 src/ffi.rs create mode 100644 src/generated.rs diff --git a/Cargo.toml b/Cargo.toml index 139b7c75eaf7f..834d175cc6fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "mikktspace" version = "0.1.1" -authors = ["Benjamin Wasty ", "David Harvey-Macaulay "] +edition = "2018" +authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" repository = "https://github.com/gltf-rs/mikktspace" @@ -9,17 +10,14 @@ homepage = "https://github.com/gltf-rs/mikktspace" readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] license = "MIT/Apache-2.0" -build = "build.rs" exclude = ["examples/**"] [badges] travis-ci = { repository = "gltf-rs/mikktspace" } -[build-dependencies] -cc = "1.0" - -[dev-dependencies] -cgmath = "0.15" +[dependencies] +libc = "0.2.54" +nalgebra = "0.18.0" [[example]] name = "generate" diff --git a/README.md b/README.md index 61cdd10385f41..6a8aa1ccccb4a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) [![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) -Bindings to the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. +Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. + +Machine generated with modifications for better integration with Rust. ## Examples @@ -15,16 +17,6 @@ Demonstrates generating tangents for a cube with 4 triangular faces per side. cargo run --example generate ``` -There is also an equivalent C example to check the correctness of the Rust bindings. - -```sh -cd examples -cmake ../libmikktspace -make -cc generate.c libmikktspace.a -I../libmikktspace -lm -o generate -./generate -``` - ## License agreement Licensed under either of diff --git a/build.rs b/build.rs deleted file mode 100644 index 2994458387e00..0000000000000 --- a/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -extern crate cc; - -fn main() { - cc::Build::new() - .file("libmikktspace/mikktspace.c") - .include("libmikktspace") - .compile("mikktspace"); -} diff --git a/examples/generate.c b/examples/generate.c deleted file mode 100644 index ae230376c5d2c..0000000000000 --- a/examples/generate.c +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "mikktspace.h" - -struct tangent { - // Vector. - float v[3]; - - // Sign. - float s; -}; - -struct vertex { - // Borrows from `input.positions`. - float (*position)[3]; - - // Borrows from `input.positions`. - float (*normal)[3]; - - // Borrows from `input.positions`. - float (*tex_coord)[2]; -}; - -struct face { - // Borrows from `positions`, `normals`, and `tex_coords`. - struct vertex vertices[3]; -}; - -struct input { - // Owned. - float (*positions)[3]; - - // Owned. - float (*normals)[3]; - - // Owned. - float (*tex_coords)[2]; - - // Borrows from `positions`, `normals`, and `tex_coords`. - struct face *faces; - - // Number of entries in `positions`, `normals`, and `tex_coords`. - size_t nr_vertices; - - // Number of entries in `faces`. - size_t nr_faces; -} input; - -void print_vec2(float (*t)[2]) { - printf("[%f, %f]", (*t)[0], (*t)[1]); -} - -void print_vec3(float (*t)[3]) { - printf("[%f, %f, %f]", (*t)[0], (*t)[1], (*t)[2]); -} - -void print_tangent(const struct tangent *t) { - printf("[%f, %f, %f, %f]", t->v[0], t->v[1], t->v[2], t->s); -} - -int get_num_faces(const SMikkTSpaceContext *x) { - return input.nr_faces; -} - -int get_num_vertices_of_face(const SMikkTSpaceContext *x, int f) { - return 3; -} - -void get_position(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[3] = input.faces[f].vertices[v].position; - memcpy(dst, *src, 3 * sizeof(float)); -} - -void get_normal(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[3] = input.faces[f].vertices[v].normal; - memcpy(dst, *src, 3 * sizeof(float)); -} - -void get_tex_coord(const SMikkTSpaceContext *x, float *dst, int f, int v) { - float (*src)[2] = input.faces[f].vertices[v].tex_coord; - memcpy(dst, *src, 2 * sizeof(float)); -} - -void set_tspace_basic( - const SMikkTSpaceContext *x, - const float *t, - float s, - int f, - int v -) { - // The index of the last output (vertex, tangent) pair. - static int i = 0; - - struct vertex *in = &input.faces[f].vertices[v]; - - printf("%d: { v: ", i); - print_vec3(in->position); - printf(", vn: "); - print_vec3(in->normal); - printf(", vt: "); - print_vec2(in->tex_coord); - printf(", vx: [%f, %f, %f, %f] }", t[0], t[1], t[2], s); - puts(""); - - ++i; -} - -void set_tspace( - const SMikkTSpaceContext *x, - const float *t, - const float *b, - float mag_s, - float mag_t, - tbool op, - int f, - int v -) { - set_tspace_basic(x, t, op != 0 ? 1.0f : -1.0f, f, v); -} - -int main() { - input.nr_vertices = 30; - input.nr_faces = 24; - - input.positions = calloc(input.nr_vertices, sizeof(*input.positions)); - input.normals = calloc(input.nr_vertices, sizeof(*input.normals)); - input.tex_coords = calloc(input.nr_vertices, sizeof(*input.tex_coords)); - input.faces = calloc(input.nr_faces, sizeof(*input.faces)); - - FILE *fi = fopen("cube.obj", "rb"); - assert(fi); - char buffer[1024]; - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "v %f %f %f", - &input.positions[i][0], - &input.positions[i][1], - &input.positions[i][2] - ); - } - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "vn %f %f %f", - &input.normals[i][0], - &input.normals[i][1], - &input.normals[i][2] - ); - } - - for (size_t i = 0; i < input.nr_vertices; ++i) { - fgets(buffer, sizeof(buffer), fi); - sscanf( - buffer, - "vt %f %f", - &input.tex_coords[i][0], - &input.tex_coords[i][1] - ); - } - - for (size_t i = 0; i < input.nr_faces; ++i) { - fgets(buffer, sizeof(buffer), fi); - int v[3]; - sscanf( - buffer, - "f %d/%d/%d %d/%d/%d %d/%d/%d", - &v[0], &v[0], &v[0], - &v[1], &v[1], &v[1], - &v[2], &v[2], &v[2] - ); - for (size_t j = 0; j < 3; ++j) { - input.faces[i].vertices[j].position = &input.positions[v[j] - 1]; - input.faces[i].vertices[j].normal = &input.normals[v[j] - 1]; - input.faces[i].vertices[j].tex_coord = &input.tex_coords[v[j] - 1]; - } - } - - SMikkTSpaceInterface interface = { - .m_getNumFaces = get_num_faces, - .m_getNumVerticesOfFace = get_num_vertices_of_face, - .m_getPosition = get_position, - .m_getNormal = get_normal, - .m_getTexCoord = get_tex_coord, - .m_setTSpaceBasic = set_tspace_basic, - .m_setTSpace = set_tspace, - }; - SMikkTSpaceContext context = { - .m_pInterface = &interface, - .m_pUserData = NULL, - }; - - genTangSpaceDefault(&context); - - fclose(fi); - free(input.positions); - free(input.normals); - free(input.tex_coords); - free(input.faces); - - return 0; -} diff --git a/examples/generate.rs b/examples/generate.rs index 10f912a997498..41882e369a62d 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -1,23 +1,62 @@ -extern crate cgmath; -extern crate mikktspace; - -use cgmath::prelude::*; +use nalgebra::{Point2, Point3, Vector3, Vector4}; pub type Face = [u32; 3]; -pub type Vec2 = [f32; 2]; -pub type Vec3 = [f32; 3]; #[derive(Debug)] struct Vertex { - position: Vec3, - normal: Vec3, - tex_coord: Vec2, + position: Point3, + normal: Vector3, + tex_coord: Point2, +} + +struct Mesh { + faces: Vec, + vertices: Vec, +} + +fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { + let vs: &[u32; 3] = &mesh.faces[face]; + &mesh.vertices[vs[vert] as usize] +} + +impl mikktspace::Geometry for Mesh { + fn get_num_faces(&self) -> usize { + self.faces.len() + } + + fn get_num_vertices_of_face(&self, _face: usize) -> usize { + 3 + } + + fn get_position(&self, face: usize, vert: usize) -> Point3 { + vertex(self, face, vert).position + } + + fn get_normal(&self, face: usize, vert: usize) -> Vector3 { + vertex(self, face, vert).normal + } + + fn get_tex_coord(&self, face: usize, vert: usize) -> Point2 { + vertex(self, face, vert).tex_coord + } + + fn set_tangent_encoded(&mut self, tangent: Vector4, face: usize, vert: usize) { + println!( + "{face}-{vert}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", + face = face, + vert = vert, + v = vertex(self, face, vert).position.coords.data, + vn = vertex(self, face, vert).normal.data, + vt = vertex(self, face, vert).tex_coord.coords.data, + vx = tangent.data, + ); + } } -fn make_cube() -> (Vec, Vec) { +fn make_cube() -> Mesh { struct ControlPoint { - uv: Vec2, - dir: Vec3, + uv: [f32; 2], + dir: [f32; 3], } let mut faces = Vec::new(); let mut ctl_pts = Vec::new(); @@ -108,9 +147,9 @@ fn make_cube() -> (Vec, Vec) { } for pt in ctl_pts { - let p: cgmath::Vector3 = pt.dir.into(); - let n: cgmath::Vector3 = p.normalize(); - let t: cgmath::Vector2 = pt.uv.into(); + let p: Point3 = pt.dir.into(); + let n: Vector3 = p.coords.normalize(); + let t: Point2 = pt.uv.into(); vertices.push(Vertex { position: (p / 2.0).into(), normal: n.into(), @@ -118,42 +157,14 @@ fn make_cube() -> (Vec, Vec) { }); } - (faces, vertices) + Mesh { + faces, + vertices, + } } fn main() { - let (faces, vertices) = make_cube(); - let vertex = |face, vert| { - let vs: &[u32; 3] = &faces[face]; - &vertices[vs[vert] as usize] - }; - let vertices_per_face = || 3; - let face_count = || faces.len(); - let position = |face, vert| &vertex(face, vert).position; - let normal = |face, vert| &vertex(face, vert).normal; - let tex_coord = |face, vert| &vertex(face, vert).tex_coord; - - { - let mut i = 0; - let mut set_tangent = |face, vert, tangent| { - println!( - "{index}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", - index = i, - v = position(face, vert), - vn = normal(face, vert), - vt = tex_coord(face, vert), - vx = tangent, - ); - i += 1; - }; - let ret = mikktspace::generate_tangents( - &vertices_per_face, - &face_count, - &position, - &normal, - &tex_coord, - &mut set_tangent, - ); - assert_eq!(true, ret); - } + let mut cube = make_cube(); + let ret = mikktspace::generate_tangents_default(&mut cube); + assert_eq!(true, ret); } diff --git a/libmikktspace/mikktspace.c b/libmikktspace/mikktspace.c deleted file mode 100644 index 62aa2da251740..0000000000000 --- a/libmikktspace/mikktspace.c +++ /dev/null @@ -1,1890 +0,0 @@ -/** \file mikktspace/mikktspace.c - * \ingroup mikktspace - */ -/** - * Copyright (C) 2011 by Morten S. Mikkelsen - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - */ - -#include -#include -#include -#include -#include -#include - -#include "mikktspace.h" - -#define TFALSE 0 -#define TTRUE 1 - -#ifndef M_PI -#define M_PI 3.1415926535897932384626433832795 -#endif - -#define INTERNAL_RND_SORT_SEED 39871946 - -// internal structure -typedef struct { - float x, y, z; -} SVec3; - -static tbool veq( const SVec3 v1, const SVec3 v2 ) -{ - return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); -} - -static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x + v2.x; - vRes.y = v1.y + v2.y; - vRes.z = v1.z + v2.z; - - return vRes; -} - - -static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) -{ - SVec3 vRes; - - vRes.x = v1.x - v2.x; - vRes.y = v1.y - v2.y; - vRes.z = v1.z - v2.z; - - return vRes; -} - -static SVec3 vscale(const float fS, const SVec3 v) -{ - SVec3 vRes; - - vRes.x = fS * v.x; - vRes.y = fS * v.y; - vRes.z = fS * v.z; - - return vRes; -} - -static float LengthSquared( const SVec3 v ) -{ - return v.x*v.x + v.y*v.y + v.z*v.z; -} - -static float Length( const SVec3 v ) -{ - return sqrtf(LengthSquared(v)); -} - -static SVec3 Normalize( const SVec3 v ) -{ - return vscale(1 / Length(v), v); -} - -static float vdot( const SVec3 v1, const SVec3 v2) -{ - return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; -} - - -static tbool NotZero(const float fX) -{ - // could possibly use FLT_EPSILON instead - return fabsf(fX) > FLT_MIN; -} - -static tbool VNotZero(const SVec3 v) -{ - // might change this to an epsilon based test - return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); -} - - - -typedef struct { - int iNrFaces; - int * pTriMembers; -} SSubGroup; - -typedef struct { - int iNrFaces; - int * pFaceIndices; - int iVertexRepresentitive; - tbool bOrientPreservering; -} SGroup; - -// -#define MARK_DEGENERATE 1 -#define QUAD_ONE_DEGEN_TRI 2 -#define GROUP_WITH_ANY 4 -#define ORIENT_PRESERVING 8 - - - -typedef struct { - int FaceNeighbors[3]; - SGroup * AssignedGroup[3]; - - // normalized first order face derivatives - SVec3 vOs, vOt; - float fMagS, fMagT; // original magnitudes - - // determines if the current and the next triangle are a quad. - int iOrgFaceNumber; - int iFlag, iTSpacesOffs; - unsigned char vert_num[4]; -} STriInfo; - -typedef struct { - SVec3 vOs; - float fMagS; - SVec3 vOt; - float fMagT; - int iCounter; // this is to average back into quads. - tbool bOrient; -} STSpace; - -static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); -static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, - const SMikkTSpaceContext * pContext); - -static int MakeIndex(const int iFace, const int iVert) -{ - assert(iVert>=0 && iVert<4 && iFace>=0); - return (iFace<<2) | (iVert&0x3); -} - -static void IndexToData(int * piFace, int * piVert, const int iIndexIn) -{ - piVert[0] = iIndexIn&0x3; - piFace[0] = iIndexIn>>2; -} - -static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) -{ - STSpace ts_res; - - // this if is important. Due to floating point precision - // averaging when ts0==ts1 will cause a slight difference - // which results in tangent space splits later on - if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && - veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) - { - ts_res.fMagS = pTS0->fMagS; - ts_res.fMagT = pTS0->fMagT; - ts_res.vOs = pTS0->vOs; - ts_res.vOt = pTS0->vOt; - } - else - { - ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); - ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); - ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); - ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); - if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); - if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); - } - - return ts_res; -} - - - -static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); - - -// degen triangles -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); - - -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) -{ - return genTangSpace(pContext, 180.0f); -} - -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) -{ - // count nr_triangles - int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; - STriInfo * pTriInfos = NULL; - SGroup * pGroups = NULL; - STSpace * psTspace = NULL; - int iNrTrianglesIn = 0, f=0, t=0, i=0; - int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; - int iNrActiveGroups = 0, index = 0; - const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); - tbool bRes = TFALSE; - const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); - - // verify all call-backs have been set - if ( pContext->m_pInterface->m_getNumFaces==NULL || - pContext->m_pInterface->m_getNumVerticesOfFace==NULL || - pContext->m_pInterface->m_getPosition==NULL || - pContext->m_pInterface->m_getNormal==NULL || - pContext->m_pInterface->m_getTexCoord==NULL ) - return TFALSE; - - // count triangles on supported faces - for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts==3) ++iNrTrianglesIn; - else if (verts==4) iNrTrianglesIn += 2; - } - if (iNrTrianglesIn<=0) return TFALSE; - - // allocate memory for an index list - piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); - pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); - if (piTriListIn==NULL || pTriInfos==NULL) - { - if (piTriListIn!=NULL) free(piTriListIn); - if (pTriInfos!=NULL) free(pTriInfos); - return TFALSE; - } - - // make an initial triangle --> face index list - iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); - - // make a welded index list of identical positions and attributes (pos, norm, texc) - //printf("gen welded index list begin\n"); - GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); - //printf("gen welded index list end\n"); - - // Mark all degenerate triangles - iTotTris = iNrTrianglesIn; - iDegenTriangles = 0; - for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - - // I've decided to let degenerate triangles and group-with-anythings - // vary between left/right hand coordinate systems at the vertices. - // All healthy triangles on the other hand are built to always be either or. - - /*// force the coordinate system orientation to be uniform for every face. - // (this is already the case for good triangles but not for - // degenerate ones and those with bGroupWithAnything==true) - bool bOrient = psTspace[index].bOrient; - if (psTspace[index].iCounter == 0) // tspace was not derived from a group - { - // look for a space created in GenerateTSpaces() by iCounter>0 - bool bNotFound = true; - int i=1; - while (i 0) bNotFound=false; - else ++i; - } - if (!bNotFound) bOrient = psTspace[index+i].bOrient; - }*/ - - // set data - for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; - float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; - if (pContext->m_pInterface->m_setTSpace!=NULL) - pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); - if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) - pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); - - ++index; - } - } - - free(psTspace); - - - return TTRUE; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef struct { - float vert[3]; - int index; -} STmpVert; - -static const int g_iCells = 2048; - -#ifdef _MSC_VER - #define NOINLINE __declspec(noinline) -#else - #define NOINLINE __attribute__ ((noinline)) -#endif - -// it is IMPORTANT that this function is called to evaluate the hash since -// inlining could potentially reorder instructions and generate different -// results for the same effective input value fVal. -static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) -{ - const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); - const int iIndex = (int)fIndex; - return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); -} - -static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); -static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); -static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); - -static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - - // Generate bounding box - int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; - STmpVert * pTmpVert = NULL; - int i=0, iChannel=0, k=0, e=0; - int iMaxCount=0; - SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; - float fMin, fMax; - for (i=1; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - - const SVec3 vP = GetPosition(pContext, index); - if (vMin.x > vP.x) vMin.x = vP.x; - else if (vMax.x < vP.x) vMax.x = vP.x; - if (vMin.y > vP.y) vMin.y = vP.y; - else if (vMax.y < vP.y) vMax.y = vP.y; - if (vMin.z > vP.z) vMin.z = vP.z; - else if (vMax.z < vP.z) vMax.z = vP.z; - } - - vDim = vsub(vMax,vMin); - iChannel = 0; - fMin = vMin.x; fMax=vMax.x; - if (vDim.y>vDim.x && vDim.y>vDim.z) - { - iChannel=1; - fMin = vMin.y, fMax=vMax.y; - } - else if (vDim.z>vDim.x) - { - iChannel=2; - fMin = vMin.z, fMax=vMax.z; - } - - // make allocations - piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); - piHashCount = (int *) malloc(sizeof(int)*g_iCells); - piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); - piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); - - if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) - { - if (piHashTable!=NULL) free(piHashTable); - if (piHashCount!=NULL) free(piHashCount); - if (piHashOffsets!=NULL) free(piHashOffsets); - if (piHashCount2!=NULL) free(piHashCount2); - GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); - return; - } - memset(piHashCount, 0, sizeof(int)*g_iCells); - memset(piHashCount2, 0, sizeof(int)*g_iCells); - - // count amount of elements in each cell unit - for (i=0; i<(iNrTrianglesIn*3); i++) - { - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); - const int iCell = FindGridCell(fMin, fMax, fVal); - ++piHashCount[iCell]; - } - - // evaluate start index of each cell. - piHashOffsets[0]=0; - for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; - else if (fvMax[c]dx && dy>dz) channel=1; - else if (dz>dx) channel=2; - - fSep = 0.5f*(fvMax[channel]+fvMin[channel]); - - // terminate recursion when the separation/average value - // is no longer strictly between fMin and fMax values. - if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) - { - // complete the weld - for (l=iL_in; l<=iR_in; l++) - { - int i = pTmpVert[l].index; - const int index = piTriList_in_and_out[i]; - const SVec3 vP = GetPosition(pContext, index); - const SVec3 vN = GetNormal(pContext, index); - const SVec3 vT = GetTexCoord(pContext, index); - - tbool bNotFound = TTRUE; - int l2=iL_in, i2rec=-1; - while (l20); // at least 2 entries - - // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] - while (iL < iR) - { - tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; - while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); - bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); - bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) - { - const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); - if (verts!=3 && verts!=4) continue; - - pTriInfos[iDstTriIndex].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; - - if (verts==3) - { - unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; - pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; - piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); - piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); - piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); - ++iDstTriIndex; // next - } - else - { - { - pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; - pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; - } - - { - // need an order independent way to evaluate - // tspace on quads. This is done by splitting - // along the shortest diagonal. - const int i0 = MakeIndex(f, 0); - const int i1 = MakeIndex(f, 1); - const int i2 = MakeIndex(f, 2); - const int i3 = MakeIndex(f, 3); - const SVec3 T0 = GetTexCoord(pContext, i0); - const SVec3 T1 = GetTexCoord(pContext, i1); - const SVec3 T2 = GetTexCoord(pContext, i2); - const SVec3 T3 = GetTexCoord(pContext, i3); - const float distSQ_02 = LengthSquared(vsub(T2,T0)); - const float distSQ_13 = LengthSquared(vsub(T3,T1)); - tbool bQuadDiagIs_02; - if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); - res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; - return res; -} - -static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float norm[3]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); - res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; - return res; -} - -static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) -{ - int iF, iI; - SVec3 res; float texc[2]; - IndexToData(&iF, &iI, index); - pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); - res.x=texc[0]; res.y=texc[1]; res.z=1.0f; - return res; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef union { - struct - { - int i0, i1, f; - }; - int array[3]; -} SEdge; - -static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); -static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); - -// returns the texture area times 2 -static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) -{ - const SVec3 t1 = GetTexCoord(pContext, indices[0]); - const SVec3 t2 = GetTexCoord(pContext, indices[1]); - const SVec3 t3 = GetTexCoord(pContext, indices[2]); - - const float t21x = t2.x-t1.x; - const float t21y = t2.y-t1.y; - const float t31x = t3.x-t1.x; - const float t31y = t3.y-t1.y; - - const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; - - return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; -} - -static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) -{ - int f=0, i=0, t=0; - // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. - - // generate neighbor info list - for (f=0; f0 ? ORIENT_PRESERVING : 0); - - if ( NotZero(fSignedAreaSTx2) ) - { - const float fAbsArea = fabsf(fSignedAreaSTx2); - const float fLenOs = Length(vOs); - const float fLenOt = Length(vOt); - const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; - if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); - if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); - - // evaluate magnitudes prior to normalization of vOs and vOt - pTriInfos[f].fMagS = fLenOs / fAbsArea; - pTriInfos[f].fMagT = fLenOt / fAbsArea; - - // if this is a good triangle - if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) - pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); - } - } - - // force otherwise healthy quads to a fixed orientation - while (t<(iNrTrianglesIn-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - - // bad triangles should already have been removed by - // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false - if ((bIsDeg_a||bIsDeg_b)==TFALSE) - { - const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - // if this happens the quad has extremely bad mapping!! - if (bOrientA!=bOrientB) - { - //printf("found quad with bad mapping\n"); - tbool bChooseOrientFirstTri = TFALSE; - if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; - else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) - bChooseOrientFirstTri = TTRUE; - - // force match - { - const int t0 = bChooseOrientFirstTri ? t : (t+1); - const int t1 = bChooseOrientFirstTri ? (t+1) : t; - pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first - pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit - } - } - } - t += 2; - } - else - ++t; - } - - // match up edge pairs - { - SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); - if (pEdges==NULL) - BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); - else - { - BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); - - free(pEdges); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); - -static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) -{ - const int iNrMaxGroups = iNrTrianglesIn*3; - int iNrActiveGroups = 0; - int iOffset = 0, f=0, i=0; - (void)iNrMaxGroups; /* quiet warnings in non debug mode */ - for (f=0; fiVertexRepresentitive = vert_index; - pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; - pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; - pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; - ++iNrActiveGroups; - - AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); - bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - neigh_indexL = pTriInfos[f].FaceNeighbors[i]; - neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexL, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - if (neigh_indexR>=0) // neighbor - { - const tbool bAnswer = - AssignRecur(piTriListIn, pTriInfos, neigh_indexR, - pTriInfos[f].AssignedGroup[i] ); - - const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; - assert(bAnswer || bDiff); - (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ - } - - // update offset - iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; - // since the groups are disjoint a triangle can never - // belong to more than 3 groups. Subsequently something - // is completely screwed if this assertion ever hits. - assert(iOffset <= iNrMaxGroups); - } - } - } - - return iNrActiveGroups; -} - -static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) -{ - pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; - ++pGroup->iNrFaces; -} - -static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], - const int iMyTriIndex, SGroup * pGroup) -{ - STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; - - // track down vertex - const int iVertRep = pGroup->iVertexRepresentitive; - const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; - int i=-1; - if (pVerts[0]==iVertRep) i=0; - else if (pVerts[1]==iVertRep) i=1; - else if (pVerts[2]==iVertRep) i=2; - assert(i>=0 && i<3); - - // early out - if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; - else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; - if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) - { - // first to group with a group-with-anything triangle - // determines it's orientation. - // This is the only existing order dependency in the code!! - if ( pMyTriInfo->AssignedGroup[0] == NULL && - pMyTriInfo->AssignedGroup[1] == NULL && - pMyTriInfo->AssignedGroup[2] == NULL ) - { - pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); - pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); - } - } - { - const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; - if (bOrient != pGroup->bOrientPreservering) return TFALSE; - } - - AddTriToGroup(pGroup, iMyTriIndex); - pMyTriInfo->AssignedGroup[i] = pGroup; - - { - const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; - const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; - if (neigh_indexL>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); - if (neigh_indexR>=0) - AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); - } - - - - return TTRUE; -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////////////// - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); -static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); - -static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], - const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, - const SMikkTSpaceContext * pContext) -{ - STSpace * pSubGroupTspace = NULL; - SSubGroup * pUniSubGroups = NULL; - int * pTmpMembers = NULL; - int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; - for (g=0; giNrFaces; i++) // triangles - { - const int f = pGroup->pFaceIndices[i]; // triangle number - int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; - SSubGroup tmp_group; - tbool bFound; - SVec3 n, vOs, vOt; - if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; - else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; - else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; - assert(index>=0 && index<3); - - iVertIndex = piTriListIn[f*3+index]; - assert(iVertIndex==pGroup->iVertexRepresentitive); - - // is normalized already - n = GetNormal(pContext, iVertIndex); - - // project - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - // original face number - iOF_1 = pTriInfos[f].iOrgFaceNumber; - - iMembers = 0; - for (j=0; jiNrFaces; j++) - { - const int t = pGroup->pFaceIndices[j]; // triangle number - const int iOF_2 = pTriInfos[t].iOrgFaceNumber; - - // project - SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); - SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); - if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); - if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); - - { - const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; - // make sure triangles which belong to the same quad are joined. - const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; - - const float fCosS = vdot(vOs,vOs2); - const float fCosT = vdot(vOt,vOt2); - - assert(f!=t || bSameOrgFace); // sanity check - if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) - pTmpMembers[iMembers++] = t; - } - } - - // sort pTmpMembers - tmp_group.iNrFaces = iMembers; - tmp_group.pTriMembers = pTmpMembers; - if (iMembers>1) - { - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - QuickSort(pTmpMembers, 0, iMembers-1, uSeed); - } - - // look for an existing match - bFound = TFALSE; - l=0; - while (liVertexRepresentitive); - ++iUniqueSubGroups; - } - - // output tspace - { - const int iOffs = pTriInfos[f].iTSpacesOffs; - const int iVert = pTriInfos[f].vert_num[index]; - STSpace * pTS_out = &psTspace[iOffs+iVert]; - assert(pTS_out->iCounter<2); - assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); - if (pTS_out->iCounter==1) - { - *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); - pTS_out->iCounter = 2; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - else - { - assert(pTS_out->iCounter==0); - *pTS_out = pSubGroupTspace[l]; - pTS_out->iCounter = 1; // update counter - pTS_out->bOrient = pGroup->bOrientPreservering; - } - } - } - - // clean up and offset iUniqueTspaces - for (s=0; s=0 && i<3); - - // project - index = piTriListIn[3*f+i]; - n = GetNormal(pContext, index); - vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); - vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); - if ( VNotZero(vOs) ) vOs = Normalize(vOs); - if ( VNotZero(vOt) ) vOt = Normalize(vOt); - - i2 = piTriListIn[3*f + (i<2?(i+1):0)]; - i1 = piTriListIn[3*f + i]; - i0 = piTriListIn[3*f + (i>0?(i-1):2)]; - - p0 = GetPosition(pContext, i0); - p1 = GetPosition(pContext, i1); - p2 = GetPosition(pContext, i2); - v1 = vsub(p0,p1); - v2 = vsub(p2,p1); - - // project - v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); - v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); - - // weight contribution by the angle - // between the two edge vectors - fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); - fAngle = (float) acos(fCos); - fMagS = pTriInfos[f].fMagS; - fMagT = pTriInfos[f].fMagT; - - res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); - res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); - res.fMagS+=(fAngle*fMagS); - res.fMagT+=(fAngle*fMagT); - fAngleSum += fAngle; - } - } - - // normalize - if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); - if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); - if (fAngleSum>0) - { - res.fMagS /= fAngleSum; - res.fMagT /= fAngleSum; - } - - return res; -} - -static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) -{ - tbool bStillSame=TTRUE; - int i=0; - if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; - while (iiNrFaces && bStillSame) - { - bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; - if (bStillSame) ++i; - } - return bStillSame; -} - -static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) -{ - int iL, iR, n, index, iMid, iTmp; - - // Random - unsigned int t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft; iR=iRight; - n = (iR-iL)+1; - assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL]; - - - do - { - while (pSortBuffer[iL] < iMid) - ++iL; - while (pSortBuffer[iR] > iMid) - --iR; - - if (iL <= iR) - { - iTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = iTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSort(pSortBuffer, iLeft, iR, uSeed); - if (iL < iRight) - QuickSort(pSortBuffer, iL, iRight, uSeed); -} - -///////////////////////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////////////////////// - -static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); - -static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) -{ - // build array of edges - unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? - int iEntries=0, iCurStartIndex=-1, f=0, i=0; - for (f=0; f pSortBuffer[iRight].array[channel]) - { - sTmp = pSortBuffer[iLeft]; - pSortBuffer[iLeft] = pSortBuffer[iRight]; - pSortBuffer[iRight] = sTmp; - } - return; - } - - // Random - t=uSeed&31; - t=(uSeed<>(32-t)); - uSeed=uSeed+t+3; - // Random end - - iL=iLeft, iR=iRight; - n = (iR-iL)+1; - assert(n>=0); - index = (int) (uSeed%n); - - iMid=pSortBuffer[index + iL].array[channel]; - - do - { - while (pSortBuffer[iL].array[channel] < iMid) - ++iL; - while (pSortBuffer[iR].array[channel] > iMid) - --iR; - - if (iL <= iR) - { - sTmp = pSortBuffer[iL]; - pSortBuffer[iL] = pSortBuffer[iR]; - pSortBuffer[iR] = sTmp; - ++iL; --iR; - } - } - while (iL <= iR); - - if (iLeft < iR) - QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); - if (iL < iRight) - QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); -} - -// resolve ordering and edge number -static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) -{ - *edgenum_out = -1; - - // test if first index is on the edge - if (indices[0]==i0_in || indices[0]==i1_in) - { - // test if second index is on the edge - if (indices[1]==i0_in || indices[1]==i1_in) - { - edgenum_out[0]=0; // first edge - i0_out[0]=indices[0]; - i1_out[0]=indices[1]; - } - else - { - edgenum_out[0]=2; // third edge - i0_out[0]=indices[2]; - i1_out[0]=indices[0]; - } - } - else - { - // only second and third index is on the edge - edgenum_out[0]=1; // second edge - i0_out[0]=indices[1]; - i1_out[0]=indices[2]; - } -} - - -///////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////// Degenerate triangles //////////////////////////////////// - -static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) -{ - int iNextGoodTriangleSearchIndex=-1; - tbool bStillFindingGoodOnes; - - // locate quads with only one good triangle - int t=0; - while (t<(iTotTris-1)) - { - const int iFO_a = pTriInfos[t].iOrgFaceNumber; - const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; - if (iFO_a==iFO_b) // this is a quad - { - const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; - if ((bIsDeg_a^bIsDeg_b)!=0) - { - pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; - pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; - } - t += 2; - } - else - ++t; - } - - // reorder list so all degen triangles are moved to the back - // without reordering the good triangles - iNextGoodTriangleSearchIndex = 1; - t=0; - bStillFindingGoodOnes = TTRUE; - while (t (t+1)); - - // swap triangle t0 and t1 - if (!bJustADegenerate) - { - int i=0; - for (i=0; i<3; i++) - { - const int index = piTriList_out[t0*3+i]; - piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; - piTriList_out[t1*3+i] = index; - } - { - const STriInfo tri_info = pTriInfos[t0]; - pTriInfos[t0] = pTriInfos[t1]; - pTriInfos[t1] = tri_info; - } - } - else - bStillFindingGoodOnes = TFALSE; // this is not supposed to happen - } - - if (bStillFindingGoodOnes) ++t; - } - - assert(bStillFindingGoodOnes); // code will still work. - assert(iNrTrianglesIn == t); -} - -static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) -{ - int t=0, i=0; - // deal with degenerate triangles - // punishment for degenerate triangles is O(N^2) - for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf - * Note that though the tangent spaces at the vertices are generated in an order-independent way, - * by this implementation, the interpolated tangent space is still affected by which diagonal is - * chosen to split each quad. A sensible solution is to have your tools pipeline always - * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. - * If these have the same length then compare the diagonals defined by the texture coordinates. - * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin - * and also quad triangulator plugin. - */ - - -typedef int tbool; -typedef struct SMikkTSpaceContext SMikkTSpaceContext; - -typedef struct { - // Returns the number of faces (triangles/quads) on the mesh to be processed. - int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); - - // Returns the number of vertices on face number iFace - // iFace is a number in the range {0, 1, ..., getNumFaces()-1} - int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); - - // returns the position/normal/texcoord of the referenced face of vertex number iVert. - // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. - void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); - void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); - void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); - - // either (or both) of the two setTSpace callbacks can be set. - // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - // This function is used to return the tangent and fSign to the application. - // fvTangent is a unit length vector. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); - - // This function is used to return tangent space results to the application. - // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - // true magnitudes which can be used for relief mapping effects. - // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - // However, both are perpendicular to the vertex normal. - // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - // bitangent = fSign * cross(vN, tangent); - // Note that the results are returned unindexed. It is possible to generate a new index list - // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - // DO NOT! use an already existing index list. - void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, - const tbool bIsOrientationPreserving, const int iFace, const int iVert); -} SMikkTSpaceInterface; - -struct SMikkTSpaceContext -{ - SMikkTSpaceInterface * m_pInterface; // initialized with callback functions - void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) -}; - -// these are both thread safe! -tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); - - -// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -// normal map sampler must use the exact inverse of the pixel shader transformation. -// The most efficient transformation we can possibly do in the pixel shader is -// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -// pixel shader (fast transform out) -// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -// where vNt is the tangent space normal. The normal map sampler must likewise use the -// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -// sampler does (exact inverse of pixel shader): -// float3 row0 = cross(vB, vN); -// float3 row1 = cross(vN, vT); -// float3 row2 = cross(vT, vB); -// float fSign = dot(vT, row0)<0 ? -1 : 1; -// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -// where vNout is the sampled normal in some chosen 3D space. -// -// Should you choose to reconstruct the bitangent in the pixel shader instead -// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -// However, this must be used both by the sampler and your tools/rendering pipeline. - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/ffi.rs b/src/ffi.rs deleted file mode 100644 index 385fd822cfa2a..0000000000000 --- a/src/ffi.rs +++ /dev/null @@ -1,129 +0,0 @@ - -#![allow(bad_style)] -#![allow(dead_code)] - -use std::os::raw::*; - -pub type tbool = c_int; -pub const TFALSE: tbool = 0; -pub const TTRUE: tbool = 1; - -#[repr(C)] -pub struct SMikkTSpaceInterface { - /// Returns the number of faces (triangles/quads) on the mesh to be processed. - pub m_getNumFaces: extern "C" fn(pContext: *const SMikkTSpaceContext) -> c_int, - - /// Returns the number of vertices on face number iFace - /// iFace is a number in the range {0, 1, ..., getNumFaces()-1} - pub m_getNumVerticesOfFace: extern "C" fn( - pContext: *const SMikkTSpaceContext, - iFace: c_int, - ) -> c_int, - - /// Returns the position of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getPosition: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvPosOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// Returns the normal of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getNormal: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvNormOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// Returns the texcoord of the referenced face of vertex number - /// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. - pub m_getTexCoord: extern "C" fn( - pContext: *const SMikkTSpaceContext, - fvTexcOut: *mut c_float, - iFace: c_int, - iVert: c_int, - ), - - /// either (or both) of the two setTSpace callbacks can be set. - /// The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. - - /// This function is used to return the tangent and fSign to the application. - /// fvTangent is a unit length vector. - /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - /// bitangent = fSign * cross(vN, tangent); - /// Note that the results are returned unindexed. It is possible to generate a new index list - /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - /// DO NOT! use an already existing index list. - pub m_setTSpaceBasic: extern "C" fn( - pContext: *mut SMikkTSpaceContext, - fvTangent: *const c_float, - fSign: c_float, - iFace: c_int, - iVert: c_int, - ), - - /// This function is used to return tangent space results to the application. - /// fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their - /// true magnitudes which can be used for relief mapping effects. - /// fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. - /// However, both are perpendicular to the vertex normal. - /// For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. - /// fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); - /// bitangent = fSign * cross(vN, tangent); - /// Note that the results are returned unindexed. It is possible to generate a new index list - /// But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. - /// DO NOT! use an already existing index list. - pub m_setTSpace: extern "C" fn( - pContext: *mut SMikkTSpaceContext, - fvTangent: *const c_float, - fvBiTangent: *const c_float, - fMagS: c_float, - fMagT: c_float, - bIsOrientationPreserving: tbool, - iFace: c_int, - iVert: c_int, - ), -} - -/// these are both thread safe! -/// Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) -extern "system" { - pub fn genTangSpaceDefault(pContext: *const SMikkTSpaceContext) -> tbool; - #[allow(dead_code)] - pub fn genTangSpace(pContext: *const SMikkTSpaceContext, fAngularThreshold: c_float) -> tbool; -} - -/// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the -/// normal map sampler must use the exact inverse of the pixel shader transformation. -/// The most efficient transformation we can possibly do in the pixel shader is -/// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. -/// pixel shader (fast transform out) -/// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); -/// where vNt is the tangent space normal. The normal map sampler must likewise use the -/// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. -/// sampler does (exact inverse of pixel shader): -/// float3 row0 = cross(vB, vN); -/// float3 row1 = cross(vN, vT); -/// float3 row2 = cross(vT, vB); -/// float fSign = dot(vT, row0)<0 ? -1 : 1; -/// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); -/// where vNout is the sampled normal in some chosen 3D space. -/// -/// Should you choose to reconstruct the bitangent in the pixel shader instead -/// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. -/// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of -/// quads as your renderer then problems will occur since the interpolated tangent spaces will differ -/// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before -/// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. -/// However, this must be used both by the sampler and your tools/rendering pipeline. - -#[repr(C)] -pub struct SMikkTSpaceContext { - /// initialized with callback functions - pub m_pInterface: *const SMikkTSpaceInterface, - /// pointer to client side mesh data etc. (passed as the first parameter with every interface call) - pub m_pUserData: *mut c_void, -} diff --git a/src/generated.rs b/src/generated.rs new file mode 100644 index 0000000000000..a4ea227e1ed3e --- /dev/null +++ b/src/generated.rs @@ -0,0 +1,2036 @@ +#![allow( + dead_code, + mutable_transmutes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_mut, + unused_assignments, + unused_variables +)] + +use std::mem::size_of; + +use { + libc::{c_int, c_ulong}, + nalgebra::Vector3, +}; + +use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; + +extern "C" { + #[no_mangle] + fn memcpy(_: *mut libc::c_void, _: *const libc::c_void, _: libc::c_ulong) -> *mut libc::c_void; + #[no_mangle] + fn memset(_: *mut libc::c_void, _: libc::c_int, _: libc::c_ulong) -> *mut libc::c_void; + #[no_mangle] + fn malloc(_: libc::c_ulong) -> *mut libc::c_void; + #[no_mangle] + fn free(__ptr: *mut libc::c_void); +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct STSpace { + pub vOs: Vector3, + pub fMagS: libc::c_float, + pub vOt: Vector3, + pub fMagT: libc::c_float, + pub iCounter: libc::c_int, + pub bOrient: bool, +} +// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the +// normal map sampler must use the exact inverse of the pixel shader transformation. +// The most efficient transformation we can possibly do in the pixel shader is +// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. +// pixel shader (fast transform out) +// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); +// where vNt is the tangent space normal. The normal map sampler must likewise use the +// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. +// sampler does (exact inverse of pixel shader): +// float3 row0 = cross(vB, vN); +// float3 row1 = cross(vN, vT); +// float3 row2 = cross(vT, vB); +// float fSign = dot(vT, row0)<0 ? -1 : 1; +// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); +// where vNout is the sampled normal in some chosen 3D space. +// +// Should you choose to reconstruct the bitangent in the pixel shader instead +// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. +// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of +// quads as your renderer then problems will occur since the interpolated tangent spaces will differ +// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before +// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. +// However, this must be used both by the sampler and your tools/rendering pipeline. +// internal structure + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct STriInfo { + pub FaceNeighbors: [libc::c_int; 3], + pub AssignedGroup: [*mut SGroup; 3], + pub vOs: Vector3, + pub vOt: Vector3, + pub fMagS: libc::c_float, + pub fMagT: libc::c_float, + pub iOrgFaceNumber: libc::c_int, + pub iFlag: libc::c_int, + pub iTSpacesOffs: libc::c_int, + pub vert_num: [libc::c_uchar; 4], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct SGroup { + pub iNrFaces: libc::c_int, + pub pFaceIndices: *mut libc::c_int, + pub iVertexRepresentitive: libc::c_int, + pub bOrientPreservering: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct SSubGroup { + pub iNrFaces: libc::c_int, + pub pTriMembers: *mut libc::c_int, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub union SEdge { + pub unnamed: unnamed, + pub array: [libc::c_int; 3], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct unnamed { + pub i0: libc::c_int, + pub i1: libc::c_int, + pub f: libc::c_int, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct STmpVert { + pub vert: [libc::c_float; 3], + pub index: libc::c_int, +} + +pub unsafe fn genTangSpace( + geometry: &mut I, + fAngularThreshold: libc::c_float, +) -> bool { + // count nr_triangles + let mut piTriListIn: *mut libc::c_int = 0 as *mut libc::c_int; + let mut piGroupTrianglesBuffer: *mut libc::c_int = 0 as *mut libc::c_int; + let mut pTriInfos: *mut STriInfo = 0 as *mut STriInfo; + let mut pGroups: *mut SGroup = 0 as *mut SGroup; + let mut psTspace: *mut STSpace = 0 as *mut STSpace; + let mut iNrTrianglesIn = 0; + let mut f = 0; + let mut t = 0; + let mut i = 0; + let mut iNrTSPaces = 0; + let mut iTotTris = 0; + let mut iDegenTriangles = 0; + let mut iNrMaxGroups = 0; + let mut iNrActiveGroups: libc::c_int = 0i32; + let mut index: libc::c_int = 0i32; + let iNrFaces = geometry.get_num_faces(); + let mut bRes: bool = false; + let fThresCos: libc::c_float = ((fAngularThreshold * 3.14159265358979323846f64 as libc::c_float + / 180.0f32) as libc::c_double) + .cos() as libc::c_float; + f = 0; + while f < iNrFaces { + let verts = geometry.get_num_vertices_of_face(f); + if verts == 3 { + iNrTrianglesIn += 1 + } else if verts == 4 { + iNrTrianglesIn += 2 + } + f += 1 + } + if iNrTrianglesIn <= 0 { + return false; + } + piTriListIn = malloc((size_of::() * 3 * iNrTrianglesIn) as c_ulong) as *mut c_int; + pTriInfos = malloc((size_of::() * iNrTrianglesIn) as c_ulong) as *mut STriInfo; + if piTriListIn.is_null() || pTriInfos.is_null() { + if !piTriListIn.is_null() { + free(piTriListIn as *mut libc::c_void); + } + if !pTriInfos.is_null() { + free(pTriInfos as *mut libc::c_void); + } + return false; + } + iNrTSPaces = + GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, geometry, iNrTrianglesIn as c_int); + GenerateSharedVerticesIndexList(piTriListIn, geometry, iNrTrianglesIn); + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + t = 0; + while t < iTotTris as usize { + let i0 = *piTriListIn.offset((t * 3 + 0) as isize); + let i1 = *piTriListIn.offset((t * 3 + 1) as isize); + let i2 = *piTriListIn.offset((t * 3 + 2) as isize); + let p0 = get_position(geometry, i0 as usize); + let p1 = get_position(geometry, i1 as usize); + let p2 = get_position(geometry, i2 as usize); + if p0 == p1 || p0 == p2 || p1 == p2 { + (*pTriInfos.offset(t as isize)).iFlag |= 1i32; + iDegenTriangles += 1 + } + t += 1 + } + iNrTrianglesIn = iTotTris - iDegenTriangles; + DegenPrologue( + pTriInfos, + piTriListIn, + iNrTrianglesIn as c_int, + iTotTris as c_int, + ); + InitTriInfo( + pTriInfos, + piTriListIn as *const libc::c_int, + geometry, + iNrTrianglesIn, + ); + iNrMaxGroups = iNrTrianglesIn * 3; + pGroups = malloc((size_of::() * iNrMaxGroups) as c_ulong) as *mut SGroup; + piGroupTrianglesBuffer = + malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; + if pGroups.is_null() || piGroupTrianglesBuffer.is_null() { + if !pGroups.is_null() { + free(pGroups as *mut libc::c_void); + } + if !piGroupTrianglesBuffer.is_null() { + free(piGroupTrianglesBuffer as *mut libc::c_void); + } + free(piTriListIn as *mut libc::c_void); + free(pTriInfos as *mut libc::c_void); + return false; + } + iNrActiveGroups = Build4RuleGroups( + pTriInfos, + pGroups, + piGroupTrianglesBuffer, + piTriListIn as *const libc::c_int, + iNrTrianglesIn as c_int, + ); + psTspace = malloc((size_of::() * iNrTSPaces) as c_ulong) as *mut STSpace; + if psTspace.is_null() { + free(piTriListIn as *mut libc::c_void); + free(pTriInfos as *mut libc::c_void); + free(pGroups as *mut libc::c_void); + free(piGroupTrianglesBuffer as *mut libc::c_void); + return false; + } + memset( + psTspace as *mut libc::c_void, + 0i32, + (::std::mem::size_of::() as libc::c_ulong) + .wrapping_mul(iNrTSPaces as libc::c_ulong), + ); + t = 0; + while t < iNrTSPaces { + (*psTspace.offset(t as isize)).vOs.x = 1.0f32; + (*psTspace.offset(t as isize)).vOs.y = 0.0f32; + (*psTspace.offset(t as isize)).vOs.z = 0.0f32; + (*psTspace.offset(t as isize)).fMagS = 1.0f32; + (*psTspace.offset(t as isize)).vOt.x = 0.0f32; + (*psTspace.offset(t as isize)).vOt.y = 1.0f32; + (*psTspace.offset(t as isize)).vOt.z = 0.0f32; + (*psTspace.offset(t as isize)).fMagT = 1.0f32; + t += 1 + } + bRes = GenerateTSpaces( + psTspace, + pTriInfos as *const STriInfo, + pGroups as *const SGroup, + iNrActiveGroups, + piTriListIn as *const libc::c_int, + fThresCos, + geometry, + ); + free(pGroups as *mut libc::c_void); + free(piGroupTrianglesBuffer as *mut libc::c_void); + if !bRes { + free(pTriInfos as *mut libc::c_void); + free(piTriListIn as *mut libc::c_void); + free(psTspace as *mut libc::c_void); + return false; + } + DegenEpilogue( + psTspace, + pTriInfos, + piTriListIn, + geometry, + iNrTrianglesIn as c_int, + iTotTris as c_int, + ); + free(pTriInfos as *mut libc::c_void); + free(piTriListIn as *mut libc::c_void); + index = 0i32; + f = 0; + while f < iNrFaces { + let verts_0 = geometry.get_num_vertices_of_face(f); + if !(verts_0 != 3 && verts_0 != 4) { + i = 0; + while i < verts_0 { + let mut pTSpace: *const STSpace = + &mut *psTspace.offset(index as isize) as *mut STSpace; + let mut tang = Vector3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); + let mut bitang = Vector3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); + geometry.set_tangent( + tang, + bitang, + (*pTSpace).fMagS, + (*pTSpace).fMagT, + (*pTSpace).bOrient, + f, + i, + ); + index += 1; + i += 1 + } + } + f += 1 + } + free(psTspace as *mut libc::c_void); + return true; +} +unsafe fn DegenEpilogue( + mut psTspace: *mut STSpace, + mut pTriInfos: *mut STriInfo, + mut piTriListIn: *mut libc::c_int, + geometry: &mut I, + iNrTrianglesIn: libc::c_int, + iTotTris: libc::c_int, +) { + let mut t: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + t = iNrTrianglesIn; + while t < iTotTris { + let bSkip: bool = if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { + true + } else { + false + }; + if !bSkip { + i = 0i32; + while i < 3i32 { + let index1: libc::c_int = *piTriListIn.offset((t * 3i32 + i) as isize); + let mut bNotFound: bool = true; + let mut j: libc::c_int = 0i32; + while bNotFound && j < 3i32 * iNrTrianglesIn { + let index2: libc::c_int = *piTriListIn.offset(j as isize); + if index1 == index2 { + bNotFound = false + } else { + j += 1 + } + } + if !bNotFound { + let iTri: libc::c_int = j / 3i32; + let iVert: libc::c_int = j % 3i32; + let iSrcVert: libc::c_int = + (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as libc::c_int; + let iSrcOffs: libc::c_int = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; + let iDstVert: libc::c_int = + (*pTriInfos.offset(t as isize)).vert_num[i as usize] as libc::c_int; + let iDstOffs: libc::c_int = (*pTriInfos.offset(t as isize)).iTSpacesOffs; + *psTspace.offset((iDstOffs + iDstVert) as isize) = + *psTspace.offset((iSrcOffs + iSrcVert) as isize) + } + i += 1 + } + } + t += 1 + } + t = 0i32; + while t < iNrTrianglesIn { + if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { + let mut vDstP = Vector3::new(0.0, 0.0, 0.0); + let mut iOrgF: libc::c_int = -1i32; + let mut i_0: libc::c_int = 0i32; + let mut bNotFound_0: bool = false; + let mut pV: *mut libc::c_uchar = (*pTriInfos.offset(t as isize)).vert_num.as_mut_ptr(); + let mut iFlag: libc::c_int = 1i32 << *pV.offset(0isize) as libc::c_int + | 1i32 << *pV.offset(1isize) as libc::c_int + | 1i32 << *pV.offset(2isize) as libc::c_int; + let mut iMissingIndex: libc::c_int = 0i32; + if iFlag & 2i32 == 0i32 { + iMissingIndex = 1i32 + } else if iFlag & 4i32 == 0i32 { + iMissingIndex = 2i32 + } else if iFlag & 8i32 == 0i32 { + iMissingIndex = 3i32 + } + iOrgF = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + vDstP = get_position( + geometry, + face_vert_to_index(iOrgF as usize, iMissingIndex as usize), + ); + bNotFound_0 = true; + i_0 = 0i32; + while bNotFound_0 && i_0 < 3i32 { + let iVert_0: libc::c_int = *pV.offset(i_0 as isize) as libc::c_int; + let vSrcP = get_position( + geometry, + face_vert_to_index(iOrgF as usize, iVert_0 as usize), + ); + if vSrcP == vDstP { + let iOffs: libc::c_int = (*pTriInfos.offset(t as isize)).iTSpacesOffs; + *psTspace.offset((iOffs + iMissingIndex) as isize) = + *psTspace.offset((iOffs + iVert_0) as isize); + bNotFound_0 = false + } else { + i_0 += 1 + } + } + } + t += 1 + } +} + +unsafe fn GenerateTSpaces( + mut psTspace: *mut STSpace, + mut pTriInfos: *const STriInfo, + mut pGroups: *const SGroup, + iNrActiveGroups: libc::c_int, + mut piTriListIn: *const libc::c_int, + fThresCos: libc::c_float, + geometry: &mut I, +) -> bool { + let mut pSubGroupTspace: *mut STSpace = 0 as *mut STSpace; + let mut pUniSubGroups: *mut SSubGroup = 0 as *mut SSubGroup; + let mut pTmpMembers: *mut libc::c_int = 0 as *mut libc::c_int; + let mut iMaxNrFaces = 0; + let mut iUniqueTspaces: libc::c_int = 0i32; + let mut g: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + g = 0i32; + while g < iNrActiveGroups { + if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces { + iMaxNrFaces = (*pGroups.offset(g as isize)).iNrFaces + } + g += 1 + } + if iMaxNrFaces == 0i32 { + return true; + } + pSubGroupTspace = + malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut STSpace; + pUniSubGroups = + malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut SSubGroup; + pTmpMembers = + malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut c_int; + if pSubGroupTspace.is_null() || pUniSubGroups.is_null() || pTmpMembers.is_null() { + if !pSubGroupTspace.is_null() { + free(pSubGroupTspace as *mut libc::c_void); + } + if !pUniSubGroups.is_null() { + free(pUniSubGroups as *mut libc::c_void); + } + if !pTmpMembers.is_null() { + free(pTmpMembers as *mut libc::c_void); + } + return false; + } + iUniqueTspaces = 0i32; + g = 0i32; + while g < iNrActiveGroups { + let mut pGroup: *const SGroup = &*pGroups.offset(g as isize) as *const SGroup; + let mut iUniqueSubGroups: libc::c_int = 0i32; + let mut s: libc::c_int = 0i32; + i = 0i32; + while i < (*pGroup).iNrFaces { + let f: libc::c_int = *(*pGroup).pFaceIndices.offset(i as isize); + let mut index: libc::c_int = -1i32; + let mut iVertIndex: libc::c_int = -1i32; + let mut iOF_1: libc::c_int = -1i32; + let mut iMembers: usize = 0; + let mut j: libc::c_int = 0i32; + let mut l: libc::c_int = 0i32; + let mut tmp_group: SSubGroup = SSubGroup { + iNrFaces: 0, + pTriMembers: 0 as *mut libc::c_int, + }; + let mut bFound: bool = false; + let mut n = Vector3::new(0.0, 0.0, 0.0); + let mut vOs = Vector3::new(0.0, 0.0, 0.0); + let mut vOt = Vector3::new(0.0, 0.0, 0.0); + if (*pTriInfos.offset(f as isize)).AssignedGroup[0usize] == pGroup as *mut SGroup { + index = 0i32 + } else if (*pTriInfos.offset(f as isize)).AssignedGroup[1usize] == pGroup as *mut SGroup + { + index = 1i32 + } else if (*pTriInfos.offset(f as isize)).AssignedGroup[2usize] == pGroup as *mut SGroup + { + index = 2i32 + } + iVertIndex = *piTriListIn.offset((f * 3i32 + index) as isize); + n = get_normal(geometry, iVertIndex as usize); + vOs = (*pTriInfos.offset(f as isize)).vOs + - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); + vOt = (*pTriInfos.offset(f as isize)).vOt + - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); + if VNotZero(vOs) { + vOs = Normalize(vOs) + } + if VNotZero(vOt) { + vOt = Normalize(vOt) + } + iOF_1 = (*pTriInfos.offset(f as isize)).iOrgFaceNumber; + iMembers = 0; + j = 0i32; + while j < (*pGroup).iNrFaces { + let t: libc::c_int = *(*pGroup).pFaceIndices.offset(j as isize); + let iOF_2: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs + - (n.dot(&(*pTriInfos.offset(t as isize)).vOs) * n); + let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt + - (n.dot(&(*pTriInfos.offset(t as isize)).vOt) * n); + if VNotZero(vOs2) { + vOs2 = Normalize(vOs2) + } + if VNotZero(vOt2) { + vOt2 = Normalize(vOt2) + } + let bAny: bool = if ((*pTriInfos.offset(f as isize)).iFlag + | (*pTriInfos.offset(t as isize)).iFlag) + & 4i32 + != 0i32 + { + true + } else { + false + }; + let bSameOrgFace: bool = iOF_1 == iOF_2; + let fCosS: libc::c_float = vOs.dot(&vOs2); + let fCosT: libc::c_float = vOt.dot(&vOt2); + if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { + let fresh0 = iMembers; + iMembers = iMembers + 1; + *pTmpMembers.offset(fresh0 as isize) = t + } + j += 1 + } + tmp_group.iNrFaces = iMembers as c_int; + tmp_group.pTriMembers = pTmpMembers; + if iMembers > 1 { + let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; + QuickSort(pTmpMembers, 0i32, (iMembers - 1) as c_int, uSeed); + } + bFound = false; + l = 0i32; + while l < iUniqueSubGroups && !bFound { + bFound = CompareSubGroups(&mut tmp_group, &mut *pUniSubGroups.offset(l as isize)); + if !bFound { + l += 1 + } + } + if !bFound { + let mut pIndices = malloc((size_of::() * iMembers) as c_ulong) as *mut c_int; + if pIndices.is_null() { + let mut s_0: libc::c_int = 0i32; + s_0 = 0i32; + while s_0 < iUniqueSubGroups { + free( + (*pUniSubGroups.offset(s_0 as isize)).pTriMembers as *mut libc::c_void, + ); + s_0 += 1 + } + free(pUniSubGroups as *mut libc::c_void); + free(pTmpMembers as *mut libc::c_void); + free(pSubGroupTspace as *mut libc::c_void); + return false; + } + (*pUniSubGroups.offset(iUniqueSubGroups as isize)).iNrFaces = iMembers as c_int; + let ref mut fresh1 = (*pUniSubGroups.offset(iUniqueSubGroups as isize)).pTriMembers; + *fresh1 = pIndices; + memcpy( + pIndices as *mut libc::c_void, + tmp_group.pTriMembers as *const libc::c_void, + (iMembers as libc::c_ulong) + .wrapping_mul(::std::mem::size_of::() as libc::c_ulong), + ); + *pSubGroupTspace.offset(iUniqueSubGroups as isize) = EvalTspace( + tmp_group.pTriMembers, + iMembers as c_int, + piTriListIn, + pTriInfos, + geometry, + (*pGroup).iVertexRepresentitive, + ); + iUniqueSubGroups += 1 + } + let iOffs: libc::c_int = (*pTriInfos.offset(f as isize)).iTSpacesOffs; + let iVert: libc::c_int = + (*pTriInfos.offset(f as isize)).vert_num[index as usize] as libc::c_int; + let mut pTS_out: *mut STSpace = + &mut *psTspace.offset((iOffs + iVert) as isize) as *mut STSpace; + if (*pTS_out).iCounter == 1i32 { + *pTS_out = AvgTSpace(pTS_out, &mut *pSubGroupTspace.offset(l as isize)); + (*pTS_out).iCounter = 2i32; + (*pTS_out).bOrient = (*pGroup).bOrientPreservering + } else { + *pTS_out = *pSubGroupTspace.offset(l as isize); + (*pTS_out).iCounter = 1i32; + (*pTS_out).bOrient = (*pGroup).bOrientPreservering + } + i += 1 + } + s = 0i32; + while s < iUniqueSubGroups { + free((*pUniSubGroups.offset(s as isize)).pTriMembers as *mut libc::c_void); + s += 1 + } + iUniqueTspaces += iUniqueSubGroups; + g += 1 + } + free(pUniSubGroups as *mut libc::c_void); + free(pTmpMembers as *mut libc::c_void); + free(pSubGroupTspace as *mut libc::c_void); + return true; +} +unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace { + let mut ts_res: STSpace = STSpace { + vOs: Vector3::new(0.0, 0.0, 0.0), + fMagS: 0., + vOt: Vector3::new(0.0, 0.0, 0.0), + fMagT: 0., + iCounter: 0, + bOrient: false, + }; + if (*pTS0).fMagS == (*pTS1).fMagS + && (*pTS0).fMagT == (*pTS1).fMagT + && (*pTS0).vOs == (*pTS1).vOs + && (*pTS0).vOt == (*pTS1).vOt + { + ts_res.fMagS = (*pTS0).fMagS; + ts_res.fMagT = (*pTS0).fMagT; + ts_res.vOs = (*pTS0).vOs; + ts_res.vOt = (*pTS0).vOt + } else { + ts_res.fMagS = 0.5f32 * ((*pTS0).fMagS + (*pTS1).fMagS); + ts_res.fMagT = 0.5f32 * ((*pTS0).fMagT + (*pTS1).fMagT); + ts_res.vOs = (*pTS0).vOs + (*pTS1).vOs; + ts_res.vOt = (*pTS0).vOt + (*pTS1).vOt; + if VNotZero(ts_res.vOs) { + ts_res.vOs = Normalize(ts_res.vOs) + } + if VNotZero(ts_res.vOt) { + ts_res.vOt = Normalize(ts_res.vOt) + } + } + return ts_res; +} + +unsafe fn Normalize(v: Vector3) -> Vector3 { + return (1.0 / v.magnitude()) * v; +} + +unsafe fn VNotZero(v: Vector3) -> bool { + NotZero(v.x) || NotZero(v.y) || NotZero(v.z) +} + +unsafe fn NotZero(fX: libc::c_float) -> bool { + fX.abs() > 1.17549435e-38f32 +} + +unsafe fn EvalTspace( + mut face_indices: *mut libc::c_int, + iFaces: libc::c_int, + mut piTriListIn: *const libc::c_int, + mut pTriInfos: *const STriInfo, + geometry: &mut I, + iVertexRepresentitive: libc::c_int, +) -> STSpace { + let mut res: STSpace = STSpace { + vOs: Vector3::new(0.0, 0.0, 0.0), + fMagS: 0., + vOt: Vector3::new(0.0, 0.0, 0.0), + fMagT: 0., + iCounter: 0, + bOrient: false, + }; + let mut fAngleSum: libc::c_float = 0i32 as libc::c_float; + let mut face: libc::c_int = 0i32; + res.vOs.x = 0.0f32; + res.vOs.y = 0.0f32; + res.vOs.z = 0.0f32; + res.vOt.x = 0.0f32; + res.vOt.y = 0.0f32; + res.vOt.z = 0.0f32; + res.fMagS = 0i32 as libc::c_float; + res.fMagT = 0i32 as libc::c_float; + face = 0i32; + while face < iFaces { + let f: libc::c_int = *face_indices.offset(face as isize); + if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 { + let mut n = Vector3::new(0.0, 0.0, 0.0); + let mut vOs = Vector3::new(0.0, 0.0, 0.0); + let mut vOt = Vector3::new(0.0, 0.0, 0.0); + let mut p0 = Vector3::new(0.0, 0.0, 0.0); + let mut p1 = Vector3::new(0.0, 0.0, 0.0); + let mut p2 = Vector3::new(0.0, 0.0, 0.0); + let mut v1 = Vector3::new(0.0, 0.0, 0.0); + let mut v2 = Vector3::new(0.0, 0.0, 0.0); + let mut fCos: libc::c_float = 0.; + let mut fAngle: libc::c_float = 0.; + let mut fMagS: libc::c_float = 0.; + let mut fMagT: libc::c_float = 0.; + let mut i: libc::c_int = -1i32; + let mut index: libc::c_int = -1i32; + let mut i0: libc::c_int = -1i32; + let mut i1: libc::c_int = -1i32; + let mut i2: libc::c_int = -1i32; + if *piTriListIn.offset((3i32 * f + 0i32) as isize) == iVertexRepresentitive { + i = 0i32 + } else if *piTriListIn.offset((3i32 * f + 1i32) as isize) == iVertexRepresentitive { + i = 1i32 + } else if *piTriListIn.offset((3i32 * f + 2i32) as isize) == iVertexRepresentitive { + i = 2i32 + } + index = *piTriListIn.offset((3i32 * f + i) as isize); + n = get_normal(geometry, index as usize); + vOs = (*pTriInfos.offset(f as isize)).vOs + - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); + vOt = (*pTriInfos.offset(f as isize)).vOt + - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); + if VNotZero(vOs) { + vOs = Normalize(vOs) + } + if VNotZero(vOt) { + vOt = Normalize(vOt) + } + i2 = *piTriListIn.offset((3i32 * f + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); + i1 = *piTriListIn.offset((3i32 * f + i) as isize); + i0 = *piTriListIn.offset((3i32 * f + if i > 0i32 { i - 1i32 } else { 2i32 }) as isize); + p0 = get_position(geometry, i0 as usize); + p1 = get_position(geometry, i1 as usize); + p2 = get_position(geometry, i2 as usize); + v1 = p0 - p1; + v2 = p2 - p1; + v1 = v1 - (n.dot(&v1) * n); + if VNotZero(v1) { + v1 = Normalize(v1) + } + v2 = v2 - (n.dot(&v2) * n); + if VNotZero(v2) { + v2 = Normalize(v2) + } + fCos = v1.dot(&v2); + fCos = if fCos > 1i32 as libc::c_float { + 1i32 as libc::c_float + } else if fCos < -1i32 as libc::c_float { + -1i32 as libc::c_float + } else { + fCos + }; + fAngle = (fCos as libc::c_double).acos() as libc::c_float; + fMagS = (*pTriInfos.offset(f as isize)).fMagS; + fMagT = (*pTriInfos.offset(f as isize)).fMagT; + res.vOs = res.vOs + (fAngle * vOs); + res.vOt = res.vOt + (fAngle * vOt); + res.fMagS += fAngle * fMagS; + res.fMagT += fAngle * fMagT; + fAngleSum += fAngle + } + face += 1 + } + if VNotZero(res.vOs) { + res.vOs = Normalize(res.vOs) + } + if VNotZero(res.vOt) { + res.vOt = Normalize(res.vOt) + } + if fAngleSum > 0i32 as libc::c_float { + res.fMagS /= fAngleSum; + res.fMagT /= fAngleSum + } + return res; +} + +unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) -> bool { + let mut bStillSame: bool = true; + let mut i: libc::c_int = 0i32; + if (*pg1).iNrFaces != (*pg2).iNrFaces { + return false; + } + while i < (*pg1).iNrFaces && bStillSame { + bStillSame = + if *(*pg1).pTriMembers.offset(i as isize) == *(*pg2).pTriMembers.offset(i as isize) { + true + } else { + false + }; + if bStillSame { + i += 1 + } + } + return bStillSame; +} +unsafe fn QuickSort( + mut pSortBuffer: *mut libc::c_int, + mut iLeft: libc::c_int, + mut iRight: libc::c_int, + mut uSeed: libc::c_uint, +) { + let mut iL: libc::c_int = 0; + let mut iR: libc::c_int = 0; + let mut n: libc::c_int = 0; + let mut index: libc::c_int = 0; + let mut iMid: libc::c_int = 0; + let mut iTmp: libc::c_int = 0; + + // Random + let mut t: libc::c_uint = uSeed & 31i32 as libc::c_uint; + t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as libc::c_uint).wrapping_sub(t)); + uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as libc::c_uint); + // Random end + + iL = iLeft; + iR = iRight; + n = iR - iL + 1i32; + index = uSeed.wrapping_rem(n as libc::c_uint) as libc::c_int; + iMid = *pSortBuffer.offset((index + iL) as isize); + loop { + while *pSortBuffer.offset(iL as isize) < iMid { + iL += 1 + } + while *pSortBuffer.offset(iR as isize) > iMid { + iR -= 1 + } + if iL <= iR { + iTmp = *pSortBuffer.offset(iL as isize); + *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); + *pSortBuffer.offset(iR as isize) = iTmp; + iL += 1; + iR -= 1 + } + if !(iL <= iR) { + break; + } + } + if iLeft < iR { + QuickSort(pSortBuffer, iLeft, iR, uSeed); + } + if iL < iRight { + QuickSort(pSortBuffer, iL, iRight, uSeed); + }; +} +unsafe fn Build4RuleGroups( + mut pTriInfos: *mut STriInfo, + mut pGroups: *mut SGroup, + mut piGroupTrianglesBuffer: *mut libc::c_int, + mut piTriListIn: *const libc::c_int, + iNrTrianglesIn: libc::c_int, +) -> libc::c_int { + let iNrMaxGroups: libc::c_int = iNrTrianglesIn * 3i32; + let mut iNrActiveGroups: libc::c_int = 0i32; + let mut iOffset: libc::c_int = 0i32; + let mut f: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + f = 0i32; + while f < iNrTrianglesIn { + i = 0i32; + while i < 3i32 { + if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 + && (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize].is_null() + { + let mut bOrPre: bool = false; + let mut neigh_indexL: libc::c_int = 0; + let mut neigh_indexR: libc::c_int = 0; + let vert_index: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); + let ref mut fresh2 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; + *fresh2 = &mut *pGroups.offset(iNrActiveGroups as isize) as *mut SGroup; + (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]) + .iVertexRepresentitive = vert_index; + (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).bOrientPreservering = + (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32; + (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces = 0i32; + let ref mut fresh3 = + (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).pFaceIndices; + *fresh3 = &mut *piGroupTrianglesBuffer.offset(iOffset as isize) as *mut libc::c_int; + iNrActiveGroups += 1; + AddTriToGroup((*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], f); + bOrPre = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + neigh_indexL = (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize]; + neigh_indexR = (*pTriInfos.offset(f as isize)).FaceNeighbors + [(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; + if neigh_indexL >= 0i32 { + let bAnswer: bool = AssignRecur( + piTriListIn, + pTriInfos, + neigh_indexL, + (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], + ); + let bOrPre2: bool = + if (*pTriInfos.offset(neigh_indexL as isize)).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + let bDiff: bool = if bOrPre != bOrPre2 { true } else { false }; + } + if neigh_indexR >= 0i32 { + let bAnswer_0: bool = AssignRecur( + piTriListIn, + pTriInfos, + neigh_indexR, + (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], + ); + let bOrPre2_0: bool = + if (*pTriInfos.offset(neigh_indexR as isize)).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + let bDiff_0: bool = if bOrPre != bOrPre2_0 { true } else { false }; + } + iOffset += (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces + } + i += 1 + } + f += 1 + } + return iNrActiveGroups; +} +// /////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////// +unsafe fn AssignRecur( + mut piTriListIn: *const libc::c_int, + mut psTriInfos: *mut STriInfo, + iMyTriIndex: libc::c_int, + mut pGroup: *mut SGroup, +) -> bool { + let mut pMyTriInfo: *mut STriInfo = + &mut *psTriInfos.offset(iMyTriIndex as isize) as *mut STriInfo; + // track down vertex + let iVertRep: libc::c_int = (*pGroup).iVertexRepresentitive; + let mut pVerts: *const libc::c_int = + &*piTriListIn.offset((3i32 * iMyTriIndex + 0i32) as isize) as *const libc::c_int; + let mut i: libc::c_int = -1i32; + if *pVerts.offset(0isize) == iVertRep { + i = 0i32 + } else if *pVerts.offset(1isize) == iVertRep { + i = 1i32 + } else if *pVerts.offset(2isize) == iVertRep { + i = 2i32 + } + if (*pMyTriInfo).AssignedGroup[i as usize] == pGroup { + return true; + } else { + if !(*pMyTriInfo).AssignedGroup[i as usize].is_null() { + return false; + } + } + if (*pMyTriInfo).iFlag & 4i32 != 0i32 { + if (*pMyTriInfo).AssignedGroup[0usize].is_null() + && (*pMyTriInfo).AssignedGroup[1usize].is_null() + && (*pMyTriInfo).AssignedGroup[2usize].is_null() + { + (*pMyTriInfo).iFlag &= !8i32; + (*pMyTriInfo).iFlag |= if (*pGroup).bOrientPreservering { + 8i32 + } else { + 0i32 + } + } + } + let bOrient: bool = if (*pMyTriInfo).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + if bOrient != (*pGroup).bOrientPreservering { + return false; + } + AddTriToGroup(pGroup, iMyTriIndex); + (*pMyTriInfo).AssignedGroup[i as usize] = pGroup; + let neigh_indexL: libc::c_int = (*pMyTriInfo).FaceNeighbors[i as usize]; + let neigh_indexR: libc::c_int = + (*pMyTriInfo).FaceNeighbors[(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; + if neigh_indexL >= 0i32 { + AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + } + if neigh_indexR >= 0i32 { + AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + return true; +} +unsafe fn AddTriToGroup(mut pGroup: *mut SGroup, iTriIndex: libc::c_int) { + *(*pGroup).pFaceIndices.offset((*pGroup).iNrFaces as isize) = iTriIndex; + (*pGroup).iNrFaces += 1; +} +unsafe fn InitTriInfo( + mut pTriInfos: *mut STriInfo, + mut piTriListIn: *const libc::c_int, + geometry: &mut I, + iNrTrianglesIn: usize, +) { + let mut f = 0; + let mut i = 0; + let mut t = 0; + f = 0; + while f < iNrTrianglesIn { + i = 0i32; + while i < 3i32 { + (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] = -1i32; + let ref mut fresh4 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; + *fresh4 = 0 as *mut SGroup; + (*pTriInfos.offset(f as isize)).vOs.x = 0.0f32; + (*pTriInfos.offset(f as isize)).vOs.y = 0.0f32; + (*pTriInfos.offset(f as isize)).vOs.z = 0.0f32; + (*pTriInfos.offset(f as isize)).vOt.x = 0.0f32; + (*pTriInfos.offset(f as isize)).vOt.y = 0.0f32; + (*pTriInfos.offset(f as isize)).vOt.z = 0.0f32; + (*pTriInfos.offset(f as isize)).fMagS = 0i32 as libc::c_float; + (*pTriInfos.offset(f as isize)).fMagT = 0i32 as libc::c_float; + (*pTriInfos.offset(f as isize)).iFlag |= 4i32; + i += 1 + } + f += 1 + } + f = 0; + while f < iNrTrianglesIn { + let v1 = get_position(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); + let v2 = get_position(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); + let v3 = get_position(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); + let t1 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); + let t2 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); + let t3 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); + let t21x: libc::c_float = t2.x - t1.x; + let t21y: libc::c_float = t2.y - t1.y; + let t31x: libc::c_float = t3.x - t1.x; + let t31y: libc::c_float = t3.y - t1.y; + let d1 = v2 - v1; + let d2 = v3 - v1; + let fSignedAreaSTx2: libc::c_float = t21x * t31y - t21y * t31x; + let mut vOs = (t31y * d1) - (t21y * d2); + let mut vOt = (-t31x * d1) + (t21x * d2); + (*pTriInfos.offset(f as isize)).iFlag |= if fSignedAreaSTx2 > 0i32 as libc::c_float { + 8i32 + } else { + 0i32 + }; + if NotZero(fSignedAreaSTx2) { + let fAbsArea: libc::c_float = fSignedAreaSTx2.abs(); + let fLenOs: libc::c_float = vOs.magnitude(); + let fLenOt: libc::c_float = vOt.magnitude(); + let fS: libc::c_float = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { + -1.0f32 + } else { + 1.0f32 + }; + if NotZero(fLenOs) { + (*pTriInfos.offset(f as isize)).vOs = (fS / fLenOs) * vOs + } + if NotZero(fLenOt) { + (*pTriInfos.offset(f as isize)).vOt = (fS / fLenOt) * vOt + } + (*pTriInfos.offset(f as isize)).fMagS = fLenOs / fAbsArea; + (*pTriInfos.offset(f as isize)).fMagT = fLenOt / fAbsArea; + if NotZero((*pTriInfos.offset(f as isize)).fMagS) + && NotZero((*pTriInfos.offset(f as isize)).fMagT) + { + (*pTriInfos.offset(f as isize)).iFlag &= !4i32 + } + } + f += 1 + } + while t < iNrTrianglesIn - 1 { + let iFO_a: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let iFO_b: libc::c_int = (*pTriInfos.offset((t + 1) as isize)).iOrgFaceNumber; + if iFO_a == iFO_b { + let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { + true + } else { + false + }; + let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 1i32 != 0i32 { + true + } else { + false + }; + if !(bIsDeg_a || bIsDeg_b) { + let bOrientA: bool = if (*pTriInfos.offset(t as isize)).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + let bOrientB: bool = if (*pTriInfos.offset((t + 1) as isize)).iFlag & 8i32 != 0i32 { + true + } else { + false + }; + if bOrientA != bOrientB { + let mut bChooseOrientFirstTri: bool = false; + if (*pTriInfos.offset((t + 1) as isize)).iFlag & 4i32 != 0i32 { + bChooseOrientFirstTri = true + } else if CalcTexArea(geometry, &*piTriListIn.offset((t * 3 + 0) as isize)) + >= CalcTexArea(geometry, &*piTriListIn.offset(((t + 1) * 3 + 0) as isize)) + { + bChooseOrientFirstTri = true + } + let t0 = if bChooseOrientFirstTri { t } else { t + 1 }; + let t1_0 = if bChooseOrientFirstTri { t + 1 } else { t }; + (*pTriInfos.offset(t1_0 as isize)).iFlag &= !8i32; + (*pTriInfos.offset(t1_0 as isize)).iFlag |= + (*pTriInfos.offset(t0 as isize)).iFlag & 8i32 + } + } + t += 2 + } else { + t += 1 + } + } + let mut pEdges: *mut SEdge = + malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut SEdge; + if pEdges.is_null() { + BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn as c_int); + } else { + BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn as c_int); + free(pEdges as *mut libc::c_void); + }; +} +unsafe fn BuildNeighborsFast( + mut pTriInfos: *mut STriInfo, + mut pEdges: *mut SEdge, + mut piTriListIn: *const libc::c_int, + iNrTrianglesIn: libc::c_int, +) { + // build array of edges + // could replace with a random seed? + let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; + let mut iEntries: libc::c_int = 0i32; + let mut iCurStartIndex: libc::c_int = -1i32; + let mut f: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + f = 0i32; + while f < iNrTrianglesIn { + i = 0i32; + while i < 3i32 { + let i0: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); + let i1: libc::c_int = + *piTriListIn.offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); + (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i0 = if i0 < i1 { i0 } else { i1 }; + (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i1 = if !(i0 < i1) { i0 } else { i1 }; + (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.f = f; + i += 1 + } + f += 1 + } + QuickSortEdges(pEdges, 0i32, iNrTrianglesIn * 3i32 - 1i32, 0i32, uSeed); + iEntries = iNrTrianglesIn * 3i32; + iCurStartIndex = 0i32; + i = 1i32; + while i < iEntries { + if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 + != (*pEdges.offset(i as isize)).unnamed.i0 + { + let iL: libc::c_int = iCurStartIndex; + let iR: libc::c_int = i - 1i32; + iCurStartIndex = i; + QuickSortEdges(pEdges, iL, iR, 1i32, uSeed); + } + i += 1 + } + iCurStartIndex = 0i32; + i = 1i32; + while i < iEntries { + if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 + != (*pEdges.offset(i as isize)).unnamed.i0 + || (*pEdges.offset(iCurStartIndex as isize)).unnamed.i1 + != (*pEdges.offset(i as isize)).unnamed.i1 + { + let iL_0: libc::c_int = iCurStartIndex; + let iR_0: libc::c_int = i - 1i32; + iCurStartIndex = i; + QuickSortEdges(pEdges, iL_0, iR_0, 2i32, uSeed); + } + i += 1 + } + i = 0i32; + while i < iEntries { + let i0_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.i0; + let i1_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.i1; + let f_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.f; + let mut bUnassigned_A: bool = false; + let mut i0_A: libc::c_int = 0; + let mut i1_A: libc::c_int = 0; + let mut edgenum_A: libc::c_int = 0; + let mut edgenum_B: libc::c_int = 0i32; + GetEdge( + &mut i0_A, + &mut i1_A, + &mut edgenum_A, + &*piTriListIn.offset((f_0 * 3i32) as isize), + i0_0, + i1_0, + ); + bUnassigned_A = + if (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] == -1i32 { + true + } else { + false + }; + if bUnassigned_A { + let mut j: libc::c_int = i + 1i32; + let mut t: libc::c_int = 0; + let mut bNotFound: bool = true; + while j < iEntries + && i0_0 == (*pEdges.offset(j as isize)).unnamed.i0 + && i1_0 == (*pEdges.offset(j as isize)).unnamed.i1 + && bNotFound + { + let mut bUnassigned_B: bool = false; + let mut i0_B: libc::c_int = 0; + let mut i1_B: libc::c_int = 0; + t = (*pEdges.offset(j as isize)).unnamed.f; + GetEdge( + &mut i1_B, + &mut i0_B, + &mut edgenum_B, + &*piTriListIn.offset((t * 3i32) as isize), + (*pEdges.offset(j as isize)).unnamed.i0, + (*pEdges.offset(j as isize)).unnamed.i1, + ); + bUnassigned_B = + if (*pTriInfos.offset(t as isize)).FaceNeighbors[edgenum_B as usize] == -1i32 { + true + } else { + false + }; + if i0_A == i0_B && i1_A == i1_B && bUnassigned_B { + bNotFound = false + } else { + j += 1 + } + } + if !bNotFound { + let mut t_0: libc::c_int = (*pEdges.offset(j as isize)).unnamed.f; + (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] = t_0; + (*pTriInfos.offset(t_0 as isize)).FaceNeighbors[edgenum_B as usize] = f_0 + } + } + i += 1 + } +} +unsafe fn GetEdge( + mut i0_out: *mut libc::c_int, + mut i1_out: *mut libc::c_int, + mut edgenum_out: *mut libc::c_int, + mut indices: *const libc::c_int, + i0_in: libc::c_int, + i1_in: libc::c_int, +) { + *edgenum_out = -1i32; + if *indices.offset(0isize) == i0_in || *indices.offset(0isize) == i1_in { + if *indices.offset(1isize) == i0_in || *indices.offset(1isize) == i1_in { + *edgenum_out.offset(0isize) = 0i32; + *i0_out.offset(0isize) = *indices.offset(0isize); + *i1_out.offset(0isize) = *indices.offset(1isize) + } else { + *edgenum_out.offset(0isize) = 2i32; + *i0_out.offset(0isize) = *indices.offset(2isize); + *i1_out.offset(0isize) = *indices.offset(0isize) + } + } else { + *edgenum_out.offset(0isize) = 1i32; + *i0_out.offset(0isize) = *indices.offset(1isize); + *i1_out.offset(0isize) = *indices.offset(2isize) + }; +} +// /////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////// +unsafe fn QuickSortEdges( + mut pSortBuffer: *mut SEdge, + mut iLeft: libc::c_int, + mut iRight: libc::c_int, + channel: libc::c_int, + mut uSeed: libc::c_uint, +) { + let mut t: libc::c_uint = 0; + let mut iL: libc::c_int = 0; + let mut iR: libc::c_int = 0; + let mut n: libc::c_int = 0; + let mut index: libc::c_int = 0; + let mut iMid: libc::c_int = 0; + // early out + let mut sTmp: SEdge = SEdge { + unnamed: unnamed { i0: 0, i1: 0, f: 0 }, + }; + let iElems: libc::c_int = iRight - iLeft + 1i32; + if iElems < 2i32 { + return; + } else { + if iElems == 2i32 { + if (*pSortBuffer.offset(iLeft as isize)).array[channel as usize] + > (*pSortBuffer.offset(iRight as isize)).array[channel as usize] + { + sTmp = *pSortBuffer.offset(iLeft as isize); + *pSortBuffer.offset(iLeft as isize) = *pSortBuffer.offset(iRight as isize); + *pSortBuffer.offset(iRight as isize) = sTmp + } + return; + } + } + + // Random + t = uSeed & 31i32 as libc::c_uint; + t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as libc::c_uint).wrapping_sub(t)); + uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as libc::c_uint); + // Random end + + iL = iLeft; + iR = iRight; + n = iR - iL + 1i32; + index = uSeed.wrapping_rem(n as libc::c_uint) as libc::c_int; + iMid = (*pSortBuffer.offset((index + iL) as isize)).array[channel as usize]; + loop { + while (*pSortBuffer.offset(iL as isize)).array[channel as usize] < iMid { + iL += 1 + } + while (*pSortBuffer.offset(iR as isize)).array[channel as usize] > iMid { + iR -= 1 + } + if iL <= iR { + sTmp = *pSortBuffer.offset(iL as isize); + *pSortBuffer.offset(iL as isize) = *pSortBuffer.offset(iR as isize); + *pSortBuffer.offset(iR as isize) = sTmp; + iL += 1; + iR -= 1 + } + if !(iL <= iR) { + break; + } + } + if iLeft < iR { + QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + } + if iL < iRight { + QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); + }; +} +unsafe fn BuildNeighborsSlow( + mut pTriInfos: *mut STriInfo, + mut piTriListIn: *const libc::c_int, + iNrTrianglesIn: libc::c_int, +) { + let mut f: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + f = 0i32; + while f < iNrTrianglesIn { + i = 0i32; + while i < 3i32 { + if (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] == -1i32 { + let i0_A: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); + let i1_A: libc::c_int = *piTriListIn + .offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); + let mut bFound: bool = false; + let mut t: libc::c_int = 0i32; + let mut j: libc::c_int = 0i32; + while !bFound && t < iNrTrianglesIn { + if t != f { + j = 0i32; + while !bFound && j < 3i32 { + let i1_B: libc::c_int = *piTriListIn.offset((t * 3i32 + j) as isize); + let i0_B: libc::c_int = *piTriListIn.offset( + (t * 3i32 + if j < 2i32 { j + 1i32 } else { 0i32 }) as isize, + ); + if i0_A == i0_B && i1_A == i1_B { + bFound = true + } else { + j += 1 + } + } + } + if !bFound { + t += 1 + } + } + if bFound { + (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] = t; + (*pTriInfos.offset(t as isize)).FaceNeighbors[j as usize] = f + } + } + i += 1 + } + f += 1 + } +} +// returns the texture area times 2 +unsafe fn CalcTexArea( + geometry: &mut I, + mut indices: *const libc::c_int, +) -> libc::c_float { + let t1 = get_tex_coord(geometry, *indices.offset(0isize) as usize); + let t2 = get_tex_coord(geometry, *indices.offset(1isize) as usize); + let t3 = get_tex_coord(geometry, *indices.offset(2isize) as usize); + let t21x: libc::c_float = t2.x - t1.x; + let t21y: libc::c_float = t2.y - t1.y; + let t31x: libc::c_float = t3.x - t1.x; + let t31y: libc::c_float = t3.y - t1.y; + let fSignedAreaSTx2: libc::c_float = t21x * t31y - t21y * t31x; + return if fSignedAreaSTx2 < 0i32 as libc::c_float { + -fSignedAreaSTx2 + } else { + fSignedAreaSTx2 + }; +} + +// degen triangles +unsafe fn DegenPrologue( + mut pTriInfos: *mut STriInfo, + mut piTriList_out: *mut libc::c_int, + iNrTrianglesIn: libc::c_int, + iTotTris: libc::c_int, +) { + let mut iNextGoodTriangleSearchIndex: libc::c_int = -1i32; + let mut bStillFindingGoodOnes: bool = false; + // locate quads with only one good triangle + let mut t: libc::c_int = 0i32; + while t < iTotTris - 1i32 { + let iFO_a: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let iFO_b: libc::c_int = (*pTriInfos.offset((t + 1i32) as isize)).iOrgFaceNumber; + if iFO_a == iFO_b { + let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { + true + } else { + false + }; + let bIsDeg_b: bool = if (*pTriInfos.offset((t + 1i32) as isize)).iFlag & 1i32 != 0i32 { + true + } else { + false + }; + if bIsDeg_a ^ bIsDeg_b != false { + (*pTriInfos.offset(t as isize)).iFlag |= 2i32; + (*pTriInfos.offset((t + 1i32) as isize)).iFlag |= 2i32 + } + t += 2i32 + } else { + t += 1 + } + } + iNextGoodTriangleSearchIndex = 1i32; + t = 0i32; + bStillFindingGoodOnes = true; + while t < iNrTrianglesIn && bStillFindingGoodOnes { + let bIsGood: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 == 0i32 { + true + } else { + false + }; + if bIsGood { + if iNextGoodTriangleSearchIndex < t + 2i32 { + iNextGoodTriangleSearchIndex = t + 2i32 + } + } else { + let mut t0: libc::c_int = 0; + let mut t1: libc::c_int = 0; + let mut bJustADegenerate: bool = true; + while bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris { + let bIsGood_0: bool = + if (*pTriInfos.offset(iNextGoodTriangleSearchIndex as isize)).iFlag & 1i32 + == 0i32 + { + true + } else { + false + }; + if bIsGood_0 { + bJustADegenerate = false + } else { + iNextGoodTriangleSearchIndex += 1 + } + } + t0 = t; + t1 = iNextGoodTriangleSearchIndex; + iNextGoodTriangleSearchIndex += 1; + if !bJustADegenerate { + let mut i: libc::c_int = 0i32; + i = 0i32; + while i < 3i32 { + let index: libc::c_int = *piTriList_out.offset((t0 * 3i32 + i) as isize); + *piTriList_out.offset((t0 * 3i32 + i) as isize) = + *piTriList_out.offset((t1 * 3i32 + i) as isize); + *piTriList_out.offset((t1 * 3i32 + i) as isize) = index; + i += 1 + } + let tri_info: STriInfo = *pTriInfos.offset(t0 as isize); + *pTriInfos.offset(t0 as isize) = *pTriInfos.offset(t1 as isize); + *pTriInfos.offset(t1 as isize) = tri_info + } else { + bStillFindingGoodOnes = false + } + } + if bStillFindingGoodOnes { + t += 1 + } + } +} +unsafe fn GenerateSharedVerticesIndexList( + mut piTriList_in_and_out: *mut libc::c_int, + geometry: &mut I, + iNrTrianglesIn: usize, +) { + // Generate bounding box + let mut piHashTable: *mut libc::c_int = 0 as *mut libc::c_int; + let mut piHashCount: *mut libc::c_int = 0 as *mut libc::c_int; + let mut piHashOffsets: *mut libc::c_int = 0 as *mut libc::c_int; + let mut piHashCount2: *mut libc::c_int = 0 as *mut libc::c_int; + let mut pTmpVert: *mut STmpVert = 0 as *mut STmpVert; + let mut i = 0; + let mut iChannel: libc::c_int = 0i32; + let mut k: libc::c_int = 0i32; + let mut e: libc::c_int = 0i32; + let mut iMaxCount: libc::c_int = 0i32; + let mut vMin = get_position(geometry, 0); + let mut vMax = vMin; + let mut vDim = Vector3::new(0.0, 0.0, 0.0); + let mut fMin: libc::c_float = 0.; + let mut fMax: libc::c_float = 0.; + i = 1; + while i < iNrTrianglesIn * 3 { + let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let vP = get_position(geometry, index as usize); + if vMin.x > vP.x { + vMin.x = vP.x + } else if vMax.x < vP.x { + vMax.x = vP.x + } + if vMin.y > vP.y { + vMin.y = vP.y + } else if vMax.y < vP.y { + vMax.y = vP.y + } + if vMin.z > vP.z { + vMin.z = vP.z + } else if vMax.z < vP.z { + vMax.z = vP.z + } + i += 1 + } + vDim = vMax - vMin; + iChannel = 0i32; + fMin = vMin.x; + fMax = vMax.x; + if vDim.y > vDim.x && vDim.y > vDim.z { + iChannel = 1i32; + fMin = vMin.y; + fMax = vMax.y + } else if vDim.z > vDim.x { + iChannel = 2i32; + fMin = vMin.z; + fMax = vMax.z + } + piHashTable = malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; + piHashCount = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; + piHashOffsets = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; + piHashCount2 = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; + if piHashTable.is_null() + || piHashCount.is_null() + || piHashOffsets.is_null() + || piHashCount2.is_null() + { + if !piHashTable.is_null() { + free(piHashTable as *mut libc::c_void); + } + if !piHashCount.is_null() { + free(piHashCount as *mut libc::c_void); + } + if !piHashOffsets.is_null() { + free(piHashOffsets as *mut libc::c_void); + } + if !piHashCount2.is_null() { + free(piHashCount2 as *mut libc::c_void); + } + GenerateSharedVerticesIndexListSlow( + piTriList_in_and_out, + geometry, + iNrTrianglesIn as c_int, + ); + return; + } + memset( + piHashCount as *mut libc::c_void, + 0i32, + (::std::mem::size_of::() as libc::c_ulong) + .wrapping_mul(g_iCells as libc::c_ulong), + ); + memset( + piHashCount2 as *mut libc::c_void, + 0i32, + (::std::mem::size_of::() as libc::c_ulong) + .wrapping_mul(g_iCells as libc::c_ulong), + ); + i = 0; + while i < iNrTrianglesIn * 3 { + let index_0: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let vP_0 = get_position(geometry, index_0 as usize); + let fVal: libc::c_float = if iChannel == 0i32 { + vP_0.x + } else if iChannel == 1i32 { + vP_0.y + } else { + vP_0.z + }; + let iCell: libc::c_int = FindGridCell(fMin, fMax, fVal); + let ref mut fresh5 = *piHashCount.offset(iCell as isize); + *fresh5 += 1; + i += 1 + } + *piHashOffsets.offset(0isize) = 0i32; + k = 1i32; + while k < g_iCells { + *piHashOffsets.offset(k as isize) = + *piHashOffsets.offset((k - 1i32) as isize) + *piHashCount.offset((k - 1i32) as isize); + k += 1 + } + i = 0; + while i < iNrTrianglesIn * 3 { + let index_1: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let vP_1 = get_position(geometry, index_1 as usize); + let fVal_0: libc::c_float = if iChannel == 0i32 { + vP_1.x + } else if iChannel == 1i32 { + vP_1.y + } else { + vP_1.z + }; + let iCell_0: libc::c_int = FindGridCell(fMin, fMax, fVal_0); + let mut pTable: *mut libc::c_int = 0 as *mut libc::c_int; + pTable = &mut *piHashTable.offset(*piHashOffsets.offset(iCell_0 as isize) as isize) + as *mut libc::c_int; + *pTable.offset(*piHashCount2.offset(iCell_0 as isize) as isize) = i as c_int; + let ref mut fresh6 = *piHashCount2.offset(iCell_0 as isize); + *fresh6 += 1; + i += 1 + } + k = 0i32; + while k < g_iCells { + k += 1 + } + free(piHashCount2 as *mut libc::c_void); + iMaxCount = *piHashCount.offset(0isize); + k = 1i32; + while k < g_iCells { + if iMaxCount < *piHashCount.offset(k as isize) { + iMaxCount = *piHashCount.offset(k as isize) + } + k += 1 + } + pTmpVert = malloc( + (::std::mem::size_of::() as libc::c_ulong) + .wrapping_mul(iMaxCount as libc::c_ulong), + ) as *mut STmpVert; + k = 0i32; + while k < g_iCells { + // extract table of cell k and amount of entries in it + let mut pTable_0: *mut libc::c_int = &mut *piHashTable + .offset(*piHashOffsets.offset(k as isize) as isize) + as *mut libc::c_int; + let iEntries: libc::c_int = *piHashCount.offset(k as isize); + if !(iEntries < 2i32) { + if !pTmpVert.is_null() { + e = 0i32; + while e < iEntries { + let mut i_0: libc::c_int = *pTable_0.offset(e as isize); + let vP_2 = get_position( + geometry, + *piTriList_in_and_out.offset(i_0 as isize) as usize, + ); + (*pTmpVert.offset(e as isize)).vert[0usize] = vP_2.x; + (*pTmpVert.offset(e as isize)).vert[1usize] = vP_2.y; + (*pTmpVert.offset(e as isize)).vert[2usize] = vP_2.z; + (*pTmpVert.offset(e as isize)).index = i_0; + e += 1 + } + MergeVertsFast( + piTriList_in_and_out, + pTmpVert, + geometry, + 0i32, + iEntries - 1i32, + ); + } else { + MergeVertsSlow( + piTriList_in_and_out, + geometry, + pTable_0 as *const libc::c_int, + iEntries, + ); + } + } + k += 1 + } + if !pTmpVert.is_null() { + free(pTmpVert as *mut libc::c_void); + } + free(piHashTable as *mut libc::c_void); + free(piHashCount as *mut libc::c_void); + free(piHashOffsets as *mut libc::c_void); +} +unsafe fn MergeVertsSlow( + mut piTriList_in_and_out: *mut libc::c_int, + geometry: &mut I, + mut pTable: *const libc::c_int, + iEntries: libc::c_int, +) { + // this can be optimized further using a tree structure or more hashing. + let mut e: libc::c_int = 0i32; + e = 0i32; + while e < iEntries { + let mut i: libc::c_int = *pTable.offset(e as isize); + let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let vP = get_position(geometry, index as usize); + let vN = get_normal(geometry, index as usize); + let vT = get_tex_coord(geometry, index as usize); + let mut bNotFound: bool = true; + let mut e2: libc::c_int = 0i32; + let mut i2rec: libc::c_int = -1i32; + while e2 < e && bNotFound { + let i2: libc::c_int = *pTable.offset(e2 as isize); + let index2: libc::c_int = *piTriList_in_and_out.offset(i2 as isize); + let vP2 = get_position(geometry, index2 as usize); + let vN2 = get_normal(geometry, index2 as usize); + let vT2 = get_tex_coord(geometry, index2 as usize); + i2rec = i2; + if vP == vP2 && vN == vN2 && vT == vT2 { + bNotFound = false + } else { + e2 += 1 + } + } + if !bNotFound { + *piTriList_in_and_out.offset(i as isize) = *piTriList_in_and_out.offset(i2rec as isize) + } + e += 1 + } +} +unsafe fn MergeVertsFast( + mut piTriList_in_and_out: *mut libc::c_int, + mut pTmpVert: *mut STmpVert, + geometry: &mut I, + iL_in: libc::c_int, + iR_in: libc::c_int, +) { + // make bbox + let mut c: libc::c_int = 0i32; + let mut l: libc::c_int = 0i32; + let mut channel: libc::c_int = 0i32; + let mut fvMin: [libc::c_float; 3] = [0.; 3]; + let mut fvMax: [libc::c_float; 3] = [0.; 3]; + let mut dx: libc::c_float = 0i32 as libc::c_float; + let mut dy: libc::c_float = 0i32 as libc::c_float; + let mut dz: libc::c_float = 0i32 as libc::c_float; + let mut fSep: libc::c_float = 0i32 as libc::c_float; + c = 0i32; + while c < 3i32 { + fvMin[c as usize] = (*pTmpVert.offset(iL_in as isize)).vert[c as usize]; + fvMax[c as usize] = fvMin[c as usize]; + c += 1 + } + l = iL_in + 1i32; + while l <= iR_in { + c = 0i32; + while c < 3i32 { + if fvMin[c as usize] > (*pTmpVert.offset(l as isize)).vert[c as usize] { + fvMin[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] + } else if fvMax[c as usize] < (*pTmpVert.offset(l as isize)).vert[c as usize] { + fvMax[c as usize] = (*pTmpVert.offset(l as isize)).vert[c as usize] + } + c += 1 + } + l += 1 + } + dx = fvMax[0usize] - fvMin[0usize]; + dy = fvMax[1usize] - fvMin[1usize]; + dz = fvMax[2usize] - fvMin[2usize]; + channel = 0i32; + if dy > dx && dy > dz { + channel = 1i32 + } else if dz > dx { + channel = 2i32 + } + fSep = 0.5f32 * (fvMax[channel as usize] + fvMin[channel as usize]); + if fSep >= fvMax[channel as usize] || fSep <= fvMin[channel as usize] { + l = iL_in; + while l <= iR_in { + let mut i: libc::c_int = (*pTmpVert.offset(l as isize)).index; + let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let vP = get_position(geometry, index as usize); + let vN = get_normal(geometry, index as usize); + let vT = get_tex_coord(geometry, index as usize); + let mut bNotFound: bool = true; + let mut l2: libc::c_int = iL_in; + let mut i2rec: libc::c_int = -1i32; + while l2 < l && bNotFound { + let i2: libc::c_int = (*pTmpVert.offset(l2 as isize)).index; + let index2: libc::c_int = *piTriList_in_and_out.offset(i2 as isize); + let vP2 = get_position(geometry, index2 as usize); + let vN2 = get_normal(geometry, index2 as usize); + let vT2 = get_tex_coord(geometry, index2 as usize); + i2rec = i2; + if vP.x == vP2.x + && vP.y == vP2.y + && vP.z == vP2.z + && vN.x == vN2.x + && vN.y == vN2.y + && vN.z == vN2.z + && vT.x == vT2.x + && vT.y == vT2.y + && vT.z == vT2.z + { + bNotFound = false + } else { + l2 += 1 + } + } + if !bNotFound { + *piTriList_in_and_out.offset(i as isize) = + *piTriList_in_and_out.offset(i2rec as isize) + } + l += 1 + } + } else { + let mut iL: libc::c_int = iL_in; + let mut iR: libc::c_int = iR_in; + while iL < iR { + let mut bReadyLeftSwap: bool = false; + let mut bReadyRightSwap: bool = false; + while !bReadyLeftSwap && iL < iR { + bReadyLeftSwap = !((*pTmpVert.offset(iL as isize)).vert[channel as usize] < fSep); + if !bReadyLeftSwap { + iL += 1 + } + } + while !bReadyRightSwap && iL < iR { + bReadyRightSwap = (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; + if !bReadyRightSwap { + iR -= 1 + } + } + if bReadyLeftSwap && bReadyRightSwap { + let sTmp: STmpVert = *pTmpVert.offset(iL as isize); + *pTmpVert.offset(iL as isize) = *pTmpVert.offset(iR as isize); + *pTmpVert.offset(iR as isize) = sTmp; + iL += 1; + iR -= 1 + } + } + if iL == iR { + let bReadyRightSwap_0: bool = + (*pTmpVert.offset(iR as isize)).vert[channel as usize] < fSep; + if bReadyRightSwap_0 { + iL += 1 + } else { + iR -= 1 + } + } + if iL_in < iR { + MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL_in, iR); + } + if iL < iR_in { + MergeVertsFast(piTriList_in_and_out, pTmpVert, geometry, iL, iR_in); + } + }; +} +static mut g_iCells: libc::c_int = 2048i32; +// it is IMPORTANT that this function is called to evaluate the hash since +// inlining could potentially reorder instructions and generate different +// results for the same effective input value fVal. +#[inline(never)] +unsafe fn FindGridCell( + fMin: libc::c_float, + fMax: libc::c_float, + fVal: libc::c_float, +) -> libc::c_int { + let fIndex: libc::c_float = g_iCells as libc::c_float * ((fVal - fMin) / (fMax - fMin)); + let iIndex: libc::c_int = fIndex as libc::c_int; + return if iIndex < g_iCells { + if iIndex >= 0i32 { + iIndex + } else { + 0i32 + } + } else { + g_iCells - 1i32 + }; +} +unsafe fn GenerateSharedVerticesIndexListSlow( + mut piTriList_in_and_out: *mut libc::c_int, + geometry: &mut I, + iNrTrianglesIn: libc::c_int, +) { + let mut iNumUniqueVerts: libc::c_int = 0i32; + let mut t: libc::c_int = 0i32; + let mut i: libc::c_int = 0i32; + t = 0i32; + while t < iNrTrianglesIn { + i = 0i32; + while i < 3i32 { + let offs: libc::c_int = t * 3i32 + i; + let index: libc::c_int = *piTriList_in_and_out.offset(offs as isize); + let vP = get_position(geometry, index as usize); + let vN = get_normal(geometry, index as usize); + let vT = get_tex_coord(geometry, index as usize); + let mut bFound: bool = false; + let mut t2: libc::c_int = 0i32; + let mut index2rec: libc::c_int = -1i32; + while !bFound && t2 <= t { + let mut j: libc::c_int = 0i32; + while !bFound && j < 3i32 { + let index2: libc::c_int = + *piTriList_in_and_out.offset((t2 * 3i32 + j) as isize); + let vP2 = get_position(geometry, index2 as usize); + let vN2 = get_normal(geometry, index2 as usize); + let vT2 = get_tex_coord(geometry, index2 as usize); + if vP == vP2 && vN == vN2 && vT == vT2 { + bFound = true + } else { + j += 1 + } + } + if !bFound { + t2 += 1 + } + } + if index2rec == index { + iNumUniqueVerts += 1 + } + *piTriList_in_and_out.offset(offs as isize) = index2rec; + i += 1 + } + t += 1 + } +} +unsafe fn GenerateInitialVerticesIndexList( + mut pTriInfos: *mut STriInfo, + mut piTriList_out: *mut libc::c_int, + geometry: &mut I, + iNrTrianglesIn: libc::c_int, +) -> usize { + let mut iTSpacesOffs: usize = 0; + let mut f = 0; + let mut t: libc::c_int = 0i32; + let mut iDstTriIndex: libc::c_int = 0i32; + f = 0; + while f < geometry.get_num_faces() { + let verts = geometry.get_num_vertices_of_face(f); + if !(verts != 3 && verts != 4) { + (*pTriInfos.offset(iDstTriIndex as isize)).iOrgFaceNumber = f as c_int; + (*pTriInfos.offset(iDstTriIndex as isize)).iTSpacesOffs = iTSpacesOffs as c_int; + if verts == 3 { + let mut pVerts: *mut libc::c_uchar = (*pTriInfos.offset(iDstTriIndex as isize)) + .vert_num + .as_mut_ptr(); + *pVerts.offset(0isize) = 0i32 as libc::c_uchar; + *pVerts.offset(1isize) = 1i32 as libc::c_uchar; + *pVerts.offset(2isize) = 2i32 as libc::c_uchar; + *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = + face_vert_to_index(f, 0) as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = + face_vert_to_index(f, 1) as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = + face_vert_to_index(f, 2) as c_int; + iDstTriIndex += 1 + } else { + (*pTriInfos.offset((iDstTriIndex + 1i32) as isize)).iOrgFaceNumber = f as c_int; + (*pTriInfos.offset((iDstTriIndex + 1i32) as isize)).iTSpacesOffs = + iTSpacesOffs as c_int; + let i0 = face_vert_to_index(f, 0); + let i1 = face_vert_to_index(f, 1); + let i2 = face_vert_to_index(f, 2); + let i3 = face_vert_to_index(f, 3); + let T0 = get_tex_coord(geometry, i0); + let T1 = get_tex_coord(geometry, i1); + let T2 = get_tex_coord(geometry, i2); + let T3 = get_tex_coord(geometry, i3); + let distSQ_02: libc::c_float = (T2 - T0).magnitude_squared(); + let distSQ_13: libc::c_float = (T3 - T1).magnitude_squared(); + let mut bQuadDiagIs_02: bool = false; + if distSQ_02 < distSQ_13 { + bQuadDiagIs_02 = true + } else if distSQ_13 < distSQ_02 { + bQuadDiagIs_02 = false + } else { + let P0 = get_position(geometry, i0); + let P1 = get_position(geometry, i1); + let P2 = get_position(geometry, i2); + let P3 = get_position(geometry, i3); + let distSQ_02_0: libc::c_float = (P2 - P0).magnitude_squared(); + let distSQ_13_0: libc::c_float = (P3 - P1).magnitude_squared(); + bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { + false + } else { + true + } + } + if bQuadDiagIs_02 { + let mut pVerts_A: *mut libc::c_uchar = (*pTriInfos + .offset(iDstTriIndex as isize)) + .vert_num + .as_mut_ptr(); + *pVerts_A.offset(0isize) = 0i32 as libc::c_uchar; + *pVerts_A.offset(1isize) = 1i32 as libc::c_uchar; + *pVerts_A.offset(2isize) = 2i32 as libc::c_uchar; + *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i1 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i2 as c_int; + iDstTriIndex += 1; + let mut pVerts_B: *mut libc::c_uchar = (*pTriInfos + .offset(iDstTriIndex as isize)) + .vert_num + .as_mut_ptr(); + *pVerts_B.offset(0isize) = 0i32 as libc::c_uchar; + *pVerts_B.offset(1isize) = 2i32 as libc::c_uchar; + *pVerts_B.offset(2isize) = 3i32 as libc::c_uchar; + *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i2 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + iDstTriIndex += 1 + } else { + let mut pVerts_A_0: *mut libc::c_uchar = (*pTriInfos + .offset(iDstTriIndex as isize)) + .vert_num + .as_mut_ptr(); + *pVerts_A_0.offset(0isize) = 0i32 as libc::c_uchar; + *pVerts_A_0.offset(1isize) = 1i32 as libc::c_uchar; + *pVerts_A_0.offset(2isize) = 3i32 as libc::c_uchar; + *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i1 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + iDstTriIndex += 1; + let mut pVerts_B_0: *mut libc::c_uchar = (*pTriInfos + .offset(iDstTriIndex as isize)) + .vert_num + .as_mut_ptr(); + *pVerts_B_0.offset(0isize) = 1i32 as libc::c_uchar; + *pVerts_B_0.offset(1isize) = 2i32 as libc::c_uchar; + *pVerts_B_0.offset(2isize) = 3i32 as libc::c_uchar; + *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i1 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i2 as c_int; + *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + iDstTriIndex += 1 + } + } + iTSpacesOffs += verts + } + f += 1 + } + t = 0i32; + while t < iNrTrianglesIn { + (*pTriInfos.offset(t as isize)).iFlag = 0i32; + t += 1 + } + return iTSpacesOffs; +} diff --git a/src/lib.rs b/src/lib.rs index 488b98f554918..83aeb76e7086a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,173 +1,73 @@ -#![allow(bad_style)] +mod generated; -mod ffi; +use nalgebra::{Point2, Point3, Vector3, Vector4}; -use std::os::raw::*; -use std::mem; -use std::ptr; - -/// Rust FFI for the MikkTSpace implementation. -const INTERFACE: ffi::SMikkTSpaceInterface = ffi::SMikkTSpaceInterface { - m_getNumFaces: faces, - m_getNumVerticesOfFace: vertices, - m_getPosition: position, - m_getNormal: normal, - m_getTexCoord: tex_coord, - m_setTSpaceBasic: set_tspace_basic, - m_setTSpace: set_tspace, -}; - -/// Rust front-end API for tangent generation. -struct Closures<'a> { - /// Returns the number of vertices per face. - pub vertices_per_face: &'a Fn() -> usize, - +/// The interface by which mikktspace interacts with your geometry. +pub trait Geometry { /// Returns the number of faces. - pub face_count: &'a Fn() -> usize, - - /// Returns the positions of the indexed face. - pub position: &'a Fn(usize, usize) -> &'a [f32; 3], - - /// Returns the normals of the indexed face. - pub normal: &'a Fn(usize, usize) -> &'a [f32; 3], - - /// Returns the texture co-ordinates of the indexed face. - pub tex_coord: &'a Fn(usize, usize) -> &'a [f32; 2], - - /// Sets the generated tangent for the indexed face. - pub set_tangent: &'a mut FnMut(usize, usize, [f32; 4]), -} - -/// Returns the number of faces (triangles/quads) on the mesh to be processed. -extern "C" fn faces(pContext: *const ffi::SMikkTSpaceContext) -> c_int { - unsafe { - let x = (*pContext).m_pUserData as *const Closures; - ((*x).face_count)() as c_int - } -} - -/// Returns the number of vertices on face number iFace -/// iFace is a number in the range {0, 1, ..., getNumFaces()-1} -extern "C" fn vertices( - pContext: *const ffi::SMikkTSpaceContext, - _iFace: c_int, -) -> c_int { - unsafe { - let x = (*pContext).m_pUserData as *const Closures; - ((*x).vertices_per_face)() as c_int + fn get_num_faces(&self) -> usize; + + /// Returns the number of vertices of a face. + fn get_num_vertices_of_face(&self, face: usize) -> usize; + + /// Returns the position of a vertex. + fn get_position(&self, face: usize, vert: usize) -> Point3; + + /// Returns the normal of a vertex. + fn get_normal(&self, face: usize, vert: usize) -> Vector3; + + /// Returns the texture coordinate of a vertex. + fn get_tex_coord(&self, face: usize, vert: usize) -> Point2; + + /// Sets a vertex' generated tangent. + fn set_tangent( + &mut self, + tangent: Vector3, + _bi_tangent: Vector3, + _f_mag_s: f32, + _f_mag_t: f32, + bi_tangent_preserves_orientation: bool, + face: usize, + vert: usize, + ) { + let sign = if bi_tangent_preserves_orientation { + 1.0 + } else { + -1.0 + }; + self.set_tangent_encoded(tangent.insert_row(3, sign), face, vert); } -} -/// Returns the position of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -extern "C" fn position( - pContext: *const ffi::SMikkTSpaceContext, - fvPosOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let x = (*pContext).m_pUserData as *const Closures; - let slice = ((*x).position)(iFace as usize, iVert as usize); - let src = slice.as_ptr() as *const c_float; - ptr::copy_nonoverlapping::(src, fvPosOut, 3); - } + /// Sets a vertex' generated tangent with the bi-tangent encoded as the W component in the + /// tangent. The W component marks if the bi-tangent is flipped. This will only be called if + /// `set_tangent` is not implemented. + fn set_tangent_encoded(&mut self, _tangent: Vector4, _face: usize, _vert: usize) {} } -/// Returns the normal of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -extern "C" fn normal( - pContext: *const ffi::SMikkTSpaceContext, - fvNormOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let x = (*pContext).m_pUserData as *const Closures; - let slice = ((*x).normal)(iFace as usize, iVert as usize); - let src = slice.as_ptr() as *const c_float; - ptr::copy_nonoverlapping::(src, fvNormOut, 3); - } +/// Default (recommended) Angular Threshold is 180 degrees, which means threshold disabled. +pub fn generate_tangents_default(geometry: &mut I) -> bool { + unsafe { generated::genTangSpace(geometry, 180.0) } } -/// Returns the texcoord of the referenced face of vertex number -/// iVert, in the range {0,1,2} for triangles, and {0,1,2,3} for quads. -extern "C" fn tex_coord( - pContext: *const ffi::SMikkTSpaceContext, - fvTexcOut: *mut c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let x = (*pContext).m_pUserData as *const Closures; - let slice = ((*x).tex_coord)(iFace as usize, iVert as usize); - let src = slice.as_ptr() as *const c_float; - ptr::copy_nonoverlapping::(src, fvTexcOut, 2); - } +fn get_position(geometry: &mut I, index: usize) -> Vector3 { + let (face, vert) = index_to_face_vert(index); + geometry.get_position(face, vert).coords } -/// Returns the tangent and its sign to the application. -extern "C" fn set_tspace_basic( - pContext: *mut ffi::SMikkTSpaceContext, - fvTangent: *const c_float, - fSign: c_float, - iFace: c_int, - iVert: c_int, -) { - unsafe { - let x = (*pContext).m_pUserData as *mut Closures; - let mut tangent: [f32; 4] = mem::uninitialized(); - let dst: *mut c_float = tangent.as_mut_ptr(); - ptr::copy_nonoverlapping::(fvTangent, dst, 3); - tangent[3] = fSign; - ((*x).set_tangent)(iFace as usize, iVert as usize, tangent); - } +fn get_tex_coord(geometry: &mut I, index: usize) -> Vector3 { + let (face, vert) = index_to_face_vert(index); + geometry.get_tex_coord(face, vert).coords.insert_row(2, 1.0) } -/// Returns tangent space results to the application. -extern "C" fn set_tspace( - pContext: *mut ffi::SMikkTSpaceContext, - fvTangent: *const c_float, - _fvBiTangent: *const c_float, - _fMagS: c_float, - _fMagT: c_float, - bIsOrientationPreserving: ffi::tbool, - iFace: c_int, - iVert: c_int, -) { - let fSign = if bIsOrientationPreserving != 0 { 1.0 } else { -1.0 }; - set_tspace_basic(pContext, fvTangent, fSign, iFace, iVert); +fn get_normal(geometry: &mut I, index: usize) -> Vector3 { + let (face, vert) = index_to_face_vert(index); + geometry.get_normal(face, vert) } -impl<'a> Closures<'a> { - /// Generates tangents. - pub fn generate(mut self) -> bool { - let ctx = ffi::SMikkTSpaceContext { - m_pInterface: &INTERFACE, - m_pUserData: &mut self as *mut Closures as *mut c_void, - }; - unsafe { - ffi::genTangSpaceDefault(&ctx) == ffi::TTRUE - } - } +fn index_to_face_vert(index: usize) -> (usize, usize) { + (index >> 2, index & 0x3) } -/// Generates tangents. -pub fn generate_tangents<'a>( - vertices_per_face: &'a Fn() -> usize, - face_count: &'a Fn() -> usize, - position: &'a Fn(usize, usize) -> &'a [f32; 3], - normal: &'a Fn(usize, usize) -> &'a [f32; 3], - tex_coord: &'a Fn(usize, usize) -> &'a [f32; 2], - set_tangent: &'a mut FnMut(usize, usize, [f32; 4]), -) -> bool { - let closures = Closures { - vertices_per_face, - face_count, - position, - normal, - tex_coord, - set_tangent, - }; - closures.generate() +fn face_vert_to_index(face: usize, vert: usize) -> usize { + face << 2 | vert & 0x3 } From c7785265f54d64e60333b874f1cf35377c11adeb Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 19:42:24 +0200 Subject: [PATCH 43/87] Simplify memset calls, preparation for replacement --- src/generated.rs | 58 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index a4ea227e1ed3e..e3a341a0e1822 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -227,9 +227,8 @@ pub unsafe fn genTangSpace( } memset( psTspace as *mut libc::c_void, - 0i32, - (::std::mem::size_of::() as libc::c_ulong) - .wrapping_mul(iNrTSPaces as libc::c_ulong), + 0, + (size_of::() * iNrTSPaces) as c_ulong, ); t = 0; while t < iNrTSPaces { @@ -1490,7 +1489,7 @@ unsafe fn GenerateSharedVerticesIndexList( let mut pTmpVert: *mut STmpVert = 0 as *mut STmpVert; let mut i = 0; let mut iChannel: libc::c_int = 0i32; - let mut k: libc::c_int = 0i32; + let mut k = 0; let mut e: libc::c_int = 0i32; let mut iMaxCount: libc::c_int = 0i32; let mut vMin = get_position(geometry, 0); @@ -1533,9 +1532,9 @@ unsafe fn GenerateSharedVerticesIndexList( fMax = vMax.z } piHashTable = malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; - piHashCount = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; - piHashOffsets = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; - piHashCount2 = malloc((size_of::() * g_iCells as usize) as c_ulong) as *mut c_int; + piHashCount = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; + piHashOffsets = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; + piHashCount2 = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; if piHashTable.is_null() || piHashCount.is_null() || piHashOffsets.is_null() @@ -1562,15 +1561,13 @@ unsafe fn GenerateSharedVerticesIndexList( } memset( piHashCount as *mut libc::c_void, - 0i32, - (::std::mem::size_of::() as libc::c_ulong) - .wrapping_mul(g_iCells as libc::c_ulong), + 0, + (size_of::() * g_iCells) as c_ulong, ); memset( piHashCount2 as *mut libc::c_void, - 0i32, - (::std::mem::size_of::() as libc::c_ulong) - .wrapping_mul(g_iCells as libc::c_ulong), + 0, + (size_of::() * g_iCells) as c_ulong, ); i = 0; while i < iNrTrianglesIn * 3 { @@ -1583,16 +1580,16 @@ unsafe fn GenerateSharedVerticesIndexList( } else { vP_0.z }; - let iCell: libc::c_int = FindGridCell(fMin, fMax, fVal); + let iCell = FindGridCell(fMin, fMax, fVal); let ref mut fresh5 = *piHashCount.offset(iCell as isize); *fresh5 += 1; i += 1 } *piHashOffsets.offset(0isize) = 0i32; - k = 1i32; + k = 1; while k < g_iCells { *piHashOffsets.offset(k as isize) = - *piHashOffsets.offset((k - 1i32) as isize) + *piHashCount.offset((k - 1i32) as isize); + *piHashOffsets.offset((k - 1) as isize) + *piHashCount.offset((k - 1) as isize); k += 1 } i = 0; @@ -1606,7 +1603,7 @@ unsafe fn GenerateSharedVerticesIndexList( } else { vP_1.z }; - let iCell_0: libc::c_int = FindGridCell(fMin, fMax, fVal_0); + let iCell_0 = FindGridCell(fMin, fMax, fVal_0); let mut pTable: *mut libc::c_int = 0 as *mut libc::c_int; pTable = &mut *piHashTable.offset(*piHashOffsets.offset(iCell_0 as isize) as isize) as *mut libc::c_int; @@ -1615,13 +1612,13 @@ unsafe fn GenerateSharedVerticesIndexList( *fresh6 += 1; i += 1 } - k = 0i32; + k = 0; while k < g_iCells { k += 1 } free(piHashCount2 as *mut libc::c_void); iMaxCount = *piHashCount.offset(0isize); - k = 1i32; + k = 1; while k < g_iCells { if iMaxCount < *piHashCount.offset(k as isize) { iMaxCount = *piHashCount.offset(k as isize) @@ -1632,7 +1629,7 @@ unsafe fn GenerateSharedVerticesIndexList( (::std::mem::size_of::() as libc::c_ulong) .wrapping_mul(iMaxCount as libc::c_ulong), ) as *mut STmpVert; - k = 0i32; + k = 0; while k < g_iCells { // extract table of cell k and amount of entries in it let mut pTable_0: *mut libc::c_int = &mut *piHashTable @@ -1844,7 +1841,9 @@ unsafe fn MergeVertsFast( } }; } -static mut g_iCells: libc::c_int = 2048i32; + +const g_iCells: usize = 2048; + // it is IMPORTANT that this function is called to evaluate the hash since // inlining could potentially reorder instructions and generate different // results for the same effective input value fVal. @@ -1853,19 +1852,20 @@ unsafe fn FindGridCell( fMin: libc::c_float, fMax: libc::c_float, fVal: libc::c_float, -) -> libc::c_int { - let fIndex: libc::c_float = g_iCells as libc::c_float * ((fVal - fMin) / (fMax - fMin)); - let iIndex: libc::c_int = fIndex as libc::c_int; - return if iIndex < g_iCells { - if iIndex >= 0i32 { - iIndex +) -> usize { + let fIndex = g_iCells as f32 * ((fVal - fMin) / (fMax - fMin)); + let iIndex = fIndex as isize; + return if iIndex < g_iCells as isize { + if iIndex >= 0 { + iIndex as usize } else { - 0i32 + 0 } } else { - g_iCells - 1i32 + g_iCells - 1 }; } + unsafe fn GenerateSharedVerticesIndexListSlow( mut piTriList_in_and_out: *mut libc::c_int, geometry: &mut I, From 9132a97dcc90bb34f5cad5bf1495b51ba4cdfa32 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 19:53:15 +0200 Subject: [PATCH 44/87] Replace psTspace with vec --- src/generated.rs | 84 ++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index e3a341a0e1822..6af54742c8fea 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -13,7 +13,7 @@ use std::mem::size_of; use { libc::{c_int, c_ulong}, - nalgebra::Vector3, + nalgebra::{zero, Vector3}, }; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; @@ -39,6 +39,20 @@ pub struct STSpace { pub iCounter: libc::c_int, pub bOrient: bool, } + +impl STSpace { + pub fn zero() -> Self { + Self { + vOs: zero(), + fMagS: 0.0, + vOt: zero(), + fMagT: 0.0, + iCounter: 0, + bOrient: false, + } + } +} + // To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the // normal map sampler must use the exact inverse of the pixel shader transformation. // The most efficient transformation we can possibly do in the pixel shader is @@ -123,7 +137,6 @@ pub unsafe fn genTangSpace( let mut piGroupTrianglesBuffer: *mut libc::c_int = 0 as *mut libc::c_int; let mut pTriInfos: *mut STriInfo = 0 as *mut STriInfo; let mut pGroups: *mut SGroup = 0 as *mut SGroup; - let mut psTspace: *mut STSpace = 0 as *mut STSpace; let mut iNrTrianglesIn = 0; let mut f = 0; let mut t = 0; @@ -133,7 +146,7 @@ pub unsafe fn genTangSpace( let mut iDegenTriangles = 0; let mut iNrMaxGroups = 0; let mut iNrActiveGroups: libc::c_int = 0i32; - let mut index: libc::c_int = 0i32; + let mut index = 0; let iNrFaces = geometry.get_num_faces(); let mut bRes: bool = false; let fThresCos: libc::c_float = ((fAngularThreshold * 3.14159265358979323846f64 as libc::c_float @@ -217,33 +230,20 @@ pub unsafe fn genTangSpace( piTriListIn as *const libc::c_int, iNrTrianglesIn as c_int, ); - psTspace = malloc((size_of::() * iNrTSPaces) as c_ulong) as *mut STSpace; - if psTspace.is_null() { - free(piTriListIn as *mut libc::c_void); - free(pTriInfos as *mut libc::c_void); - free(pGroups as *mut libc::c_void); - free(piGroupTrianglesBuffer as *mut libc::c_void); - return false; - } - memset( - psTspace as *mut libc::c_void, - 0, - (size_of::() * iNrTSPaces) as c_ulong, - ); - t = 0; - while t < iNrTSPaces { - (*psTspace.offset(t as isize)).vOs.x = 1.0f32; - (*psTspace.offset(t as isize)).vOs.y = 0.0f32; - (*psTspace.offset(t as isize)).vOs.z = 0.0f32; - (*psTspace.offset(t as isize)).fMagS = 1.0f32; - (*psTspace.offset(t as isize)).vOt.x = 0.0f32; - (*psTspace.offset(t as isize)).vOt.y = 1.0f32; - (*psTspace.offset(t as isize)).vOt.z = 0.0f32; - (*psTspace.offset(t as isize)).fMagT = 1.0f32; - t += 1 - } + + let mut psTspace = vec![ + STSpace { + vOs: Vector3::new(1.0, 0.0, 0.0), + fMagS: 1.0, + vOt: Vector3::new(0.0, 1.0, 0.0), + fMagT: 1.0, + ..STSpace::zero() + }; + iNrTSPaces + ]; + bRes = GenerateTSpaces( - psTspace, + &mut psTspace, pTriInfos as *const STriInfo, pGroups as *const SGroup, iNrActiveGroups, @@ -256,11 +256,10 @@ pub unsafe fn genTangSpace( if !bRes { free(pTriInfos as *mut libc::c_void); free(piTriListIn as *mut libc::c_void); - free(psTspace as *mut libc::c_void); return false; } DegenEpilogue( - psTspace, + psTspace.as_mut_ptr(), pTriInfos, piTriListIn, geometry, @@ -269,15 +268,14 @@ pub unsafe fn genTangSpace( ); free(pTriInfos as *mut libc::c_void); free(piTriListIn as *mut libc::c_void); - index = 0i32; + index = 0; f = 0; while f < iNrFaces { let verts_0 = geometry.get_num_vertices_of_face(f); if !(verts_0 != 3 && verts_0 != 4) { i = 0; while i < verts_0 { - let mut pTSpace: *const STSpace = - &mut *psTspace.offset(index as isize) as *mut STSpace; + let mut pTSpace: *const STSpace = &mut psTspace[index] as *mut STSpace; let mut tang = Vector3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); let mut bitang = Vector3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); geometry.set_tangent( @@ -295,7 +293,7 @@ pub unsafe fn genTangSpace( } f += 1 } - free(psTspace as *mut libc::c_void); + return true; } unsafe fn DegenEpilogue( @@ -393,7 +391,7 @@ unsafe fn DegenEpilogue( } unsafe fn GenerateTSpaces( - mut psTspace: *mut STSpace, + psTspace: &mut [STSpace], mut pTriInfos: *const STriInfo, mut pGroups: *const SGroup, iNrActiveGroups: libc::c_int, @@ -564,11 +562,11 @@ unsafe fn GenerateTSpaces( ); iUniqueSubGroups += 1 } - let iOffs: libc::c_int = (*pTriInfos.offset(f as isize)).iTSpacesOffs; - let iVert: libc::c_int = - (*pTriInfos.offset(f as isize)).vert_num[index as usize] as libc::c_int; + let iOffs = (*pTriInfos.offset(f as isize)).iTSpacesOffs as usize; + let iVert = + (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; let mut pTS_out: *mut STSpace = - &mut *psTspace.offset((iOffs + iVert) as isize) as *mut STSpace; + &mut psTspace[iOffs + iVert] as *mut STSpace; if (*pTS_out).iCounter == 1i32 { *pTS_out = AvgTSpace(pTS_out, &mut *pSubGroupTspace.offset(l as isize)); (*pTS_out).iCounter = 2i32; @@ -1848,11 +1846,7 @@ const g_iCells: usize = 2048; // inlining could potentially reorder instructions and generate different // results for the same effective input value fVal. #[inline(never)] -unsafe fn FindGridCell( - fMin: libc::c_float, - fMax: libc::c_float, - fVal: libc::c_float, -) -> usize { +unsafe fn FindGridCell(fMin: libc::c_float, fMax: libc::c_float, fVal: libc::c_float) -> usize { let fIndex = g_iCells as f32 * ((fVal - fMin) / (fMax - fMin)); let iIndex = fIndex as isize; return if iIndex < g_iCells as isize { From 3c90f723a22578cf238f82d5c6d717f85e5cf7bc Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:00:13 +0200 Subject: [PATCH 45/87] Replace piHashCount and piHashcount2 with vec --- src/generated.rs | 46 ++++++++++++---------------------------------- 1 file changed, 12 insertions(+), 34 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 6af54742c8fea..6260e6e98ec1c 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -1481,9 +1481,7 @@ unsafe fn GenerateSharedVerticesIndexList( ) { // Generate bounding box let mut piHashTable: *mut libc::c_int = 0 as *mut libc::c_int; - let mut piHashCount: *mut libc::c_int = 0 as *mut libc::c_int; let mut piHashOffsets: *mut libc::c_int = 0 as *mut libc::c_int; - let mut piHashCount2: *mut libc::c_int = 0 as *mut libc::c_int; let mut pTmpVert: *mut STmpVert = 0 as *mut STmpVert; let mut i = 0; let mut iChannel: libc::c_int = 0i32; @@ -1530,26 +1528,16 @@ unsafe fn GenerateSharedVerticesIndexList( fMax = vMax.z } piHashTable = malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; - piHashCount = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; piHashOffsets = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; - piHashCount2 = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; if piHashTable.is_null() - || piHashCount.is_null() || piHashOffsets.is_null() - || piHashCount2.is_null() { if !piHashTable.is_null() { free(piHashTable as *mut libc::c_void); } - if !piHashCount.is_null() { - free(piHashCount as *mut libc::c_void); - } if !piHashOffsets.is_null() { free(piHashOffsets as *mut libc::c_void); } - if !piHashCount2.is_null() { - free(piHashCount2 as *mut libc::c_void); - } GenerateSharedVerticesIndexListSlow( piTriList_in_and_out, geometry, @@ -1557,16 +1545,10 @@ unsafe fn GenerateSharedVerticesIndexList( ); return; } - memset( - piHashCount as *mut libc::c_void, - 0, - (size_of::() * g_iCells) as c_ulong, - ); - memset( - piHashCount2 as *mut libc::c_void, - 0, - (size_of::() * g_iCells) as c_ulong, - ); + + let mut piHashCount = vec!(0i32; g_iCells); + let mut piHashCount2 = vec!(0i32; g_iCells); + i = 0; while i < iNrTrianglesIn * 3 { let index_0: libc::c_int = *piTriList_in_and_out.offset(i as isize); @@ -1579,15 +1561,14 @@ unsafe fn GenerateSharedVerticesIndexList( vP_0.z }; let iCell = FindGridCell(fMin, fMax, fVal); - let ref mut fresh5 = *piHashCount.offset(iCell as isize); - *fresh5 += 1; + piHashCount[iCell] += 1; i += 1 } *piHashOffsets.offset(0isize) = 0i32; k = 1; while k < g_iCells { *piHashOffsets.offset(k as isize) = - *piHashOffsets.offset((k - 1) as isize) + *piHashCount.offset((k - 1) as isize); + *piHashOffsets.offset((k - 1) as isize) + piHashCount[k - 1]; k += 1 } i = 0; @@ -1605,21 +1586,19 @@ unsafe fn GenerateSharedVerticesIndexList( let mut pTable: *mut libc::c_int = 0 as *mut libc::c_int; pTable = &mut *piHashTable.offset(*piHashOffsets.offset(iCell_0 as isize) as isize) as *mut libc::c_int; - *pTable.offset(*piHashCount2.offset(iCell_0 as isize) as isize) = i as c_int; - let ref mut fresh6 = *piHashCount2.offset(iCell_0 as isize); - *fresh6 += 1; + *pTable.offset(piHashCount2[iCell_0] as isize) = i as c_int; + piHashCount2[iCell_0] += 1; i += 1 } k = 0; while k < g_iCells { k += 1 } - free(piHashCount2 as *mut libc::c_void); - iMaxCount = *piHashCount.offset(0isize); + iMaxCount = piHashCount[0]; k = 1; while k < g_iCells { - if iMaxCount < *piHashCount.offset(k as isize) { - iMaxCount = *piHashCount.offset(k as isize) + if iMaxCount < piHashCount[k] { + iMaxCount = piHashCount[k] } k += 1 } @@ -1633,7 +1612,7 @@ unsafe fn GenerateSharedVerticesIndexList( let mut pTable_0: *mut libc::c_int = &mut *piHashTable .offset(*piHashOffsets.offset(k as isize) as isize) as *mut libc::c_int; - let iEntries: libc::c_int = *piHashCount.offset(k as isize); + let iEntries: libc::c_int = piHashCount[k]; if !(iEntries < 2i32) { if !pTmpVert.is_null() { e = 0i32; @@ -1671,7 +1650,6 @@ unsafe fn GenerateSharedVerticesIndexList( free(pTmpVert as *mut libc::c_void); } free(piHashTable as *mut libc::c_void); - free(piHashCount as *mut libc::c_void); free(piHashOffsets as *mut libc::c_void); } unsafe fn MergeVertsSlow( From 16ad3f16c819e53a5549435e648c32534e6dcf6f Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:00:42 +0200 Subject: [PATCH 46/87] Remove memset as it's no longer used --- src/generated.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 6260e6e98ec1c..41fefedabddc8 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -22,8 +22,6 @@ extern "C" { #[no_mangle] fn memcpy(_: *mut libc::c_void, _: *const libc::c_void, _: libc::c_ulong) -> *mut libc::c_void; #[no_mangle] - fn memset(_: *mut libc::c_void, _: libc::c_int, _: libc::c_ulong) -> *mut libc::c_void; - #[no_mangle] fn malloc(_: libc::c_ulong) -> *mut libc::c_void; #[no_mangle] fn free(__ptr: *mut libc::c_void); From 54a4a4ed8e69f45a370439e4ea04401672612f7f Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:09:12 +0200 Subject: [PATCH 47/87] Replace piTriListIn and pTriInfos with vec --- src/generated.rs | 95 +++++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 41fefedabddc8..7b4e870b4e667 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -9,7 +9,7 @@ unused_variables )] -use std::mem::size_of; +use std::{mem::size_of, ptr::null_mut}; use { libc::{c_int, c_ulong}, @@ -90,6 +90,24 @@ pub struct STriInfo { pub iTSpacesOffs: libc::c_int, pub vert_num: [libc::c_uchar; 4], } + +impl STriInfo { + fn zero() -> Self { + Self { + FaceNeighbors: [0, 0, 0], + AssignedGroup: [null_mut(), null_mut(), null_mut()], + vOs: zero(), + vOt: zero(), + fMagS: 0.0, + fMagT: 0.0, + iOrgFaceNumber: 0, + iFlag: 0, + iTSpacesOffs: 0, + vert_num: [0, 0, 0, 0], + } + } +} + #[derive(Copy, Clone)] #[repr(C)] pub struct SGroup { @@ -131,9 +149,7 @@ pub unsafe fn genTangSpace( fAngularThreshold: libc::c_float, ) -> bool { // count nr_triangles - let mut piTriListIn: *mut libc::c_int = 0 as *mut libc::c_int; let mut piGroupTrianglesBuffer: *mut libc::c_int = 0 as *mut libc::c_int; - let mut pTriInfos: *mut STriInfo = 0 as *mut STriInfo; let mut pGroups: *mut SGroup = 0 as *mut SGroup; let mut iNrTrianglesIn = 0; let mut f = 0; @@ -163,46 +179,43 @@ pub unsafe fn genTangSpace( if iNrTrianglesIn <= 0 { return false; } - piTriListIn = malloc((size_of::() * 3 * iNrTrianglesIn) as c_ulong) as *mut c_int; - pTriInfos = malloc((size_of::() * iNrTrianglesIn) as c_ulong) as *mut STriInfo; - if piTriListIn.is_null() || pTriInfos.is_null() { - if !piTriListIn.is_null() { - free(piTriListIn as *mut libc::c_void); - } - if !pTriInfos.is_null() { - free(pTriInfos as *mut libc::c_void); - } - return false; - } - iNrTSPaces = - GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, geometry, iNrTrianglesIn as c_int); - GenerateSharedVerticesIndexList(piTriListIn, geometry, iNrTrianglesIn); + + let mut piTriListIn = vec![0i32; 3 * iNrTrianglesIn]; + let mut pTriInfos = vec![STriInfo::zero(); iNrTrianglesIn]; + + iNrTSPaces = GenerateInitialVerticesIndexList( + pTriInfos.as_mut_ptr(), + piTriListIn.as_mut_ptr(), + geometry, + iNrTrianglesIn as c_int, + ); + GenerateSharedVerticesIndexList(piTriListIn.as_mut_ptr(), geometry, iNrTrianglesIn); iTotTris = iNrTrianglesIn; iDegenTriangles = 0; t = 0; while t < iTotTris as usize { - let i0 = *piTriListIn.offset((t * 3 + 0) as isize); - let i1 = *piTriListIn.offset((t * 3 + 1) as isize); - let i2 = *piTriListIn.offset((t * 3 + 2) as isize); + let i0 = piTriListIn[t * 3 + 0]; + let i1 = piTriListIn[t * 3 + 1]; + let i2 = piTriListIn[t * 3 + 2]; let p0 = get_position(geometry, i0 as usize); let p1 = get_position(geometry, i1 as usize); let p2 = get_position(geometry, i2 as usize); if p0 == p1 || p0 == p2 || p1 == p2 { - (*pTriInfos.offset(t as isize)).iFlag |= 1i32; + pTriInfos[t].iFlag |= 1i32; iDegenTriangles += 1 } t += 1 } iNrTrianglesIn = iTotTris - iDegenTriangles; DegenPrologue( - pTriInfos, - piTriListIn, + pTriInfos.as_mut_ptr(), + piTriListIn.as_mut_ptr(), iNrTrianglesIn as c_int, iTotTris as c_int, ); InitTriInfo( - pTriInfos, - piTriListIn as *const libc::c_int, + pTriInfos.as_mut_ptr(), + piTriListIn.as_ptr(), geometry, iNrTrianglesIn, ); @@ -217,15 +230,13 @@ pub unsafe fn genTangSpace( if !piGroupTrianglesBuffer.is_null() { free(piGroupTrianglesBuffer as *mut libc::c_void); } - free(piTriListIn as *mut libc::c_void); - free(pTriInfos as *mut libc::c_void); return false; } iNrActiveGroups = Build4RuleGroups( - pTriInfos, + pTriInfos.as_mut_ptr(), pGroups, piGroupTrianglesBuffer, - piTriListIn as *const libc::c_int, + piTriListIn.as_ptr(), iNrTrianglesIn as c_int, ); @@ -242,30 +253,26 @@ pub unsafe fn genTangSpace( bRes = GenerateTSpaces( &mut psTspace, - pTriInfos as *const STriInfo, + pTriInfos.as_ptr(), pGroups as *const SGroup, iNrActiveGroups, - piTriListIn as *const libc::c_int, + piTriListIn.as_ptr(), fThresCos, geometry, ); free(pGroups as *mut libc::c_void); free(piGroupTrianglesBuffer as *mut libc::c_void); if !bRes { - free(pTriInfos as *mut libc::c_void); - free(piTriListIn as *mut libc::c_void); return false; } DegenEpilogue( psTspace.as_mut_ptr(), - pTriInfos, - piTriListIn, + pTriInfos.as_mut_ptr(), + piTriListIn.as_mut_ptr(), geometry, iNrTrianglesIn as c_int, iTotTris as c_int, ); - free(pTriInfos as *mut libc::c_void); - free(piTriListIn as *mut libc::c_void); index = 0; f = 0; while f < iNrFaces { @@ -561,10 +568,8 @@ unsafe fn GenerateTSpaces( iUniqueSubGroups += 1 } let iOffs = (*pTriInfos.offset(f as isize)).iTSpacesOffs as usize; - let iVert = - (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; - let mut pTS_out: *mut STSpace = - &mut psTspace[iOffs + iVert] as *mut STSpace; + let iVert = (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; + let mut pTS_out: *mut STSpace = &mut psTspace[iOffs + iVert] as *mut STSpace; if (*pTS_out).iCounter == 1i32 { *pTS_out = AvgTSpace(pTS_out, &mut *pSubGroupTspace.offset(l as isize)); (*pTS_out).iCounter = 2i32; @@ -1527,9 +1532,7 @@ unsafe fn GenerateSharedVerticesIndexList( } piHashTable = malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; piHashOffsets = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; - if piHashTable.is_null() - || piHashOffsets.is_null() - { + if piHashTable.is_null() || piHashOffsets.is_null() { if !piHashTable.is_null() { free(piHashTable as *mut libc::c_void); } @@ -1544,8 +1547,8 @@ unsafe fn GenerateSharedVerticesIndexList( return; } - let mut piHashCount = vec!(0i32; g_iCells); - let mut piHashCount2 = vec!(0i32; g_iCells); + let mut piHashCount = vec![0i32; g_iCells]; + let mut piHashCount2 = vec![0i32; g_iCells]; i = 0; while i < iNrTrianglesIn * 3 { From e0d086dc69fe7a7a16fe7d1acd7fec5288ffac1a Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:22:17 +0200 Subject: [PATCH 48/87] Change GenerateInitialVerticesIndexList to slices --- src/generated.rs | 118 ++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 7b4e870b4e667..cf0b3c27aa972 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -184,10 +184,10 @@ pub unsafe fn genTangSpace( let mut pTriInfos = vec![STriInfo::zero(); iNrTrianglesIn]; iNrTSPaces = GenerateInitialVerticesIndexList( - pTriInfos.as_mut_ptr(), - piTriListIn.as_mut_ptr(), + &mut pTriInfos, + &mut piTriListIn, geometry, - iNrTrianglesIn as c_int, + iNrTrianglesIn, ); GenerateSharedVerticesIndexList(piTriListIn.as_mut_ptr(), geometry, iNrTrianglesIn); iTotTris = iNrTrianglesIn; @@ -1887,39 +1887,33 @@ unsafe fn GenerateSharedVerticesIndexListSlow( } } unsafe fn GenerateInitialVerticesIndexList( - mut pTriInfos: *mut STriInfo, - mut piTriList_out: *mut libc::c_int, + pTriInfos: &mut [STriInfo], + piTriList_out: &mut [i32], geometry: &mut I, - iNrTrianglesIn: libc::c_int, + iNrTrianglesIn: usize, ) -> usize { let mut iTSpacesOffs: usize = 0; let mut f = 0; - let mut t: libc::c_int = 0i32; - let mut iDstTriIndex: libc::c_int = 0i32; + let mut t: usize = 0; + let mut iDstTriIndex = 0; f = 0; while f < geometry.get_num_faces() { let verts = geometry.get_num_vertices_of_face(f); if !(verts != 3 && verts != 4) { - (*pTriInfos.offset(iDstTriIndex as isize)).iOrgFaceNumber = f as c_int; - (*pTriInfos.offset(iDstTriIndex as isize)).iTSpacesOffs = iTSpacesOffs as c_int; + pTriInfos[iDstTriIndex].iOrgFaceNumber = f as c_int; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as c_int; if verts == 3 { - let mut pVerts: *mut libc::c_uchar = (*pTriInfos.offset(iDstTriIndex as isize)) - .vert_num - .as_mut_ptr(); - *pVerts.offset(0isize) = 0i32 as libc::c_uchar; - *pVerts.offset(1isize) = 1i32 as libc::c_uchar; - *pVerts.offset(2isize) = 2i32 as libc::c_uchar; - *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = - face_vert_to_index(f, 0) as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = - face_vert_to_index(f, 1) as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = - face_vert_to_index(f, 2) as c_int; + let mut pVerts = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts[0] = 0; + pVerts[1] = 1; + pVerts[2] = 2; + piTriList_out[iDstTriIndex * 3 + 0] = face_vert_to_index(f, 0) as c_int; + piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as c_int; + piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as c_int; iDstTriIndex += 1 } else { - (*pTriInfos.offset((iDstTriIndex + 1i32) as isize)).iOrgFaceNumber = f as c_int; - (*pTriInfos.offset((iDstTriIndex + 1i32) as isize)).iTSpacesOffs = - iTSpacesOffs as c_int; + pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as c_int; + pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as c_int; let i0 = face_vert_to_index(f, 0); let i1 = face_vert_to_index(f, 1); let i2 = face_vert_to_index(f, 2); @@ -1949,50 +1943,40 @@ unsafe fn GenerateInitialVerticesIndexList( } } if bQuadDiagIs_02 { - let mut pVerts_A: *mut libc::c_uchar = (*pTriInfos - .offset(iDstTriIndex as isize)) - .vert_num - .as_mut_ptr(); - *pVerts_A.offset(0isize) = 0i32 as libc::c_uchar; - *pVerts_A.offset(1isize) = 1i32 as libc::c_uchar; - *pVerts_A.offset(2isize) = 2i32 as libc::c_uchar; - *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i1 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i2 as c_int; + let mut pVerts_A = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 2; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as c_int; + piTriList_out[iDstTriIndex * 3 + 2] = i2 as c_int; iDstTriIndex += 1; - let mut pVerts_B: *mut libc::c_uchar = (*pTriInfos - .offset(iDstTriIndex as isize)) - .vert_num - .as_mut_ptr(); - *pVerts_B.offset(0isize) = 0i32 as libc::c_uchar; - *pVerts_B.offset(1isize) = 2i32 as libc::c_uchar; - *pVerts_B.offset(2isize) = 3i32 as libc::c_uchar; - *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i2 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + + let mut pVerts_B = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_B[0] = 0; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as c_int; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; iDstTriIndex += 1 } else { - let mut pVerts_A_0: *mut libc::c_uchar = (*pTriInfos - .offset(iDstTriIndex as isize)) - .vert_num - .as_mut_ptr(); - *pVerts_A_0.offset(0isize) = 0i32 as libc::c_uchar; - *pVerts_A_0.offset(1isize) = 1i32 as libc::c_uchar; - *pVerts_A_0.offset(2isize) = 3i32 as libc::c_uchar; - *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i0 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i1 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + let mut pVerts_A_0 = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_A_0[0] = 0; + pVerts_A_0[1] = 1; + pVerts_A_0[2] = 3; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as c_int; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; iDstTriIndex += 1; - let mut pVerts_B_0: *mut libc::c_uchar = (*pTriInfos - .offset(iDstTriIndex as isize)) - .vert_num - .as_mut_ptr(); - *pVerts_B_0.offset(0isize) = 1i32 as libc::c_uchar; - *pVerts_B_0.offset(1isize) = 2i32 as libc::c_uchar; - *pVerts_B_0.offset(2isize) = 3i32 as libc::c_uchar; - *piTriList_out.offset((iDstTriIndex * 3i32 + 0i32) as isize) = i1 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 1i32) as isize) = i2 as c_int; - *piTriList_out.offset((iDstTriIndex * 3i32 + 2i32) as isize) = i3 as c_int; + + let mut pVerts_B_0 = &mut pTriInfos[iDstTriIndex].vert_num; + pVerts_B_0[0] = 1; + pVerts_B_0[1] = 2; + pVerts_B_0[2] = 3; + piTriList_out[iDstTriIndex * 3 + 0] = i1 as c_int; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as c_int; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; iDstTriIndex += 1 } } @@ -2000,9 +1984,9 @@ unsafe fn GenerateInitialVerticesIndexList( } f += 1 } - t = 0i32; + t = 0; while t < iNrTrianglesIn { - (*pTriInfos.offset(t as isize)).iFlag = 0i32; + pTriInfos[t].iFlag = 0; t += 1 } return iTSpacesOffs; From 324c9a7f98f35977a3abbdefae0f14704e483d60 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:26:33 +0200 Subject: [PATCH 49/87] Replace pGroups, piGroupTrianglesBuffer with vec --- src/generated.rs | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index cf0b3c27aa972..f4207c7b6f552 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -77,7 +77,6 @@ impl STSpace { // internal structure #[derive(Copy, Clone)] -#[repr(C)] pub struct STriInfo { pub FaceNeighbors: [libc::c_int; 3], pub AssignedGroup: [*mut SGroup; 3], @@ -109,28 +108,36 @@ impl STriInfo { } #[derive(Copy, Clone)] -#[repr(C)] pub struct SGroup { pub iNrFaces: libc::c_int, pub pFaceIndices: *mut libc::c_int, pub iVertexRepresentitive: libc::c_int, pub bOrientPreservering: bool, } + +impl SGroup { + fn zero() -> Self { + Self { + iNrFaces: 0, + pFaceIndices: null_mut(), + iVertexRepresentitive: 0, + bOrientPreservering: false, + } + } +} + #[derive(Copy, Clone)] -#[repr(C)] pub struct SSubGroup { pub iNrFaces: libc::c_int, pub pTriMembers: *mut libc::c_int, } #[derive(Copy, Clone)] -#[repr(C)] pub union SEdge { pub unnamed: unnamed, pub array: [libc::c_int; 3], } #[derive(Copy, Clone)] -#[repr(C)] pub struct unnamed { pub i0: libc::c_int, pub i1: libc::c_int, @@ -138,7 +145,6 @@ pub struct unnamed { } #[derive(Copy, Clone)] -#[repr(C)] pub struct STmpVert { pub vert: [libc::c_float; 3], pub index: libc::c_int, @@ -148,9 +154,6 @@ pub unsafe fn genTangSpace( geometry: &mut I, fAngularThreshold: libc::c_float, ) -> bool { - // count nr_triangles - let mut piGroupTrianglesBuffer: *mut libc::c_int = 0 as *mut libc::c_int; - let mut pGroups: *mut SGroup = 0 as *mut SGroup; let mut iNrTrianglesIn = 0; let mut f = 0; let mut t = 0; @@ -220,22 +223,14 @@ pub unsafe fn genTangSpace( iNrTrianglesIn, ); iNrMaxGroups = iNrTrianglesIn * 3; - pGroups = malloc((size_of::() * iNrMaxGroups) as c_ulong) as *mut SGroup; - piGroupTrianglesBuffer = - malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; - if pGroups.is_null() || piGroupTrianglesBuffer.is_null() { - if !pGroups.is_null() { - free(pGroups as *mut libc::c_void); - } - if !piGroupTrianglesBuffer.is_null() { - free(piGroupTrianglesBuffer as *mut libc::c_void); - } - return false; - } + + let mut pGroups = vec!(SGroup::zero(); iNrMaxGroups); + let mut piGroupTrianglesBuffer = vec!(0; iNrTrianglesIn * 3); + iNrActiveGroups = Build4RuleGroups( pTriInfos.as_mut_ptr(), - pGroups, - piGroupTrianglesBuffer, + pGroups.as_mut_ptr(), + piGroupTrianglesBuffer.as_mut_ptr(), piTriListIn.as_ptr(), iNrTrianglesIn as c_int, ); @@ -254,14 +249,12 @@ pub unsafe fn genTangSpace( bRes = GenerateTSpaces( &mut psTspace, pTriInfos.as_ptr(), - pGroups as *const SGroup, + pGroups.as_ptr(), iNrActiveGroups, piTriListIn.as_ptr(), fThresCos, geometry, ); - free(pGroups as *mut libc::c_void); - free(piGroupTrianglesBuffer as *mut libc::c_void); if !bRes { return false; } From 2c1a59d038d5cea83c835a23a9a0cb0295dc8413 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:34:50 +0200 Subject: [PATCH 50/87] Replace pSubGroupTspace pUniSubGroups pTmpMembers --- src/generated.rs | 89 ++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 52 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index f4207c7b6f552..8e9ff6a5f1642 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -132,6 +132,15 @@ pub struct SSubGroup { pub pTriMembers: *mut libc::c_int, } +impl SSubGroup { + fn zero() -> Self { + Self { + iNrFaces: 0, + pTriMembers: null_mut(), + } + } +} + #[derive(Copy, Clone)] pub union SEdge { pub unnamed: unnamed, @@ -397,47 +406,31 @@ unsafe fn GenerateTSpaces( fThresCos: libc::c_float, geometry: &mut I, ) -> bool { - let mut pSubGroupTspace: *mut STSpace = 0 as *mut STSpace; - let mut pUniSubGroups: *mut SSubGroup = 0 as *mut SSubGroup; - let mut pTmpMembers: *mut libc::c_int = 0 as *mut libc::c_int; - let mut iMaxNrFaces = 0; - let mut iUniqueTspaces: libc::c_int = 0i32; + let mut iMaxNrFaces: usize = 0; + let mut iUniqueTspaces = 0; let mut g: libc::c_int = 0i32; let mut i: libc::c_int = 0i32; g = 0i32; while g < iNrActiveGroups { - if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces { - iMaxNrFaces = (*pGroups.offset(g as isize)).iNrFaces + if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces as usize { + iMaxNrFaces = (*pGroups.offset(g as isize)).iNrFaces as usize } g += 1 } - if iMaxNrFaces == 0i32 { + if iMaxNrFaces == 0 { return true; } - pSubGroupTspace = - malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut STSpace; - pUniSubGroups = - malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut SSubGroup; - pTmpMembers = - malloc((size_of::() * iMaxNrFaces as usize) as c_ulong) as *mut c_int; - if pSubGroupTspace.is_null() || pUniSubGroups.is_null() || pTmpMembers.is_null() { - if !pSubGroupTspace.is_null() { - free(pSubGroupTspace as *mut libc::c_void); - } - if !pUniSubGroups.is_null() { - free(pUniSubGroups as *mut libc::c_void); - } - if !pTmpMembers.is_null() { - free(pTmpMembers as *mut libc::c_void); - } - return false; - } - iUniqueTspaces = 0i32; + + let mut pSubGroupTspace = vec!(STSpace::zero(); iMaxNrFaces); + let mut pUniSubGroups = vec!(SSubGroup::zero(); iMaxNrFaces); + let mut pTmpMembers = vec!(0i32; iMaxNrFaces); + + iUniqueTspaces = 0; g = 0i32; while g < iNrActiveGroups { let mut pGroup: *const SGroup = &*pGroups.offset(g as isize) as *const SGroup; - let mut iUniqueSubGroups: libc::c_int = 0i32; - let mut s: libc::c_int = 0i32; + let mut iUniqueSubGroups = 0; + let mut s = 0; i = 0i32; while i < (*pGroup).iNrFaces { let f: libc::c_int = *(*pGroup).pFaceIndices.offset(i as isize); @@ -446,7 +439,7 @@ unsafe fn GenerateTSpaces( let mut iOF_1: libc::c_int = -1i32; let mut iMembers: usize = 0; let mut j: libc::c_int = 0i32; - let mut l: libc::c_int = 0i32; + let mut l: usize = 0; let mut tmp_group: SSubGroup = SSubGroup { iNrFaces: 0, pTriMembers: 0 as *mut libc::c_int, @@ -507,20 +500,20 @@ unsafe fn GenerateTSpaces( if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { let fresh0 = iMembers; iMembers = iMembers + 1; - *pTmpMembers.offset(fresh0 as isize) = t + pTmpMembers[fresh0] = t } j += 1 } tmp_group.iNrFaces = iMembers as c_int; - tmp_group.pTriMembers = pTmpMembers; + tmp_group.pTriMembers = pTmpMembers.as_mut_ptr(); if iMembers > 1 { let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; - QuickSort(pTmpMembers, 0i32, (iMembers - 1) as c_int, uSeed); + QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as c_int, uSeed); } bFound = false; - l = 0i32; + l = 0; while l < iUniqueSubGroups && !bFound { - bFound = CompareSubGroups(&mut tmp_group, &mut *pUniSubGroups.offset(l as isize)); + bFound = CompareSubGroups(&mut tmp_group, &mut pUniSubGroups[l]); if !bFound { l += 1 } @@ -528,29 +521,24 @@ unsafe fn GenerateTSpaces( if !bFound { let mut pIndices = malloc((size_of::() * iMembers) as c_ulong) as *mut c_int; if pIndices.is_null() { - let mut s_0: libc::c_int = 0i32; - s_0 = 0i32; + let mut s_0 = 0; while s_0 < iUniqueSubGroups { free( - (*pUniSubGroups.offset(s_0 as isize)).pTriMembers as *mut libc::c_void, + pUniSubGroups[s_0].pTriMembers as *mut libc::c_void, ); s_0 += 1 } - free(pUniSubGroups as *mut libc::c_void); - free(pTmpMembers as *mut libc::c_void); - free(pSubGroupTspace as *mut libc::c_void); return false; } - (*pUniSubGroups.offset(iUniqueSubGroups as isize)).iNrFaces = iMembers as c_int; - let ref mut fresh1 = (*pUniSubGroups.offset(iUniqueSubGroups as isize)).pTriMembers; - *fresh1 = pIndices; + pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as c_int; + pUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices; memcpy( pIndices as *mut libc::c_void, tmp_group.pTriMembers as *const libc::c_void, (iMembers as libc::c_ulong) .wrapping_mul(::std::mem::size_of::() as libc::c_ulong), ); - *pSubGroupTspace.offset(iUniqueSubGroups as isize) = EvalTspace( + pSubGroupTspace[iUniqueSubGroups] = EvalTspace( tmp_group.pTriMembers, iMembers as c_int, piTriListIn, @@ -564,27 +552,24 @@ unsafe fn GenerateTSpaces( let iVert = (*pTriInfos.offset(f as isize)).vert_num[index as usize] as usize; let mut pTS_out: *mut STSpace = &mut psTspace[iOffs + iVert] as *mut STSpace; if (*pTS_out).iCounter == 1i32 { - *pTS_out = AvgTSpace(pTS_out, &mut *pSubGroupTspace.offset(l as isize)); + *pTS_out = AvgTSpace(pTS_out, &mut pSubGroupTspace[l]); (*pTS_out).iCounter = 2i32; (*pTS_out).bOrient = (*pGroup).bOrientPreservering } else { - *pTS_out = *pSubGroupTspace.offset(l as isize); + *pTS_out = pSubGroupTspace[l]; (*pTS_out).iCounter = 1i32; (*pTS_out).bOrient = (*pGroup).bOrientPreservering } i += 1 } - s = 0i32; + s = 0; while s < iUniqueSubGroups { - free((*pUniSubGroups.offset(s as isize)).pTriMembers as *mut libc::c_void); + free(pUniSubGroups[s].pTriMembers as *mut libc::c_void); s += 1 } iUniqueTspaces += iUniqueSubGroups; g += 1 } - free(pUniSubGroups as *mut libc::c_void); - free(pTmpMembers as *mut libc::c_void); - free(pSubGroupTspace as *mut libc::c_void); return true; } unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace { From 042ca9a6622ae10516cc78bf6d012d1c56293a4e Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 20:45:06 +0200 Subject: [PATCH 51/87] Remove memset --- src/generated.rs | 73 ++++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 8e9ff6a5f1642..4205f501b7616 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -19,8 +19,6 @@ use { use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; extern "C" { - #[no_mangle] - fn memcpy(_: *mut libc::c_void, _: *const libc::c_void, _: libc::c_ulong) -> *mut libc::c_void; #[no_mangle] fn malloc(_: libc::c_ulong) -> *mut libc::c_void; #[no_mangle] @@ -126,17 +124,17 @@ impl SGroup { } } -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct SSubGroup { pub iNrFaces: libc::c_int, - pub pTriMembers: *mut libc::c_int, + pub pTriMembers: Vec, } impl SSubGroup { fn zero() -> Self { Self { iNrFaces: 0, - pTriMembers: null_mut(), + pTriMembers: Vec::new(), } } } @@ -233,8 +231,8 @@ pub unsafe fn genTangSpace( ); iNrMaxGroups = iNrTrianglesIn * 3; - let mut pGroups = vec!(SGroup::zero(); iNrMaxGroups); - let mut piGroupTrianglesBuffer = vec!(0; iNrTrianglesIn * 3); + let mut pGroups = vec![SGroup::zero(); iNrMaxGroups]; + let mut piGroupTrianglesBuffer = vec![0; iNrTrianglesIn * 3]; iNrActiveGroups = Build4RuleGroups( pTriInfos.as_mut_ptr(), @@ -421,9 +419,9 @@ unsafe fn GenerateTSpaces( return true; } - let mut pSubGroupTspace = vec!(STSpace::zero(); iMaxNrFaces); - let mut pUniSubGroups = vec!(SSubGroup::zero(); iMaxNrFaces); - let mut pTmpMembers = vec!(0i32; iMaxNrFaces); + let mut pSubGroupTspace = vec![STSpace::zero(); iMaxNrFaces]; + let mut pUniSubGroups = vec![SSubGroup::zero(); iMaxNrFaces]; + let mut pTmpMembers = vec![0i32; iMaxNrFaces]; iUniqueTspaces = 0; g = 0i32; @@ -442,7 +440,7 @@ unsafe fn GenerateTSpaces( let mut l: usize = 0; let mut tmp_group: SSubGroup = SSubGroup { iNrFaces: 0, - pTriMembers: 0 as *mut libc::c_int, + pTriMembers: Vec::new(), }; let mut bFound: bool = false; let mut n = Vector3::new(0.0, 0.0, 0.0); @@ -504,12 +502,17 @@ unsafe fn GenerateTSpaces( } j += 1 } - tmp_group.iNrFaces = iMembers as c_int; - tmp_group.pTriMembers = pTmpMembers.as_mut_ptr(); if iMembers > 1 { let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; - QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as c_int, uSeed); + QuickSort( + pTmpMembers.as_mut_ptr(), + 0i32, + (iMembers - 1) as c_int, + uSeed, + ); } + tmp_group.iNrFaces = iMembers as c_int; + tmp_group.pTriMembers = pTmpMembers.clone(); bFound = false; l = 0; while l < iUniqueSubGroups && !bFound { @@ -519,27 +522,11 @@ unsafe fn GenerateTSpaces( } } if !bFound { - let mut pIndices = malloc((size_of::() * iMembers) as c_ulong) as *mut c_int; - if pIndices.is_null() { - let mut s_0 = 0; - while s_0 < iUniqueSubGroups { - free( - pUniSubGroups[s_0].pTriMembers as *mut libc::c_void, - ); - s_0 += 1 - } - return false; - } pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as c_int; - pUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices; - memcpy( - pIndices as *mut libc::c_void, - tmp_group.pTriMembers as *const libc::c_void, - (iMembers as libc::c_ulong) - .wrapping_mul(::std::mem::size_of::() as libc::c_ulong), - ); + pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers.clone(); + pSubGroupTspace[iUniqueSubGroups] = EvalTspace( - tmp_group.pTriMembers, + tmp_group.pTriMembers.as_mut_ptr(), iMembers as c_int, piTriListIn, pTriInfos, @@ -562,11 +549,6 @@ unsafe fn GenerateTSpaces( } i += 1 } - s = 0; - while s < iUniqueSubGroups { - free(pUniSubGroups[s].pTriMembers as *mut libc::c_void); - s += 1 - } iUniqueTspaces += iUniqueSubGroups; g += 1 } @@ -733,17 +715,16 @@ unsafe fn EvalTspace( unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) -> bool { let mut bStillSame: bool = true; - let mut i: libc::c_int = 0i32; + let mut i = 0; if (*pg1).iNrFaces != (*pg2).iNrFaces { return false; } - while i < (*pg1).iNrFaces && bStillSame { - bStillSame = - if *(*pg1).pTriMembers.offset(i as isize) == *(*pg2).pTriMembers.offset(i as isize) { - true - } else { - false - }; + while i < (*pg1).iNrFaces as usize && bStillSame { + bStillSame = if (*pg1).pTriMembers[i] == (*pg2).pTriMembers[i] { + true + } else { + false + }; if bStillSame { i += 1 } From 8390d57c8bfa1733092c5513da2dd38359d70b83 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 21:00:42 +0200 Subject: [PATCH 52/87] Remove remaining malloc and free --- src/generated.rs | 278 ++++++++++------------------------------------- 1 file changed, 60 insertions(+), 218 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 4205f501b7616..a38590383006a 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -9,22 +9,15 @@ unused_variables )] -use std::{mem::size_of, ptr::null_mut}; +use std::ptr::null_mut; use { - libc::{c_int, c_ulong}, + libc::c_int, nalgebra::{zero, Vector3}, }; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; -extern "C" { - #[no_mangle] - fn malloc(_: libc::c_ulong) -> *mut libc::c_void; - #[no_mangle] - fn free(__ptr: *mut libc::c_void); -} - #[derive(Copy, Clone)] #[repr(C)] pub struct STSpace { @@ -144,6 +137,13 @@ pub union SEdge { pub unnamed: unnamed, pub array: [libc::c_int; 3], } + +impl SEdge { + fn zero() -> Self { + Self { array: [0, 0, 0] } + } +} + #[derive(Copy, Clone)] pub struct unnamed { pub i0: libc::c_int, @@ -157,6 +157,15 @@ pub struct STmpVert { pub index: libc::c_int, } +impl STmpVert { + fn zero() -> Self { + Self { + vert: [0.0, 0.0, 0.0], + index: 0, + } + } +} + pub unsafe fn genTangSpace( geometry: &mut I, fAngularThreshold: libc::c_float, @@ -1050,15 +1059,16 @@ unsafe fn InitTriInfo( t += 1 } } - let mut pEdges: *mut SEdge = - malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut SEdge; - if pEdges.is_null() { - BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn as c_int); - } else { - BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn as c_int); - free(pEdges as *mut libc::c_void); - }; + + let mut pEdges = vec![SEdge::zero(); iNrTrianglesIn * 3]; + BuildNeighborsFast( + pTriInfos, + pEdges.as_mut_ptr(), + piTriListIn, + iNrTrianglesIn as c_int, + ); } + unsafe fn BuildNeighborsFast( mut pTriInfos: *mut STriInfo, mut pEdges: *mut SEdge, @@ -1278,53 +1288,7 @@ unsafe fn QuickSortEdges( QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); }; } -unsafe fn BuildNeighborsSlow( - mut pTriInfos: *mut STriInfo, - mut piTriListIn: *const libc::c_int, - iNrTrianglesIn: libc::c_int, -) { - let mut f: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; - f = 0i32; - while f < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - if (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] == -1i32 { - let i0_A: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); - let i1_A: libc::c_int = *piTriListIn - .offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); - let mut bFound: bool = false; - let mut t: libc::c_int = 0i32; - let mut j: libc::c_int = 0i32; - while !bFound && t < iNrTrianglesIn { - if t != f { - j = 0i32; - while !bFound && j < 3i32 { - let i1_B: libc::c_int = *piTriListIn.offset((t * 3i32 + j) as isize); - let i0_B: libc::c_int = *piTriListIn.offset( - (t * 3i32 + if j < 2i32 { j + 1i32 } else { 0i32 }) as isize, - ); - if i0_A == i0_B && i1_A == i1_B { - bFound = true - } else { - j += 1 - } - } - } - if !bFound { - t += 1 - } - } - if bFound { - (*pTriInfos.offset(f as isize)).FaceNeighbors[i as usize] = t; - (*pTriInfos.offset(t as isize)).FaceNeighbors[j as usize] = f - } - } - i += 1 - } - f += 1 - } -} + // returns the texture area times 2 unsafe fn CalcTexArea( geometry: &mut I, @@ -1441,15 +1405,11 @@ unsafe fn GenerateSharedVerticesIndexList( geometry: &mut I, iNrTrianglesIn: usize, ) { - // Generate bounding box - let mut piHashTable: *mut libc::c_int = 0 as *mut libc::c_int; - let mut piHashOffsets: *mut libc::c_int = 0 as *mut libc::c_int; - let mut pTmpVert: *mut STmpVert = 0 as *mut STmpVert; let mut i = 0; let mut iChannel: libc::c_int = 0i32; let mut k = 0; - let mut e: libc::c_int = 0i32; - let mut iMaxCount: libc::c_int = 0i32; + let mut e = 0; + let mut iMaxCount = 0; let mut vMin = get_position(geometry, 0); let mut vMax = vMin; let mut vDim = Vector3::new(0.0, 0.0, 0.0); @@ -1489,23 +1449,9 @@ unsafe fn GenerateSharedVerticesIndexList( fMin = vMin.z; fMax = vMax.z } - piHashTable = malloc((size_of::() * iNrTrianglesIn * 3) as c_ulong) as *mut c_int; - piHashOffsets = malloc((size_of::() * g_iCells) as c_ulong) as *mut c_int; - if piHashTable.is_null() || piHashOffsets.is_null() { - if !piHashTable.is_null() { - free(piHashTable as *mut libc::c_void); - } - if !piHashOffsets.is_null() { - free(piHashOffsets as *mut libc::c_void); - } - GenerateSharedVerticesIndexListSlow( - piTriList_in_and_out, - geometry, - iNrTrianglesIn as c_int, - ); - return; - } + let mut piHashTable = vec![0i32; iNrTrianglesIn * 3]; + let mut piHashOffsets = vec![0i32; g_iCells]; let mut piHashCount = vec![0i32; g_iCells]; let mut piHashCount2 = vec![0i32; g_iCells]; @@ -1524,11 +1470,10 @@ unsafe fn GenerateSharedVerticesIndexList( piHashCount[iCell] += 1; i += 1 } - *piHashOffsets.offset(0isize) = 0i32; + piHashOffsets[0] = 0i32; k = 1; while k < g_iCells { - *piHashOffsets.offset(k as isize) = - *piHashOffsets.offset((k - 1) as isize) + piHashCount[k - 1]; + piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; k += 1 } i = 0; @@ -1544,8 +1489,7 @@ unsafe fn GenerateSharedVerticesIndexList( }; let iCell_0 = FindGridCell(fMin, fMax, fVal_0); let mut pTable: *mut libc::c_int = 0 as *mut libc::c_int; - pTable = &mut *piHashTable.offset(*piHashOffsets.offset(iCell_0 as isize) as isize) - as *mut libc::c_int; + pTable = &mut piHashTable[piHashOffsets[iCell_0] as usize] as *mut libc::c_int; *pTable.offset(piHashCount2[iCell_0] as isize) = i as c_int; piHashCount2[iCell_0] += 1; i += 1 @@ -1554,101 +1498,46 @@ unsafe fn GenerateSharedVerticesIndexList( while k < g_iCells { k += 1 } - iMaxCount = piHashCount[0]; + iMaxCount = piHashCount[0] as usize; k = 1; while k < g_iCells { - if iMaxCount < piHashCount[k] { - iMaxCount = piHashCount[k] + if iMaxCount < piHashCount[k] as usize { + iMaxCount = piHashCount[k] as usize } k += 1 } - pTmpVert = malloc( - (::std::mem::size_of::() as libc::c_ulong) - .wrapping_mul(iMaxCount as libc::c_ulong), - ) as *mut STmpVert; + let mut pTmpVert = vec!(STmpVert::zero(); iMaxCount); k = 0; while k < g_iCells { // extract table of cell k and amount of entries in it - let mut pTable_0: *mut libc::c_int = &mut *piHashTable - .offset(*piHashOffsets.offset(k as isize) as isize) - as *mut libc::c_int; - let iEntries: libc::c_int = piHashCount[k]; - if !(iEntries < 2i32) { - if !pTmpVert.is_null() { - e = 0i32; - while e < iEntries { - let mut i_0: libc::c_int = *pTable_0.offset(e as isize); - let vP_2 = get_position( - geometry, - *piTriList_in_and_out.offset(i_0 as isize) as usize, - ); - (*pTmpVert.offset(e as isize)).vert[0usize] = vP_2.x; - (*pTmpVert.offset(e as isize)).vert[1usize] = vP_2.y; - (*pTmpVert.offset(e as isize)).vert[2usize] = vP_2.z; - (*pTmpVert.offset(e as isize)).index = i_0; - e += 1 - } - MergeVertsFast( - piTriList_in_and_out, - pTmpVert, + let mut pTable_0 = &mut piHashTable[piHashOffsets[k] as usize] as *mut c_int; + let iEntries = piHashCount[k] as usize; + if !(iEntries < 2) { + e = 0; + while e < iEntries { + let mut i_0: libc::c_int = *pTable_0.offset(e as isize); + let vP_2 = get_position( geometry, - 0i32, - iEntries - 1i32, - ); - } else { - MergeVertsSlow( - piTriList_in_and_out, - geometry, - pTable_0 as *const libc::c_int, - iEntries, + *piTriList_in_and_out.offset(i_0 as isize) as usize, ); + pTmpVert[e].vert[0usize] = vP_2.x; + pTmpVert[e].vert[1usize] = vP_2.y; + pTmpVert[e].vert[2usize] = vP_2.z; + pTmpVert[e].index = i_0; + e += 1 } + MergeVertsFast( + piTriList_in_and_out, + pTmpVert.as_mut_ptr(), + geometry, + 0i32, + (iEntries - 1) as i32, + ); } k += 1 } - if !pTmpVert.is_null() { - free(pTmpVert as *mut libc::c_void); - } - free(piHashTable as *mut libc::c_void); - free(piHashOffsets as *mut libc::c_void); -} -unsafe fn MergeVertsSlow( - mut piTriList_in_and_out: *mut libc::c_int, - geometry: &mut I, - mut pTable: *const libc::c_int, - iEntries: libc::c_int, -) { - // this can be optimized further using a tree structure or more hashing. - let mut e: libc::c_int = 0i32; - e = 0i32; - while e < iEntries { - let mut i: libc::c_int = *pTable.offset(e as isize); - let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); - let vP = get_position(geometry, index as usize); - let vN = get_normal(geometry, index as usize); - let vT = get_tex_coord(geometry, index as usize); - let mut bNotFound: bool = true; - let mut e2: libc::c_int = 0i32; - let mut i2rec: libc::c_int = -1i32; - while e2 < e && bNotFound { - let i2: libc::c_int = *pTable.offset(e2 as isize); - let index2: libc::c_int = *piTriList_in_and_out.offset(i2 as isize); - let vP2 = get_position(geometry, index2 as usize); - let vN2 = get_normal(geometry, index2 as usize); - let vT2 = get_tex_coord(geometry, index2 as usize); - i2rec = i2; - if vP == vP2 && vN == vN2 && vT == vT2 { - bNotFound = false - } else { - e2 += 1 - } - } - if !bNotFound { - *piTriList_in_and_out.offset(i as isize) = *piTriList_in_and_out.offset(i2rec as isize) - } - e += 1 - } } + unsafe fn MergeVertsFast( mut piTriList_in_and_out: *mut libc::c_int, mut pTmpVert: *mut STmpVert, @@ -1798,53 +1687,6 @@ unsafe fn FindGridCell(fMin: libc::c_float, fMax: libc::c_float, fVal: libc::c_f }; } -unsafe fn GenerateSharedVerticesIndexListSlow( - mut piTriList_in_and_out: *mut libc::c_int, - geometry: &mut I, - iNrTrianglesIn: libc::c_int, -) { - let mut iNumUniqueVerts: libc::c_int = 0i32; - let mut t: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; - t = 0i32; - while t < iNrTrianglesIn { - i = 0i32; - while i < 3i32 { - let offs: libc::c_int = t * 3i32 + i; - let index: libc::c_int = *piTriList_in_and_out.offset(offs as isize); - let vP = get_position(geometry, index as usize); - let vN = get_normal(geometry, index as usize); - let vT = get_tex_coord(geometry, index as usize); - let mut bFound: bool = false; - let mut t2: libc::c_int = 0i32; - let mut index2rec: libc::c_int = -1i32; - while !bFound && t2 <= t { - let mut j: libc::c_int = 0i32; - while !bFound && j < 3i32 { - let index2: libc::c_int = - *piTriList_in_and_out.offset((t2 * 3i32 + j) as isize); - let vP2 = get_position(geometry, index2 as usize); - let vN2 = get_normal(geometry, index2 as usize); - let vT2 = get_tex_coord(geometry, index2 as usize); - if vP == vP2 && vN == vN2 && vT == vT2 { - bFound = true - } else { - j += 1 - } - } - if !bFound { - t2 += 1 - } - } - if index2rec == index { - iNumUniqueVerts += 1 - } - *piTriList_in_and_out.offset(offs as isize) = index2rec; - i += 1 - } - t += 1 - } -} unsafe fn GenerateInitialVerticesIndexList( pTriInfos: &mut [STriInfo], piTriList_out: &mut [i32], From 49a6c4e728030e8a386fb56d4a21847713da9285 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 21:02:38 +0200 Subject: [PATCH 53/87] Remove remaining libc types --- Cargo.toml | 1 - src/generated.rs | 553 +++++++++++++++++++++++------------------------ 2 files changed, 275 insertions(+), 279 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 834d175cc6fa8..a832fa3338cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,6 @@ exclude = ["examples/**"] travis-ci = { repository = "gltf-rs/mikktspace" } [dependencies] -libc = "0.2.54" nalgebra = "0.18.0" [[example]] diff --git a/src/generated.rs b/src/generated.rs index a38590383006a..c7b53cad03cac 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -11,10 +11,7 @@ use std::ptr::null_mut; -use { - libc::c_int, - nalgebra::{zero, Vector3}, -}; +use nalgebra::{zero, Vector3}; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; @@ -22,10 +19,10 @@ use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometr #[repr(C)] pub struct STSpace { pub vOs: Vector3, - pub fMagS: libc::c_float, + pub fMagS: f32, pub vOt: Vector3, - pub fMagT: libc::c_float, - pub iCounter: libc::c_int, + pub fMagT: f32, + pub iCounter: i32, pub bOrient: bool, } @@ -69,16 +66,16 @@ impl STSpace { #[derive(Copy, Clone)] pub struct STriInfo { - pub FaceNeighbors: [libc::c_int; 3], + pub FaceNeighbors: [i32; 3], pub AssignedGroup: [*mut SGroup; 3], pub vOs: Vector3, pub vOt: Vector3, - pub fMagS: libc::c_float, - pub fMagT: libc::c_float, - pub iOrgFaceNumber: libc::c_int, - pub iFlag: libc::c_int, - pub iTSpacesOffs: libc::c_int, - pub vert_num: [libc::c_uchar; 4], + pub fMagS: f32, + pub fMagT: f32, + pub iOrgFaceNumber: i32, + pub iFlag: i32, + pub iTSpacesOffs: i32, + pub vert_num: [u8; 4], } impl STriInfo { @@ -100,9 +97,9 @@ impl STriInfo { #[derive(Copy, Clone)] pub struct SGroup { - pub iNrFaces: libc::c_int, - pub pFaceIndices: *mut libc::c_int, - pub iVertexRepresentitive: libc::c_int, + pub iNrFaces: i32, + pub pFaceIndices: *mut i32, + pub iVertexRepresentitive: i32, pub bOrientPreservering: bool, } @@ -119,7 +116,7 @@ impl SGroup { #[derive(Clone)] pub struct SSubGroup { - pub iNrFaces: libc::c_int, + pub iNrFaces: i32, pub pTriMembers: Vec, } @@ -135,7 +132,7 @@ impl SSubGroup { #[derive(Copy, Clone)] pub union SEdge { pub unnamed: unnamed, - pub array: [libc::c_int; 3], + pub array: [i32; 3], } impl SEdge { @@ -146,15 +143,15 @@ impl SEdge { #[derive(Copy, Clone)] pub struct unnamed { - pub i0: libc::c_int, - pub i1: libc::c_int, - pub f: libc::c_int, + pub i0: i32, + pub i1: i32, + pub f: i32, } #[derive(Copy, Clone)] pub struct STmpVert { - pub vert: [libc::c_float; 3], - pub index: libc::c_int, + pub vert: [f32; 3], + pub index: i32, } impl STmpVert { @@ -168,7 +165,7 @@ impl STmpVert { pub unsafe fn genTangSpace( geometry: &mut I, - fAngularThreshold: libc::c_float, + fAngularThreshold: f32, ) -> bool { let mut iNrTrianglesIn = 0; let mut f = 0; @@ -178,13 +175,13 @@ pub unsafe fn genTangSpace( let mut iTotTris = 0; let mut iDegenTriangles = 0; let mut iNrMaxGroups = 0; - let mut iNrActiveGroups: libc::c_int = 0i32; + let mut iNrActiveGroups: i32 = 0i32; let mut index = 0; let iNrFaces = geometry.get_num_faces(); let mut bRes: bool = false; - let fThresCos: libc::c_float = ((fAngularThreshold * 3.14159265358979323846f64 as libc::c_float - / 180.0f32) as libc::c_double) - .cos() as libc::c_float; + let fThresCos: f32 = ((fAngularThreshold * 3.14159265358979323846f64 as f32 + / 180.0f32) as f64) + .cos() as f32; f = 0; while f < iNrFaces { let verts = geometry.get_num_vertices_of_face(f); @@ -229,8 +226,8 @@ pub unsafe fn genTangSpace( DegenPrologue( pTriInfos.as_mut_ptr(), piTriListIn.as_mut_ptr(), - iNrTrianglesIn as c_int, - iTotTris as c_int, + iNrTrianglesIn as i32, + iTotTris as i32, ); InitTriInfo( pTriInfos.as_mut_ptr(), @@ -248,7 +245,7 @@ pub unsafe fn genTangSpace( pGroups.as_mut_ptr(), piGroupTrianglesBuffer.as_mut_ptr(), piTriListIn.as_ptr(), - iNrTrianglesIn as c_int, + iNrTrianglesIn as i32, ); let mut psTspace = vec![ @@ -279,8 +276,8 @@ pub unsafe fn genTangSpace( pTriInfos.as_mut_ptr(), piTriListIn.as_mut_ptr(), geometry, - iNrTrianglesIn as c_int, - iTotTris as c_int, + iNrTrianglesIn as i32, + iTotTris as i32, ); index = 0; f = 0; @@ -313,13 +310,13 @@ pub unsafe fn genTangSpace( unsafe fn DegenEpilogue( mut psTspace: *mut STSpace, mut pTriInfos: *mut STriInfo, - mut piTriListIn: *mut libc::c_int, + mut piTriListIn: *mut i32, geometry: &mut I, - iNrTrianglesIn: libc::c_int, - iTotTris: libc::c_int, + iNrTrianglesIn: i32, + iTotTris: i32, ) { - let mut t: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; + let mut t: i32 = 0i32; + let mut i: i32 = 0i32; t = iNrTrianglesIn; while t < iTotTris { let bSkip: bool = if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { @@ -330,11 +327,11 @@ unsafe fn DegenEpilogue( if !bSkip { i = 0i32; while i < 3i32 { - let index1: libc::c_int = *piTriListIn.offset((t * 3i32 + i) as isize); + let index1: i32 = *piTriListIn.offset((t * 3i32 + i) as isize); let mut bNotFound: bool = true; - let mut j: libc::c_int = 0i32; + let mut j: i32 = 0i32; while bNotFound && j < 3i32 * iNrTrianglesIn { - let index2: libc::c_int = *piTriListIn.offset(j as isize); + let index2: i32 = *piTriListIn.offset(j as isize); if index1 == index2 { bNotFound = false } else { @@ -342,14 +339,14 @@ unsafe fn DegenEpilogue( } } if !bNotFound { - let iTri: libc::c_int = j / 3i32; - let iVert: libc::c_int = j % 3i32; - let iSrcVert: libc::c_int = - (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as libc::c_int; - let iSrcOffs: libc::c_int = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; - let iDstVert: libc::c_int = - (*pTriInfos.offset(t as isize)).vert_num[i as usize] as libc::c_int; - let iDstOffs: libc::c_int = (*pTriInfos.offset(t as isize)).iTSpacesOffs; + let iTri: i32 = j / 3i32; + let iVert: i32 = j % 3i32; + let iSrcVert: i32 = + (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as i32; + let iSrcOffs: i32 = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; + let iDstVert: i32 = + (*pTriInfos.offset(t as isize)).vert_num[i as usize] as i32; + let iDstOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; *psTspace.offset((iDstOffs + iDstVert) as isize) = *psTspace.offset((iSrcOffs + iSrcVert) as isize) } @@ -362,14 +359,14 @@ unsafe fn DegenEpilogue( while t < iNrTrianglesIn { if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { let mut vDstP = Vector3::new(0.0, 0.0, 0.0); - let mut iOrgF: libc::c_int = -1i32; - let mut i_0: libc::c_int = 0i32; + let mut iOrgF: i32 = -1i32; + let mut i_0: i32 = 0i32; let mut bNotFound_0: bool = false; - let mut pV: *mut libc::c_uchar = (*pTriInfos.offset(t as isize)).vert_num.as_mut_ptr(); - let mut iFlag: libc::c_int = 1i32 << *pV.offset(0isize) as libc::c_int - | 1i32 << *pV.offset(1isize) as libc::c_int - | 1i32 << *pV.offset(2isize) as libc::c_int; - let mut iMissingIndex: libc::c_int = 0i32; + let mut pV: *mut u8 = (*pTriInfos.offset(t as isize)).vert_num.as_mut_ptr(); + let mut iFlag: i32 = 1i32 << *pV.offset(0isize) as i32 + | 1i32 << *pV.offset(1isize) as i32 + | 1i32 << *pV.offset(2isize) as i32; + let mut iMissingIndex: i32 = 0i32; if iFlag & 2i32 == 0i32 { iMissingIndex = 1i32 } else if iFlag & 4i32 == 0i32 { @@ -385,13 +382,13 @@ unsafe fn DegenEpilogue( bNotFound_0 = true; i_0 = 0i32; while bNotFound_0 && i_0 < 3i32 { - let iVert_0: libc::c_int = *pV.offset(i_0 as isize) as libc::c_int; + let iVert_0: i32 = *pV.offset(i_0 as isize) as i32; let vSrcP = get_position( geometry, face_vert_to_index(iOrgF as usize, iVert_0 as usize), ); if vSrcP == vDstP { - let iOffs: libc::c_int = (*pTriInfos.offset(t as isize)).iTSpacesOffs; + let iOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; *psTspace.offset((iOffs + iMissingIndex) as isize) = *psTspace.offset((iOffs + iVert_0) as isize); bNotFound_0 = false @@ -408,15 +405,15 @@ unsafe fn GenerateTSpaces( psTspace: &mut [STSpace], mut pTriInfos: *const STriInfo, mut pGroups: *const SGroup, - iNrActiveGroups: libc::c_int, - mut piTriListIn: *const libc::c_int, - fThresCos: libc::c_float, + iNrActiveGroups: i32, + mut piTriListIn: *const i32, + fThresCos: f32, geometry: &mut I, ) -> bool { let mut iMaxNrFaces: usize = 0; let mut iUniqueTspaces = 0; - let mut g: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; + let mut g: i32 = 0i32; + let mut i: i32 = 0i32; g = 0i32; while g < iNrActiveGroups { if iMaxNrFaces < (*pGroups.offset(g as isize)).iNrFaces as usize { @@ -440,12 +437,12 @@ unsafe fn GenerateTSpaces( let mut s = 0; i = 0i32; while i < (*pGroup).iNrFaces { - let f: libc::c_int = *(*pGroup).pFaceIndices.offset(i as isize); - let mut index: libc::c_int = -1i32; - let mut iVertIndex: libc::c_int = -1i32; - let mut iOF_1: libc::c_int = -1i32; + let f: i32 = *(*pGroup).pFaceIndices.offset(i as isize); + let mut index: i32 = -1i32; + let mut iVertIndex: i32 = -1i32; + let mut iOF_1: i32 = -1i32; let mut iMembers: usize = 0; - let mut j: libc::c_int = 0i32; + let mut j: i32 = 0i32; let mut l: usize = 0; let mut tmp_group: SSubGroup = SSubGroup { iNrFaces: 0, @@ -480,8 +477,8 @@ unsafe fn GenerateTSpaces( iMembers = 0; j = 0i32; while j < (*pGroup).iNrFaces { - let t: libc::c_int = *(*pGroup).pFaceIndices.offset(j as isize); - let iOF_2: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let t: i32 = *(*pGroup).pFaceIndices.offset(j as isize); + let iOF_2: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - (n.dot(&(*pTriInfos.offset(t as isize)).vOs) * n); let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt @@ -502,8 +499,8 @@ unsafe fn GenerateTSpaces( false }; let bSameOrgFace: bool = iOF_1 == iOF_2; - let fCosS: libc::c_float = vOs.dot(&vOs2); - let fCosT: libc::c_float = vOt.dot(&vOt2); + let fCosS: f32 = vOs.dot(&vOs2); + let fCosT: f32 = vOt.dot(&vOt2); if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { let fresh0 = iMembers; iMembers = iMembers + 1; @@ -512,15 +509,15 @@ unsafe fn GenerateTSpaces( j += 1 } if iMembers > 1 { - let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; + let mut uSeed: u32 = 39871946i32 as u32; QuickSort( pTmpMembers.as_mut_ptr(), 0i32, - (iMembers - 1) as c_int, + (iMembers - 1) as i32, uSeed, ); } - tmp_group.iNrFaces = iMembers as c_int; + tmp_group.iNrFaces = iMembers as i32; tmp_group.pTriMembers = pTmpMembers.clone(); bFound = false; l = 0; @@ -531,12 +528,12 @@ unsafe fn GenerateTSpaces( } } if !bFound { - pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as c_int; + pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers as i32; pUniSubGroups[iUniqueSubGroups].pTriMembers = tmp_group.pTriMembers.clone(); pSubGroupTspace[iUniqueSubGroups] = EvalTspace( tmp_group.pTriMembers.as_mut_ptr(), - iMembers as c_int, + iMembers as i32, piTriListIn, pTriInfos, geometry, @@ -604,17 +601,17 @@ unsafe fn VNotZero(v: Vector3) -> bool { NotZero(v.x) || NotZero(v.y) || NotZero(v.z) } -unsafe fn NotZero(fX: libc::c_float) -> bool { +unsafe fn NotZero(fX: f32) -> bool { fX.abs() > 1.17549435e-38f32 } unsafe fn EvalTspace( - mut face_indices: *mut libc::c_int, - iFaces: libc::c_int, - mut piTriListIn: *const libc::c_int, + mut face_indices: *mut i32, + iFaces: i32, + mut piTriListIn: *const i32, mut pTriInfos: *const STriInfo, geometry: &mut I, - iVertexRepresentitive: libc::c_int, + iVertexRepresentitive: i32, ) -> STSpace { let mut res: STSpace = STSpace { vOs: Vector3::new(0.0, 0.0, 0.0), @@ -624,19 +621,19 @@ unsafe fn EvalTspace( iCounter: 0, bOrient: false, }; - let mut fAngleSum: libc::c_float = 0i32 as libc::c_float; - let mut face: libc::c_int = 0i32; + let mut fAngleSum: f32 = 0i32 as f32; + let mut face: i32 = 0i32; res.vOs.x = 0.0f32; res.vOs.y = 0.0f32; res.vOs.z = 0.0f32; res.vOt.x = 0.0f32; res.vOt.y = 0.0f32; res.vOt.z = 0.0f32; - res.fMagS = 0i32 as libc::c_float; - res.fMagT = 0i32 as libc::c_float; + res.fMagS = 0i32 as f32; + res.fMagT = 0i32 as f32; face = 0i32; while face < iFaces { - let f: libc::c_int = *face_indices.offset(face as isize); + let f: i32 = *face_indices.offset(face as isize); if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 { let mut n = Vector3::new(0.0, 0.0, 0.0); let mut vOs = Vector3::new(0.0, 0.0, 0.0); @@ -646,15 +643,15 @@ unsafe fn EvalTspace( let mut p2 = Vector3::new(0.0, 0.0, 0.0); let mut v1 = Vector3::new(0.0, 0.0, 0.0); let mut v2 = Vector3::new(0.0, 0.0, 0.0); - let mut fCos: libc::c_float = 0.; - let mut fAngle: libc::c_float = 0.; - let mut fMagS: libc::c_float = 0.; - let mut fMagT: libc::c_float = 0.; - let mut i: libc::c_int = -1i32; - let mut index: libc::c_int = -1i32; - let mut i0: libc::c_int = -1i32; - let mut i1: libc::c_int = -1i32; - let mut i2: libc::c_int = -1i32; + let mut fCos: f32 = 0.; + let mut fAngle: f32 = 0.; + let mut fMagS: f32 = 0.; + let mut fMagT: f32 = 0.; + let mut i: i32 = -1i32; + let mut index: i32 = -1i32; + let mut i0: i32 = -1i32; + let mut i1: i32 = -1i32; + let mut i2: i32 = -1i32; if *piTriListIn.offset((3i32 * f + 0i32) as isize) == iVertexRepresentitive { i = 0i32 } else if *piTriListIn.offset((3i32 * f + 1i32) as isize) == iVertexRepresentitive { @@ -691,14 +688,14 @@ unsafe fn EvalTspace( v2 = Normalize(v2) } fCos = v1.dot(&v2); - fCos = if fCos > 1i32 as libc::c_float { - 1i32 as libc::c_float - } else if fCos < -1i32 as libc::c_float { - -1i32 as libc::c_float + fCos = if fCos > 1i32 as f32 { + 1i32 as f32 + } else if fCos < -1i32 as f32 { + -1i32 as f32 } else { fCos }; - fAngle = (fCos as libc::c_double).acos() as libc::c_float; + fAngle = (fCos as f64).acos() as f32; fMagS = (*pTriInfos.offset(f as isize)).fMagS; fMagT = (*pTriInfos.offset(f as isize)).fMagT; res.vOs = res.vOs + (fAngle * vOs); @@ -715,7 +712,7 @@ unsafe fn EvalTspace( if VNotZero(res.vOt) { res.vOt = Normalize(res.vOt) } - if fAngleSum > 0i32 as libc::c_float { + if fAngleSum > 0i32 as f32 { res.fMagS /= fAngleSum; res.fMagT /= fAngleSum } @@ -741,28 +738,28 @@ unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) return bStillSame; } unsafe fn QuickSort( - mut pSortBuffer: *mut libc::c_int, - mut iLeft: libc::c_int, - mut iRight: libc::c_int, - mut uSeed: libc::c_uint, + mut pSortBuffer: *mut i32, + mut iLeft: i32, + mut iRight: i32, + mut uSeed: u32, ) { - let mut iL: libc::c_int = 0; - let mut iR: libc::c_int = 0; - let mut n: libc::c_int = 0; - let mut index: libc::c_int = 0; - let mut iMid: libc::c_int = 0; - let mut iTmp: libc::c_int = 0; + let mut iL: i32 = 0; + let mut iR: i32 = 0; + let mut n: i32 = 0; + let mut index: i32 = 0; + let mut iMid: i32 = 0; + let mut iTmp: i32 = 0; // Random - let mut t: libc::c_uint = uSeed & 31i32 as libc::c_uint; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as libc::c_uint).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as libc::c_uint); + let mut t: u32 = uSeed & 31i32 as u32; + t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); + uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); // Random end iL = iLeft; iR = iRight; n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as libc::c_uint) as libc::c_int; + index = uSeed.wrapping_rem(n as u32) as i32; iMid = *pSortBuffer.offset((index + iL) as isize); loop { while *pSortBuffer.offset(iL as isize) < iMid { @@ -792,15 +789,15 @@ unsafe fn QuickSort( unsafe fn Build4RuleGroups( mut pTriInfos: *mut STriInfo, mut pGroups: *mut SGroup, - mut piGroupTrianglesBuffer: *mut libc::c_int, - mut piTriListIn: *const libc::c_int, - iNrTrianglesIn: libc::c_int, -) -> libc::c_int { - let iNrMaxGroups: libc::c_int = iNrTrianglesIn * 3i32; - let mut iNrActiveGroups: libc::c_int = 0i32; - let mut iOffset: libc::c_int = 0i32; - let mut f: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; + mut piGroupTrianglesBuffer: *mut i32, + mut piTriListIn: *const i32, + iNrTrianglesIn: i32, +) -> i32 { + let iNrMaxGroups: i32 = iNrTrianglesIn * 3i32; + let mut iNrActiveGroups: i32 = 0i32; + let mut iOffset: i32 = 0i32; + let mut f: i32 = 0i32; + let mut i: i32 = 0i32; f = 0i32; while f < iNrTrianglesIn { i = 0i32; @@ -809,9 +806,9 @@ unsafe fn Build4RuleGroups( && (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize].is_null() { let mut bOrPre: bool = false; - let mut neigh_indexL: libc::c_int = 0; - let mut neigh_indexR: libc::c_int = 0; - let vert_index: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); + let mut neigh_indexL: i32 = 0; + let mut neigh_indexR: i32 = 0; + let vert_index: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); let ref mut fresh2 = (*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]; *fresh2 = &mut *pGroups.offset(iNrActiveGroups as isize) as *mut SGroup; (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]) @@ -821,7 +818,7 @@ unsafe fn Build4RuleGroups( (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).iNrFaces = 0i32; let ref mut fresh3 = (*(*pTriInfos.offset(f as isize)).AssignedGroup[i as usize]).pFaceIndices; - *fresh3 = &mut *piGroupTrianglesBuffer.offset(iOffset as isize) as *mut libc::c_int; + *fresh3 = &mut *piGroupTrianglesBuffer.offset(iOffset as isize) as *mut i32; iNrActiveGroups += 1; AddTriToGroup((*pTriInfos.offset(f as isize)).AssignedGroup[i as usize], f); bOrPre = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 != 0i32 { @@ -873,18 +870,18 @@ unsafe fn Build4RuleGroups( // /////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// unsafe fn AssignRecur( - mut piTriListIn: *const libc::c_int, + mut piTriListIn: *const i32, mut psTriInfos: *mut STriInfo, - iMyTriIndex: libc::c_int, + iMyTriIndex: i32, mut pGroup: *mut SGroup, ) -> bool { let mut pMyTriInfo: *mut STriInfo = &mut *psTriInfos.offset(iMyTriIndex as isize) as *mut STriInfo; // track down vertex - let iVertRep: libc::c_int = (*pGroup).iVertexRepresentitive; - let mut pVerts: *const libc::c_int = - &*piTriListIn.offset((3i32 * iMyTriIndex + 0i32) as isize) as *const libc::c_int; - let mut i: libc::c_int = -1i32; + let iVertRep: i32 = (*pGroup).iVertexRepresentitive; + let mut pVerts: *const i32 = + &*piTriListIn.offset((3i32 * iMyTriIndex + 0i32) as isize) as *const i32; + let mut i: i32 = -1i32; if *pVerts.offset(0isize) == iVertRep { i = 0i32 } else if *pVerts.offset(1isize) == iVertRep { @@ -922,8 +919,8 @@ unsafe fn AssignRecur( } AddTriToGroup(pGroup, iMyTriIndex); (*pMyTriInfo).AssignedGroup[i as usize] = pGroup; - let neigh_indexL: libc::c_int = (*pMyTriInfo).FaceNeighbors[i as usize]; - let neigh_indexR: libc::c_int = + let neigh_indexL: i32 = (*pMyTriInfo).FaceNeighbors[i as usize]; + let neigh_indexR: i32 = (*pMyTriInfo).FaceNeighbors[(if i > 0i32 { i - 1i32 } else { 2i32 }) as usize]; if neigh_indexL >= 0i32 { AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); @@ -933,13 +930,13 @@ unsafe fn AssignRecur( } return true; } -unsafe fn AddTriToGroup(mut pGroup: *mut SGroup, iTriIndex: libc::c_int) { +unsafe fn AddTriToGroup(mut pGroup: *mut SGroup, iTriIndex: i32) { *(*pGroup).pFaceIndices.offset((*pGroup).iNrFaces as isize) = iTriIndex; (*pGroup).iNrFaces += 1; } unsafe fn InitTriInfo( mut pTriInfos: *mut STriInfo, - mut piTriListIn: *const libc::c_int, + mut piTriListIn: *const i32, geometry: &mut I, iNrTrianglesIn: usize, ) { @@ -959,8 +956,8 @@ unsafe fn InitTriInfo( (*pTriInfos.offset(f as isize)).vOt.x = 0.0f32; (*pTriInfos.offset(f as isize)).vOt.y = 0.0f32; (*pTriInfos.offset(f as isize)).vOt.z = 0.0f32; - (*pTriInfos.offset(f as isize)).fMagS = 0i32 as libc::c_float; - (*pTriInfos.offset(f as isize)).fMagT = 0i32 as libc::c_float; + (*pTriInfos.offset(f as isize)).fMagS = 0i32 as f32; + (*pTriInfos.offset(f as isize)).fMagT = 0i32 as f32; (*pTriInfos.offset(f as isize)).iFlag |= 4i32; i += 1 } @@ -974,25 +971,25 @@ unsafe fn InitTriInfo( let t1 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 0) as isize) as usize); let t2 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 1) as isize) as usize); let t3 = get_tex_coord(geometry, *piTriListIn.offset((f * 3 + 2) as isize) as usize); - let t21x: libc::c_float = t2.x - t1.x; - let t21y: libc::c_float = t2.y - t1.y; - let t31x: libc::c_float = t3.x - t1.x; - let t31y: libc::c_float = t3.y - t1.y; + let t21x: f32 = t2.x - t1.x; + let t21y: f32 = t2.y - t1.y; + let t31x: f32 = t3.x - t1.x; + let t31y: f32 = t3.y - t1.y; let d1 = v2 - v1; let d2 = v3 - v1; - let fSignedAreaSTx2: libc::c_float = t21x * t31y - t21y * t31x; + let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; let mut vOs = (t31y * d1) - (t21y * d2); let mut vOt = (-t31x * d1) + (t21x * d2); - (*pTriInfos.offset(f as isize)).iFlag |= if fSignedAreaSTx2 > 0i32 as libc::c_float { + (*pTriInfos.offset(f as isize)).iFlag |= if fSignedAreaSTx2 > 0i32 as f32 { 8i32 } else { 0i32 }; if NotZero(fSignedAreaSTx2) { - let fAbsArea: libc::c_float = fSignedAreaSTx2.abs(); - let fLenOs: libc::c_float = vOs.magnitude(); - let fLenOt: libc::c_float = vOt.magnitude(); - let fS: libc::c_float = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { + let fAbsArea: f32 = fSignedAreaSTx2.abs(); + let fLenOs: f32 = vOs.magnitude(); + let fLenOt: f32 = vOt.magnitude(); + let fS: f32 = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { -1.0f32 } else { 1.0f32 @@ -1014,8 +1011,8 @@ unsafe fn InitTriInfo( f += 1 } while t < iNrTrianglesIn - 1 { - let iFO_a: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: libc::c_int = (*pTriInfos.offset((t + 1) as isize)).iOrgFaceNumber; + let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let iFO_b: i32 = (*pTriInfos.offset((t + 1) as isize)).iOrgFaceNumber; if iFO_a == iFO_b { let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { true @@ -1065,29 +1062,29 @@ unsafe fn InitTriInfo( pTriInfos, pEdges.as_mut_ptr(), piTriListIn, - iNrTrianglesIn as c_int, + iNrTrianglesIn as i32, ); } unsafe fn BuildNeighborsFast( mut pTriInfos: *mut STriInfo, mut pEdges: *mut SEdge, - mut piTriListIn: *const libc::c_int, - iNrTrianglesIn: libc::c_int, + mut piTriListIn: *const i32, + iNrTrianglesIn: i32, ) { // build array of edges // could replace with a random seed? - let mut uSeed: libc::c_uint = 39871946i32 as libc::c_uint; - let mut iEntries: libc::c_int = 0i32; - let mut iCurStartIndex: libc::c_int = -1i32; - let mut f: libc::c_int = 0i32; - let mut i: libc::c_int = 0i32; + let mut uSeed: u32 = 39871946i32 as u32; + let mut iEntries: i32 = 0i32; + let mut iCurStartIndex: i32 = -1i32; + let mut f: i32 = 0i32; + let mut i: i32 = 0i32; f = 0i32; while f < iNrTrianglesIn { i = 0i32; while i < 3i32 { - let i0: libc::c_int = *piTriListIn.offset((f * 3i32 + i) as isize); - let i1: libc::c_int = + let i0: i32 = *piTriListIn.offset((f * 3i32 + i) as isize); + let i1: i32 = *piTriListIn.offset((f * 3i32 + if i < 2i32 { i + 1i32 } else { 0i32 }) as isize); (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i0 = if i0 < i1 { i0 } else { i1 }; (*pEdges.offset((f * 3i32 + i) as isize)).unnamed.i1 = if !(i0 < i1) { i0 } else { i1 }; @@ -1104,8 +1101,8 @@ unsafe fn BuildNeighborsFast( if (*pEdges.offset(iCurStartIndex as isize)).unnamed.i0 != (*pEdges.offset(i as isize)).unnamed.i0 { - let iL: libc::c_int = iCurStartIndex; - let iR: libc::c_int = i - 1i32; + let iL: i32 = iCurStartIndex; + let iR: i32 = i - 1i32; iCurStartIndex = i; QuickSortEdges(pEdges, iL, iR, 1i32, uSeed); } @@ -1119,8 +1116,8 @@ unsafe fn BuildNeighborsFast( || (*pEdges.offset(iCurStartIndex as isize)).unnamed.i1 != (*pEdges.offset(i as isize)).unnamed.i1 { - let iL_0: libc::c_int = iCurStartIndex; - let iR_0: libc::c_int = i - 1i32; + let iL_0: i32 = iCurStartIndex; + let iR_0: i32 = i - 1i32; iCurStartIndex = i; QuickSortEdges(pEdges, iL_0, iR_0, 2i32, uSeed); } @@ -1128,14 +1125,14 @@ unsafe fn BuildNeighborsFast( } i = 0i32; while i < iEntries { - let i0_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.i0; - let i1_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.i1; - let f_0: libc::c_int = (*pEdges.offset(i as isize)).unnamed.f; + let i0_0: i32 = (*pEdges.offset(i as isize)).unnamed.i0; + let i1_0: i32 = (*pEdges.offset(i as isize)).unnamed.i1; + let f_0: i32 = (*pEdges.offset(i as isize)).unnamed.f; let mut bUnassigned_A: bool = false; - let mut i0_A: libc::c_int = 0; - let mut i1_A: libc::c_int = 0; - let mut edgenum_A: libc::c_int = 0; - let mut edgenum_B: libc::c_int = 0i32; + let mut i0_A: i32 = 0; + let mut i1_A: i32 = 0; + let mut edgenum_A: i32 = 0; + let mut edgenum_B: i32 = 0i32; GetEdge( &mut i0_A, &mut i1_A, @@ -1151,8 +1148,8 @@ unsafe fn BuildNeighborsFast( false }; if bUnassigned_A { - let mut j: libc::c_int = i + 1i32; - let mut t: libc::c_int = 0; + let mut j: i32 = i + 1i32; + let mut t: i32 = 0; let mut bNotFound: bool = true; while j < iEntries && i0_0 == (*pEdges.offset(j as isize)).unnamed.i0 @@ -1160,8 +1157,8 @@ unsafe fn BuildNeighborsFast( && bNotFound { let mut bUnassigned_B: bool = false; - let mut i0_B: libc::c_int = 0; - let mut i1_B: libc::c_int = 0; + let mut i0_B: i32 = 0; + let mut i1_B: i32 = 0; t = (*pEdges.offset(j as isize)).unnamed.f; GetEdge( &mut i1_B, @@ -1184,7 +1181,7 @@ unsafe fn BuildNeighborsFast( } } if !bNotFound { - let mut t_0: libc::c_int = (*pEdges.offset(j as isize)).unnamed.f; + let mut t_0: i32 = (*pEdges.offset(j as isize)).unnamed.f; (*pTriInfos.offset(f_0 as isize)).FaceNeighbors[edgenum_A as usize] = t_0; (*pTriInfos.offset(t_0 as isize)).FaceNeighbors[edgenum_B as usize] = f_0 } @@ -1193,12 +1190,12 @@ unsafe fn BuildNeighborsFast( } } unsafe fn GetEdge( - mut i0_out: *mut libc::c_int, - mut i1_out: *mut libc::c_int, - mut edgenum_out: *mut libc::c_int, - mut indices: *const libc::c_int, - i0_in: libc::c_int, - i1_in: libc::c_int, + mut i0_out: *mut i32, + mut i1_out: *mut i32, + mut edgenum_out: *mut i32, + mut indices: *const i32, + i0_in: i32, + i1_in: i32, ) { *edgenum_out = -1i32; if *indices.offset(0isize) == i0_in || *indices.offset(0isize) == i1_in { @@ -1221,22 +1218,22 @@ unsafe fn GetEdge( ///////////////////////////////////////////////////////////////////////////////////////////// unsafe fn QuickSortEdges( mut pSortBuffer: *mut SEdge, - mut iLeft: libc::c_int, - mut iRight: libc::c_int, - channel: libc::c_int, - mut uSeed: libc::c_uint, + mut iLeft: i32, + mut iRight: i32, + channel: i32, + mut uSeed: u32, ) { - let mut t: libc::c_uint = 0; - let mut iL: libc::c_int = 0; - let mut iR: libc::c_int = 0; - let mut n: libc::c_int = 0; - let mut index: libc::c_int = 0; - let mut iMid: libc::c_int = 0; + let mut t: u32 = 0; + let mut iL: i32 = 0; + let mut iR: i32 = 0; + let mut n: i32 = 0; + let mut index: i32 = 0; + let mut iMid: i32 = 0; // early out let mut sTmp: SEdge = SEdge { unnamed: unnamed { i0: 0, i1: 0, f: 0 }, }; - let iElems: libc::c_int = iRight - iLeft + 1i32; + let iElems: i32 = iRight - iLeft + 1i32; if iElems < 2i32 { return; } else { @@ -1253,15 +1250,15 @@ unsafe fn QuickSortEdges( } // Random - t = uSeed & 31i32 as libc::c_uint; - t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as libc::c_uint).wrapping_sub(t)); - uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as libc::c_uint); + t = uSeed & 31i32 as u32; + t = uSeed.rotate_left(t) | uSeed.rotate_right((32i32 as u32).wrapping_sub(t)); + uSeed = uSeed.wrapping_add(t).wrapping_add(3i32 as u32); // Random end iL = iLeft; iR = iRight; n = iR - iL + 1i32; - index = uSeed.wrapping_rem(n as libc::c_uint) as libc::c_int; + index = uSeed.wrapping_rem(n as u32) as i32; iMid = (*pSortBuffer.offset((index + iL) as isize)).array[channel as usize]; loop { while (*pSortBuffer.offset(iL as isize)).array[channel as usize] < iMid { @@ -1292,17 +1289,17 @@ unsafe fn QuickSortEdges( // returns the texture area times 2 unsafe fn CalcTexArea( geometry: &mut I, - mut indices: *const libc::c_int, -) -> libc::c_float { + mut indices: *const i32, +) -> f32 { let t1 = get_tex_coord(geometry, *indices.offset(0isize) as usize); let t2 = get_tex_coord(geometry, *indices.offset(1isize) as usize); let t3 = get_tex_coord(geometry, *indices.offset(2isize) as usize); - let t21x: libc::c_float = t2.x - t1.x; - let t21y: libc::c_float = t2.y - t1.y; - let t31x: libc::c_float = t3.x - t1.x; - let t31y: libc::c_float = t3.y - t1.y; - let fSignedAreaSTx2: libc::c_float = t21x * t31y - t21y * t31x; - return if fSignedAreaSTx2 < 0i32 as libc::c_float { + let t21x: f32 = t2.x - t1.x; + let t21y: f32 = t2.y - t1.y; + let t31x: f32 = t3.x - t1.x; + let t31y: f32 = t3.y - t1.y; + let fSignedAreaSTx2: f32 = t21x * t31y - t21y * t31x; + return if fSignedAreaSTx2 < 0i32 as f32 { -fSignedAreaSTx2 } else { fSignedAreaSTx2 @@ -1312,17 +1309,17 @@ unsafe fn CalcTexArea( // degen triangles unsafe fn DegenPrologue( mut pTriInfos: *mut STriInfo, - mut piTriList_out: *mut libc::c_int, - iNrTrianglesIn: libc::c_int, - iTotTris: libc::c_int, + mut piTriList_out: *mut i32, + iNrTrianglesIn: i32, + iTotTris: i32, ) { - let mut iNextGoodTriangleSearchIndex: libc::c_int = -1i32; + let mut iNextGoodTriangleSearchIndex: i32 = -1i32; let mut bStillFindingGoodOnes: bool = false; // locate quads with only one good triangle - let mut t: libc::c_int = 0i32; + let mut t: i32 = 0i32; while t < iTotTris - 1i32 { - let iFO_a: libc::c_int = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - let iFO_b: libc::c_int = (*pTriInfos.offset((t + 1i32) as isize)).iOrgFaceNumber; + let iFO_a: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + let iFO_b: i32 = (*pTriInfos.offset((t + 1i32) as isize)).iOrgFaceNumber; if iFO_a == iFO_b { let bIsDeg_a: bool = if (*pTriInfos.offset(t as isize)).iFlag & 1i32 != 0i32 { true @@ -1357,8 +1354,8 @@ unsafe fn DegenPrologue( iNextGoodTriangleSearchIndex = t + 2i32 } } else { - let mut t0: libc::c_int = 0; - let mut t1: libc::c_int = 0; + let mut t0: i32 = 0; + let mut t1: i32 = 0; let mut bJustADegenerate: bool = true; while bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris { let bIsGood_0: bool = @@ -1379,10 +1376,10 @@ unsafe fn DegenPrologue( t1 = iNextGoodTriangleSearchIndex; iNextGoodTriangleSearchIndex += 1; if !bJustADegenerate { - let mut i: libc::c_int = 0i32; + let mut i: i32 = 0i32; i = 0i32; while i < 3i32 { - let index: libc::c_int = *piTriList_out.offset((t0 * 3i32 + i) as isize); + let index: i32 = *piTriList_out.offset((t0 * 3i32 + i) as isize); *piTriList_out.offset((t0 * 3i32 + i) as isize) = *piTriList_out.offset((t1 * 3i32 + i) as isize); *piTriList_out.offset((t1 * 3i32 + i) as isize) = index; @@ -1401,23 +1398,23 @@ unsafe fn DegenPrologue( } } unsafe fn GenerateSharedVerticesIndexList( - mut piTriList_in_and_out: *mut libc::c_int, + mut piTriList_in_and_out: *mut i32, geometry: &mut I, iNrTrianglesIn: usize, ) { let mut i = 0; - let mut iChannel: libc::c_int = 0i32; + let mut iChannel: i32 = 0i32; let mut k = 0; let mut e = 0; let mut iMaxCount = 0; let mut vMin = get_position(geometry, 0); let mut vMax = vMin; let mut vDim = Vector3::new(0.0, 0.0, 0.0); - let mut fMin: libc::c_float = 0.; - let mut fMax: libc::c_float = 0.; + let mut fMin: f32 = 0.; + let mut fMax: f32 = 0.; i = 1; while i < iNrTrianglesIn * 3 { - let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let index: i32 = *piTriList_in_and_out.offset(i as isize); let vP = get_position(geometry, index as usize); if vMin.x > vP.x { vMin.x = vP.x @@ -1457,9 +1454,9 @@ unsafe fn GenerateSharedVerticesIndexList( i = 0; while i < iNrTrianglesIn * 3 { - let index_0: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let index_0: i32 = *piTriList_in_and_out.offset(i as isize); let vP_0 = get_position(geometry, index_0 as usize); - let fVal: libc::c_float = if iChannel == 0i32 { + let fVal: f32 = if iChannel == 0i32 { vP_0.x } else if iChannel == 1i32 { vP_0.y @@ -1478,9 +1475,9 @@ unsafe fn GenerateSharedVerticesIndexList( } i = 0; while i < iNrTrianglesIn * 3 { - let index_1: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let index_1: i32 = *piTriList_in_and_out.offset(i as isize); let vP_1 = get_position(geometry, index_1 as usize); - let fVal_0: libc::c_float = if iChannel == 0i32 { + let fVal_0: f32 = if iChannel == 0i32 { vP_1.x } else if iChannel == 1i32 { vP_1.y @@ -1488,9 +1485,9 @@ unsafe fn GenerateSharedVerticesIndexList( vP_1.z }; let iCell_0 = FindGridCell(fMin, fMax, fVal_0); - let mut pTable: *mut libc::c_int = 0 as *mut libc::c_int; - pTable = &mut piHashTable[piHashOffsets[iCell_0] as usize] as *mut libc::c_int; - *pTable.offset(piHashCount2[iCell_0] as isize) = i as c_int; + let mut pTable: *mut i32 = 0 as *mut i32; + pTable = &mut piHashTable[piHashOffsets[iCell_0] as usize] as *mut i32; + *pTable.offset(piHashCount2[iCell_0] as isize) = i as i32; piHashCount2[iCell_0] += 1; i += 1 } @@ -1510,12 +1507,12 @@ unsafe fn GenerateSharedVerticesIndexList( k = 0; while k < g_iCells { // extract table of cell k and amount of entries in it - let mut pTable_0 = &mut piHashTable[piHashOffsets[k] as usize] as *mut c_int; + let mut pTable_0 = &mut piHashTable[piHashOffsets[k] as usize] as *mut i32; let iEntries = piHashCount[k] as usize; if !(iEntries < 2) { e = 0; while e < iEntries { - let mut i_0: libc::c_int = *pTable_0.offset(e as isize); + let mut i_0: i32 = *pTable_0.offset(e as isize); let vP_2 = get_position( geometry, *piTriList_in_and_out.offset(i_0 as isize) as usize, @@ -1539,22 +1536,22 @@ unsafe fn GenerateSharedVerticesIndexList( } unsafe fn MergeVertsFast( - mut piTriList_in_and_out: *mut libc::c_int, + mut piTriList_in_and_out: *mut i32, mut pTmpVert: *mut STmpVert, geometry: &mut I, - iL_in: libc::c_int, - iR_in: libc::c_int, + iL_in: i32, + iR_in: i32, ) { // make bbox - let mut c: libc::c_int = 0i32; - let mut l: libc::c_int = 0i32; - let mut channel: libc::c_int = 0i32; - let mut fvMin: [libc::c_float; 3] = [0.; 3]; - let mut fvMax: [libc::c_float; 3] = [0.; 3]; - let mut dx: libc::c_float = 0i32 as libc::c_float; - let mut dy: libc::c_float = 0i32 as libc::c_float; - let mut dz: libc::c_float = 0i32 as libc::c_float; - let mut fSep: libc::c_float = 0i32 as libc::c_float; + let mut c: i32 = 0i32; + let mut l: i32 = 0i32; + let mut channel: i32 = 0i32; + let mut fvMin: [f32; 3] = [0.; 3]; + let mut fvMax: [f32; 3] = [0.; 3]; + let mut dx: f32 = 0i32 as f32; + let mut dy: f32 = 0i32 as f32; + let mut dz: f32 = 0i32 as f32; + let mut fSep: f32 = 0i32 as f32; c = 0i32; while c < 3i32 { fvMin[c as usize] = (*pTmpVert.offset(iL_in as isize)).vert[c as usize]; @@ -1587,17 +1584,17 @@ unsafe fn MergeVertsFast( if fSep >= fvMax[channel as usize] || fSep <= fvMin[channel as usize] { l = iL_in; while l <= iR_in { - let mut i: libc::c_int = (*pTmpVert.offset(l as isize)).index; - let index: libc::c_int = *piTriList_in_and_out.offset(i as isize); + let mut i: i32 = (*pTmpVert.offset(l as isize)).index; + let index: i32 = *piTriList_in_and_out.offset(i as isize); let vP = get_position(geometry, index as usize); let vN = get_normal(geometry, index as usize); let vT = get_tex_coord(geometry, index as usize); let mut bNotFound: bool = true; - let mut l2: libc::c_int = iL_in; - let mut i2rec: libc::c_int = -1i32; + let mut l2: i32 = iL_in; + let mut i2rec: i32 = -1i32; while l2 < l && bNotFound { - let i2: libc::c_int = (*pTmpVert.offset(l2 as isize)).index; - let index2: libc::c_int = *piTriList_in_and_out.offset(i2 as isize); + let i2: i32 = (*pTmpVert.offset(l2 as isize)).index; + let index2: i32 = *piTriList_in_and_out.offset(i2 as isize); let vP2 = get_position(geometry, index2 as usize); let vN2 = get_normal(geometry, index2 as usize); let vT2 = get_tex_coord(geometry, index2 as usize); @@ -1624,8 +1621,8 @@ unsafe fn MergeVertsFast( l += 1 } } else { - let mut iL: libc::c_int = iL_in; - let mut iR: libc::c_int = iR_in; + let mut iL: i32 = iL_in; + let mut iR: i32 = iR_in; while iL < iR { let mut bReadyLeftSwap: bool = false; let mut bReadyRightSwap: bool = false; @@ -1673,7 +1670,7 @@ const g_iCells: usize = 2048; // inlining could potentially reorder instructions and generate different // results for the same effective input value fVal. #[inline(never)] -unsafe fn FindGridCell(fMin: libc::c_float, fMax: libc::c_float, fVal: libc::c_float) -> usize { +unsafe fn FindGridCell(fMin: f32, fMax: f32, fVal: f32) -> usize { let fIndex = g_iCells as f32 * ((fVal - fMin) / (fMax - fMin)); let iIndex = fIndex as isize; return if iIndex < g_iCells as isize { @@ -1701,20 +1698,20 @@ unsafe fn GenerateInitialVerticesIndexList( while f < geometry.get_num_faces() { let verts = geometry.get_num_vertices_of_face(f); if !(verts != 3 && verts != 4) { - pTriInfos[iDstTriIndex].iOrgFaceNumber = f as c_int; - pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as c_int; + pTriInfos[iDstTriIndex].iOrgFaceNumber = f as i32; + pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as i32; if verts == 3 { let mut pVerts = &mut pTriInfos[iDstTriIndex].vert_num; pVerts[0] = 0; pVerts[1] = 1; pVerts[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = face_vert_to_index(f, 0) as c_int; - piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as c_int; - piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as c_int; + piTriList_out[iDstTriIndex * 3 + 0] = face_vert_to_index(f, 0) as i32; + piTriList_out[iDstTriIndex * 3 + 1] = face_vert_to_index(f, 1) as i32; + piTriList_out[iDstTriIndex * 3 + 2] = face_vert_to_index(f, 2) as i32; iDstTriIndex += 1 } else { - pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as c_int; - pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as c_int; + pTriInfos[iDstTriIndex + 1].iOrgFaceNumber = f as i32; + pTriInfos[iDstTriIndex + 1].iTSpacesOffs = iTSpacesOffs as i32; let i0 = face_vert_to_index(f, 0); let i1 = face_vert_to_index(f, 1); let i2 = face_vert_to_index(f, 2); @@ -1723,8 +1720,8 @@ unsafe fn GenerateInitialVerticesIndexList( let T1 = get_tex_coord(geometry, i1); let T2 = get_tex_coord(geometry, i2); let T3 = get_tex_coord(geometry, i3); - let distSQ_02: libc::c_float = (T2 - T0).magnitude_squared(); - let distSQ_13: libc::c_float = (T3 - T1).magnitude_squared(); + let distSQ_02: f32 = (T2 - T0).magnitude_squared(); + let distSQ_13: f32 = (T3 - T1).magnitude_squared(); let mut bQuadDiagIs_02: bool = false; if distSQ_02 < distSQ_13 { bQuadDiagIs_02 = true @@ -1735,8 +1732,8 @@ unsafe fn GenerateInitialVerticesIndexList( let P1 = get_position(geometry, i1); let P2 = get_position(geometry, i2); let P3 = get_position(geometry, i3); - let distSQ_02_0: libc::c_float = (P2 - P0).magnitude_squared(); - let distSQ_13_0: libc::c_float = (P3 - P1).magnitude_squared(); + let distSQ_02_0: f32 = (P2 - P0).magnitude_squared(); + let distSQ_13_0: f32 = (P3 - P1).magnitude_squared(); bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { false } else { @@ -1748,36 +1745,36 @@ unsafe fn GenerateInitialVerticesIndexList( pVerts_A[0] = 0; pVerts_A[1] = 1; pVerts_A[2] = 2; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as c_int; - piTriList_out[iDstTriIndex * 3 + 2] = i2 as c_int; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i2 as i32; iDstTriIndex += 1; let mut pVerts_B = &mut pTriInfos[iDstTriIndex].vert_num; pVerts_B[0] = 0; pVerts_B[1] = 2; pVerts_B[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as c_int; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; iDstTriIndex += 1 } else { let mut pVerts_A_0 = &mut pTriInfos[iDstTriIndex].vert_num; pVerts_A_0[0] = 0; pVerts_A_0[1] = 1; pVerts_A_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i0 as c_int; - piTriList_out[iDstTriIndex * 3 + 1] = i1 as c_int; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; + piTriList_out[iDstTriIndex * 3 + 0] = i0 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; iDstTriIndex += 1; let mut pVerts_B_0 = &mut pTriInfos[iDstTriIndex].vert_num; pVerts_B_0[0] = 1; pVerts_B_0[1] = 2; pVerts_B_0[2] = 3; - piTriList_out[iDstTriIndex * 3 + 0] = i1 as c_int; - piTriList_out[iDstTriIndex * 3 + 1] = i2 as c_int; - piTriList_out[iDstTriIndex * 3 + 2] = i3 as c_int; + piTriList_out[iDstTriIndex * 3 + 0] = i1 as i32; + piTriList_out[iDstTriIndex * 3 + 1] = i2 as i32; + piTriList_out[iDstTriIndex * 3 + 2] = i3 as i32; iDstTriIndex += 1 } } From 144467e5492ba45638e5b8e5ce3e6662470de795 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 21:04:30 +0200 Subject: [PATCH 54/87] Remove remaining repr(C) --- src/generated.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/generated.rs b/src/generated.rs index c7b53cad03cac..00dc714da9a9a 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -16,7 +16,6 @@ use nalgebra::{zero, Vector3}; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; #[derive(Copy, Clone)] -#[repr(C)] pub struct STSpace { pub vOs: Vector3, pub fMagS: f32, From 1edb5486ca1a6e00557cfc9f5f19e28f600b110f Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sat, 4 May 2019 21:07:03 +0200 Subject: [PATCH 55/87] Final documentation touches --- README.md | 2 -- src/generated.rs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a8aa1ccccb4a..25c9c036519a7 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. -Machine generated with modifications for better integration with Rust. - ## Examples ### generate diff --git a/src/generated.rs b/src/generated.rs index 00dc714da9a9a..7a475fe25beb7 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -1,3 +1,6 @@ +//! Everything in this module is pending to be refactored, turned into idiomatic-rust, and moved to +//! other modules. + #![allow( dead_code, mutable_transmutes, From f9a5d92eabde66b83cc343c4327eebf805b1d862 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sun, 12 May 2019 20:53:14 +0200 Subject: [PATCH 56/87] Remove get_ prefix from accessors --- examples/generate.rs | 10 +++++----- src/generated.rs | 10 +++++----- src/lib.rs | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index 41882e369a62d..a3ea6f0584127 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -20,23 +20,23 @@ fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { } impl mikktspace::Geometry for Mesh { - fn get_num_faces(&self) -> usize { + fn num_faces(&self) -> usize { self.faces.len() } - fn get_num_vertices_of_face(&self, _face: usize) -> usize { + fn num_vertices_of_face(&self, _face: usize) -> usize { 3 } - fn get_position(&self, face: usize, vert: usize) -> Point3 { + fn position(&self, face: usize, vert: usize) -> Point3 { vertex(self, face, vert).position } - fn get_normal(&self, face: usize, vert: usize) -> Vector3 { + fn normal(&self, face: usize, vert: usize) -> Vector3 { vertex(self, face, vert).normal } - fn get_tex_coord(&self, face: usize, vert: usize) -> Point2 { + fn tex_coord(&self, face: usize, vert: usize) -> Point2 { vertex(self, face, vert).tex_coord } diff --git a/src/generated.rs b/src/generated.rs index 7a475fe25beb7..198d9a322db90 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -179,14 +179,14 @@ pub unsafe fn genTangSpace( let mut iNrMaxGroups = 0; let mut iNrActiveGroups: i32 = 0i32; let mut index = 0; - let iNrFaces = geometry.get_num_faces(); + let iNrFaces = geometry.num_faces(); let mut bRes: bool = false; let fThresCos: f32 = ((fAngularThreshold * 3.14159265358979323846f64 as f32 / 180.0f32) as f64) .cos() as f32; f = 0; while f < iNrFaces { - let verts = geometry.get_num_vertices_of_face(f); + let verts = geometry.num_vertices_of_face(f); if verts == 3 { iNrTrianglesIn += 1 } else if verts == 4 { @@ -284,7 +284,7 @@ pub unsafe fn genTangSpace( index = 0; f = 0; while f < iNrFaces { - let verts_0 = geometry.get_num_vertices_of_face(f); + let verts_0 = geometry.num_vertices_of_face(f); if !(verts_0 != 3 && verts_0 != 4) { i = 0; while i < verts_0 { @@ -1697,8 +1697,8 @@ unsafe fn GenerateInitialVerticesIndexList( let mut t: usize = 0; let mut iDstTriIndex = 0; f = 0; - while f < geometry.get_num_faces() { - let verts = geometry.get_num_vertices_of_face(f); + while f < geometry.num_faces() { + let verts = geometry.num_vertices_of_face(f); if !(verts != 3 && verts != 4) { pTriInfos[iDstTriIndex].iOrgFaceNumber = f as i32; pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs as i32; diff --git a/src/lib.rs b/src/lib.rs index 83aeb76e7086a..a3c8441845836 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,19 +5,19 @@ use nalgebra::{Point2, Point3, Vector3, Vector4}; /// The interface by which mikktspace interacts with your geometry. pub trait Geometry { /// Returns the number of faces. - fn get_num_faces(&self) -> usize; + fn num_faces(&self) -> usize; /// Returns the number of vertices of a face. - fn get_num_vertices_of_face(&self, face: usize) -> usize; + fn num_vertices_of_face(&self, face: usize) -> usize; /// Returns the position of a vertex. - fn get_position(&self, face: usize, vert: usize) -> Point3; + fn position(&self, face: usize, vert: usize) -> Point3; /// Returns the normal of a vertex. - fn get_normal(&self, face: usize, vert: usize) -> Vector3; + fn normal(&self, face: usize, vert: usize) -> Vector3; /// Returns the texture coordinate of a vertex. - fn get_tex_coord(&self, face: usize, vert: usize) -> Point2; + fn tex_coord(&self, face: usize, vert: usize) -> Point2; /// Sets a vertex' generated tangent. fn set_tangent( @@ -51,17 +51,17 @@ pub fn generate_tangents_default(geometry: &mut I) -> bool { fn get_position(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.get_position(face, vert).coords + geometry.position(face, vert).coords } fn get_tex_coord(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.get_tex_coord(face, vert).coords.insert_row(2, 1.0) + geometry.tex_coord(face, vert).coords.insert_row(2, 1.0) } fn get_normal(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.get_normal(face, vert) + geometry.normal(face, vert) } fn index_to_face_vert(index: usize) -> (usize, usize) { From dbd69b6f57d481de0ca4015f4c143b54cc5cd000 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sun, 12 May 2019 20:58:11 +0200 Subject: [PATCH 57/87] Change nalgebra types in public API to arrays --- src/generated.rs | 4 ++-- src/lib.rs | 23 ++++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/generated.rs b/src/generated.rs index 198d9a322db90..335f07331271e 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -292,8 +292,8 @@ pub unsafe fn genTangSpace( let mut tang = Vector3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); let mut bitang = Vector3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); geometry.set_tangent( - tang, - bitang, + tang.into(), + bitang.into(), (*pTSpace).fMagS, (*pTSpace).fMagT, (*pTSpace).bOrient, diff --git a/src/lib.rs b/src/lib.rs index a3c8441845836..2d470ba9080cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ mod generated; -use nalgebra::{Point2, Point3, Vector3, Vector4}; +use nalgebra::{Vector2, Vector3}; /// The interface by which mikktspace interacts with your geometry. pub trait Geometry { @@ -11,19 +11,19 @@ pub trait Geometry { fn num_vertices_of_face(&self, face: usize) -> usize; /// Returns the position of a vertex. - fn position(&self, face: usize, vert: usize) -> Point3; + fn position(&self, face: usize, vert: usize) -> [f32; 3]; /// Returns the normal of a vertex. - fn normal(&self, face: usize, vert: usize) -> Vector3; + fn normal(&self, face: usize, vert: usize) -> [f32; 3]; /// Returns the texture coordinate of a vertex. - fn tex_coord(&self, face: usize, vert: usize) -> Point2; + fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2]; /// Sets a vertex' generated tangent. fn set_tangent( &mut self, - tangent: Vector3, - _bi_tangent: Vector3, + tangent: [f32; 3], + _bi_tangent: [f32; 3], _f_mag_s: f32, _f_mag_t: f32, bi_tangent_preserves_orientation: bool, @@ -35,13 +35,13 @@ pub trait Geometry { } else { -1.0 }; - self.set_tangent_encoded(tangent.insert_row(3, sign), face, vert); + self.set_tangent_encoded([tangent[0], tangent[1], tangent[2], sign], face, vert); } /// Sets a vertex' generated tangent with the bi-tangent encoded as the W component in the /// tangent. The W component marks if the bi-tangent is flipped. This will only be called if /// `set_tangent` is not implemented. - fn set_tangent_encoded(&mut self, _tangent: Vector4, _face: usize, _vert: usize) {} + fn set_tangent_encoded(&mut self, _tangent: [f32; 4], _face: usize, _vert: usize) {} } /// Default (recommended) Angular Threshold is 180 degrees, which means threshold disabled. @@ -51,17 +51,18 @@ pub fn generate_tangents_default(geometry: &mut I) -> bool { fn get_position(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.position(face, vert).coords + geometry.position(face, vert).into() } fn get_tex_coord(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.tex_coord(face, vert).coords.insert_row(2, 1.0) + let tex_coord: Vector2 = geometry.tex_coord(face, vert).into(); + tex_coord.insert_row(2, 1.0) } fn get_normal(geometry: &mut I, index: usize) -> Vector3 { let (face, vert) = index_to_face_vert(index); - geometry.normal(face, vert) + geometry.normal(face, vert).into() } fn index_to_face_vert(index: usize) -> (usize, usize) { From f3f2c68ad1a54830597579e3b6af61bca3c7a7b1 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sun, 12 May 2019 20:59:37 +0200 Subject: [PATCH 58/87] Add 2018 edition minimum Rust version requirement --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 25c9c036519a7..1c908d987ea3c 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. +Requires at least Rust 1.31.0. + ## Examples ### generate From cec2d7ca9f4a4bcd22d2f68a68955fee31f28412 Mon Sep 17 00:00:00 2001 From: Layl <2385329-layl@users.noreply.gitlab.com> Date: Sun, 12 May 2019 22:10:47 +0200 Subject: [PATCH 59/87] Fix example not compiling --- examples/generate.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index a3ea6f0584127..6fe50ca696748 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -1,4 +1,4 @@ -use nalgebra::{Point2, Point3, Vector3, Vector4}; +use nalgebra::{Point2, Point3, Vector3}; pub type Face = [u32; 3]; @@ -28,19 +28,19 @@ impl mikktspace::Geometry for Mesh { 3 } - fn position(&self, face: usize, vert: usize) -> Point3 { - vertex(self, face, vert).position + fn position(&self, face: usize, vert: usize) -> [f32; 3] { + vertex(self, face, vert).position.coords.into() } - fn normal(&self, face: usize, vert: usize) -> Vector3 { - vertex(self, face, vert).normal + fn normal(&self, face: usize, vert: usize) -> [f32; 3] { + vertex(self, face, vert).normal.into() } - fn tex_coord(&self, face: usize, vert: usize) -> Point2 { - vertex(self, face, vert).tex_coord + fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { + vertex(self, face, vert).tex_coord.coords.into() } - fn set_tangent_encoded(&mut self, tangent: Vector4, face: usize, vert: usize) { + fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { println!( "{face}-{vert}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", face = face, @@ -48,7 +48,7 @@ impl mikktspace::Geometry for Mesh { v = vertex(self, face, vert).position.coords.data, vn = vertex(self, face, vert).normal.data, vt = vertex(self, face, vert).tex_coord.coords.data, - vx = tangent.data, + vx = tangent, ); } } From 35025353990b348afd6a4bbd17e1e2e8ae63ba25 Mon Sep 17 00:00:00 2001 From: Azriel Hoh Date: Wed, 8 Jan 2020 09:16:58 +1300 Subject: [PATCH 60/87] Bumped `nalgebra` to `0.19.0`. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a832fa3338cdd..0bfebfb6964b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["examples/**"] travis-ci = { repository = "gltf-rs/mikktspace" } [dependencies] -nalgebra = "0.18.0" +nalgebra = "0.19.0" [[example]] name = "generate" From 95d76ff668fac756239735cfadd22aacc376f39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Va=C5=A1ko?= Date: Sun, 12 Jan 2020 21:09:37 +0100 Subject: [PATCH 61/87] Add a regression test on the example cube All the values in the `expected_results` vector are generated by the actual reference implementation of the MikkTSpace algorithm with the cube constructed in examples/generate.rs as an input. --- tests/regression_test.rs | 879 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 879 insertions(+) create mode 100644 tests/regression_test.rs diff --git a/tests/regression_test.rs b/tests/regression_test.rs new file mode 100644 index 0000000000000..874a7b6471cad --- /dev/null +++ b/tests/regression_test.rs @@ -0,0 +1,879 @@ +use mikktspace::{generate_tangents_default, Geometry}; +use nalgebra::{Point2, Point3, Vector3}; + +pub type Face = [u32; 3]; + +#[derive(Debug)] +struct Vertex { + position: Point3, + normal: Vector3, + tex_coord: Point2, +} + +#[derive(Debug, PartialEq)] +struct Result { + tangent: [f32; 3], + bi_tangent: [f32; 3], + mag_s: f32, + mag_t: f32, + bi_tangent_preserves_orientation: bool, + face: usize, + vert: usize, +} + +impl Result { + fn new( + tangent: [f32; 3], + bi_tangent: [f32; 3], + mag_s: f32, + mag_t: f32, + bi_tangent_preserves_orientation: bool, + face: usize, + vert: usize, + ) -> Self { + Self { + tangent, + bi_tangent, + mag_s, + mag_t, + bi_tangent_preserves_orientation, + face, + vert, + } + } +} + +struct Mesh { + faces: Vec, + vertices: Vec, +} + +struct Context { + mesh: Mesh, + results: Vec, +} + +fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { + let vs: &[u32; 3] = &mesh.faces[face]; + &mesh.vertices[vs[vert] as usize] +} + +impl Geometry for Context { + fn num_faces(&self) -> usize { + self.mesh.faces.len() + } + + fn num_vertices_of_face(&self, _face: usize) -> usize { + 3 + } + + fn position(&self, face: usize, vert: usize) -> [f32; 3] { + vertex(&self.mesh, face, vert).position.coords.into() + } + + fn normal(&self, face: usize, vert: usize) -> [f32; 3] { + vertex(&self.mesh, face, vert).normal.into() + } + + fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { + vertex(&self.mesh, face, vert).tex_coord.coords.into() + } + + fn set_tangent( + &mut self, + tangent: [f32; 3], + bi_tangent: [f32; 3], + mag_s: f32, + mag_t: f32, + bi_tangent_preserves_orientation: bool, + face: usize, + vert: usize, + ) { + self.results.push(Result { + tangent, + bi_tangent, + mag_s, + mag_t, + bi_tangent_preserves_orientation, + face, + vert, + }) + } +} + +struct ControlPoint { + uv: [f32; 2], + dir: [f32; 3], +} + +impl ControlPoint { + fn new(uv: [f32; 2], dir: [f32; 3]) -> Self { + Self { uv, dir } + } +} + +fn make_cube() -> Mesh { + let mut faces = Vec::new(); + let mut ctl_pts = Vec::new(); + let mut vertices = Vec::new(); + + // +x plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.5, 0.5], [1.0, 0.0, 0.0])); + } + + // -x plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([1.0, 0.0], [-1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([1.0, 1.0], [-1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.5, 0.5], [-1.0, 0.0, 0.0])); + } + + // +y plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.5], [0.0, 1.0, 0.0])); + } + + // -y plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.5], [0.0, -1.0, 0.0])); + } + + // +z plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, -1.0, 1.0])); + ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, 1.0])); + ctl_pts.push(ControlPoint::new([0.5, 0.5], [0.0, 0.0, 1.0])); + } + + // -z plane + { + let base = ctl_pts.len() as u32; + faces.push([base, base + 1, base + 4]); + faces.push([base + 1, base + 2, base + 4]); + faces.push([base + 2, base + 3, base + 4]); + faces.push([base + 3, base, base + 4]); + ctl_pts.push(ControlPoint::new([1.0, 0.0], [1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([1.0, 1.0], [1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 1.0], [-1.0, -1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.0, 0.0], [-1.0, 1.0, -1.0])); + ctl_pts.push(ControlPoint::new([0.5, 0.5], [0.0, 0.0, -1.0])); + } + + for pt in ctl_pts { + let p: Point3 = pt.dir.into(); + let n: Vector3 = p.coords.normalize(); + let t: Point2 = pt.uv.into(); + vertices.push(Vertex { + position: (p / 2.0).into(), + normal: n.into(), + tex_coord: t.into(), + }); + } + + Mesh { faces, vertices } +} + +#[test] +fn cube_tangents_should_equal_reference_values() { + let mut context = Context { + mesh: make_cube(), + results: Vec::new(), + }; + let ret = generate_tangents_default(&mut context); + assert_eq!(true, ret); + + let expected_results: Vec = vec![ + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 0, + 0, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 0, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + false, + 0, + 2, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 1, + 0, + ), + Result::new( + [-0.40824825, 0.81649655, 0.40824825], + [-0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 1, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + false, + 1, + 2, + ), + Result::new( + [-0.40824825, 0.81649655, 0.40824825], + [-0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 2, + 0, + ), + Result::new( + [-0.40824825, 0.81649655, -0.40824825], + [0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 2, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + false, + 2, + 2, + ), + Result::new( + [-0.40824825, 0.81649655, -0.40824825], + [0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 3, + 0, + ), + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 3, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + false, + 3, + 2, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 4, + 0, + ), + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 4, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + true, + 4, + 2, + ), + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 5, + 0, + ), + Result::new( + [-0.40824825, 0.81649655, -0.40824825], + [0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 5, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + true, + 5, + 2, + ), + Result::new( + [-0.40824825, 0.81649655, -0.40824825], + [0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 6, + 0, + ), + Result::new( + [-0.40824825, 0.81649655, 0.40824825], + [-0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 6, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + true, + 6, + 2, + ), + Result::new( + [-0.40824825, 0.81649655, 0.40824825], + [-0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 7, + 0, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 7, + 1, + ), + Result::new( + [0.00000000, 1.00000000, 0.00000000], + [0.00000000, 0.00000000, -1.00000000], + 1.00000000, + 1.00000000, + true, + 7, + 2, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 8, + 0, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 8, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 8, + 2, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 9, + 0, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 9, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 9, + 2, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 10, + 0, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 10, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 10, + 2, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 11, + 0, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 11, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 11, + 2, + ), + Result::new( + [-0.40824825, 0.81649655, 0.40824825], + [-0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 12, + 0, + ), + Result::new( + [-0.40824825, 0.81649655, -0.40824825], + [0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + true, + 12, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 12, + 2, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 13, + 0, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 13, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 13, + 2, + ), + Result::new( + [0.40824825, 0.81649655, -0.40824825], + [-0.40824825, 0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 14, + 0, + ), + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 14, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 14, + 2, + ), + Result::new( + [0.40824825, 0.81649655, 0.40824825], + [0.40824825, -0.40824825, -0.81649655], + 1.00000000, + 1.00000000, + false, + 15, + 0, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 15, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, 1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 15, + 2, + ), + Result::new( + [0.81649655, 0.40824825, 0.40824825], + [-0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + false, + 16, + 0, + ), + Result::new( + [0.81649655, -0.40824825, 0.40824825], + [0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + false, + 16, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 16, + 2, + ), + Result::new( + [0.81649655, -0.40824825, 0.40824825], + [0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + false, + 17, + 0, + ), + Result::new( + [0.81649655, 0.40824825, -0.40824825], + [-0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + false, + 17, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 17, + 2, + ), + Result::new( + [0.81649655, 0.40824825, -0.40824825], + [-0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + false, + 18, + 0, + ), + Result::new( + [0.81649655, -0.40824825, -0.40824825], + [0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + false, + 18, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 18, + 2, + ), + Result::new( + [0.81649655, -0.40824825, -0.40824825], + [0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + false, + 19, + 0, + ), + Result::new( + [0.81649655, 0.40824825, 0.40824825], + [-0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + false, + 19, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + false, + 19, + 2, + ), + Result::new( + [0.81649655, -0.40824825, 0.40824825], + [0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + true, + 20, + 0, + ), + Result::new( + [0.81649655, 0.40824825, 0.40824825], + [-0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + true, + 20, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + true, + 20, + 2, + ), + Result::new( + [0.81649655, 0.40824825, 0.40824825], + [-0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + true, + 21, + 0, + ), + Result::new( + [0.81649655, -0.40824825, -0.40824825], + [0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + true, + 21, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + true, + 21, + 2, + ), + Result::new( + [0.81649655, -0.40824825, -0.40824825], + [0.40824825, -0.81649655, 0.40824825], + 1.00000000, + 1.00000000, + true, + 22, + 0, + ), + Result::new( + [0.81649655, 0.40824825, -0.40824825], + [-0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + true, + 22, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + true, + 22, + 2, + ), + Result::new( + [0.81649655, 0.40824825, -0.40824825], + [-0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + true, + 23, + 0, + ), + Result::new( + [0.81649655, -0.40824825, 0.40824825], + [0.40824825, -0.81649655, -0.40824825], + 1.00000000, + 1.00000000, + true, + 23, + 1, + ), + Result::new( + [1.00000000, 0.00000000, 0.00000000], + [0.00000000, -1.00000000, 0.00000000], + 1.00000000, + 1.00000000, + true, + 23, + 2, + ), + ]; + + assert_eq!(expected_results, context.results); +} From 43d2802d42353d0ae94553e0c5150ae5e46997d8 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sat, 1 Feb 2020 09:23:16 +0000 Subject: [PATCH 62/87] Rename generate_tangents_default as generate_tangents --- examples/generate.rs | 2 +- src/lib.rs | 20 ++++++++++++++------ tests/regression_test.rs | 4 ++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/generate.rs b/examples/generate.rs index 6fe50ca696748..4415e1443e9ed 100644 --- a/examples/generate.rs +++ b/examples/generate.rs @@ -165,6 +165,6 @@ fn make_cube() -> Mesh { fn main() { let mut cube = make_cube(); - let ret = mikktspace::generate_tangents_default(&mut cube); + let ret = mikktspace::generate_tangents(&mut cube); assert_eq!(true, ret); } diff --git a/src/lib.rs b/src/lib.rs index 2d470ba9080cd..dd163a19415a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,9 @@ pub trait Geometry { /// Returns the texture coordinate of a vertex. fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2]; - /// Sets a vertex' generated tangent. + /// Sets the generated tangent for a vertex. + /// Leave this function unimplemented if you are implementing + /// `set_tangent_encoded`. fn set_tangent( &mut self, tangent: [f32; 3], @@ -38,14 +40,20 @@ pub trait Geometry { self.set_tangent_encoded([tangent[0], tangent[1], tangent[2], sign], face, vert); } - /// Sets a vertex' generated tangent with the bi-tangent encoded as the W component in the - /// tangent. The W component marks if the bi-tangent is flipped. This will only be called if - /// `set_tangent` is not implemented. + /// Sets the generated tangent for a vertex with its bi-tangent encoded as the 'W' (4th) + /// component in the tangent. The 'W' component marks if the bi-tangent is flipped. This + /// is called by the default implementation of `set_tangent`; therefore, this function will + /// not be called by the crate unless `set_tangent` is unimplemented. fn set_tangent_encoded(&mut self, _tangent: [f32; 4], _face: usize, _vert: usize) {} } -/// Default (recommended) Angular Threshold is 180 degrees, which means threshold disabled. -pub fn generate_tangents_default(geometry: &mut I) -> bool { +/// Generates tangents for the input geometry. +/// +/// # Errors +/// +/// Returns `false` if the geometry is unsuitable for tangent generation including, +/// but not limited to, lack of vertices. +pub fn generate_tangents(geometry: &mut I) -> bool { unsafe { generated::genTangSpace(geometry, 180.0) } } diff --git a/tests/regression_test.rs b/tests/regression_test.rs index 874a7b6471cad..761284571b101 100644 --- a/tests/regression_test.rs +++ b/tests/regression_test.rs @@ -1,4 +1,4 @@ -use mikktspace::{generate_tangents_default, Geometry}; +use mikktspace::{generate_tangents, Geometry}; use nalgebra::{Point2, Point3, Vector3}; pub type Face = [u32; 3]; @@ -221,7 +221,7 @@ fn cube_tangents_should_equal_reference_values() { mesh: make_cube(), results: Vec::new(), }; - let ret = generate_tangents_default(&mut context); + let ret = generate_tangents(&mut context); assert_eq!(true, ret); let expected_results: Vec = vec![ From 99a235d8e18b9035bd290c94ec8b120424db3c9b Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sat, 1 Feb 2020 09:24:05 +0000 Subject: [PATCH 63/87] Version 0.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0bfebfb6964b0..3a03340e88682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mikktspace" -version = "0.1.1" +version = "0.2.0" edition = "2018" authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] description = "Mikkelsen tangent space algorithm" From d028fb394b6f5748ec83aab6f96364a9bd84e932 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sat, 1 Feb 2020 09:33:55 +0000 Subject: [PATCH 64/87] Include example code in release --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3a03340e88682..3148830d0bf73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ homepage = "https://github.com/gltf-rs/mikktspace" readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] license = "MIT/Apache-2.0" -exclude = ["examples/**"] [badges] travis-ci = { repository = "gltf-rs/mikktspace" } From fa8e685a59079e5203f44b7ae7c8088dd85fb12d Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 1 Apr 2021 10:55:51 +0200 Subject: [PATCH 65/87] add glam feature --- Cargo.toml | 9 ++- src/generated.rs | 163 ++++++++++++++++++++++++++++++++++------------- src/lib.rs | 24 +++++-- 3 files changed, 143 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3148830d0bf73..b0fce8ed2d1c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,18 @@ readme = "README.md" keywords = ["3D", "graphics", "algorithm", "tangent"] license = "MIT/Apache-2.0" +[features] +default = ["nalgebra"] + [badges] travis-ci = { repository = "gltf-rs/mikktspace" } [dependencies] -nalgebra = "0.19.0" +nalgebra = { version = "0.25.0", optional = true } +glam = { version = "0.13.0", optional = true } [[example]] name = "generate" + +[dev-dependencies] +nalgebra = "0.25" \ No newline at end of file diff --git a/src/generated.rs b/src/generated.rs index 335f07331271e..57d2ad6c94d79 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -14,15 +14,27 @@ use std::ptr::null_mut; -use nalgebra::{zero, Vector3}; +#[cfg(all(feature = "glam", feature = "nalgebra"))] +compile_error!("Can't have both glam and nalgebra"); + +#[cfg(all(not(feature = "glam"), not(feature = "nalgebra")))] +compile_error!("Please select either the `glam` or `nalgebra` feature"); + +#[cfg(feature = "nalgebra")] +type Vec3 = nalgebra::Vector3; + +#[cfg(feature = "glam")] +type Vec3 = glam::Vec3; +#[cfg(feature = "glam")] +type Vec2 = glam::Vec2; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; #[derive(Copy, Clone)] pub struct STSpace { - pub vOs: Vector3, + pub vOs: Vec3, pub fMagS: f32, - pub vOt: Vector3, + pub vOt: Vec3, pub fMagT: f32, pub iCounter: i32, pub bOrient: bool, @@ -31,9 +43,9 @@ pub struct STSpace { impl STSpace { pub fn zero() -> Self { Self { - vOs: zero(), + vOs: Default::default(), fMagS: 0.0, - vOt: zero(), + vOt: Default::default(), fMagT: 0.0, iCounter: 0, bOrient: false, @@ -70,8 +82,8 @@ impl STSpace { pub struct STriInfo { pub FaceNeighbors: [i32; 3], pub AssignedGroup: [*mut SGroup; 3], - pub vOs: Vector3, - pub vOt: Vector3, + pub vOs: Vec3, + pub vOt: Vec3, pub fMagS: f32, pub fMagT: f32, pub iOrgFaceNumber: i32, @@ -85,8 +97,8 @@ impl STriInfo { Self { FaceNeighbors: [0, 0, 0], AssignedGroup: [null_mut(), null_mut(), null_mut()], - vOs: zero(), - vOt: zero(), + vOs: Default::default(), + vOt: Default::default(), fMagS: 0.0, fMagT: 0.0, iOrgFaceNumber: 0, @@ -181,9 +193,8 @@ pub unsafe fn genTangSpace( let mut index = 0; let iNrFaces = geometry.num_faces(); let mut bRes: bool = false; - let fThresCos: f32 = ((fAngularThreshold * 3.14159265358979323846f64 as f32 - / 180.0f32) as f64) - .cos() as f32; + let fThresCos: f32 = + ((fAngularThreshold * 3.14159265358979323846f64 as f32 / 180.0f32) as f64).cos() as f32; f = 0; while f < iNrFaces { let verts = geometry.num_vertices_of_face(f); @@ -252,9 +263,9 @@ pub unsafe fn genTangSpace( let mut psTspace = vec![ STSpace { - vOs: Vector3::new(1.0, 0.0, 0.0), + vOs: Vec3::new(1.0, 0.0, 0.0), fMagS: 1.0, - vOt: Vector3::new(0.0, 1.0, 0.0), + vOt: Vec3::new(0.0, 1.0, 0.0), fMagT: 1.0, ..STSpace::zero() }; @@ -289,8 +300,8 @@ pub unsafe fn genTangSpace( i = 0; while i < verts_0 { let mut pTSpace: *const STSpace = &mut psTspace[index] as *mut STSpace; - let mut tang = Vector3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); - let mut bitang = Vector3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); + let mut tang = Vec3::new((*pTSpace).vOs.x, (*pTSpace).vOs.y, (*pTSpace).vOs.z); + let mut bitang = Vec3::new((*pTSpace).vOt.x, (*pTSpace).vOt.y, (*pTSpace).vOt.z); geometry.set_tangent( tang.into(), bitang.into(), @@ -346,8 +357,7 @@ unsafe fn DegenEpilogue( let iSrcVert: i32 = (*pTriInfos.offset(iTri as isize)).vert_num[iVert as usize] as i32; let iSrcOffs: i32 = (*pTriInfos.offset(iTri as isize)).iTSpacesOffs; - let iDstVert: i32 = - (*pTriInfos.offset(t as isize)).vert_num[i as usize] as i32; + let iDstVert: i32 = (*pTriInfos.offset(t as isize)).vert_num[i as usize] as i32; let iDstOffs: i32 = (*pTriInfos.offset(t as isize)).iTSpacesOffs; *psTspace.offset((iDstOffs + iDstVert) as isize) = *psTspace.offset((iSrcOffs + iSrcVert) as isize) @@ -360,7 +370,7 @@ unsafe fn DegenEpilogue( t = 0i32; while t < iNrTrianglesIn { if (*pTriInfos.offset(t as isize)).iFlag & 2i32 != 0i32 { - let mut vDstP = Vector3::new(0.0, 0.0, 0.0); + let mut vDstP = Vec3::new(0.0, 0.0, 0.0); let mut iOrgF: i32 = -1i32; let mut i_0: i32 = 0i32; let mut bNotFound_0: bool = false; @@ -451,9 +461,9 @@ unsafe fn GenerateTSpaces( pTriMembers: Vec::new(), }; let mut bFound: bool = false; - let mut n = Vector3::new(0.0, 0.0, 0.0); - let mut vOs = Vector3::new(0.0, 0.0, 0.0); - let mut vOt = Vector3::new(0.0, 0.0, 0.0); + let mut n = Vec3::new(0.0, 0.0, 0.0); + let mut vOs = Vec3::new(0.0, 0.0, 0.0); + let mut vOt = Vec3::new(0.0, 0.0, 0.0); if (*pTriInfos.offset(f as isize)).AssignedGroup[0usize] == pGroup as *mut SGroup { index = 0i32 } else if (*pTriInfos.offset(f as isize)).AssignedGroup[1usize] == pGroup as *mut SGroup @@ -465,10 +475,18 @@ unsafe fn GenerateTSpaces( } iVertIndex = *piTriListIn.offset((f * 3i32 + index) as isize); n = get_normal(geometry, iVertIndex as usize); - vOs = (*pTriInfos.offset(f as isize)).vOs + #[cfg(feature = "nalgebra")] + let mut vOs = (*pTriInfos.offset(f as isize)).vOs - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); - vOt = (*pTriInfos.offset(f as isize)).vOt + #[cfg(feature = "nalgebra")] + let mut vOt = (*pTriInfos.offset(f as isize)).vOt - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); + #[cfg(feature = "glam")] + let mut vOs = (*pTriInfos.offset(f as isize)).vOs + - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); + #[cfg(feature = "glam")] + let mut vOt = (*pTriInfos.offset(f as isize)).vOt + - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); if VNotZero(vOs) { vOs = Normalize(vOs) } @@ -481,10 +499,18 @@ unsafe fn GenerateTSpaces( while j < (*pGroup).iNrFaces { let t: i32 = *(*pGroup).pFaceIndices.offset(j as isize); let iOF_2: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; + #[cfg(feature = "nalgebra")] let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - (n.dot(&(*pTriInfos.offset(t as isize)).vOs) * n); + #[cfg(feature = "nalgebra")] let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt - (n.dot(&(*pTriInfos.offset(t as isize)).vOt) * n); + #[cfg(feature = "glam")] + let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs + - (n.dot((*pTriInfos.offset(t as isize)).vOs) * n); + #[cfg(feature = "glam")] + let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt + - (n.dot((*pTriInfos.offset(t as isize)).vOt) * n); if VNotZero(vOs2) { vOs2 = Normalize(vOs2) } @@ -501,8 +527,14 @@ unsafe fn GenerateTSpaces( false }; let bSameOrgFace: bool = iOF_1 == iOF_2; + #[cfg(feature = "nalgebra")] let fCosS: f32 = vOs.dot(&vOs2); + #[cfg(feature = "nalgebra")] let fCosT: f32 = vOt.dot(&vOt2); + #[cfg(feature = "glam")] + let fCosS: f32 = vOs.dot(vOs2); + #[cfg(feature = "glam")] + let fCosT: f32 = vOt.dot(vOt2); if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { let fresh0 = iMembers; iMembers = iMembers + 1; @@ -564,9 +596,9 @@ unsafe fn GenerateTSpaces( } unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpace { let mut ts_res: STSpace = STSpace { - vOs: Vector3::new(0.0, 0.0, 0.0), + vOs: Vec3::new(0.0, 0.0, 0.0), fMagS: 0., - vOt: Vector3::new(0.0, 0.0, 0.0), + vOt: Vec3::new(0.0, 0.0, 0.0), fMagT: 0., iCounter: 0, bOrient: false, @@ -595,11 +627,14 @@ unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpa return ts_res; } -unsafe fn Normalize(v: Vector3) -> Vector3 { +unsafe fn Normalize(v: Vec3) -> Vec3 { + #[cfg(feature = "nalgebra")] return (1.0 / v.magnitude()) * v; + #[cfg(feature = "glam")] + return (1.0 / v.length()) * v; } -unsafe fn VNotZero(v: Vector3) -> bool { +unsafe fn VNotZero(v: Vec3) -> bool { NotZero(v.x) || NotZero(v.y) || NotZero(v.z) } @@ -616,9 +651,9 @@ unsafe fn EvalTspace( iVertexRepresentitive: i32, ) -> STSpace { let mut res: STSpace = STSpace { - vOs: Vector3::new(0.0, 0.0, 0.0), + vOs: Vec3::new(0.0, 0.0, 0.0), fMagS: 0., - vOt: Vector3::new(0.0, 0.0, 0.0), + vOt: Vec3::new(0.0, 0.0, 0.0), fMagT: 0., iCounter: 0, bOrient: false, @@ -637,14 +672,14 @@ unsafe fn EvalTspace( while face < iFaces { let f: i32 = *face_indices.offset(face as isize); if (*pTriInfos.offset(f as isize)).iFlag & 4i32 == 0i32 { - let mut n = Vector3::new(0.0, 0.0, 0.0); - let mut vOs = Vector3::new(0.0, 0.0, 0.0); - let mut vOt = Vector3::new(0.0, 0.0, 0.0); - let mut p0 = Vector3::new(0.0, 0.0, 0.0); - let mut p1 = Vector3::new(0.0, 0.0, 0.0); - let mut p2 = Vector3::new(0.0, 0.0, 0.0); - let mut v1 = Vector3::new(0.0, 0.0, 0.0); - let mut v2 = Vector3::new(0.0, 0.0, 0.0); + let mut n = Vec3::new(0.0, 0.0, 0.0); + let mut vOs = Vec3::new(0.0, 0.0, 0.0); + let mut vOt = Vec3::new(0.0, 0.0, 0.0); + let mut p0 = Vec3::new(0.0, 0.0, 0.0); + let mut p1 = Vec3::new(0.0, 0.0, 0.0); + let mut p2 = Vec3::new(0.0, 0.0, 0.0); + let mut v1 = Vec3::new(0.0, 0.0, 0.0); + let mut v2 = Vec3::new(0.0, 0.0, 0.0); let mut fCos: f32 = 0.; let mut fAngle: f32 = 0.; let mut fMagS: f32 = 0.; @@ -663,10 +698,18 @@ unsafe fn EvalTspace( } index = *piTriListIn.offset((3i32 * f + i) as isize); n = get_normal(geometry, index as usize); - vOs = (*pTriInfos.offset(f as isize)).vOs + #[cfg(feature = "nalgebra")] + let mut vOs = (*pTriInfos.offset(f as isize)).vOs - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); - vOt = (*pTriInfos.offset(f as isize)).vOt + #[cfg(feature = "nalgebra")] + let mut vOt = (*pTriInfos.offset(f as isize)).vOt - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); + #[cfg(feature = "glam")] + let mut vOs = (*pTriInfos.offset(f as isize)).vOs + - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); + #[cfg(feature = "glam")] + let mut vOt = (*pTriInfos.offset(f as isize)).vOt + - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); if VNotZero(vOs) { vOs = Normalize(vOs) } @@ -681,16 +724,26 @@ unsafe fn EvalTspace( p2 = get_position(geometry, i2 as usize); v1 = p0 - p1; v2 = p2 - p1; - v1 = v1 - (n.dot(&v1) * n); + #[cfg(feature = "nalgebra")] + let mut v1 = v1 - (n.dot(&v1) * n); + #[cfg(feature = "glam")] + let mut v1 = v1 - (n.dot(v1) * n); if VNotZero(v1) { v1 = Normalize(v1) } - v2 = v2 - (n.dot(&v2) * n); + #[cfg(feature = "nalgebra")] + let mut v2 = v2 - (n.dot(&v2) * n); + #[cfg(feature = "glam")] + let mut v2 = v2 - (n.dot(v2) * n); if VNotZero(v2) { v2 = Normalize(v2) } - fCos = v1.dot(&v2); - fCos = if fCos > 1i32 as f32 { + #[cfg(feature = "nalgebra")] + let fCos = v1.dot(&v2); + #[cfg(feature = "glam")] + let fCos = v1.dot(v2); + + let fCos = if fCos > 1i32 as f32 { 1i32 as f32 } else if fCos < -1i32 as f32 { -1i32 as f32 @@ -989,8 +1042,14 @@ unsafe fn InitTriInfo( }; if NotZero(fSignedAreaSTx2) { let fAbsArea: f32 = fSignedAreaSTx2.abs(); + #[cfg(feature = "nalgebra")] let fLenOs: f32 = vOs.magnitude(); + #[cfg(feature = "nalgebra")] let fLenOt: f32 = vOt.magnitude(); + #[cfg(feature = "glam")] + let fLenOs: f32 = vOs.length(); + #[cfg(feature = "glam")] + let fLenOt: f32 = vOt.length(); let fS: f32 = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { -1.0f32 } else { @@ -1411,7 +1470,7 @@ unsafe fn GenerateSharedVerticesIndexList( let mut iMaxCount = 0; let mut vMin = get_position(geometry, 0); let mut vMax = vMin; - let mut vDim = Vector3::new(0.0, 0.0, 0.0); + let mut vDim = Vec3::new(0.0, 0.0, 0.0); let mut fMin: f32 = 0.; let mut fMax: f32 = 0.; i = 1; @@ -1505,7 +1564,7 @@ unsafe fn GenerateSharedVerticesIndexList( } k += 1 } - let mut pTmpVert = vec!(STmpVert::zero(); iMaxCount); + let mut pTmpVert = vec![STmpVert::zero(); iMaxCount]; k = 0; while k < g_iCells { // extract table of cell k and amount of entries in it @@ -1722,8 +1781,14 @@ unsafe fn GenerateInitialVerticesIndexList( let T1 = get_tex_coord(geometry, i1); let T2 = get_tex_coord(geometry, i2); let T3 = get_tex_coord(geometry, i3); + #[cfg(feature = "nalgebra")] let distSQ_02: f32 = (T2 - T0).magnitude_squared(); + #[cfg(feature = "nalgebra")] let distSQ_13: f32 = (T3 - T1).magnitude_squared(); + #[cfg(feature = "glam")] + let distSQ_02: f32 = (T2 - T0).length_squared(); + #[cfg(feature = "glam")] + let distSQ_13: f32 = (T3 - T1).length_squared(); let mut bQuadDiagIs_02: bool = false; if distSQ_02 < distSQ_13 { bQuadDiagIs_02 = true @@ -1734,8 +1799,14 @@ unsafe fn GenerateInitialVerticesIndexList( let P1 = get_position(geometry, i1); let P2 = get_position(geometry, i2); let P3 = get_position(geometry, i3); + #[cfg(feature = "nalgebra")] let distSQ_02_0: f32 = (P2 - P0).magnitude_squared(); + #[cfg(feature = "nalgebra")] let distSQ_13_0: f32 = (P3 - P1).magnitude_squared(); + #[cfg(feature = "glam")] + let distSQ_02_0: f32 = (P2 - P0).length_squared(); + #[cfg(feature = "glam")] + let distSQ_13_0: f32 = (P3 - P1).length_squared(); bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { false } else { diff --git a/src/lib.rs b/src/lib.rs index dd163a19415a4..e38de3d9d5f44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,14 @@ mod generated; -use nalgebra::{Vector2, Vector3}; +#[cfg(feature = "nalgebra")] +type Vec3 = nalgebra::Vector3; +#[cfg(feature = "nalgebra")] +type Vec2 = nalgebra::Vector2; + +#[cfg(feature = "glam")] +type Vec3 = glam::Vec3; +#[cfg(feature = "glam")] +type Vec2 = glam::Vec2; /// The interface by which mikktspace interacts with your geometry. pub trait Geometry { @@ -57,18 +65,22 @@ pub fn generate_tangents(geometry: &mut I) -> bool { unsafe { generated::genTangSpace(geometry, 180.0) } } -fn get_position(geometry: &mut I, index: usize) -> Vector3 { +fn get_position(geometry: &mut I, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); geometry.position(face, vert).into() } -fn get_tex_coord(geometry: &mut I, index: usize) -> Vector3 { +fn get_tex_coord(geometry: &mut I, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); - let tex_coord: Vector2 = geometry.tex_coord(face, vert).into(); - tex_coord.insert_row(2, 1.0) + let tex_coord: Vec2 = geometry.tex_coord(face, vert).into(); + #[cfg(feature = "nalgebra")] + let val = tex_coord.insert_row(2, 1.0); + #[cfg(feature = "glam")] + let val = tex_coord.extend(1.0); + val } -fn get_normal(geometry: &mut I, index: usize) -> Vector3 { +fn get_normal(geometry: &mut I, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); geometry.normal(face, vert).into() } From 7d08f8f60d7f183e3be5190076ff37f0459da722 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 19 May 2021 23:26:49 +0200 Subject: [PATCH 66/87] update glam and nalgebra --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b0fce8ed2d1c5..e8127065bc425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,11 @@ default = ["nalgebra"] travis-ci = { repository = "gltf-rs/mikktspace" } [dependencies] -nalgebra = { version = "0.25.0", optional = true } -glam = { version = "0.13.0", optional = true } +nalgebra = { version = "0.26.0", optional = true } +glam = { version = "0.15.0", optional = true } [[example]] name = "generate" [dev-dependencies] -nalgebra = "0.25" \ No newline at end of file +nalgebra = "0.26" \ No newline at end of file From 89f36ea831ae167efa8ec1714bea4b5f5a623261 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sat, 12 Feb 2022 22:28:05 +0000 Subject: [PATCH 67/87] Add acknowledgement to original source code --- src/generated.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/generated.rs b/src/generated.rs index 57d2ad6c94d79..f84c7c9fa380f 100644 --- a/src/generated.rs +++ b/src/generated.rs @@ -1,6 +1,32 @@ //! Everything in this module is pending to be refactored, turned into idiomatic-rust, and moved to //! other modules. +//! The contents of this file are a combination of transpilation and human +//! modification to Morten S. Mikkelsen's original tangent space algorithm +//! implementation written in C. The original source code can be found at +//! https://archive.blender.org/wiki/index.php/Dev:Shading/Tangent_Space_Normal_Maps +//! and includes the following licence: +//! +//! Copyright (C) 2011 by Morten S. Mikkelsen +//! +//! This software is provided 'as-is', without any express or implied +//! warranty. In no event will the authors be held liable for any damages +//! arising from the use of this software. +//! +//! Permission is granted to anyone to use this software for any purpose, +//! including commercial applications, and to alter it and redistribute it +//! freely, subject to the following restrictions: +//! +//! 1. The origin of this software must not be misrepresented; you must not +//! claim that you wrote the original software. If you use this software +//! in a product, an acknowledgment in the product documentation would be +//! appreciated but is not required. +//! +//! 2. Altered source versions must be plainly marked as such, and must not be +//! misrepresented as being the original software. +//! +//! 3. This notice may not be removed or altered from any source distribution. + #![allow( dead_code, mutable_transmutes, From c1e6f163ec0ac9e77bffcf6144c9b09ab75dc412 Mon Sep 17 00:00:00 2001 From: David Harvey-Macaulay Date: Sat, 12 Feb 2022 22:28:59 +0000 Subject: [PATCH 68/87] Version 0.3 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8127065bc425..44573eb42c490 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mikktspace" -version = "0.2.0" +version = "0.3.0" edition = "2018" authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] description = "Mikkelsen tangent space algorithm" @@ -25,4 +25,4 @@ glam = { version = "0.15.0", optional = true } name = "generate" [dev-dependencies] -nalgebra = "0.26" \ No newline at end of file +nalgebra = "0.26" From f8b5bd93b0f1c27722e30954b5e52423e53c55f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sat, 19 Feb 2022 02:39:14 +0100 Subject: [PATCH 69/87] updated nalgebra and glam --- Cargo.toml | 6 +++--- README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44573eb42c490..d32b4de9ce8d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,11 +18,11 @@ default = ["nalgebra"] travis-ci = { repository = "gltf-rs/mikktspace" } [dependencies] -nalgebra = { version = "0.26.0", optional = true } -glam = { version = "0.15.0", optional = true } +nalgebra = { version = "0.30", optional = true } +glam = { version = "0.20", optional = true } [[example]] name = "generate" [dev-dependencies] -nalgebra = "0.26" +nalgebra = "0.30" diff --git a/README.md b/README.md index 1c908d987ea3c..2e6ce34329973 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. -Requires at least Rust 1.31.0. +Requires at least Rust 1.52.1. ## Examples From 55fa2650056f0c6163cb8b238bab040c2fbac24b Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Fri, 8 Apr 2022 12:33:08 -0700 Subject: [PATCH 70/87] Add Bevy fork info --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2e6ce34329973..1fbab2afd634c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # mikktspace +This is a fork of https://github.com/gltf-rs/mikktspace, which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It will be vendored in the bevy repository itself as crates/bevy_mikktspace. + [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) [![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) From 978e6872de107b5e79130ad9daa439186e95d445 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 31 Mar 2021 19:45:23 +0200 Subject: [PATCH 71/87] generate tangents if missing and a normal map exists --- crates/bevy_gltf/src/loader.rs | 22 ++-- crates/bevy_render/Cargo.toml | 2 + crates/bevy_render/src/mesh/mesh/mod.rs | 128 ++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 7 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 2b95f3e5541b4..d80f40fcac3f3 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -64,6 +64,8 @@ pub enum GltfError { AssetIoError(#[from] AssetIoError), #[error("Missing sampler for animation {0}")] MissingAnimationSampler(usize), + #[error("failed to generate tangents: {0}")] + GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError), } /// Loads glTF files with all of their data as their corresponding bevy representations. @@ -250,13 +252,6 @@ async fn load_gltf<'a, 'b>( mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vertex_attribute); } - if let Some(vertex_attribute) = reader - .read_tangents() - .map(|v| VertexAttributeValues::Float32x4(v.collect())) - { - mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); - } - if let Some(vertex_attribute) = reader .read_tex_coords(0) .map(|v| VertexAttributeValues::Float32x2(v.into_f32().collect())) @@ -309,6 +304,18 @@ async fn load_gltf<'a, 'b>( } } + if let Some(vertex_attribute) = reader + .read_tangents() + .map(|v| VertexAttributeValues::Float32x4(v.collect())) + { + mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); + } else if primitive.material().normal_texture().is_some() { + bevy_log::debug!( + "Missing vertex tangents, computing them using the mikktspace algorithm" + ); + mesh.generate_tangents()?; + } + let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); primitives.push(super::GltfPrimitive { mesh, @@ -318,6 +325,7 @@ async fn load_gltf<'a, 'b>( .and_then(|i| materials.get(i).cloned()), }); } + let handle = load_context.set_labeled_asset( &mesh_label(&mesh), LoadedAsset::new(super::GltfMesh { primitives }), diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index bfd441b781473..dd5e43f3e7d3b 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -67,3 +67,5 @@ flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.2.4", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.2.0", optional = true } +# For generating vertex tangents +mikktspace = { git = "https://github.com/gltf-rs/mikktspace", default-features = false, features = ["glam"] } diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 73488a60af884..0f2565419053d 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -326,6 +326,16 @@ impl Mesh { self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } + /// Generate tangents for the mesh using the `mikktspace` algorithm. + /// + /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. + /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. + pub fn generate_tangents(&mut self) -> Result<(), GenerateTangentsError> { + let tangents = generate_tangents_for_mesh(self)?; + self.set_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); + Ok(()) + } + /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space pub fn compute_aabb(&self) -> Option { if let Some(VertexAttributeValues::Float32x3(values)) = @@ -817,3 +827,121 @@ impl RenderAsset for Mesh { }) } } + +struct MikktspaceGeometryHelper<'a> { + indices: &'a Indices, + positions: &'a Vec<[f32; 3]>, + normals: &'a Vec<[f32; 3]>, + uvs: &'a Vec<[f32; 2]>, + tangents: Vec<[f32; 4]>, +} +impl MikktspaceGeometryHelper<'_> { + fn index(&self, face: usize, vert: usize) -> usize { + let index_index = face * 3 + vert; + + match self.indices { + Indices::U16(indices) => indices[index_index] as usize, + Indices::U32(indices) => indices[index_index] as usize, + } + } +} +impl mikktspace::Geometry for MikktspaceGeometryHelper<'_> { + fn num_faces(&self) -> usize { + self.indices.len() / 3 + } + + fn num_vertices_of_face(&self, _: usize) -> usize { + 3 + } + + fn position(&self, face: usize, vert: usize) -> [f32; 3] { + self.positions[self.index(face, vert)] + } + + fn normal(&self, face: usize, vert: usize) -> [f32; 3] { + self.normals[self.index(face, vert)] + } + + fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { + self.uvs[self.index(face, vert)] + } + + fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { + let idx = self.index(face, vert); + self.tangents[idx] = tangent; + } +} + +#[derive(thiserror::Error, Debug)] +/// Failed to generate tangents for the mesh. +pub enum GenerateTangentsError { + #[error("cannot generate tangents for {0:?}")] + UnsupportedTopology(PrimitiveTopology), + #[error("missing indices")] + MissingIndices, + #[error("missing vertex attributes '{0}'")] + MissingVertexAttribute(&'static str), + #[error("the '{0}' vertex attribute should have {1:?} format")] + InvalidVertexAttributeFormat(&'static str, VertexFormat), + #[error("mesh not suitable for tangent generation")] + MikktspaceError, +} +fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTangentsError> { + match mesh.primitive_topology() { + PrimitiveTopology::TriangleList => {} + other => return Err(GenerateTangentsError::UnsupportedTopology(other)), + }; + + let positions = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION), + )? { + VertexAttributeValues::Float32x3(vertices) => vertices, + _ => { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_POSITION, + VertexFormat::Float32x3, + )) + } + }; + let normals = match mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL), + )? { + VertexAttributeValues::Float32x3(vertices) => vertices, + _ => { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_NORMAL, + VertexFormat::Float32x3, + )) + } + }; + let uvs = match mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0), + )? { + VertexAttributeValues::Float32x2(vertices) => vertices, + _ => { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_UV_0, + VertexFormat::Float32x2, + )) + } + }; + let indices = mesh + .indices() + .ok_or(GenerateTangentsError::MissingIndices)?; + + let len = positions.len(); + let tangents = vec![[0., 0., 0., 0.]; len]; + let mut mikktspace_mesh = MikktspaceGeometryHelper { + indices, + positions, + normals, + uvs, + tangents, + }; + let success = mikktspace::generate_tangents(&mut mikktspace_mesh); + if !success { + return Err(GenerateTangentsError::MikktspaceError); + } + + Ok(mikktspace_mesh.tangents) +} From c0eaf75bddead059dede923701b80cbcb7ac6c20 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 5 Feb 2022 15:42:16 +0100 Subject: [PATCH 72/87] Add validated support for vertex tangent generation using mikktspace --- crates/bevy_gltf/src/loader.rs | 7 ++++++- crates/bevy_pbr/src/render/pbr.wgsl | 15 ++++++++++++--- crates/bevy_render/Cargo.toml | 4 ++-- crates/bevy_render/src/mesh/mesh/mod.rs | 8 ++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index d80f40fcac3f3..61bf8e4be32f4 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -313,7 +313,12 @@ async fn load_gltf<'a, 'b>( bevy_log::debug!( "Missing vertex tangents, computing them using the mikktspace algorithm" ); - mesh.generate_tangents()?; + if let Err(err) = mesh.generate_tangents() { + bevy_log::warn!( + "Failed to generate vertex tangents using the mikktspace algorithm: {:?}", + err + ); + } } let mesh = load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); diff --git a/crates/bevy_pbr/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl index 8cb483173c9b0..d67773896ebfa 100644 --- a/crates/bevy_pbr/src/render/pbr.wgsl +++ b/crates/bevy_pbr/src/render/pbr.wgsl @@ -500,8 +500,12 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { #ifdef VERTEX_TANGENTS #ifdef STANDARDMATERIAL_NORMAL_MAP - var T: vec3 = normalize(in.world_tangent.xyz - N * dot(in.world_tangent.xyz, N)); - var B: vec3 = cross(N, T) * in.world_tangent.w; + // NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be + // normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the + // vertex tangent! Do not change this code unless you really know what you are doing. + // http://www.mikktspace.com/ + var T: vec3 = in.world_tangent.xyz; + var B: vec3 = in.world_tangent.w * cross(N, T); #endif #endif @@ -533,7 +537,12 @@ fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { if ((material.flags & STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { Nt.y = -Nt.y; } - N = normalize(TBN * Nt); + // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from + // the normal map texture in this way to be an EXACT inverse of how the normal map baker + // calculates the normal maps so there is no error introduced. Do not change this code + // unless you really know what you are doing. + // http://www.mikktspace.com/ + N = normalize(Nt.x * T + Nt.y * B + Nt.z * N); #endif #endif diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index dd5e43f3e7d3b..3201b20093025 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -67,5 +67,5 @@ flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.2.4", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.2.0", optional = true } -# For generating vertex tangents -mikktspace = { git = "https://github.com/gltf-rs/mikktspace", default-features = false, features = ["glam"] } +## For correct generation of vertex tangents +mikktspace = { git = "https://github.com/gltf-rs/mikktspace", rev = "a96883cf", default-features = false, features = ["glam"] } diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index 0f2565419053d..f222448965b9b 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -835,6 +835,7 @@ struct MikktspaceGeometryHelper<'a> { uvs: &'a Vec<[f32; 2]>, tangents: Vec<[f32; 4]>, } + impl MikktspaceGeometryHelper<'_> { fn index(&self, face: usize, vert: usize) -> usize { let index_index = face * 3 + vert; @@ -845,6 +846,7 @@ impl MikktspaceGeometryHelper<'_> { } } } + impl mikktspace::Geometry for MikktspaceGeometryHelper<'_> { fn num_faces(&self) -> usize { self.indices.len() / 3 @@ -886,6 +888,7 @@ pub enum GenerateTangentsError { #[error("mesh not suitable for tangent generation")] MikktspaceError, } + fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTangentsError> { match mesh.primitive_topology() { PrimitiveTopology::TriangleList => {} @@ -943,5 +946,10 @@ fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTang return Err(GenerateTangentsError::MikktspaceError); } + // mikktspace seems to assume left-handedness so we can flip the sign to correct for this + for tangent in &mut mikktspace_mesh.tangents { + tangent[3] = -tangent[3]; + } + Ok(mikktspace_mesh.tangents) } From ef7016497a32b0e93c2f094238c6c7320fde281e Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 9 Apr 2022 23:44:57 +0200 Subject: [PATCH 73/87] bevy_mikktspace: Remove travis.yml, conform Cargo.toml, and cargo fmt --- crates/bevy_mikktspace/.travis.yml | 12 -- crates/bevy_mikktspace/Cargo.toml | 22 ++- crates/bevy_mikktspace/examples/generate.rs | 155 +++++++++++++++----- crates/bevy_mikktspace/src/generated.rs | 24 +-- 4 files changed, 134 insertions(+), 79 deletions(-) delete mode 100644 crates/bevy_mikktspace/.travis.yml diff --git a/crates/bevy_mikktspace/.travis.yml b/crates/bevy_mikktspace/.travis.yml deleted file mode 100644 index 058887cc14ea9..0000000000000 --- a/crates/bevy_mikktspace/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -dist: trusty -language: rust -rust: - - stable - - beta - - nightly -matrix: - allow_failures: - - rust: nightly -os: - - linux - - osx diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index d32b4de9ce8d3..795936bc44d34 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -1,25 +1,21 @@ [package] -name = "mikktspace" -version = "0.3.0" -edition = "2018" +name = "bevy_mikktspace" +version = "0.7.0-dev" +edition = "2021" authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" -repository = "https://github.com/gltf-rs/mikktspace" -homepage = "https://github.com/gltf-rs/mikktspace" -readme = "README.md" -keywords = ["3D", "graphics", "algorithm", "tangent"] -license = "MIT/Apache-2.0" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"] [features] -default = ["nalgebra"] - -[badges] -travis-ci = { repository = "gltf-rs/mikktspace" } +default = ["glam"] [dependencies] nalgebra = { version = "0.30", optional = true } -glam = { version = "0.20", optional = true } +glam = { version = "0.20.0", optional = true } [[example]] name = "generate" diff --git a/crates/bevy_mikktspace/examples/generate.rs b/crates/bevy_mikktspace/examples/generate.rs index 4415e1443e9ed..cf34beab0d2d9 100644 --- a/crates/bevy_mikktspace/examples/generate.rs +++ b/crates/bevy_mikktspace/examples/generate.rs @@ -69,11 +69,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [1.0, 0.0, 0.0] }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 1.0], + dir: [1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 0.0], + dir: [1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.5, 0.5], + dir: [1.0, 0.0, 0.0], + }); } // -x plane @@ -83,11 +98,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [-1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [-1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [-1.0, 0.0, 0.0] }); + ctl_pts.push(ControlPoint { + uv: [1.0, 0.0], + dir: [-1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 1.0], + dir: [-1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [-1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [-1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.5, 0.5], + dir: [-1.0, 0.0, 0.0], + }); } // +y plane @@ -97,11 +127,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.5], dir: [0.0, 1.0, 0.0] }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [-1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [-1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.5], + dir: [0.0, 1.0, 0.0], + }); } // -y plane @@ -111,11 +156,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.5], dir: [0.0, -1.0, 0.0] }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [-1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [-1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.5], + dir: [0.0, -1.0, 0.0], + }); } // +z plane @@ -125,11 +185,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, -1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, 1.0] }); - ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [0.0, 0.0, 1.0] }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [-1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [-1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 1.0], + dir: [1.0, -1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 0.0], + dir: [1.0, 1.0, 1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.5, 0.5], + dir: [0.0, 0.0, 1.0], + }); } // -z plane @@ -139,11 +214,26 @@ fn make_cube() -> Mesh { faces.push([base + 1, base + 2, base + 4]); faces.push([base + 2, base + 3, base + 4]); faces.push([base + 3, base, base + 4]); - ctl_pts.push(ControlPoint { uv: [1.0, 0.0], dir: [1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [1.0, 1.0], dir: [1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 1.0], dir: [-1.0, -1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.0, 0.0], dir: [-1.0, 1.0, -1.0] }); - ctl_pts.push(ControlPoint { uv: [0.5, 0.5], dir: [0.0, 0.0, -1.0] }); + ctl_pts.push(ControlPoint { + uv: [1.0, 0.0], + dir: [1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [1.0, 1.0], + dir: [1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 1.0], + dir: [-1.0, -1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.0, 0.0], + dir: [-1.0, 1.0, -1.0], + }); + ctl_pts.push(ControlPoint { + uv: [0.5, 0.5], + dir: [0.0, 0.0, -1.0], + }); } for pt in ctl_pts { @@ -157,10 +247,7 @@ fn make_cube() -> Mesh { }); } - Mesh { - faces, - vertices, - } + Mesh { faces, vertices } } fn main() { diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index f84c7c9fa380f..4fc6832bc788b 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -203,10 +203,7 @@ impl STmpVert { } } -pub unsafe fn genTangSpace( - geometry: &mut I, - fAngularThreshold: f32, -) -> bool { +pub unsafe fn genTangSpace(geometry: &mut I, fAngularThreshold: f32) -> bool { let mut iNrTrianglesIn = 0; let mut f = 0; let mut t = 0; @@ -570,12 +567,7 @@ unsafe fn GenerateTSpaces( } if iMembers > 1 { let mut uSeed: u32 = 39871946i32 as u32; - QuickSort( - pTmpMembers.as_mut_ptr(), - 0i32, - (iMembers - 1) as i32, - uSeed, - ); + QuickSort(pTmpMembers.as_mut_ptr(), 0i32, (iMembers - 1) as i32, uSeed); } tmp_group.iNrFaces = iMembers as i32; tmp_group.pTriMembers = pTmpMembers.clone(); @@ -818,12 +810,7 @@ unsafe fn CompareSubGroups(mut pg1: *const SSubGroup, mut pg2: *const SSubGroup) } return bStillSame; } -unsafe fn QuickSort( - mut pSortBuffer: *mut i32, - mut iLeft: i32, - mut iRight: i32, - mut uSeed: u32, -) { +unsafe fn QuickSort(mut pSortBuffer: *mut i32, mut iLeft: i32, mut iRight: i32, mut uSeed: u32) { let mut iL: i32 = 0; let mut iR: i32 = 0; let mut n: i32 = 0; @@ -1374,10 +1361,7 @@ unsafe fn QuickSortEdges( } // returns the texture area times 2 -unsafe fn CalcTexArea( - geometry: &mut I, - mut indices: *const i32, -) -> f32 { +unsafe fn CalcTexArea(geometry: &mut I, mut indices: *const i32) -> f32 { let t1 = get_tex_coord(geometry, *indices.offset(0isize) as usize); let t2 = get_tex_coord(geometry, *indices.offset(1isize) as usize); let t3 = get_tex_coord(geometry, *indices.offset(2isize) as usize); From 0f5b2a0bacc14dec4e9eaf78c83c7af058535613 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sat, 9 Apr 2022 23:46:39 +0200 Subject: [PATCH 74/87] bevy_render: Use bevy_mikktspace --- crates/bevy_gltf/src/loader.rs | 2 +- crates/bevy_render/Cargo.toml | 3 +-- crates/bevy_render/src/mesh/mesh/mod.rs | 18 +++++++++--------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 61bf8e4be32f4..a29d6a8618e70 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -308,7 +308,7 @@ async fn load_gltf<'a, 'b>( .read_tangents() .map(|v| VertexAttributeValues::Float32x4(v.collect())) { - mesh.set_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); + mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); } else if primitive.material().normal_texture().is_some() { bevy_log::debug!( "Missing vertex tangents, computing them using the mikktspace algorithm" diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 3201b20093025..b2edbd9ec43bd 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -35,6 +35,7 @@ bevy_crevice = { path = "../bevy_crevice", version = "0.7.0-dev", features = ["g bevy_derive = { path = "../bevy_derive", version = "0.7.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.7.0-dev" } bevy_math = { path = "../bevy_math", version = "0.7.0-dev" } +bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.7.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.7.0-dev", features = ["bevy"] } bevy_transform = { path = "../bevy_transform", version = "0.7.0-dev" } bevy_window = { path = "../bevy_window", version = "0.7.0-dev" } @@ -67,5 +68,3 @@ flate2 = { version = "1.0.22", optional = true } ruzstd = { version = "0.2.4", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.2.0", optional = true } -## For correct generation of vertex tangents -mikktspace = { git = "https://github.com/gltf-rs/mikktspace", rev = "a96883cf", default-features = false, features = ["glam"] } diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs index f222448965b9b..4993ea59feefd 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -332,7 +332,7 @@ impl Mesh { /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. pub fn generate_tangents(&mut self) -> Result<(), GenerateTangentsError> { let tangents = generate_tangents_for_mesh(self)?; - self.set_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); + self.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); Ok(()) } @@ -847,7 +847,7 @@ impl MikktspaceGeometryHelper<'_> { } } -impl mikktspace::Geometry for MikktspaceGeometryHelper<'_> { +impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { fn num_faces(&self) -> usize { self.indices.len() / 3 } @@ -896,34 +896,34 @@ fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTang }; let positions = match mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION), + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), )? { VertexAttributeValues::Float32x3(vertices) => vertices, _ => { return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_POSITION, + Mesh::ATTRIBUTE_POSITION.name, VertexFormat::Float32x3, )) } }; let normals = match mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL), + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name), )? { VertexAttributeValues::Float32x3(vertices) => vertices, _ => { return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_NORMAL, + Mesh::ATTRIBUTE_NORMAL.name, VertexFormat::Float32x3, )) } }; let uvs = match mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0), + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name), )? { VertexAttributeValues::Float32x2(vertices) => vertices, _ => { return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_UV_0, + Mesh::ATTRIBUTE_UV_0.name, VertexFormat::Float32x2, )) } @@ -941,7 +941,7 @@ fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTang uvs, tangents, }; - let success = mikktspace::generate_tangents(&mut mikktspace_mesh); + let success = bevy_mikktspace::generate_tangents(&mut mikktspace_mesh); if !success { return Err(GenerateTangentsError::MikktspaceError); } From a29ad02491b6e30d0852f8c762bc890d7b4efd68 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 10 Apr 2022 00:42:04 +0200 Subject: [PATCH 75/87] bevy_mikktspace: Only support glam, and suppress clippy --- crates/bevy_mikktspace/Cargo.toml | 9 +-- crates/bevy_mikktspace/examples/generate.rs | 30 ++++---- crates/bevy_mikktspace/src/generated.rs | 76 +------------------ crates/bevy_mikktspace/src/lib.rs | 15 +--- .../bevy_mikktspace/tests/regression_test.rs | 22 +++--- 5 files changed, 35 insertions(+), 117 deletions(-) diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index 795936bc44d34..1b87a580da48b 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -10,15 +10,8 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"] -[features] -default = ["glam"] - [dependencies] -nalgebra = { version = "0.30", optional = true } -glam = { version = "0.20.0", optional = true } +glam = "0.20.0" [[example]] name = "generate" - -[dev-dependencies] -nalgebra = "0.30" diff --git a/crates/bevy_mikktspace/examples/generate.rs b/crates/bevy_mikktspace/examples/generate.rs index cf34beab0d2d9..a8cefb8809ff4 100644 --- a/crates/bevy_mikktspace/examples/generate.rs +++ b/crates/bevy_mikktspace/examples/generate.rs @@ -1,12 +1,14 @@ -use nalgebra::{Point2, Point3, Vector3}; +#![allow(clippy::bool_assert_comparison, clippy::useless_conversion)] + +use glam::{Vec2, Vec3}; pub type Face = [u32; 3]; #[derive(Debug)] struct Vertex { - position: Point3, - normal: Vector3, - tex_coord: Point2, + position: Vec3, + normal: Vec3, + tex_coord: Vec2, } struct Mesh { @@ -19,7 +21,7 @@ fn vertex(mesh: &Mesh, face: usize, vert: usize) -> &Vertex { &mesh.vertices[vs[vert] as usize] } -impl mikktspace::Geometry for Mesh { +impl bevy_mikktspace::Geometry for Mesh { fn num_faces(&self) -> usize { self.faces.len() } @@ -29,7 +31,7 @@ impl mikktspace::Geometry for Mesh { } fn position(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(self, face, vert).position.coords.into() + vertex(self, face, vert).position.into() } fn normal(&self, face: usize, vert: usize) -> [f32; 3] { @@ -37,7 +39,7 @@ impl mikktspace::Geometry for Mesh { } fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { - vertex(self, face, vert).tex_coord.coords.into() + vertex(self, face, vert).tex_coord.into() } fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { @@ -45,9 +47,9 @@ impl mikktspace::Geometry for Mesh { "{face}-{vert}: v: {v:?}, vn: {vn:?}, vt: {vt:?}, vx: {vx:?}", face = face, vert = vert, - v = vertex(self, face, vert).position.coords.data, - vn = vertex(self, face, vert).normal.data, - vt = vertex(self, face, vert).tex_coord.coords.data, + v = vertex(self, face, vert).position, + vn = vertex(self, face, vert).normal, + vt = vertex(self, face, vert).tex_coord, vx = tangent, ); } @@ -237,9 +239,9 @@ fn make_cube() -> Mesh { } for pt in ctl_pts { - let p: Point3 = pt.dir.into(); - let n: Vector3 = p.coords.normalize(); - let t: Point2 = pt.uv.into(); + let p: Vec3 = pt.dir.into(); + let n: Vec3 = p.normalize(); + let t: Vec2 = pt.uv.into(); vertices.push(Vertex { position: (p / 2.0).into(), normal: n.into(), @@ -252,6 +254,6 @@ fn make_cube() -> Mesh { fn main() { let mut cube = make_cube(); - let ret = mikktspace::generate_tangents(&mut cube); + let ret = bevy_mikktspace::generate_tangents(&mut cube); assert_eq!(true, ret); } diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index 4fc6832bc788b..a6252bbefe56a 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -28,6 +28,8 @@ //! 3. This notice may not be removed or altered from any source distribution. #![allow( + clippy::all, + clippy::doc_markdown, dead_code, mutable_transmutes, non_camel_case_types, @@ -40,19 +42,7 @@ use std::ptr::null_mut; -#[cfg(all(feature = "glam", feature = "nalgebra"))] -compile_error!("Can't have both glam and nalgebra"); - -#[cfg(all(not(feature = "glam"), not(feature = "nalgebra")))] -compile_error!("Please select either the `glam` or `nalgebra` feature"); - -#[cfg(feature = "nalgebra")] -type Vec3 = nalgebra::Vector3; - -#[cfg(feature = "glam")] -type Vec3 = glam::Vec3; -#[cfg(feature = "glam")] -type Vec2 = glam::Vec2; +use glam::Vec3; use crate::{face_vert_to_index, get_normal, get_position, get_tex_coord, Geometry}; @@ -498,16 +488,8 @@ unsafe fn GenerateTSpaces( } iVertIndex = *piTriListIn.offset((f * 3i32 + index) as isize); n = get_normal(geometry, iVertIndex as usize); - #[cfg(feature = "nalgebra")] - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); - #[cfg(feature = "nalgebra")] - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); - #[cfg(feature = "glam")] let mut vOs = (*pTriInfos.offset(f as isize)).vOs - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - #[cfg(feature = "glam")] let mut vOt = (*pTriInfos.offset(f as isize)).vOt - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); if VNotZero(vOs) { @@ -522,16 +504,8 @@ unsafe fn GenerateTSpaces( while j < (*pGroup).iNrFaces { let t: i32 = *(*pGroup).pFaceIndices.offset(j as isize); let iOF_2: i32 = (*pTriInfos.offset(t as isize)).iOrgFaceNumber; - #[cfg(feature = "nalgebra")] - let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - - (n.dot(&(*pTriInfos.offset(t as isize)).vOs) * n); - #[cfg(feature = "nalgebra")] - let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt - - (n.dot(&(*pTriInfos.offset(t as isize)).vOt) * n); - #[cfg(feature = "glam")] let mut vOs2 = (*pTriInfos.offset(t as isize)).vOs - (n.dot((*pTriInfos.offset(t as isize)).vOs) * n); - #[cfg(feature = "glam")] let mut vOt2 = (*pTriInfos.offset(t as isize)).vOt - (n.dot((*pTriInfos.offset(t as isize)).vOt) * n); if VNotZero(vOs2) { @@ -550,13 +524,7 @@ unsafe fn GenerateTSpaces( false }; let bSameOrgFace: bool = iOF_1 == iOF_2; - #[cfg(feature = "nalgebra")] - let fCosS: f32 = vOs.dot(&vOs2); - #[cfg(feature = "nalgebra")] - let fCosT: f32 = vOt.dot(&vOt2); - #[cfg(feature = "glam")] let fCosS: f32 = vOs.dot(vOs2); - #[cfg(feature = "glam")] let fCosT: f32 = vOt.dot(vOt2); if bAny || bSameOrgFace || fCosS > fThresCos && fCosT > fThresCos { let fresh0 = iMembers; @@ -646,9 +614,6 @@ unsafe fn AvgTSpace(mut pTS0: *const STSpace, mut pTS1: *const STSpace) -> STSpa } unsafe fn Normalize(v: Vec3) -> Vec3 { - #[cfg(feature = "nalgebra")] - return (1.0 / v.magnitude()) * v; - #[cfg(feature = "glam")] return (1.0 / v.length()) * v; } @@ -716,16 +681,8 @@ unsafe fn EvalTspace( } index = *piTriListIn.offset((3i32 * f + i) as isize); n = get_normal(geometry, index as usize); - #[cfg(feature = "nalgebra")] - let mut vOs = (*pTriInfos.offset(f as isize)).vOs - - (n.dot(&(*pTriInfos.offset(f as isize)).vOs) * n); - #[cfg(feature = "nalgebra")] - let mut vOt = (*pTriInfos.offset(f as isize)).vOt - - (n.dot(&(*pTriInfos.offset(f as isize)).vOt) * n); - #[cfg(feature = "glam")] let mut vOs = (*pTriInfos.offset(f as isize)).vOs - (n.dot((*pTriInfos.offset(f as isize)).vOs) * n); - #[cfg(feature = "glam")] let mut vOt = (*pTriInfos.offset(f as isize)).vOt - (n.dot((*pTriInfos.offset(f as isize)).vOt) * n); if VNotZero(vOs) { @@ -742,23 +699,14 @@ unsafe fn EvalTspace( p2 = get_position(geometry, i2 as usize); v1 = p0 - p1; v2 = p2 - p1; - #[cfg(feature = "nalgebra")] - let mut v1 = v1 - (n.dot(&v1) * n); - #[cfg(feature = "glam")] let mut v1 = v1 - (n.dot(v1) * n); if VNotZero(v1) { v1 = Normalize(v1) } - #[cfg(feature = "nalgebra")] - let mut v2 = v2 - (n.dot(&v2) * n); - #[cfg(feature = "glam")] let mut v2 = v2 - (n.dot(v2) * n); if VNotZero(v2) { v2 = Normalize(v2) } - #[cfg(feature = "nalgebra")] - let fCos = v1.dot(&v2); - #[cfg(feature = "glam")] let fCos = v1.dot(v2); let fCos = if fCos > 1i32 as f32 { @@ -1055,13 +1003,7 @@ unsafe fn InitTriInfo( }; if NotZero(fSignedAreaSTx2) { let fAbsArea: f32 = fSignedAreaSTx2.abs(); - #[cfg(feature = "nalgebra")] - let fLenOs: f32 = vOs.magnitude(); - #[cfg(feature = "nalgebra")] - let fLenOt: f32 = vOt.magnitude(); - #[cfg(feature = "glam")] let fLenOs: f32 = vOs.length(); - #[cfg(feature = "glam")] let fLenOt: f32 = vOt.length(); let fS: f32 = if (*pTriInfos.offset(f as isize)).iFlag & 8i32 == 0i32 { -1.0f32 @@ -1791,13 +1733,7 @@ unsafe fn GenerateInitialVerticesIndexList( let T1 = get_tex_coord(geometry, i1); let T2 = get_tex_coord(geometry, i2); let T3 = get_tex_coord(geometry, i3); - #[cfg(feature = "nalgebra")] - let distSQ_02: f32 = (T2 - T0).magnitude_squared(); - #[cfg(feature = "nalgebra")] - let distSQ_13: f32 = (T3 - T1).magnitude_squared(); - #[cfg(feature = "glam")] let distSQ_02: f32 = (T2 - T0).length_squared(); - #[cfg(feature = "glam")] let distSQ_13: f32 = (T3 - T1).length_squared(); let mut bQuadDiagIs_02: bool = false; if distSQ_02 < distSQ_13 { @@ -1809,13 +1745,7 @@ unsafe fn GenerateInitialVerticesIndexList( let P1 = get_position(geometry, i1); let P2 = get_position(geometry, i2); let P3 = get_position(geometry, i3); - #[cfg(feature = "nalgebra")] - let distSQ_02_0: f32 = (P2 - P0).magnitude_squared(); - #[cfg(feature = "nalgebra")] - let distSQ_13_0: f32 = (P3 - P1).magnitude_squared(); - #[cfg(feature = "glam")] let distSQ_02_0: f32 = (P2 - P0).length_squared(); - #[cfg(feature = "glam")] let distSQ_13_0: f32 = (P3 - P1).length_squared(); bQuadDiagIs_02 = if distSQ_13_0 < distSQ_02_0 { false diff --git a/crates/bevy_mikktspace/src/lib.rs b/crates/bevy_mikktspace/src/lib.rs index e38de3d9d5f44..89d7b05427407 100644 --- a/crates/bevy_mikktspace/src/lib.rs +++ b/crates/bevy_mikktspace/src/lib.rs @@ -1,14 +1,8 @@ -mod generated; +#![allow(clippy::all)] -#[cfg(feature = "nalgebra")] -type Vec3 = nalgebra::Vector3; -#[cfg(feature = "nalgebra")] -type Vec2 = nalgebra::Vector2; +use glam::{Vec2, Vec3}; -#[cfg(feature = "glam")] -type Vec3 = glam::Vec3; -#[cfg(feature = "glam")] -type Vec2 = glam::Vec2; +mod generated; /// The interface by which mikktspace interacts with your geometry. pub trait Geometry { @@ -73,9 +67,6 @@ fn get_position(geometry: &mut I, index: usize) -> Vec3 { fn get_tex_coord(geometry: &mut I, index: usize) -> Vec3 { let (face, vert) = index_to_face_vert(index); let tex_coord: Vec2 = geometry.tex_coord(face, vert).into(); - #[cfg(feature = "nalgebra")] - let val = tex_coord.insert_row(2, 1.0); - #[cfg(feature = "glam")] let val = tex_coord.extend(1.0); val } diff --git a/crates/bevy_mikktspace/tests/regression_test.rs b/crates/bevy_mikktspace/tests/regression_test.rs index 761284571b101..4e44e1a15f497 100644 --- a/crates/bevy_mikktspace/tests/regression_test.rs +++ b/crates/bevy_mikktspace/tests/regression_test.rs @@ -1,13 +1,15 @@ -use mikktspace::{generate_tangents, Geometry}; -use nalgebra::{Point2, Point3, Vector3}; +#![allow(clippy::bool_assert_comparison, clippy::useless_conversion)] + +use bevy_mikktspace::{generate_tangents, Geometry}; +use glam::{Vec2, Vec3}; pub type Face = [u32; 3]; #[derive(Debug)] struct Vertex { - position: Point3, - normal: Vector3, - tex_coord: Point2, + position: Vec3, + normal: Vec3, + tex_coord: Vec2, } #[derive(Debug, PartialEq)] @@ -68,7 +70,7 @@ impl Geometry for Context { } fn position(&self, face: usize, vert: usize) -> [f32; 3] { - vertex(&self.mesh, face, vert).position.coords.into() + vertex(&self.mesh, face, vert).position.into() } fn normal(&self, face: usize, vert: usize) -> [f32; 3] { @@ -76,7 +78,7 @@ impl Geometry for Context { } fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { - vertex(&self.mesh, face, vert).tex_coord.coords.into() + vertex(&self.mesh, face, vert).tex_coord.into() } fn set_tangent( @@ -202,9 +204,9 @@ fn make_cube() -> Mesh { } for pt in ctl_pts { - let p: Point3 = pt.dir.into(); - let n: Vector3 = p.coords.normalize(); - let t: Point2 = pt.uv.into(); + let p: Vec3 = pt.dir.into(); + let n: Vec3 = p.normalize(); + let t: Vec2 = pt.uv.into(); vertices.push(Vertex { position: (p / 2.0).into(), normal: n.into(), From e6b284e0d146fa35e5446d8f9c02b7ca5acf67e3 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 10 Apr 2022 00:42:18 +0200 Subject: [PATCH 76/87] tools: Publish bevy_mikktspace --- tools/publish.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/publish.sh b/tools/publish.sh index a0cdd0a59dd0c..2060706502b5c 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -20,6 +20,7 @@ crates=( bevy_window bevy_crevice/bevy-crevice-derive bevy_crevice + bevy_mikktspace bevy_render bevy_core_pipeline bevy_input From c910b337b4029235363a60444d429fe91bc707cd Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 1 May 2022 23:37:50 +0200 Subject: [PATCH 77/87] Appease markdownlint --- crates/bevy_mikktspace/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/README.md b/crates/bevy_mikktspace/README.md index 5fabe817e1900..c2492ef43d4ab 100644 --- a/crates/bevy_mikktspace/README.md +++ b/crates/bevy_mikktspace/README.md @@ -1,6 +1,6 @@ # mikktspace -This is a fork of https://github.com/gltf-rs/mikktspace, which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It will be vendored in the bevy repository itself as crates/bevy_mikktspace. +This is a fork of [https://github.com/gltf-rs/mikktspace](https://github.com/gltf-rs/mikktspace), which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It will be vendored in the bevy repository itself as crates/bevy_mikktspace. [![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) [![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) From 0b8e0d6738f221756150a1b78fdabe8dc100d1e3 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Sun, 1 May 2022 23:40:10 +0200 Subject: [PATCH 78/87] Appease rustdoc --- crates/bevy_mikktspace/src/generated.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index a6252bbefe56a..d4061f288f2c4 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -4,7 +4,7 @@ //! The contents of this file are a combination of transpilation and human //! modification to Morten S. Mikkelsen's original tangent space algorithm //! implementation written in C. The original source code can be found at -//! https://archive.blender.org/wiki/index.php/Dev:Shading/Tangent_Space_Normal_Maps +//! //! and includes the following licence: //! //! Copyright (C) 2011 by Morten S. Mikkelsen From 572a0d9016fd47c10edc413e0cc977ab2950daeb Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 30 May 2022 22:58:32 +0200 Subject: [PATCH 79/87] bevy_mikktspace: Note Zlib license for mikktspace --- crates/bevy_mikktspace/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index 758d88e3e6e28..c225f767d2bc0 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -7,7 +7,7 @@ description = "Mikkelsen tangent space algorithm" documentation = "https://docs.rs/mikktspace" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" -license = "MIT OR Apache-2.0" +license = "Zlib AND (MIT OR Apache-2.0)" keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"] [dependencies] From 611231d2f7f836f4cd7d5788f64fb5a84f872f7c Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 30 May 2022 23:18:58 +0200 Subject: [PATCH 80/87] bevy_gltf: Check all dependencies for normal mapping before generating tangents --- crates/bevy_gltf/src/loader.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index a29d6a8618e70..ee527c5ff1ea9 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -309,7 +309,9 @@ async fn load_gltf<'a, 'b>( .map(|v| VertexAttributeValues::Float32x4(v.collect())) { mesh.insert_attribute(Mesh::ATTRIBUTE_TANGENT, vertex_attribute); - } else if primitive.material().normal_texture().is_some() { + } else if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_some() + && primitive.material().normal_texture().is_some() + { bevy_log::debug!( "Missing vertex tangents, computing them using the mikktspace algorithm" ); From ca1b1e65d2ae44bc01140bf989025adba327df73 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 30 May 2022 23:23:11 +0200 Subject: [PATCH 81/87] bevy_mikktspace: Clean up cruft in the readme and conform it to bevy --- crates/bevy_mikktspace/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/bevy_mikktspace/README.md b/crates/bevy_mikktspace/README.md index c2492ef43d4ab..dfa0464d9a183 100644 --- a/crates/bevy_mikktspace/README.md +++ b/crates/bevy_mikktspace/README.md @@ -1,10 +1,7 @@ -# mikktspace +# bevy_mikktspace This is a fork of [https://github.com/gltf-rs/mikktspace](https://github.com/gltf-rs/mikktspace), which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It will be vendored in the bevy repository itself as crates/bevy_mikktspace. -[![crates.io](https://img.shields.io/crates/v/mikktspace.svg)](https://crates.io/crates/mikktspace) -[![Build Status](https://travis-ci.org/gltf-rs/mikktspace.svg?branch=master)](https://travis-ci.org/gltf-rs/mikktspace) - Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. Requires at least Rust 1.52.1. From 0278521cba9c12dee1e7a82dd235d35449c359f6 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Mon, 30 May 2022 23:26:16 +0200 Subject: [PATCH 82/87] bevy_mikktspace: Remove .gitignore that duplicates top-level .gitignore rules --- crates/bevy_mikktspace/.gitignore | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 crates/bevy_mikktspace/.gitignore diff --git a/crates/bevy_mikktspace/.gitignore b/crates/bevy_mikktspace/.gitignore deleted file mode 100644 index 6aa106405a4b4..0000000000000 --- a/crates/bevy_mikktspace/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target/ -**/*.rs.bk -Cargo.lock From d9db789ae7277f3a458e297afbfbecdda480ae3b Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 31 May 2022 01:16:13 +0200 Subject: [PATCH 83/87] bevy_mikktspace: Remove unneeded APPENDIX section from license file --- crates/bevy_mikktspace/LICENSE-APACHE | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/crates/bevy_mikktspace/LICENSE-APACHE b/crates/bevy_mikktspace/LICENSE-APACHE index 1e5006dc141e2..1b5ec8b78e237 100644 --- a/crates/bevy_mikktspace/LICENSE-APACHE +++ b/crates/bevy_mikktspace/LICENSE-APACHE @@ -174,29 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - From b195cfa8e5fb82c443f67cd873b867eee56d0fc6 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 31 May 2022 01:24:14 +0200 Subject: [PATCH 84/87] bevy_mikktspace: Document that parts are covered by the Zlib license --- crates/bevy_mikktspace/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/README.md b/crates/bevy_mikktspace/README.md index dfa0464d9a183..bec13c9808a51 100644 --- a/crates/bevy_mikktspace/README.md +++ b/crates/bevy_mikktspace/README.md @@ -25,7 +25,10 @@ Licensed under either of * MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) -at your option. +at your option. AND parts of the code are licensed under: + +* Zlib license + [https://opensource.org/licenses/Zlib](https://opensource.org/licenses/Zlib) Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be From 1705cf6809caca44d375871a276c013a1235d6c5 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 31 May 2022 01:25:29 +0200 Subject: [PATCH 85/87] bevy_mikktspace: Fix docs link in Cargo.toml --- crates/bevy_mikktspace/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/Cargo.toml b/crates/bevy_mikktspace/Cargo.toml index c225f767d2bc0..c456b3008850f 100644 --- a/crates/bevy_mikktspace/Cargo.toml +++ b/crates/bevy_mikktspace/Cargo.toml @@ -4,7 +4,7 @@ version = "0.8.0-dev" edition = "2021" authors = ["Benjamin Wasty ", "David Harvey-Macaulay ", "Layl Bongers "] description = "Mikkelsen tangent space algorithm" -documentation = "https://docs.rs/mikktspace" +documentation = "https://docs.rs/bevy" homepage = "https://bevyengine.org" repository = "https://github.com/bevyengine/bevy" license = "Zlib AND (MIT OR Apache-2.0)" From 964b1abf95368e76139263716557bab27525d11a Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Tue, 31 May 2022 01:26:49 +0200 Subject: [PATCH 86/87] bevy_mikktspace: Fix vendored link --- crates/bevy_mikktspace/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/README.md b/crates/bevy_mikktspace/README.md index bec13c9808a51..b5f886ce7abf5 100644 --- a/crates/bevy_mikktspace/README.md +++ b/crates/bevy_mikktspace/README.md @@ -1,6 +1,6 @@ # bevy_mikktspace -This is a fork of [https://github.com/gltf-rs/mikktspace](https://github.com/gltf-rs/mikktspace), which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It will be vendored in the bevy repository itself as crates/bevy_mikktspace. +This is a fork of [https://github.com/gltf-rs/mikktspace](https://github.com/gltf-rs/mikktspace), which in turn is a port of the Mikkelsen Tangent Space Algorithm reference implementation to Rust. It has been forked for use in the bevy game engine to be able to update maths crate dependencies in lock-step with bevy releases. It is vendored in the bevy repository itself as [crates/bevy_mikktspace](https://github.com/bevyengine/bevy/tree/main/crates/bevy_mikktspace). Port of the [Mikkelsen Tangent Space Algorithm](https://en.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps) reference implementation. From d8827f19bcb9521d3b4a062896c172d7b6ff08ae Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Tue, 31 May 2022 15:52:05 -0700 Subject: [PATCH 87/87] suppress custom bevy lints --- crates/bevy_mikktspace/src/generated.rs | 5 +++++ crates/bevy_mikktspace/tests/regression_test.rs | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/bevy_mikktspace/src/generated.rs b/crates/bevy_mikktspace/src/generated.rs index d4061f288f2c4..c05b8c1566ee3 100644 --- a/crates/bevy_mikktspace/src/generated.rs +++ b/crates/bevy_mikktspace/src/generated.rs @@ -30,6 +30,11 @@ #![allow( clippy::all, clippy::doc_markdown, + clippy::redundant_else, + clippy::match_same_arms, + clippy::semicolon_if_nothing_returned, + clippy::explicit_iter_loop, + clippy::map_flatten, dead_code, mutable_transmutes, non_camel_case_types, diff --git a/crates/bevy_mikktspace/tests/regression_test.rs b/crates/bevy_mikktspace/tests/regression_test.rs index 4e44e1a15f497..42177cbc3496d 100644 --- a/crates/bevy_mikktspace/tests/regression_test.rs +++ b/crates/bevy_mikktspace/tests/regression_test.rs @@ -1,4 +1,12 @@ -#![allow(clippy::bool_assert_comparison, clippy::useless_conversion)] +#![allow( + clippy::bool_assert_comparison, + clippy::useless_conversion, + clippy::redundant_else, + clippy::match_same_arms, + clippy::semicolon_if_nothing_returned, + clippy::explicit_iter_loop, + clippy::map_flatten +)] use bevy_mikktspace::{generate_tangents, Geometry}; use glam::{Vec2, Vec3};