Ticket #1429: bumpy2.diff

File bumpy2.diff, 113.0 KB (added by myconid, 12 years ago)

Adds normal, parallax, specular and self-illumination mapping

  • source/graphics/Material.h

     
    3838
    3939    void SetDiffuseTexture(const CTexturePtr& texture);
    4040    const CTexturePtr& GetDiffuseTexture() const { return m_DiffuseTexture; }
     41   
     42    void SetNormalTexture(const CTexturePtr& texture);
     43    const CTexturePtr& GetNormalTexture() const { return m_NormalTexture; }
     44   
     45    void SetSpecularTexture(const CTexturePtr& texture);
     46    const CTexturePtr& GetSpecularTexture() const { return m_SpecularTexture; }
    4147
    4248    void SetShaderEffect(const CStr& effect);
    4349    CStrIntern GetShaderEffect() const { return m_ShaderEffect; }
     
    5056
    5157private:
    5258    CTexturePtr m_DiffuseTexture;
     59    CTexturePtr m_NormalTexture;
     60    CTexturePtr m_SpecularTexture;
    5361    CStrIntern m_ShaderEffect;
    5462    CShaderDefines m_ShaderDefines;
    5563    CShaderUniforms m_StaticUniforms;
  • source/graphics/ObjectEntry.h

     
    4747    CObjectBase* m_Base;
    4848
    4949    VfsPath m_TextureName;
     50    VfsPath m_NormalName;
     51    VfsPath m_SpecularName;
    5052    // model name
    5153    VfsPath m_ModelName;
    5254    // colour (used when doing alpha-channel colouring, but not doing player-colour)
  • source/graphics/mikktspace.h

     
     1/** \file mikktspace/mikktspace.h
     2 *  \ingroup mikktspace
     3 */
     4/**
     5 *  Copyright (C) 2011 by Morten S. Mikkelsen
     6 *
     7 *  This software is provided 'as-is', without any express or implied
     8 *  warranty.  In no event will the authors be held liable for any damages
     9 *  arising from the use of this software.
     10 *
     11 *  Permission is granted to anyone to use this software for any purpose,
     12 *  including commercial applications, and to alter it and redistribute it
     13 *  freely, subject to the following restrictions:
     14 *
     15 *  1. The origin of this software must not be misrepresented; you must not
     16 *     claim that you wrote the original software. If you use this software
     17 *     in a product, an acknowledgment in the product documentation would be
     18 *     appreciated but is not required.
     19 *  2. Altered source versions must be plainly marked as such, and must not be
     20 *     misrepresented as being the original software.
     21 *  3. This notice may not be removed or altered from any source distribution.
     22 */
     23
     24#ifndef __MIKKTSPACE_H__
     25#define __MIKKTSPACE_H__
     26
     27
     28#ifdef __cplusplus
     29extern "C" {
     30#endif
     31
     32/* Author: Morten S. Mikkelsen
     33 * Version: 1.0
     34 *
     35 * The files mikktspace.h and mikktspace.c are designed to be
     36 * stand-alone files and it is important that they are kept this way.
     37 * Not having dependencies on structures/classes/libraries specific
     38 * to the program, in which they are used, allows them to be copied
     39 * and used as is into any tool, program or plugin.
     40 * The code is designed to consistently generate the same
     41 * tangent spaces, for a given mesh, in any tool in which it is used.
     42 * This is done by performing an internal welding step and subsequently an order-independent evaluation
     43 * of tangent space for meshes consisting of triangles and quads.
     44 * This means faces can be received in any order and the same is true for
     45 * the order of vertices of each face. The generated result will not be affected
     46 * by such reordering. Additionally, whether degenerate (vertices or texture coordinates)
     47 * primitives are present or not will not affect the generated results either.
     48 * Once tangent space calculation is done the vertices of degenerate primitives will simply
     49 * inherit tangent space from neighboring non degenerate primitives.
     50 * The analysis behind this implementation can be found in my master's thesis
     51 * which is available for download --> http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf
     52 * Note that though the tangent spaces at the vertices are generated in an order-independent way,
     53 * by this implementation, the interpolated tangent space is still affected by which diagonal is
     54 * chosen to split each quad. A sensible solution is to have your tools pipeline always
     55 * split quads by the shortest diagonal. This choice is order-independent and works with mirroring.
     56 * If these have the same length then compare the diagonals defined by the texture coordinates.
     57 * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin
     58 * and also quad triangulator plugin.
     59 */
     60
     61
     62typedef int tbool;
     63typedef struct SMikkTSpaceContext SMikkTSpaceContext;
     64
     65typedef struct
     66{
     67    // Returns the number of faces (triangles/quads) on the mesh to be processed.
     68    int (*m_getNumFaces)(const SMikkTSpaceContext * pContext);
     69
     70    // Returns the number of vertices on face number iFace
     71    // iFace is a number in the range {0, 1, ..., getNumFaces()-1}
     72    int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace);
     73
     74    // returns the position/normal/texcoord of the referenced face of vertex number iVert.
     75    // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
     76    void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert);
     77    void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert);
     78    void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert);
     79
     80    // either (or both) of the two setTSpace callbacks can be set.
     81    // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.
     82
     83    // This function is used to return the tangent and fSign to the application.
     84    // fvTangent is a unit length vector.
     85    // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
     86    // bitangent = fSign * cross(vN, tangent);
     87    // Note that the results are returned unindexed. It is possible to generate a new index list
     88    // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
     89    // DO NOT! use an already existing index list.
     90    void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert);
     91
     92    // This function is used to return tangent space results to the application.
     93    // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their
     94    // true magnitudes which can be used for relief mapping effects.
     95    // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent.
     96    // However, both are perpendicular to the vertex normal.
     97    // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
     98    // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
     99    // bitangent = fSign * cross(vN, tangent);
     100    // Note that the results are returned unindexed. It is possible to generate a new index list
     101    // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
     102    // DO NOT! use an already existing index list.
     103    void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT,
     104                        const tbool bIsOrientationPreserving, const int iFace, const int iVert);
     105} SMikkTSpaceInterface;
     106
     107struct SMikkTSpaceContext
     108{
     109    SMikkTSpaceInterface * m_pInterface;    // initialized with callback functions
     110    void * m_pUserData;                     // pointer to client side mesh data etc. (passed as the first parameter with every interface call)
     111};
     112
     113// these are both thread safe!
     114tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled)
     115tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold);
     116
     117
     118// To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the
     119// normal map sampler must use the exact inverse of the pixel shader transformation.
     120// The most efficient transformation we can possibly do in the pixel shader is
     121// achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN.
     122// pixel shader (fast transform out)
     123// vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN );
     124// where vNt is the tangent space normal. The normal map sampler must likewise use the
     125// interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader.
     126// sampler does (exact inverse of pixel shader):
     127// float3 row0 = cross(vB, vN);
     128// float3 row1 = cross(vN, vT);
     129// float3 row2 = cross(vT, vB);
     130// float fSign = dot(vT, row0)<0 ? -1 : 1;
     131// vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) );
     132// where vNout is the sampled normal in some chosen 3D space.
     133//
     134// Should you choose to reconstruct the bitangent in the pixel shader instead
     135// of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also.
     136// Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of
     137// quads as your renderer then problems will occur since the interpolated tangent spaces will differ
     138// eventhough the vertex level tangent spaces match. This can be solved either by triangulating before
     139// sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier.
     140// However, this must be used both by the sampler and your tools/rendering pipeline.
     141
     142#ifdef __cplusplus
     143}
     144#endif
     145
     146#endif
  • source/graphics/weldmesh.cpp

     
     1/**
     2 *  Copyright (C) 2011 by Morten S. Mikkelsen
     3 *
     4 *  This software is provided 'as-is', without any express or implied
     5 *  warranty.  In no event will the authors be held liable for any damages
     6 *  arising from the use of this software.
     7 *
     8 *  Permission is granted to anyone to use this software for any purpose,
     9 *  including commercial applications, and to alter it and redistribute it
     10 *  freely, subject to the following restrictions:
     11 *
     12 *  1. The origin of this software must not be misrepresented; you must not
     13 *     claim that you wrote the original software. If you use this software
     14 *     in a product, an acknowledgment in the product documentation would be
     15 *     appreciated but is not required.
     16 *  2. Altered source versions must be plainly marked as such, and must not be
     17 *     misrepresented as being the original software.
     18 *  3. This notice may not be removed or altered from any source distribution.
     19 */
     20
     21
     22#include "weldmesh.h"
     23#include <string.h>
     24#include <assert.h>
     25
     26#ifdef __APPLE__
     27#include <stdlib.h>  /* OSX gets its malloc stuff through here */
     28#else
     29#include <malloc.h>
     30#endif
     31
     32static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs,
     33              const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert,
     34              const int iL_in, const int iR_in, const int iChannelNum);
     35
     36int WeldMesh(int * piRemapTable, float * pfVertexDataOut,
     37              const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert)
     38{
     39    int iUniqueVertices = 0, i=0;
     40    int * piVertexIDs = NULL;
     41    if(iNrVerticesIn<=0) return 0;
     42
     43
     44    iUniqueVertices = 0;
     45    piVertexIDs = (int *) malloc(sizeof(int)*iNrVerticesIn);
     46    if(piVertexIDs!=NULL)
     47    {
     48        for(i=0; i<iNrVerticesIn; i++)
     49        {
     50            piRemapTable[i] = -1;
     51            piVertexIDs[i] = i;
     52        }
     53
     54        MergeVertsFast(&iUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
     55                                         pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, 0, iNrVerticesIn-1, 0);
     56
     57        free(piVertexIDs);
     58
     59        // debug check
     60        for(i=0; i<iUniqueVertices; i++)
     61            assert(piRemapTable[i]>=0);
     62    }
     63
     64    return iUniqueVertices;
     65}
     66
     67
     68
     69
     70
     71static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs,
     72              const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert,
     73              const int iL_in, const int iR_in, const int iChannelNum)
     74{
     75    const int iCount = iR_in-iL_in+1;
     76    int l=0;
     77    float fMin, fMax, fAvg;
     78    assert(iCount>0);
     79    // make bbox
     80    fMin = pfVertexDataIn[ piVertexIDs[iL_in]*iFloatsPerVert + iChannelNum]; fMax = fMin;
     81    for(l=(iL_in+1); l<=iR_in; l++)
     82    {
     83        const int index = piVertexIDs[l]*iFloatsPerVert + iChannelNum;
     84        const float fVal = pfVertexDataIn[index];
     85        if(fMin>fVal) fMin=fVal;
     86        else if(fMax<fVal) fMax=fVal;
     87    }
     88
     89    // terminate recursion when the separation/average value
     90    // is no longer strictly between fMin and fMax values.
     91    fAvg = 0.5f*(fMax + fMin);
     92    if(fAvg<=fMin || fAvg>=fMax || iCount==1)
     93    {
     94        if((iChannelNum+1) == iFloatsPerVert || iCount==1)  // we are done, weld by hand
     95        {
     96            int iUniqueNewVertices = 0;
     97            float * pfNewUniVertsOut = &pfVertexDataOut[ piCurNrUniqueVertices[0]*iFloatsPerVert ];
     98
     99            for(l=iL_in; l<=iR_in; l++)
     100            {
     101                const int index = piVertexIDs[l]*iFloatsPerVert;
     102
     103                int iFound = 0; // didn't find copy yet.
     104                int l2=0;
     105                while(l2<iUniqueNewVertices && iFound==0)
     106                {
     107                    const int index2 = l2*iFloatsPerVert;
     108
     109                    int iAllSame = 1;
     110                    int c=0;
     111                    while(iAllSame!=0 && c<iFloatsPerVert)
     112                    {
     113                        iAllSame &= (pfVertexDataIn[index+c] == pfNewUniVertsOut[index2+c] ? 1 : 0);
     114                        ++c;
     115                    }
     116
     117                    iFound = iAllSame;
     118                    if(iFound==0) ++l2;
     119                }
     120               
     121                // generate new entry
     122                if(iFound==0)
     123                {
     124                    memcpy(pfNewUniVertsOut+iUniqueNewVertices*iFloatsPerVert, pfVertexDataIn+index, sizeof(float)*iFloatsPerVert);
     125                    ++iUniqueNewVertices;
     126                }
     127
     128                assert(piRemapTable[piVertexIDs[l]] == -1); // has not yet been assigned
     129                piRemapTable[piVertexIDs[l]] = piCurNrUniqueVertices[0] + l2;
     130            }
     131
     132            piCurNrUniqueVertices[0] += iUniqueNewVertices;
     133        }
     134        else
     135        {
     136            MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
     137                           pfVertexDataIn, iNrVerticesIn, iFloatsPerVert,
     138                            iL_in, iR_in, iChannelNum+1);
     139        }
     140    }
     141    else
     142    {
     143        int iL=iL_in, iR=iR_in, index;
     144
     145        // seperate (by fSep) all points between iL_in and iR_in in pTmpVert[]
     146        while(iL < iR)
     147        {
     148            int iReadyLeftSwap = 0;
     149            int iReadyRightSwap = 0;
     150            while(iReadyLeftSwap==0 && iL<iR)
     151            {
     152                assert(iL>=iL_in && iL<=iR_in);
     153                index = piVertexIDs[iL]*iFloatsPerVert+iChannelNum;
     154                iReadyLeftSwap = !(pfVertexDataIn[index]<fAvg) ? 1 : 0;
     155                if(iReadyLeftSwap==0) ++iL;
     156            }
     157            while(iReadyRightSwap==0 && iL<iR)
     158            {
     159                assert(iR>=iL_in && iR<=iR_in);
     160                index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum;
     161                iReadyRightSwap = pfVertexDataIn[index]<fAvg ? 1 : 0;
     162                if(iReadyRightSwap==0) --iR;
     163            }
     164            assert( (iL<iR) || (iReadyLeftSwap==0 || iReadyRightSwap==0));
     165
     166            if(iReadyLeftSwap!=0 && iReadyRightSwap!=0)
     167            {
     168                int iID=0;
     169                assert(iL<iR);
     170                iID = piVertexIDs[iL];
     171                piVertexIDs[iL] = piVertexIDs[iR];
     172                piVertexIDs[iR] = iID;
     173                ++iL; --iR;
     174            }
     175        }
     176
     177        assert(iL==(iR+1) || (iL==iR));
     178        if(iL==iR)
     179        {
     180            const int index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum;
     181            const int iReadyRightSwap = pfVertexDataIn[index]<fAvg ? 1 : 0;
     182            if(iReadyRightSwap!=0) ++iL;
     183            else --iR;
     184        }
     185
     186        // recurse
     187        if(iL_in <= iR)
     188            MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
     189                           pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, iL_in, iR, iChannelNum);  // weld all left of fSep
     190        if(iL <= iR_in)
     191            MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
     192                           pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, iL, iR_in, iChannelNum);  // weld all right of (or equal to) fSep
     193    }
     194}
     195 No newline at end of file
  • source/graphics/mikktspace.cpp

     
     1/** \file mikktspace/mikktspace.c
     2 *  \ingroup mikktspace
     3 */
     4/**
     5 *  Copyright (C) 2011 by Morten S. Mikkelsen
     6 *
     7 *  This software is provided 'as-is', without any express or implied
     8 *  warranty.  In no event will the authors be held liable for any damages
     9 *  arising from the use of this software.
     10 *
     11 *  Permission is granted to anyone to use this software for any purpose,
     12 *  including commercial applications, and to alter it and redistribute it
     13 *  freely, subject to the following restrictions:
     14 *
     15 *  1. The origin of this software must not be misrepresented; you must not
     16 *     claim that you wrote the original software. If you use this software
     17 *     in a product, an acknowledgment in the product documentation would be
     18 *     appreciated but is not required.
     19 *  2. Altered source versions must be plainly marked as such, and must not be
     20 *     misrepresented as being the original software.
     21 *  3. This notice may not be removed or altered from any source distribution.
     22 */
     23
     24#include <assert.h>
     25#include <stdio.h>
     26#include <math.h>
     27#include <string.h>
     28#include <float.h>
     29#include <stdlib.h>
     30
     31#include "mikktspace.h"
     32
     33#define TFALSE      0
     34#define TTRUE       1
     35
     36#ifndef M_PI
     37#define M_PI    3.1415926535897932384626433832795
     38#endif
     39
     40#define INTERNAL_RND_SORT_SEED      39871946
     41
     42// internal structure
     43typedef struct
     44{
     45    float x, y, z;
     46} SVec3;
     47
     48static tbool            veq( const SVec3 v1, const SVec3 v2 )
     49{
     50    return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z);
     51}
     52
     53static SVec3        vadd( const SVec3 v1, const SVec3 v2 )
     54{
     55    SVec3 vRes;
     56
     57    vRes.x = v1.x + v2.x;
     58    vRes.y = v1.y + v2.y;
     59    vRes.z = v1.z + v2.z;
     60
     61    return vRes;
     62}
     63
     64
     65static SVec3        vsub( const SVec3 v1, const SVec3 v2 )
     66{
     67    SVec3 vRes;
     68
     69    vRes.x = v1.x - v2.x;
     70    vRes.y = v1.y - v2.y;
     71    vRes.z = v1.z - v2.z;
     72
     73    return vRes;
     74}
     75
     76static SVec3        vscale(const float fS, const SVec3 v)
     77{
     78    SVec3 vRes;
     79
     80    vRes.x = fS * v.x;
     81    vRes.y = fS * v.y;
     82    vRes.z = fS * v.z;
     83
     84    return vRes;
     85}
     86
     87static float            LengthSquared( const SVec3 v )
     88{
     89    return v.x*v.x + v.y*v.y + v.z*v.z;
     90}
     91
     92static float            Length( const SVec3 v )
     93{
     94    return sqrtf(LengthSquared(v));
     95}
     96
     97static SVec3        Normalize( const SVec3 v )
     98{
     99    return vscale(1 / Length(v), v);
     100}
     101
     102static float        vdot( const SVec3 v1, const SVec3 v2)
     103{
     104    return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
     105}
     106
     107
     108static tbool NotZero(const float fX)
     109{
     110    // could possibly use FLT_EPSILON instead
     111    return fabsf(fX) > FLT_MIN;
     112}
     113
     114static tbool VNotZero(const SVec3 v)
     115{
     116    // might change this to an epsilon based test
     117    return NotZero(v.x) || NotZero(v.y) || NotZero(v.z);
     118}
     119
     120
     121
     122typedef struct
     123{
     124    int iNrFaces;
     125    int * pTriMembers;
     126} SSubGroup;
     127
     128typedef struct
     129{
     130    int iNrFaces;
     131    int * pFaceIndices;
     132    int iVertexRepresentitive;
     133    tbool bOrientPreservering;
     134} SGroup;
     135
     136//
     137#define MARK_DEGENERATE             1
     138#define QUAD_ONE_DEGEN_TRI          2
     139#define GROUP_WITH_ANY              4
     140#define ORIENT_PRESERVING           8
     141
     142
     143
     144typedef struct
     145{
     146    int FaceNeighbors[3];
     147    SGroup * AssignedGroup[3];
     148   
     149    // normalized first order face derivatives
     150    SVec3 vOs, vOt;
     151    float fMagS, fMagT; // original magnitudes
     152
     153    // determines if the current and the next triangle are a quad.
     154    int iOrgFaceNumber;
     155    int iFlag, iTSpacesOffs;
     156    unsigned char vert_num[4];
     157} STriInfo;
     158
     159typedef struct
     160{
     161    SVec3 vOs;
     162    float fMagS;
     163    SVec3 vOt;
     164    float fMagT;
     165    int iCounter;   // this is to average back into quads.
     166    tbool bOrient;
     167} STSpace;
     168
     169static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);
     170static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);
     171static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);
     172static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn);
     173static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[],
     174                             const int iNrActiveGroups, const int piTriListIn[], const float fThresCos,
     175                             const SMikkTSpaceContext * pContext);
     176
     177static int MakeIndex(const int iFace, const int iVert)
     178{
     179    assert(iVert>=0 && iVert<4 && iFace>=0);
     180    return (iFace<<2) | (iVert&0x3);
     181}
     182
     183static void IndexToData(int * piFace, int * piVert, const int iIndexIn)
     184{
     185    piVert[0] = iIndexIn&0x3;
     186    piFace[0] = iIndexIn>>2;
     187}
     188
     189static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1)
     190{
     191    STSpace ts_res;
     192
     193    // this if is important. Due to floating point precision
     194    // averaging when ts0==ts1 will cause a slight difference
     195    // which results in tangent space splits later on
     196    if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT &&
     197       veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt))
     198    {
     199        ts_res.fMagS = pTS0->fMagS;
     200        ts_res.fMagT = pTS0->fMagT;
     201        ts_res.vOs = pTS0->vOs;
     202        ts_res.vOt = pTS0->vOt;
     203    }
     204    else
     205    {
     206        ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS);
     207        ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT);
     208        ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs);
     209        ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt);
     210        if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs);
     211        if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt);
     212    }
     213
     214    return ts_res;
     215}
     216
     217
     218
     219static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index);
     220static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index);
     221static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index);
     222
     223
     224// degen triangles
     225static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris);
     226static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris);
     227
     228
     229tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext)
     230{
     231    return genTangSpace(pContext, 180.0f);
     232}
     233
     234tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold)
     235{
     236    // count nr_triangles
     237    int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL;
     238    STriInfo * pTriInfos = NULL;
     239    SGroup * pGroups = NULL;
     240    STSpace * psTspace = NULL;
     241    int iNrTrianglesIn = 0, f=0, t=0, i=0;
     242    int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0;
     243    int iNrActiveGroups = 0, index = 0;
     244    const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext);
     245    tbool bRes = TFALSE;
     246    const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f);
     247
     248    // verify all call-backs have been set
     249    if ( pContext->m_pInterface->m_getNumFaces==NULL ||
     250        pContext->m_pInterface->m_getNumVerticesOfFace==NULL ||
     251        pContext->m_pInterface->m_getPosition==NULL ||
     252        pContext->m_pInterface->m_getNormal==NULL ||
     253        pContext->m_pInterface->m_getTexCoord==NULL )
     254        return TFALSE;
     255
     256    // count triangles on supported faces
     257    for (f=0; f<iNrFaces; f++)
     258    {
     259        const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);
     260        if (verts==3) ++iNrTrianglesIn;
     261        else if(verts==4) iNrTrianglesIn += 2;
     262    }
     263    if (iNrTrianglesIn<=0) return TFALSE;
     264
     265    // allocate memory for an index list
     266    piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn);
     267    pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn);
     268    if (piTriListIn==NULL || pTriInfos==NULL)
     269    {
     270        if (piTriListIn!=NULL) free(piTriListIn);
     271        if (pTriInfos!=NULL) free(pTriInfos);
     272        return TFALSE;
     273    }
     274
     275    // make an initial triangle --> face index list
     276    iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
     277
     278    // make a welded index list of identical positions and attributes (pos, norm, texc)
     279    //printf("gen welded index list begin\n");
     280    GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn);
     281    //printf("gen welded index list end\n");
     282
     283    // Mark all degenerate triangles
     284    iTotTris = iNrTrianglesIn;
     285    iDegenTriangles = 0;
     286    for (t=0; t<iTotTris; t++)
     287    {
     288        const int i0 = piTriListIn[t*3+0];
     289        const int i1 = piTriListIn[t*3+1];
     290        const int i2 = piTriListIn[t*3+2];
     291        const SVec3 p0 = GetPosition(pContext, i0);
     292        const SVec3 p1 = GetPosition(pContext, i1);
     293        const SVec3 p2 = GetPosition(pContext, i2);
     294        if (veq(p0,p1) || veq(p0,p2) || veq(p1,p2)) // degenerate
     295        {
     296            pTriInfos[t].iFlag |= MARK_DEGENERATE;
     297            ++iDegenTriangles;
     298        }
     299    }
     300    iNrTrianglesIn = iTotTris - iDegenTriangles;
     301
     302    // mark all triangle pairs that belong to a quad with only one
     303    // good triangle. These need special treatment in DegenEpilogue().
     304    // Additionally, move all good triangles to the start of
     305    // pTriInfos[] and piTriListIn[] without changing order and
     306    // put the degenerate triangles last.
     307    DegenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);
     308
     309   
     310    // evaluate triangle level attributes and neighbor list
     311    //printf("gen neighbors list begin\n");
     312    InitTriInfo(pTriInfos, piTriListIn, pContext, iNrTrianglesIn);
     313    //printf("gen neighbors list end\n");
     314
     315   
     316    // based on the 4 rules, identify groups based on connectivity
     317    iNrMaxGroups = iNrTrianglesIn*3;
     318    pGroups = (SGroup *) malloc(sizeof(SGroup)*iNrMaxGroups);
     319    piGroupTrianglesBuffer = (int *) malloc(sizeof(int)*iNrTrianglesIn*3);
     320    if (pGroups==NULL || piGroupTrianglesBuffer==NULL)
     321    {
     322        if (pGroups!=NULL) free(pGroups);
     323        if (piGroupTrianglesBuffer!=NULL) free(piGroupTrianglesBuffer);
     324        free(piTriListIn);
     325        free(pTriInfos);
     326        return TFALSE;
     327    }
     328    //printf("gen 4rule groups begin\n");
     329    iNrActiveGroups =
     330        Build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn);
     331    //printf("gen 4rule groups end\n");
     332
     333    //
     334
     335    psTspace = (STSpace *) malloc(sizeof(STSpace)*iNrTSPaces);
     336    if (psTspace==NULL)
     337    {
     338        free(piTriListIn);
     339        free(pTriInfos);
     340        free(pGroups);
     341        free(piGroupTrianglesBuffer);
     342        return TFALSE;
     343    }
     344    memset(psTspace, 0, sizeof(STSpace)*iNrTSPaces);
     345    for (t=0; t<iNrTSPaces; t++)
     346    {
     347        psTspace[t].vOs.x=1.0f; psTspace[t].vOs.y=0.0f; psTspace[t].vOs.z=0.0f; psTspace[t].fMagS = 1.0f;
     348        psTspace[t].vOt.x=0.0f; psTspace[t].vOt.y=1.0f; psTspace[t].vOt.z=0.0f; psTspace[t].fMagT = 1.0f;
     349    }
     350
     351    // make tspaces, each group is split up into subgroups if necessary
     352    // based on fAngularThreshold. Finally a tangent space is made for
     353    // every resulting subgroup
     354    //printf("gen tspaces begin\n");
     355    bRes = GenerateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, pContext);
     356    //printf("gen tspaces end\n");
     357   
     358    // clean up
     359    free(pGroups);
     360    free(piGroupTrianglesBuffer);
     361
     362    if (!bRes)  // if an allocation in GenerateTSpaces() failed
     363    {
     364        // clean up and return false
     365        free(pTriInfos); free(piTriListIn); free(psTspace);
     366        return TFALSE;
     367    }
     368
     369
     370    // degenerate quads with one good triangle will be fixed by copying a space from
     371    // the good triangle to the coinciding vertex.
     372    // all other degenerate triangles will just copy a space from any good triangle
     373    // with the same welded index in piTriListIn[].
     374    DegenEpilogue(psTspace, pTriInfos, piTriListIn, pContext, iNrTrianglesIn, iTotTris);
     375
     376    free(pTriInfos); free(piTriListIn);
     377
     378    index = 0;
     379    for (f=0; f<iNrFaces; f++)
     380    {
     381        const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);
     382        if (verts!=3 && verts!=4) continue;
     383       
     384
     385        // I've decided to let degenerate triangles and group-with-anythings
     386        // vary between left/right hand coordinate systems at the vertices.
     387        // All healthy triangles on the other hand are built to always be either or.
     388
     389        /*// force the coordinate system orientation to be uniform for every face.
     390        // (this is already the case for good triangles but not for
     391        // degenerate ones and those with bGroupWithAnything==true)
     392        bool bOrient = psTspace[index].bOrient;
     393        if (psTspace[index].iCounter == 0)  // tspace was not derived from a group
     394        {
     395            // look for a space created in GenerateTSpaces() by iCounter>0
     396            bool bNotFound = true;
     397            int i=1;
     398            while (i<verts && bNotFound)
     399            {
     400                if (psTspace[index+i].iCounter > 0) bNotFound=false;
     401                else ++i;
     402            }
     403            if (!bNotFound) bOrient = psTspace[index+i].bOrient;
     404        }*/
     405
     406        // set data
     407        for (i=0; i<verts; i++)
     408        {
     409            const STSpace * pTSpace = &psTspace[index];
     410            float tang[] = {pTSpace->vOs.x, pTSpace->vOs.y, pTSpace->vOs.z};
     411            float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z};
     412            if (pContext->m_pInterface->m_setTSpace!=NULL)
     413                pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i);
     414            if (pContext->m_pInterface->m_setTSpaceBasic!=NULL)
     415                pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i);
     416
     417            ++index;
     418        }
     419    }
     420
     421    free(psTspace);
     422
     423   
     424    return TTRUE;
     425}
     426
     427///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     428
     429typedef struct
     430{
     431    float vert[3];
     432    int index;
     433} STmpVert;
     434
     435const int g_iCells = 2048;
     436
     437#ifdef _MSC_VER
     438    #define NOINLINE __declspec(noinline)
     439#else
     440    #define NOINLINE __attribute__ ((noinline))
     441#endif
     442
     443// it is IMPORTANT that this function is called to evaluate the hash since
     444// inlining could potentially reorder instructions and generate different
     445// results for the same effective input value fVal.
     446NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal)
     447{
     448    const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin));
     449    const int iIndex = fIndex<0?0:((int)fIndex);
     450    return iIndex<g_iCells?iIndex:(g_iCells-1);
     451}
     452
     453static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in);
     454static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries);
     455static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn);
     456
     457static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)
     458{
     459
     460    // Generate bounding box
     461    int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL;
     462    STmpVert * pTmpVert = NULL;
     463    int i=0, iChannel=0, k=0, e=0;
     464    int iMaxCount=0;
     465    SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim;
     466    float fMin, fMax;
     467    for (i=1; i<(iNrTrianglesIn*3); i++)
     468    {
     469        const int index = piTriList_in_and_out[i];
     470
     471        const SVec3 vP = GetPosition(pContext, index);
     472        if (vMin.x > vP.x) vMin.x = vP.x;
     473        else if(vMax.x < vP.x) vMax.x = vP.x;
     474        if (vMin.y > vP.y) vMin.y = vP.y;
     475        else if(vMax.y < vP.y) vMax.y = vP.y;
     476        if (vMin.z > vP.z) vMin.z = vP.z;
     477        else if(vMax.z < vP.z) vMax.z = vP.z;
     478    }
     479
     480    vDim = vsub(vMax,vMin);
     481    iChannel = 0;
     482    fMin = vMin.x; fMax=vMax.x;
     483    if (vDim.y>vDim.x && vDim.y>vDim.z)
     484    {
     485        iChannel=1;
     486        fMin = vMin.y, fMax=vMax.y;
     487    }
     488    else if(vDim.z>vDim.x)
     489    {
     490        iChannel=2;
     491        fMin = vMin.z, fMax=vMax.z;
     492    }
     493
     494    // make allocations
     495    piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3);
     496    piHashCount = (int *) malloc(sizeof(int)*g_iCells);
     497    piHashOffsets = (int *) malloc(sizeof(int)*g_iCells);
     498    piHashCount2 = (int *) malloc(sizeof(int)*g_iCells);
     499
     500    if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL)
     501    {
     502        if (piHashTable!=NULL) free(piHashTable);
     503        if (piHashCount!=NULL) free(piHashCount);
     504        if (piHashOffsets!=NULL) free(piHashOffsets);
     505        if (piHashCount2!=NULL) free(piHashCount2);
     506        GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn);
     507        return;
     508    }
     509    memset(piHashCount, 0, sizeof(int)*g_iCells);
     510    memset(piHashCount2, 0, sizeof(int)*g_iCells);
     511
     512    // count amount of elements in each cell unit
     513    for (i=0; i<(iNrTrianglesIn*3); i++)
     514    {
     515        const int index = piTriList_in_and_out[i];
     516        const SVec3 vP = GetPosition(pContext, index);
     517        const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z);
     518        const int iCell = FindGridCell(fMin, fMax, fVal);
     519        ++piHashCount[iCell];
     520    }
     521
     522    // evaluate start index of each cell.
     523    piHashOffsets[0]=0;
     524    for (k=1; k<g_iCells; k++)
     525        piHashOffsets[k]=piHashOffsets[k-1]+piHashCount[k-1];
     526
     527    // insert vertices
     528    for (i=0; i<(iNrTrianglesIn*3); i++)
     529    {
     530        const int index = piTriList_in_and_out[i];
     531        const SVec3 vP = GetPosition(pContext, index);
     532        const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z);
     533        const int iCell = FindGridCell(fMin, fMax, fVal);
     534        int * pTable = NULL;
     535
     536        assert(piHashCount2[iCell]<piHashCount[iCell]);
     537        pTable = &piHashTable[piHashOffsets[iCell]];
     538        pTable[piHashCount2[iCell]] = i;    // vertex i has been inserted.
     539        ++piHashCount2[iCell];
     540    }
     541    for (k=0; k<g_iCells; k++)
     542        assert(piHashCount2[k] == piHashCount[k]);  // verify the count
     543    free(piHashCount2);
     544
     545    // find maximum amount of entries in any hash entry
     546    iMaxCount = piHashCount[0];
     547    for (k=1; k<g_iCells; k++)
     548        if (iMaxCount<piHashCount[k])
     549            iMaxCount=piHashCount[k];
     550    pTmpVert = (STmpVert *) malloc(sizeof(STmpVert)*iMaxCount);
     551   
     552
     553    // complete the merge
     554    for (k=0; k<g_iCells; k++)
     555    {
     556        // extract table of cell k and amount of entries in it
     557        int * pTable = &piHashTable[piHashOffsets[k]];
     558        const int iEntries = piHashCount[k];
     559        if (iEntries < 2) continue;
     560
     561        if (pTmpVert!=NULL)
     562        {
     563            for (e=0; e<iEntries; e++)
     564            {
     565                int i = pTable[e];
     566                const SVec3 vP = GetPosition(pContext, piTriList_in_and_out[i]);
     567                pTmpVert[e].vert[0] = vP.x; pTmpVert[e].vert[1] = vP.y;
     568                pTmpVert[e].vert[2] = vP.z; pTmpVert[e].index = i;
     569            }
     570            MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, 0, iEntries-1);
     571        }
     572        else
     573            MergeVertsSlow(piTriList_in_and_out, pContext, pTable, iEntries);
     574    }
     575
     576    if (pTmpVert!=NULL) { free(pTmpVert); }
     577    free(piHashTable);
     578    free(piHashCount);
     579    free(piHashOffsets);
     580}
     581
     582static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in)
     583{
     584    // make bbox
     585    int c=0, l=0, channel=0;
     586    float fvMin[3], fvMax[3];
     587    float dx=0, dy=0, dz=0, fSep=0;
     588    for (c=0; c<3; c++)
     589    {   fvMin[c]=pTmpVert[iL_in].vert[c]; fvMax[c]=fvMin[c];    }
     590    for (l=(iL_in+1); l<=iR_in; l++)
     591        for (c=0; c<3; c++)
     592            if (fvMin[c]>pTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c];
     593            else if(fvMax[c]<pTmpVert[l].vert[c]) fvMax[c]=pTmpVert[l].vert[c];
     594
     595    dx = fvMax[0]-fvMin[0];
     596    dy = fvMax[1]-fvMin[1];
     597    dz = fvMax[2]-fvMin[2];
     598
     599    channel = 0;
     600    if (dy>dx && dy>dz) channel=1;
     601    else if(dz>dx) channel=2;
     602
     603    fSep = 0.5f*(fvMax[channel]+fvMin[channel]);
     604
     605    // terminate recursion when the separation/average value
     606    // is no longer strictly between fMin and fMax values.
     607    if (fSep>=fvMax[channel] || fSep<=fvMin[channel])
     608    {
     609        // complete the weld
     610        for (l=iL_in; l<=iR_in; l++)
     611        {
     612            int i = pTmpVert[l].index;
     613            const int index = piTriList_in_and_out[i];
     614            const SVec3 vP = GetPosition(pContext, index);
     615            const SVec3 vN = GetNormal(pContext, index);
     616            const SVec3 vT = GetTexCoord(pContext, index);
     617
     618            tbool bNotFound = TTRUE;
     619            int l2=iL_in, i2rec=-1;
     620            while (l2<l && bNotFound)
     621            {
     622                const int i2 = pTmpVert[l2].index;
     623                const int index2 = piTriList_in_and_out[i2];
     624                const SVec3 vP2 = GetPosition(pContext, index2);
     625                const SVec3 vN2 = GetNormal(pContext, index2);
     626                const SVec3 vT2 = GetTexCoord(pContext, index2);
     627                i2rec=i2;
     628
     629                //if(vP==vP2 && vN==vN2 && vT==vT2)
     630                if (vP.x==vP2.x && vP.y==vP2.y && vP.z==vP2.z &&
     631                    vN.x==vN2.x && vN.y==vN2.y && vN.z==vN2.z &&
     632                    vT.x==vT2.x && vT.y==vT2.y && vT.z==vT2.z)
     633                    bNotFound = TFALSE;
     634                else
     635                    ++l2;
     636            }
     637           
     638            // merge if previously found
     639            if (!bNotFound)
     640                piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
     641        }
     642    }
     643    else
     644    {
     645        int iL=iL_in, iR=iR_in;
     646        assert((iR_in-iL_in)>0);    // at least 2 entries
     647
     648        // separate (by fSep) all points between iL_in and iR_in in pTmpVert[]
     649        while (iL < iR)
     650        {
     651            tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE;
     652            while ((!bReadyLeftSwap) && iL<iR)
     653            {
     654                assert(iL>=iL_in && iL<=iR_in);
     655                bReadyLeftSwap = !(pTmpVert[iL].vert[channel]<fSep);
     656                if (!bReadyLeftSwap) ++iL;
     657            }
     658            while ((!bReadyRightSwap) && iL<iR)
     659            {
     660                assert(iR>=iL_in && iR<=iR_in);
     661                bReadyRightSwap = pTmpVert[iR].vert[channel]<fSep;
     662                if (!bReadyRightSwap) --iR;
     663            }
     664            assert( (iL<iR) || !(bReadyLeftSwap && bReadyRightSwap) );
     665
     666            if (bReadyLeftSwap && bReadyRightSwap)
     667            {
     668                const STmpVert sTmp = pTmpVert[iL];
     669                assert(iL<iR);
     670                pTmpVert[iL] = pTmpVert[iR];
     671                pTmpVert[iR] = sTmp;
     672                ++iL; --iR;
     673            }
     674        }
     675
     676        assert(iL==(iR+1) || (iL==iR));
     677        if (iL==iR)
     678        {
     679            const tbool bReadyRightSwap = pTmpVert[iR].vert[channel]<fSep;
     680            if (bReadyRightSwap) ++iL;
     681            else --iR;
     682        }
     683
     684        // only need to weld when there is more than 1 instance of the (x,y,z)
     685        if (iL_in < iR)
     686            MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL_in, iR);    // weld all left of fSep
     687        if (iL < iR_in)
     688            MergeVertsFast(piTriList_in_and_out, pTmpVert, pContext, iL, iR_in);    // weld all right of (or equal to) fSep
     689    }
     690}
     691
     692static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries)
     693{
     694    // this can be optimized further using a tree structure or more hashing.
     695    int e=0;
     696    for (e=0; e<iEntries; e++)
     697    {
     698        int i = pTable[e];
     699        const int index = piTriList_in_and_out[i];
     700        const SVec3 vP = GetPosition(pContext, index);
     701        const SVec3 vN = GetNormal(pContext, index);
     702        const SVec3 vT = GetTexCoord(pContext, index);
     703
     704        tbool bNotFound = TTRUE;
     705        int e2=0, i2rec=-1;
     706        while (e2<e && bNotFound)
     707        {
     708            const int i2 = pTable[e2];
     709            const int index2 = piTriList_in_and_out[i2];
     710            const SVec3 vP2 = GetPosition(pContext, index2);
     711            const SVec3 vN2 = GetNormal(pContext, index2);
     712            const SVec3 vT2 = GetTexCoord(pContext, index2);
     713            i2rec = i2;
     714
     715            if (veq(vP,vP2) && veq(vN,vN2) && veq(vT,vT2))
     716                bNotFound = TFALSE;
     717            else
     718                ++e2;
     719        }
     720       
     721        // merge if previously found
     722        if (!bNotFound)
     723            piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
     724    }
     725}
     726
     727static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)
     728{
     729    int iNumUniqueVerts = 0, t=0, i=0;
     730    for (t=0; t<iNrTrianglesIn; t++)
     731    {
     732        for (i=0; i<3; i++)
     733        {
     734            const int offs = t*3 + i;
     735            const int index = piTriList_in_and_out[offs];
     736
     737            const SVec3 vP = GetPosition(pContext, index);
     738            const SVec3 vN = GetNormal(pContext, index);
     739            const SVec3 vT = GetTexCoord(pContext, index);
     740
     741            tbool bFound = TFALSE;
     742            int t2=0, index2rec=-1;
     743            while (!bFound && t2<=t)
     744            {
     745                int j=0;
     746                while (!bFound && j<3)
     747                {
     748                    const int index2 = piTriList_in_and_out[t2*3 + j];
     749                    const SVec3 vP2 = GetPosition(pContext, index2);
     750                    const SVec3 vN2 = GetNormal(pContext, index2);
     751                    const SVec3 vT2 = GetTexCoord(pContext, index2);
     752                   
     753                    if (veq(vP,vP2) && veq(vN,vN2) && veq(vT,vT2))
     754                        bFound = TTRUE;
     755                    else
     756                        ++j;
     757                }
     758                if (!bFound) ++t2;
     759            }
     760
     761            assert(bFound);
     762            // if we found our own
     763            if (index2rec == index) { ++iNumUniqueVerts; }
     764
     765            piTriList_in_and_out[offs] = index2rec;
     766        }
     767    }
     768}
     769
     770static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)
     771{
     772    int iTSpacesOffs = 0, f=0, t=0;
     773    int iDstTriIndex = 0;
     774    for (f=0; f<pContext->m_pInterface->m_getNumFaces(pContext); f++)
     775    {
     776        const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f);
     777        if (verts!=3 && verts!=4) continue;
     778
     779        pTriInfos[iDstTriIndex].iOrgFaceNumber = f;
     780        pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs;
     781
     782        if (verts==3)
     783        {
     784            unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num;
     785            pVerts[0]=0; pVerts[1]=1; pVerts[2]=2;
     786            piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0);
     787            piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1);
     788            piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2);
     789            ++iDstTriIndex; // next
     790        }
     791        else
     792        {
     793            {
     794                pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f;
     795                pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs;
     796            }
     797
     798            {
     799                // need an order independent way to evaluate
     800                // tspace on quads. This is done by splitting
     801                // along the shortest diagonal.
     802                const int i0 = MakeIndex(f, 0);
     803                const int i1 = MakeIndex(f, 1);
     804                const int i2 = MakeIndex(f, 2);
     805                const int i3 = MakeIndex(f, 3);
     806                const SVec3 T0 = GetTexCoord(pContext, i0);
     807                const SVec3 T1 = GetTexCoord(pContext, i1);
     808                const SVec3 T2 = GetTexCoord(pContext, i2);
     809                const SVec3 T3 = GetTexCoord(pContext, i3);
     810                const float distSQ_02 = LengthSquared(vsub(T2,T0));
     811                const float distSQ_13 = LengthSquared(vsub(T3,T1));
     812                tbool bQuadDiagIs_02;
     813                if (distSQ_02<distSQ_13)
     814                    bQuadDiagIs_02 = TTRUE;
     815                else if(distSQ_13<distSQ_02)
     816                    bQuadDiagIs_02 = TFALSE;
     817                else
     818                {
     819                    const SVec3 P0 = GetPosition(pContext, i0);
     820                    const SVec3 P1 = GetPosition(pContext, i1);
     821                    const SVec3 P2 = GetPosition(pContext, i2);
     822                    const SVec3 P3 = GetPosition(pContext, i3);
     823                    const float distSQ_02 = LengthSquared(vsub(P2,P0));
     824                    const float distSQ_13 = LengthSquared(vsub(P3,P1));
     825
     826                    bQuadDiagIs_02 = distSQ_13<distSQ_02 ? TFALSE : TTRUE;
     827                }
     828
     829                if (bQuadDiagIs_02)
     830                {
     831                    {
     832                        unsigned char * pVerts_A = pTriInfos[iDstTriIndex].vert_num;
     833                        pVerts_A[0]=0; pVerts_A[1]=1; pVerts_A[2]=2;
     834                    }
     835                    piTriList_out[iDstTriIndex*3+0] = i0;
     836                    piTriList_out[iDstTriIndex*3+1] = i1;
     837                    piTriList_out[iDstTriIndex*3+2] = i2;
     838                    ++iDstTriIndex; // next
     839                    {
     840                        unsigned char * pVerts_B = pTriInfos[iDstTriIndex].vert_num;
     841                        pVerts_B[0]=0; pVerts_B[1]=2; pVerts_B[2]=3;
     842                    }
     843                    piTriList_out[iDstTriIndex*3+0] = i0;
     844                    piTriList_out[iDstTriIndex*3+1] = i2;
     845                    piTriList_out[iDstTriIndex*3+2] = i3;
     846                    ++iDstTriIndex; // next
     847                }
     848                else
     849                {
     850                    {
     851                        unsigned char * pVerts_A = pTriInfos[iDstTriIndex].vert_num;
     852                        pVerts_A[0]=0; pVerts_A[1]=1; pVerts_A[2]=3;
     853                    }
     854                    piTriList_out[iDstTriIndex*3+0] = i0;
     855                    piTriList_out[iDstTriIndex*3+1] = i1;
     856                    piTriList_out[iDstTriIndex*3+2] = i3;
     857                    ++iDstTriIndex; // next
     858                    {
     859                        unsigned char * pVerts_B = pTriInfos[iDstTriIndex].vert_num;
     860                        pVerts_B[0]=1; pVerts_B[1]=2; pVerts_B[2]=3;
     861                    }
     862                    piTriList_out[iDstTriIndex*3+0] = i1;
     863                    piTriList_out[iDstTriIndex*3+1] = i2;
     864                    piTriList_out[iDstTriIndex*3+2] = i3;
     865                    ++iDstTriIndex; // next
     866                }
     867            }
     868        }
     869
     870        iTSpacesOffs += verts;
     871        assert(iDstTriIndex<=iNrTrianglesIn);
     872    }
     873
     874    for (t=0; t<iNrTrianglesIn; t++)
     875        pTriInfos[t].iFlag = 0;
     876
     877    // return total amount of tspaces
     878    return iTSpacesOffs;
     879}
     880
     881static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index)
     882{
     883    int iF, iI;
     884    SVec3 res; float pos[3];
     885    IndexToData(&iF, &iI, index);
     886    pContext->m_pInterface->m_getPosition(pContext, pos, iF, iI);
     887    res.x=pos[0]; res.y=pos[1]; res.z=pos[2];
     888    return res;
     889}
     890
     891static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index)
     892{
     893    int iF, iI;
     894    SVec3 res; float norm[3];
     895    IndexToData(&iF, &iI, index);
     896    pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI);
     897    res.x=norm[0]; res.y=norm[1]; res.z=norm[2];
     898    return res;
     899}
     900
     901static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index)
     902{
     903    int iF, iI;
     904    SVec3 res; float texc[2];
     905    IndexToData(&iF, &iI, index);
     906    pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI);
     907    res.x=texc[0]; res.y=texc[1]; res.z=1.0f;
     908    return res;
     909}
     910
     911/////////////////////////////////////////////////////////////////////////////////////////////////////
     912/////////////////////////////////////////////////////////////////////////////////////////////////////
     913
     914typedef union
     915{
     916    struct
     917    {
     918        int i0, i1, f;
     919    };
     920    int array[3];
     921} SEdge;
     922
     923static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn);
     924static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn);
     925
     926// returns the texture area times 2
     927static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[])
     928{
     929    const SVec3 t1 = GetTexCoord(pContext, indices[0]);
     930    const SVec3 t2 = GetTexCoord(pContext, indices[1]);
     931    const SVec3 t3 = GetTexCoord(pContext, indices[2]);
     932
     933    const float t21x = t2.x-t1.x;
     934    const float t21y = t2.y-t1.y;
     935    const float t31x = t3.x-t1.x;
     936    const float t31y = t3.y-t1.y;
     937
     938    const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x;
     939
     940    return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2;
     941}
     942
     943static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn)
     944{
     945    int f=0, i=0, t=0;
     946    // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function.
     947
     948    // generate neighbor info list
     949    for (f=0; f<iNrTrianglesIn; f++)
     950        for (i=0; i<3; i++)
     951        {
     952            pTriInfos[f].FaceNeighbors[i] = -1;
     953            pTriInfos[f].AssignedGroup[i] = NULL;
     954
     955            pTriInfos[f].vOs.x=0.0f; pTriInfos[f].vOs.y=0.0f; pTriInfos[f].vOs.z=0.0f;
     956            pTriInfos[f].vOt.x=0.0f; pTriInfos[f].vOt.y=0.0f; pTriInfos[f].vOt.z=0.0f;
     957            pTriInfos[f].fMagS = 0;
     958            pTriInfos[f].fMagT = 0;
     959
     960            // assumed bad
     961            pTriInfos[f].iFlag |= GROUP_WITH_ANY;
     962        }
     963
     964    // evaluate first order derivatives
     965    for (f=0; f<iNrTrianglesIn; f++)
     966    {
     967        // initial values
     968        const SVec3 v1 = GetPosition(pContext, piTriListIn[f*3+0]);
     969        const SVec3 v2 = GetPosition(pContext, piTriListIn[f*3+1]);
     970        const SVec3 v3 = GetPosition(pContext, piTriListIn[f*3+2]);
     971        const SVec3 t1 = GetTexCoord(pContext, piTriListIn[f*3+0]);
     972        const SVec3 t2 = GetTexCoord(pContext, piTriListIn[f*3+1]);
     973        const SVec3 t3 = GetTexCoord(pContext, piTriListIn[f*3+2]);
     974
     975        const float t21x = t2.x-t1.x;
     976        const float t21y = t2.y-t1.y;
     977        const float t31x = t3.x-t1.x;
     978        const float t31y = t3.y-t1.y;
     979        const SVec3 d1 = vsub(v2,v1);
     980        const SVec3 d2 = vsub(v3,v1);
     981
     982        const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x;
     983        //assert(fSignedAreaSTx2!=0);
     984        SVec3 vOs = vsub(vscale(t31y,d1), vscale(t21y,d2)); // eq 18
     985        SVec3 vOt = vadd(vscale(-t31x,d1), vscale(t21x,d2)); // eq 19
     986
     987        pTriInfos[f].iFlag |= (fSignedAreaSTx2>0 ? ORIENT_PRESERVING : 0);
     988
     989        if ( NotZero(fSignedAreaSTx2) )
     990        {
     991            const float fAbsArea = fabsf(fSignedAreaSTx2);
     992            const float fLenOs = Length(vOs);
     993            const float fLenOt = Length(vOt);
     994            const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f;
     995            if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs);
     996            if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt);
     997
     998            // evaluate magnitudes prior to normalization of vOs and vOt
     999            pTriInfos[f].fMagS = fLenOs / fAbsArea;
     1000            pTriInfos[f].fMagT = fLenOt / fAbsArea;
     1001
     1002            // if this is a good triangle
     1003            if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT))
     1004                pTriInfos[f].iFlag &= (~GROUP_WITH_ANY);
     1005        }
     1006    }
     1007
     1008    // force otherwise healthy quads to a fixed orientation
     1009    while (t<(iNrTrianglesIn-1))
     1010    {
     1011        const int iFO_a = pTriInfos[t].iOrgFaceNumber;
     1012        const int iFO_b = pTriInfos[t+1].iOrgFaceNumber;
     1013        if (iFO_a==iFO_b)   // this is a quad
     1014        {
     1015            const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;
     1016            const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;
     1017           
     1018            // bad triangles should already have been removed by
     1019            // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false
     1020            if ((bIsDeg_a||bIsDeg_b)==TFALSE)
     1021            {
     1022                const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1023                const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1024                // if this happens the quad has extremely bad mapping!!
     1025                if (bOrientA!=bOrientB)
     1026                {
     1027                    //printf("found quad with bad mapping\n");
     1028                    tbool bChooseOrientFirstTri = TFALSE;
     1029                    if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE;
     1030                    else if( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) )
     1031                        bChooseOrientFirstTri = TTRUE;
     1032
     1033                    // force match
     1034                    {
     1035                        const int t0 = bChooseOrientFirstTri ? t : (t+1);
     1036                        const int t1 = bChooseOrientFirstTri ? (t+1) : t;
     1037                        pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING);    // clear first
     1038                        pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit
     1039                    }
     1040                }
     1041            }
     1042            t += 2;
     1043        }
     1044        else
     1045            ++t;
     1046    }
     1047   
     1048    // match up edge pairs
     1049    {
     1050        SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3);
     1051        if (pEdges==NULL)
     1052            BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn);
     1053        else
     1054        {
     1055            BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn);
     1056   
     1057            free(pEdges);
     1058        }
     1059    }
     1060}
     1061
     1062/////////////////////////////////////////////////////////////////////////////////////////////////////
     1063/////////////////////////////////////////////////////////////////////////////////////////////////////
     1064
     1065static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup);
     1066static void AddTriToGroup(SGroup * pGroup, const int iTriIndex);
     1067
     1068static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn)
     1069{
     1070    const int iNrMaxGroups = iNrTrianglesIn*3;
     1071    int iNrActiveGroups = 0;
     1072    int iOffset = 0, f=0, i=0;
     1073    for (f=0; f<iNrTrianglesIn; f++)
     1074    {
     1075        for (i=0; i<3; i++)
     1076        {
     1077            // if not assigned to a group
     1078            if ((pTriInfos[f].iFlag&GROUP_WITH_ANY)==0 && pTriInfos[f].AssignedGroup[i]==NULL)
     1079            {
     1080                tbool bOrPre;
     1081                int neigh_indexL, neigh_indexR;
     1082                const int vert_index = piTriListIn[f*3+i];
     1083                assert(iNrActiveGroups<iNrMaxGroups);
     1084                pTriInfos[f].AssignedGroup[i] = &pGroups[iNrActiveGroups];
     1085                pTriInfos[f].AssignedGroup[i]->iVertexRepresentitive = vert_index;
     1086                pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0;
     1087                pTriInfos[f].AssignedGroup[i]->iNrFaces = 0;
     1088                pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset];
     1089                ++iNrActiveGroups;
     1090
     1091                AddTriToGroup(pTriInfos[f].AssignedGroup[i], f);
     1092                bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1093                neigh_indexL = pTriInfos[f].FaceNeighbors[i];
     1094                neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2];
     1095                if (neigh_indexL>=0) // neighbor
     1096                {
     1097                    const tbool bAnswer =
     1098                        AssignRecur(piTriListIn, pTriInfos, neigh_indexL,
     1099                                    pTriInfos[f].AssignedGroup[i] );
     1100                   
     1101                    const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1102                    const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE;
     1103                    assert(bAnswer || bDiff);
     1104                }
     1105                if (neigh_indexR>=0) // neighbor
     1106                {
     1107                    const tbool bAnswer =
     1108                        AssignRecur(piTriListIn, pTriInfos, neigh_indexR,
     1109                                    pTriInfos[f].AssignedGroup[i] );
     1110
     1111                    const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1112                    const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE;
     1113                    assert(bAnswer || bDiff);
     1114                }
     1115
     1116                // update offset
     1117                iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces;
     1118                // since the groups are disjoint a triangle can never
     1119                // belong to more than 3 groups. Subsequently something
     1120                // is completely screwed if this assertion ever hits.
     1121                assert(iOffset <= iNrMaxGroups);
     1122            }
     1123        }
     1124    }
     1125
     1126    return iNrActiveGroups;
     1127}
     1128
     1129static void AddTriToGroup(SGroup * pGroup, const int iTriIndex)
     1130{
     1131    pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex;
     1132    ++pGroup->iNrFaces;
     1133}
     1134
     1135static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[],
     1136                 const int iMyTriIndex, SGroup * pGroup)
     1137{
     1138    STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex];
     1139
     1140    // track down vertex
     1141    const int iVertRep = pGroup->iVertexRepresentitive;
     1142    const int * pVerts = &piTriListIn[3*iMyTriIndex+0];
     1143    int i=-1;
     1144    if (pVerts[0]==iVertRep) i=0;
     1145    else if(pVerts[1]==iVertRep) i=1;
     1146    else if(pVerts[2]==iVertRep) i=2;
     1147    assert(i>=0 && i<3);
     1148
     1149    // early out
     1150    if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE;
     1151    else if(pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE;
     1152    if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0)
     1153    {
     1154        // first to group with a group-with-anything triangle
     1155        // determines it's orientation.
     1156        // This is the only existing order dependency in the code!!
     1157        if ( pMyTriInfo->AssignedGroup[0] == NULL &&
     1158            pMyTriInfo->AssignedGroup[1] == NULL &&
     1159            pMyTriInfo->AssignedGroup[2] == NULL )
     1160        {
     1161            pMyTriInfo->iFlag &= (~ORIENT_PRESERVING);
     1162            pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0);
     1163        }
     1164    }
     1165    {
     1166        const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE;
     1167        if (bOrient != pGroup->bOrientPreservering) return TFALSE;
     1168    }
     1169
     1170    AddTriToGroup(pGroup, iMyTriIndex);
     1171    pMyTriInfo->AssignedGroup[i] = pGroup;
     1172
     1173    {
     1174        const int neigh_indexL = pMyTriInfo->FaceNeighbors[i];
     1175        const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2];
     1176        if (neigh_indexL>=0)
     1177            AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);
     1178        if (neigh_indexR>=0)
     1179            AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup);
     1180    }
     1181
     1182
     1183
     1184    return TTRUE;
     1185}
     1186
     1187/////////////////////////////////////////////////////////////////////////////////////////////////////
     1188/////////////////////////////////////////////////////////////////////////////////////////////////////
     1189
     1190static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2);
     1191static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed);
     1192static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive);
     1193
     1194static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[],
     1195                             const int iNrActiveGroups, const int piTriListIn[], const float fThresCos,
     1196                             const SMikkTSpaceContext * pContext)
     1197{
     1198    STSpace * pSubGroupTspace = NULL;
     1199    SSubGroup * pUniSubGroups = NULL;
     1200    int * pTmpMembers = NULL;
     1201    int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0;
     1202    for (g=0; g<iNrActiveGroups; g++)
     1203        if (iMaxNrFaces < pGroups[g].iNrFaces)
     1204            iMaxNrFaces = pGroups[g].iNrFaces;
     1205
     1206    if (iMaxNrFaces == 0) return TTRUE;
     1207
     1208    // make initial allocations
     1209    pSubGroupTspace = (STSpace *) malloc(sizeof(STSpace)*iMaxNrFaces);
     1210    pUniSubGroups = (SSubGroup *) malloc(sizeof(SSubGroup)*iMaxNrFaces);
     1211    pTmpMembers = (int *) malloc(sizeof(int)*iMaxNrFaces);
     1212    if (pSubGroupTspace==NULL || pUniSubGroups==NULL || pTmpMembers==NULL)
     1213    {
     1214        if (pSubGroupTspace!=NULL) free(pSubGroupTspace);
     1215        if (pUniSubGroups!=NULL) free(pUniSubGroups);
     1216        if (pTmpMembers!=NULL) free(pTmpMembers);
     1217        return TFALSE;
     1218    }
     1219
     1220
     1221    iUniqueTspaces = 0;
     1222    for (g=0; g<iNrActiveGroups; g++)
     1223    {
     1224        const SGroup * pGroup = &pGroups[g];
     1225        int iUniqueSubGroups = 0, s=0;
     1226
     1227        for (i=0; i<pGroup->iNrFaces; i++)  // triangles
     1228        {
     1229            const int f = pGroup->pFaceIndices[i];  // triangle number
     1230            int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0;
     1231            SSubGroup tmp_group;
     1232            tbool bFound;
     1233            SVec3 n, vOs, vOt;
     1234            if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0;
     1235            else if(pTriInfos[f].AssignedGroup[1]==pGroup) index=1;
     1236            else if(pTriInfos[f].AssignedGroup[2]==pGroup) index=2;
     1237            assert(index>=0 && index<3);
     1238
     1239            iVertIndex = piTriListIn[f*3+index];
     1240            assert(iVertIndex==pGroup->iVertexRepresentitive);
     1241
     1242            // is normalized already
     1243            n = GetNormal(pContext, iVertIndex);
     1244           
     1245            // project
     1246            vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n));
     1247            vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n));
     1248            if ( VNotZero(vOs) ) vOs = Normalize(vOs);
     1249            if ( VNotZero(vOt) ) vOt = Normalize(vOt);
     1250
     1251            // original face number
     1252            iOF_1 = pTriInfos[f].iOrgFaceNumber;
     1253           
     1254            iMembers = 0;
     1255            for (j=0; j<pGroup->iNrFaces; j++)
     1256            {
     1257                const int t = pGroup->pFaceIndices[j];  // triangle number
     1258                const int iOF_2 = pTriInfos[t].iOrgFaceNumber;
     1259
     1260                // project
     1261                SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n));
     1262                SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n));
     1263                if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2);
     1264                if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2);
     1265
     1266                {
     1267                    const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE;
     1268                    // make sure triangles which belong to the same quad are joined.
     1269                    const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE;
     1270
     1271                    const float fCosS = vdot(vOs,vOs2);
     1272                    const float fCosT = vdot(vOt,vOt2);
     1273
     1274                    assert(f!=t || bSameOrgFace);   // sanity check
     1275                    if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos))
     1276                        pTmpMembers[iMembers++] = t;
     1277                }
     1278            }
     1279
     1280            // sort pTmpMembers
     1281            tmp_group.iNrFaces = iMembers;
     1282            tmp_group.pTriMembers = pTmpMembers;
     1283            if (iMembers>1)
     1284            {
     1285                unsigned int uSeed = INTERNAL_RND_SORT_SEED;    // could replace with a random seed?
     1286                QuickSort(pTmpMembers, 0, iMembers-1, uSeed);
     1287            }
     1288
     1289            // look for an existing match
     1290            bFound = TFALSE;
     1291            l=0;
     1292            while (l<iUniqueSubGroups && !bFound)
     1293            {
     1294                bFound = CompareSubGroups(&tmp_group, &pUniSubGroups[l]);
     1295                if (!bFound) ++l;
     1296            }
     1297           
     1298            // assign tangent space index
     1299            assert(bFound || l==iUniqueSubGroups);
     1300            //piTempTangIndices[f*3+index] = iUniqueTspaces+l;
     1301
     1302            // if no match was found we allocate a new subgroup
     1303            if (!bFound)
     1304            {
     1305                // insert new subgroup
     1306                int * pIndices = (int *) malloc(sizeof(int)*iMembers);
     1307                if (pIndices==NULL)
     1308                {
     1309                    // clean up and return false
     1310                    int s=0;
     1311                    for (s=0; s<iUniqueSubGroups; s++)
     1312                        free(pUniSubGroups[s].pTriMembers);
     1313                    free(pUniSubGroups);
     1314                    free(pTmpMembers);
     1315                    free(pSubGroupTspace);
     1316                    return TFALSE;
     1317                }
     1318                pUniSubGroups[iUniqueSubGroups].iNrFaces = iMembers;
     1319                pUniSubGroups[iUniqueSubGroups].pTriMembers = pIndices;
     1320                memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int));
     1321                pSubGroupTspace[iUniqueSubGroups] =
     1322                    EvalTspace(tmp_group.pTriMembers, iMembers, piTriListIn, pTriInfos, pContext, pGroup->iVertexRepresentitive);
     1323                ++iUniqueSubGroups;
     1324            }
     1325
     1326            // output tspace
     1327            {
     1328                const int iOffs = pTriInfos[f].iTSpacesOffs;
     1329                const int iVert = pTriInfos[f].vert_num[index];
     1330                STSpace * pTS_out = &psTspace[iOffs+iVert];
     1331                assert(pTS_out->iCounter<2);
     1332                assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering);
     1333                if (pTS_out->iCounter==1)
     1334                {
     1335                    *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]);
     1336                    pTS_out->iCounter = 2;  // update counter
     1337                    pTS_out->bOrient = pGroup->bOrientPreservering;
     1338                }
     1339                else
     1340                {
     1341                    assert(pTS_out->iCounter==0);
     1342                    *pTS_out = pSubGroupTspace[l];
     1343                    pTS_out->iCounter = 1;  // update counter
     1344                    pTS_out->bOrient = pGroup->bOrientPreservering;
     1345                }
     1346            }
     1347        }
     1348
     1349        // clean up and offset iUniqueTspaces
     1350        for (s=0; s<iUniqueSubGroups; s++)
     1351            free(pUniSubGroups[s].pTriMembers);
     1352        iUniqueTspaces += iUniqueSubGroups;
     1353    }
     1354
     1355    // clean up
     1356    free(pUniSubGroups);
     1357    free(pTmpMembers);
     1358    free(pSubGroupTspace);
     1359
     1360    return TTRUE;
     1361}
     1362
     1363static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[],
     1364                          const SMikkTSpaceContext * pContext, const int iVertexRepresentitive)
     1365{
     1366    STSpace res;
     1367    float fAngleSum = 0;
     1368    int face=0;
     1369    res.vOs.x=0.0f; res.vOs.y=0.0f; res.vOs.z=0.0f;
     1370    res.vOt.x=0.0f; res.vOt.y=0.0f; res.vOt.z=0.0f;
     1371    res.fMagS = 0; res.fMagT = 0;
     1372
     1373    for (face=0; face<iFaces; face++)
     1374    {
     1375        const int f = face_indices[face];
     1376
     1377        // only valid triangles get to add their contribution
     1378        if ( (pTriInfos[f].iFlag&GROUP_WITH_ANY)==0 )
     1379        {
     1380            SVec3 n, vOs, vOt, p0, p1, p2, v1, v2;
     1381            float fCos, fAngle, fMagS, fMagT;
     1382            int i=-1, index=-1, i0=-1, i1=-1, i2=-1;
     1383            if (piTriListIn[3*f+0]==iVertexRepresentitive) i=0;
     1384            else if(piTriListIn[3*f+1]==iVertexRepresentitive) i=1;
     1385            else if(piTriListIn[3*f+2]==iVertexRepresentitive) i=2;
     1386            assert(i>=0 && i<3);
     1387
     1388            // project
     1389            index = piTriListIn[3*f+i];
     1390            n = GetNormal(pContext, index);
     1391            vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n));
     1392            vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n));
     1393            if ( VNotZero(vOs) ) vOs = Normalize(vOs);
     1394            if ( VNotZero(vOt) ) vOt = Normalize(vOt);
     1395
     1396            i2 = piTriListIn[3*f + (i<2?(i+1):0)];
     1397            i1 = piTriListIn[3*f + i];
     1398            i0 = piTriListIn[3*f + (i>0?(i-1):2)];
     1399
     1400            p0 = GetPosition(pContext, i0);
     1401            p1 = GetPosition(pContext, i1);
     1402            p2 = GetPosition(pContext, i2);
     1403            v1 = vsub(p0,p1);
     1404            v2 = vsub(p2,p1);
     1405
     1406            // project
     1407            v1 = vsub(v1, vscale(vdot(n,v1),n)); if( VNotZero(v1) ) v1 = Normalize(v1);
     1408            v2 = vsub(v2, vscale(vdot(n,v2),n)); if( VNotZero(v2) ) v2 = Normalize(v2);
     1409
     1410            // weight contribution by the angle
     1411            // between the two edge vectors
     1412            fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos);
     1413            fAngle = (float) acos(fCos);
     1414            fMagS = pTriInfos[f].fMagS;
     1415            fMagT = pTriInfos[f].fMagT;
     1416
     1417            res.vOs=vadd(res.vOs, vscale(fAngle,vOs));
     1418            res.vOt=vadd(res.vOt,vscale(fAngle,vOt));
     1419            res.fMagS+=(fAngle*fMagS);
     1420            res.fMagT+=(fAngle*fMagT);
     1421            fAngleSum += fAngle;
     1422        }
     1423    }
     1424
     1425    // normalize
     1426    if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs);
     1427    if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt);
     1428    if (fAngleSum>0)
     1429    {
     1430        res.fMagS /= fAngleSum;
     1431        res.fMagT /= fAngleSum;
     1432    }
     1433
     1434    return res;
     1435}
     1436
     1437static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2)
     1438{
     1439    tbool bStillSame=TTRUE;
     1440    int i=0;
     1441    if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE;
     1442    while (i<pg1->iNrFaces && bStillSame)
     1443    {
     1444        bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE;
     1445        if (bStillSame) ++i;
     1446    }
     1447    return bStillSame;
     1448}
     1449
     1450static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed)
     1451{
     1452    int iL, iR, n, index, iMid, iTmp;
     1453
     1454    // Random
     1455    unsigned int t=uSeed&31;
     1456    t=(uSeed<<t)|(uSeed>>(32-t));
     1457    uSeed=uSeed+t+3;
     1458    // Random end
     1459
     1460    iL=iLeft; iR=iRight;
     1461    n = (iR-iL)+1;
     1462    assert(n>=0);
     1463    index = (int) (uSeed%n);
     1464
     1465    iMid=pSortBuffer[index + iL];
     1466
     1467
     1468    do
     1469    {
     1470        while (pSortBuffer[iL] < iMid)
     1471            ++iL;
     1472        while (pSortBuffer[iR] > iMid)
     1473            --iR;
     1474
     1475        if (iL <= iR)
     1476        {
     1477            iTmp = pSortBuffer[iL];
     1478            pSortBuffer[iL] = pSortBuffer[iR];
     1479            pSortBuffer[iR] = iTmp;
     1480            ++iL; --iR;
     1481        }
     1482    }
     1483    while (iL <= iR);
     1484
     1485    if (iLeft < iR)
     1486        QuickSort(pSortBuffer, iLeft, iR, uSeed);
     1487    if (iL < iRight)
     1488        QuickSort(pSortBuffer, iL, iRight, uSeed);
     1489}
     1490
     1491/////////////////////////////////////////////////////////////////////////////////////////////
     1492/////////////////////////////////////////////////////////////////////////////////////////////
     1493
     1494static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed);
     1495static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in);
     1496
     1497static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn)
     1498{
     1499    // build array of edges
     1500    unsigned int uSeed = INTERNAL_RND_SORT_SEED;                // could replace with a random seed?
     1501    int iEntries=0, iCurStartIndex=-1, f=0, i=0;
     1502    for (f=0; f<iNrTrianglesIn; f++)
     1503        for (i=0; i<3; i++)
     1504        {
     1505            const int i0 = piTriListIn[f*3+i];
     1506            const int i1 = piTriListIn[f*3+(i<2?(i+1):0)];
     1507            pEdges[f*3+i].i0 = i0 < i1 ? i0 : i1;           // put minimum index in i0
     1508            pEdges[f*3+i].i1 = !(i0 < i1) ? i0 : i1;        // put maximum index in i1
     1509            pEdges[f*3+i].f = f;                            // record face number
     1510        }
     1511
     1512    // sort over all edges by i0, this is the pricy one.
     1513    QuickSortEdges(pEdges, 0, iNrTrianglesIn*3-1, 0, uSeed);    // sort channel 0 which is i0
     1514
     1515    // sub sort over i1, should be fast.
     1516    // could replace this with a 64 bit int sort over (i0,i1)
     1517    // with i0 as msb in the quicksort call above.
     1518    iEntries = iNrTrianglesIn*3;
     1519    iCurStartIndex = 0;
     1520    for (i=1; i<iEntries; i++)
     1521    {
     1522        if (pEdges[iCurStartIndex].i0 != pEdges[i].i0)
     1523        {
     1524            const int iL = iCurStartIndex;
     1525            const int iR = i-1;
     1526            //const int iElems = i-iL;
     1527            iCurStartIndex = i;
     1528            QuickSortEdges(pEdges, iL, iR, 1, uSeed);   // sort channel 1 which is i1
     1529        }
     1530    }
     1531
     1532    // sub sort over f, which should be fast.
     1533    // this step is to remain compliant with BuildNeighborsSlow() when
     1534    // more than 2 triangles use the same edge (such as a butterfly topology).
     1535    iCurStartIndex = 0;
     1536    for (i=1; i<iEntries; i++)
     1537    {
     1538        if (pEdges[iCurStartIndex].i0 != pEdges[i].i0 || pEdges[iCurStartIndex].i1 != pEdges[i].i1)
     1539        {
     1540            const int iL = iCurStartIndex;
     1541            const int iR = i-1;
     1542            //const int iElems = i-iL;
     1543            iCurStartIndex = i;
     1544            QuickSortEdges(pEdges, iL, iR, 2, uSeed);   // sort channel 2 which is f
     1545        }
     1546    }
     1547
     1548    // pair up, adjacent triangles
     1549    for (i=0; i<iEntries; i++)
     1550    {
     1551        const int i0=pEdges[i].i0;
     1552        const int i1=pEdges[i].i1;
     1553        const int f = pEdges[i].f;
     1554        tbool bUnassigned_A;
     1555
     1556        int i0_A, i1_A;
     1557        int edgenum_A, edgenum_B=0; // 0,1 or 2
     1558        GetEdge(&i0_A, &i1_A, &edgenum_A, &piTriListIn[f*3], i0, i1);   // resolve index ordering and edge_num
     1559        bUnassigned_A = pTriInfos[f].FaceNeighbors[edgenum_A] == -1 ? TTRUE : TFALSE;
     1560
     1561        if (bUnassigned_A)
     1562        {
     1563            // get true index ordering
     1564            int j=i+1, t;
     1565            tbool bNotFound = TTRUE;
     1566            while (j<iEntries && i0==pEdges[j].i0 && i1==pEdges[j].i1 && bNotFound)
     1567            {
     1568                tbool bUnassigned_B;
     1569                int i0_B, i1_B;
     1570                t = pEdges[j].f;
     1571                // flip i0_B and i1_B
     1572                GetEdge(&i1_B, &i0_B, &edgenum_B, &piTriListIn[t*3], pEdges[j].i0, pEdges[j].i1);   // resolve index ordering and edge_num
     1573                //assert(!(i0_A==i1_B && i1_A==i0_B));
     1574                bUnassigned_B =  pTriInfos[t].FaceNeighbors[edgenum_B]==-1 ? TTRUE : TFALSE;
     1575                if (i0_A==i0_B && i1_A==i1_B && bUnassigned_B)
     1576                    bNotFound = TFALSE;
     1577                else
     1578                    ++j;
     1579            }
     1580
     1581            if (!bNotFound)
     1582            {
     1583                int t = pEdges[j].f;
     1584                pTriInfos[f].FaceNeighbors[edgenum_A] = t;
     1585                //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1);
     1586                pTriInfos[t].FaceNeighbors[edgenum_B] = f;
     1587            }
     1588        }
     1589    }
     1590}
     1591
     1592static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn)
     1593{
     1594    int f=0, i=0;
     1595    for (f=0; f<iNrTrianglesIn; f++)
     1596    {
     1597        for (i=0; i<3; i++)
     1598        {
     1599            // if unassigned
     1600            if (pTriInfos[f].FaceNeighbors[i] == -1)
     1601            {
     1602                const int i0_A = piTriListIn[f*3+i];
     1603                const int i1_A = piTriListIn[f*3+(i<2?(i+1):0)];
     1604
     1605                // search for a neighbor
     1606                tbool bFound = TFALSE;
     1607                int t=0, j=0;
     1608                while (!bFound && t<iNrTrianglesIn)
     1609                {
     1610                    if (t!=f)
     1611                    {
     1612                        j=0;
     1613                        while (!bFound && j<3)
     1614                        {
     1615                            // in rev order
     1616                            const int i1_B = piTriListIn[t*3+j];
     1617                            const int i0_B = piTriListIn[t*3+(j<2?(j+1):0)];
     1618                            //assert(!(i0_A==i1_B && i1_A==i0_B));
     1619                            if (i0_A==i0_B && i1_A==i1_B)
     1620                                bFound = TTRUE;
     1621                            else
     1622                                ++j;
     1623                        }
     1624                    }
     1625                   
     1626                    if (!bFound) ++t;
     1627                }
     1628
     1629                // assign neighbors
     1630                if (bFound)
     1631                {
     1632                    pTriInfos[f].FaceNeighbors[i] = t;
     1633                    //assert(pTriInfos[t].FaceNeighbors[j]==-1);
     1634                    pTriInfos[t].FaceNeighbors[j] = f;
     1635                }
     1636            }
     1637        }
     1638    }
     1639}
     1640
     1641static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed)
     1642{
     1643    unsigned int t;
     1644    int iL, iR, n, index, iMid;
     1645
     1646    // early out
     1647    SEdge sTmp;
     1648    const int iElems = iRight-iLeft+1;
     1649    if (iElems<2) return;
     1650    else if(iElems==2)
     1651    {
     1652        if (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel])
     1653        {
     1654            sTmp = pSortBuffer[iLeft];
     1655            pSortBuffer[iLeft] = pSortBuffer[iRight];
     1656            pSortBuffer[iRight] = sTmp;
     1657        }
     1658        return;
     1659    }
     1660
     1661    // Random
     1662    t=uSeed&31;
     1663    t=(uSeed<<t)|(uSeed>>(32-t));
     1664    uSeed=uSeed+t+3;
     1665    // Random end
     1666
     1667    iL=iLeft, iR=iRight;
     1668    n = (iR-iL)+1;
     1669    assert(n>=0);
     1670    index = (int) (uSeed%n);
     1671
     1672    iMid=pSortBuffer[index + iL].array[channel];
     1673
     1674    do
     1675    {
     1676        while (pSortBuffer[iL].array[channel] < iMid)
     1677            ++iL;
     1678        while (pSortBuffer[iR].array[channel] > iMid)
     1679            --iR;
     1680
     1681        if (iL <= iR)
     1682        {
     1683            sTmp = pSortBuffer[iL];
     1684            pSortBuffer[iL] = pSortBuffer[iR];
     1685            pSortBuffer[iR] = sTmp;
     1686            ++iL; --iR;
     1687        }
     1688    }
     1689    while (iL <= iR);
     1690
     1691    if (iLeft < iR)
     1692        QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed);
     1693    if (iL < iRight)
     1694        QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed);
     1695}
     1696
     1697// resolve ordering and edge number
     1698static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in)
     1699{
     1700    *edgenum_out = -1;
     1701   
     1702    // test if first index is on the edge
     1703    if (indices[0]==i0_in || indices[0]==i1_in)
     1704    {
     1705        // test if second index is on the edge
     1706        if (indices[1]==i0_in || indices[1]==i1_in)
     1707        {
     1708            edgenum_out[0]=0;   // first edge
     1709            i0_out[0]=indices[0];
     1710            i1_out[0]=indices[1];
     1711        }
     1712        else
     1713        {
     1714            edgenum_out[0]=2;   // third edge
     1715            i0_out[0]=indices[2];
     1716            i1_out[0]=indices[0];
     1717        }
     1718    }
     1719    else
     1720    {
     1721        // only second and third index is on the edge
     1722        edgenum_out[0]=1;   // second edge
     1723        i0_out[0]=indices[1];
     1724        i1_out[0]=indices[2];
     1725    }
     1726}
     1727
     1728
     1729/////////////////////////////////////////////////////////////////////////////////////////////
     1730/////////////////////////////////// Degenerate triangles ////////////////////////////////////
     1731
     1732static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris)
     1733{
     1734    int iNextGoodTriangleSearchIndex=-1;
     1735    tbool bStillFindingGoodOnes;
     1736
     1737    // locate quads with only one good triangle
     1738    int t=0;
     1739    while (t<(iTotTris-1))
     1740    {
     1741        const int iFO_a = pTriInfos[t].iOrgFaceNumber;
     1742        const int iFO_b = pTriInfos[t+1].iOrgFaceNumber;
     1743        if (iFO_a==iFO_b)   // this is a quad
     1744        {
     1745            const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;
     1746            const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE;
     1747            if ((bIsDeg_a^bIsDeg_b)!=0)
     1748            {
     1749                pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI;
     1750                pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI;
     1751            }
     1752            t += 2;
     1753        }
     1754        else
     1755            ++t;
     1756    }
     1757
     1758    // reorder list so all degen triangles are moved to the back
     1759    // without reordering the good triangles
     1760    iNextGoodTriangleSearchIndex = 1;
     1761    t=0;
     1762    bStillFindingGoodOnes = TTRUE;
     1763    while (t<iNrTrianglesIn && bStillFindingGoodOnes)
     1764    {
     1765        const tbool bIsGood = (pTriInfos[t].iFlag&MARK_DEGENERATE)==0 ? TTRUE : TFALSE;
     1766        if (bIsGood)
     1767        {
     1768            if (iNextGoodTriangleSearchIndex < (t+2))
     1769                iNextGoodTriangleSearchIndex = t+2;
     1770        }
     1771        else
     1772        {
     1773            int t0, t1;
     1774            // search for the first good triangle.
     1775            tbool bJustADegenerate = TTRUE;
     1776            while (bJustADegenerate && iNextGoodTriangleSearchIndex<iTotTris)
     1777            {
     1778                const tbool bIsGood = (pTriInfos[iNextGoodTriangleSearchIndex].iFlag&MARK_DEGENERATE)==0 ? TTRUE : TFALSE;
     1779                if (bIsGood) bJustADegenerate=TFALSE;
     1780                else ++iNextGoodTriangleSearchIndex;
     1781            }
     1782
     1783            t0 = t;
     1784            t1 = iNextGoodTriangleSearchIndex;
     1785            ++iNextGoodTriangleSearchIndex;
     1786            assert(iNextGoodTriangleSearchIndex > (t+1));
     1787
     1788            // swap triangle t0 and t1
     1789            if (!bJustADegenerate)
     1790            {
     1791                int i=0;
     1792                for (i=0; i<3; i++)
     1793                {
     1794                    const int index = piTriList_out[t0*3+i];
     1795                    piTriList_out[t0*3+i] = piTriList_out[t1*3+i];
     1796                    piTriList_out[t1*3+i] = index;
     1797                }
     1798                {
     1799                    const STriInfo tri_info = pTriInfos[t0];
     1800                    pTriInfos[t0] = pTriInfos[t1];
     1801                    pTriInfos[t1] = tri_info;
     1802                }
     1803            }
     1804            else
     1805                bStillFindingGoodOnes = TFALSE; // this is not supposed to happen
     1806        }
     1807
     1808        if (bStillFindingGoodOnes) ++t;
     1809    }
     1810
     1811    assert(bStillFindingGoodOnes);  // code will still work.
     1812    assert(iNrTrianglesIn == t);
     1813}
     1814
     1815static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris)
     1816{
     1817    int t=0, i=0;
     1818    // deal with degenerate triangles
     1819    // punishment for degenerate triangles is O(N^2)
     1820    for (t=iNrTrianglesIn; t<iTotTris; t++)
     1821    {
     1822        // degenerate triangles on a quad with one good triangle are skipped
     1823        // here but processed in the next loop
     1824        const tbool bSkip = (pTriInfos[t].iFlag&QUAD_ONE_DEGEN_TRI)!=0 ? TTRUE : TFALSE;
     1825
     1826        if (!bSkip)
     1827        {
     1828            for (i=0; i<3; i++)
     1829            {
     1830                const int index1 = piTriListIn[t*3+i];
     1831                // search through the good triangles
     1832                tbool bNotFound = TTRUE;
     1833                int j=0;
     1834                while (bNotFound && j<(3*iNrTrianglesIn))
     1835                {
     1836                    const int index2 = piTriListIn[j];
     1837                    if (index1==index2) bNotFound=TFALSE;
     1838                    else ++j;
     1839                }
     1840
     1841                if (!bNotFound)
     1842                {
     1843                    const int iTri = j/3;
     1844                    const int iVert = j%3;
     1845                    const int iSrcVert=pTriInfos[iTri].vert_num[iVert];
     1846                    const int iSrcOffs=pTriInfos[iTri].iTSpacesOffs;
     1847                    const int iDstVert=pTriInfos[t].vert_num[i];
     1848                    const int iDstOffs=pTriInfos[t].iTSpacesOffs;
     1849                   
     1850                    // copy tspace
     1851                    psTspace[iDstOffs+iDstVert] = psTspace[iSrcOffs+iSrcVert];
     1852                }
     1853            }
     1854        }
     1855    }
     1856
     1857    // deal with degenerate quads with one good triangle
     1858    for (t=0; t<iNrTrianglesIn; t++)
     1859    {
     1860        // this triangle belongs to a quad where the
     1861        // other triangle is degenerate
     1862        if ( (pTriInfos[t].iFlag&QUAD_ONE_DEGEN_TRI)!=0 )
     1863        {
     1864            SVec3 vDstP;
     1865            int iOrgF=-1, i=0;
     1866            tbool bNotFound;
     1867            unsigned char * pV = pTriInfos[t].vert_num;
     1868            int iFlag = (1<<pV[0]) | (1<<pV[1]) | (1<<pV[2]);
     1869            int iMissingIndex = 0;
     1870            if ((iFlag&2)==0) iMissingIndex=1;
     1871            else if((iFlag&4)==0) iMissingIndex=2;
     1872            else if((iFlag&8)==0) iMissingIndex=3;
     1873
     1874            iOrgF = pTriInfos[t].iOrgFaceNumber;
     1875            vDstP = GetPosition(pContext, MakeIndex(iOrgF, iMissingIndex));
     1876            bNotFound = TTRUE;
     1877            i=0;
     1878            while (bNotFound && i<3)
     1879            {
     1880                const int iVert = pV[i];
     1881                const SVec3 vSrcP = GetPosition(pContext, MakeIndex(iOrgF, iVert));
     1882                if (veq(vSrcP, vDstP)==TTRUE)
     1883                {
     1884                    const int iOffs = pTriInfos[t].iTSpacesOffs;
     1885                    psTspace[iOffs+iMissingIndex] = psTspace[iOffs+iVert];
     1886                    bNotFound=TFALSE;
     1887                }
     1888                else
     1889                    ++i;
     1890            }
     1891            assert(!bNotFound);
     1892        }
     1893    }
     1894}
  • source/graphics/ObjectBase.h

     
    8080        int m_Frequency;
    8181        VfsPath m_ModelFilename;
    8282        VfsPath m_TextureFilename;
     83        VfsPath m_NormalFilename;
     84        VfsPath m_SpecularFilename;
    8385        Decal m_Decal;
    8486        VfsPath m_Particles;
    8587        CStr m_Color;
     
    9193    struct Variation
    9294    {
    9395        VfsPath texture;
     96        VfsPath normal;
     97        VfsPath specular;
    9498        VfsPath model;
    9599        Decal decal;
    96100        VfsPath particles;
  • source/graphics/weldmesh.h

     
     1/**
     2 *  Copyright (C) 2011 by Morten S. Mikkelsen
     3 *
     4 *  This software is provided 'as-is', without any express or implied
     5 *  warranty.  In no event will the authors be held liable for any damages
     6 *  arising from the use of this software.
     7 *
     8 *  Permission is granted to anyone to use this software for any purpose,
     9 *  including commercial applications, and to alter it and redistribute it
     10 *  freely, subject to the following restrictions:
     11 *
     12 *  1. The origin of this software must not be misrepresented; you must not
     13 *     claim that you wrote the original software. If you use this software
     14 *     in a product, an acknowledgment in the product documentation would be
     15 *     appreciated but is not required.
     16 *  2. Altered source versions must be plainly marked as such, and must not be
     17 *     misrepresented as being the original software.
     18 *  3. This notice may not be removed or altered from any source distribution.
     19 */
     20
     21
     22#ifndef __WELDMESH_H__
     23#define __WELDMESH_H__
     24
     25
     26#ifdef __cplusplus
     27extern "C" {
     28#endif
     29
     30// piRemapTable must be initialized and point to an area in memory
     31// with the byte size: iNrVerticesIn * sizeof(int).
     32// pfVertexDataOut must be initialized and point to an area in memory
     33// with the byte size: iNrVerticesIn * iFloatsPerVert * sizeof(float).
     34// At the end of the WeldMesh() call the array pfVertexDataOut will contain
     35// unique vertices only. Each entry in piRemapTable contains the index to
     36// the new location of the vertex in pfVertexDataOut in units of iFloatsPerVert.
     37// Note that this code is suitable for welding both unindexed meshes but also
     38// indexed meshes which need to have duplicates removed. In the latter case
     39// one simply uses the remap table to convert the old index list to the new vertex array.
     40// Finally, the return value is the number of unique vertices found.
     41int WeldMesh(int * piRemapTable, float * pfVertexDataOut,
     42              const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert);
     43
     44#ifdef __cplusplus
     45}
     46#endif
     47
     48
     49#endif
     50 No newline at end of file
  • source/graphics/Material.cpp

     
    3232    m_DiffuseTexture = texture;
    3333}
    3434
     35void CMaterial::SetNormalTexture(const CTexturePtr& texture)
     36{
     37    m_NormalTexture = texture;
     38}
     39
     40void CMaterial::SetSpecularTexture(const CTexturePtr& texture)
     41{
     42    m_SpecularTexture = texture;
     43}
     44
    3545void CMaterial::SetShaderEffect(const CStr& effect)
    3646{
    3747    m_ShaderEffect = CStrIntern(effect);
  • source/graphics/ObjectBase.cpp

     
    6262    EL(prop);
    6363    EL(mesh);
    6464    EL(texture);
     65    EL(normal);
     66    EL(specular);
    6567    EL(colour);
    6668    EL(decal);
    6769    EL(particles);
     
    151153                    {
    152154                        currentVariant->m_TextureFilename = VfsPath("art/textures/skins") / option.GetText().FromUTF8();
    153155                    }
     156                    else if (option_name == el_normal)
     157                    {
     158                        currentVariant->m_NormalFilename = VfsPath("art/textures/skins") / option.GetText().FromUTF8();
     159                    }       
     160                    else if (option_name == el_specular)
     161                    {
     162                        currentVariant->m_SpecularFilename = VfsPath("art/textures/skins") / option.GetText().FromUTF8();
     163                    }                   
    154164                    else if (option_name == el_decal)
    155165                    {
    156166                        XMBAttributeList attrs = option.GetAttributes();
     
    399409        if (! var.m_TextureFilename.empty())
    400410            variation.texture = var.m_TextureFilename;
    401411
     412        if (! var.m_NormalFilename.empty())
     413            variation.normal = var.m_NormalFilename;
     414
     415        if (! var.m_SpecularFilename.empty())
     416            variation.specular = var.m_SpecularFilename;   
     417
    402418        if (! var.m_ModelFilename.empty())
    403419            variation.model = var.m_ModelFilename;
    404420
  • source/graphics/ObjectEntry.cpp

     
    6161    // Copy the chosen data onto this model:
    6262
    6363    m_TextureName = variation.texture;
     64    m_NormalName = variation.normal;
     65    m_SpecularName = variation.specular;
    6466    m_ModelName = variation.model;
    6567
    6668    if (! variation.color.empty())
     
    128130    texture->Prefetch(); // if we've loaded this model we're probably going to render it soon, so prefetch its texture
    129131    model->GetMaterial().SetDiffuseTexture(texture);
    130132
     133    CTextureProperties normalProps(m_NormalName);
     134    normalProps.SetWrap(GL_CLAMP_TO_EDGE);
     135    CTexturePtr normal = g_Renderer.GetTextureManager().CreateTexture(normalProps);
     136    //normal->Prefetch(); // if we've loaded this model we're probably going to render it soon, so prefetch its texture
     137    model->GetMaterial().SetNormalTexture(normal);
     138
     139    CTextureProperties specularProps(m_SpecularName);
     140    specularProps.SetWrap(GL_CLAMP_TO_EDGE);
     141    CTexturePtr specular = g_Renderer.GetTextureManager().CreateTexture(specularProps);
     142    //specular->Prefetch(); // if we've loaded this model we're probably going to render it soon, so prefetch its texture
     143    model->GetMaterial().SetSpecularTexture(specular);
     144
    131145    // calculate initial object space bounds, based on vertex positions
    132146    model->CalcStaticObjectBounds();
    133147
  • source/renderer/RenderModifiers.h

     
    7373     */
    7474    virtual void PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture) = 0;
    7575
     76    virtual void PrepareNormal(const CShaderProgramPtr& shader, CTexture& normal) = 0;
     77
     78    virtual void PrepareSpecular(const CShaderProgramPtr& shader, CTexture& specular) = 0;
     79
    7680    /**
    7781     * PrepareModel: Called before rendering the given model.
    7882     *
     
    134138    // Implementation
    135139    void BeginPass(const CShaderProgramPtr& shader);
    136140    void PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture);
     141    void PrepareNormal(const CShaderProgramPtr& shader, CTexture& normal);
     142    void PrepareSpecular(const CShaderProgramPtr& shader, CTexture& specular);
    137143    void PrepareModel(const CShaderProgramPtr& shader, CModel* model);
    138144
    139145private:
     
    141147    CShaderProgram::Binding m_BindingShadingColor;
    142148    CShaderProgram::Binding m_BindingPlayerColor;
    143149    CShaderProgram::Binding m_BindingBaseTex;
     150    CShaderProgram::Binding m_BindingNormTex;
     151    CShaderProgram::Binding m_BindingSpecTex;
    144152};
    145153
    146154#endif // INCLUDED_RENDERMODIFIERS
  • source/renderer/ModelRenderer.cpp

     
    3535#include "renderer/ModelVertexRenderer.h"
    3636#include "renderer/Renderer.h"
    3737#include "renderer/RenderModifiers.h"
     38#include "renderer/MikktspaceWrap.h"
    3839
    3940#include <boost/weak_ptr.hpp>
    4041
     
    5758#endif
    5859}
    5960
     61void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices)
     62{
     63    MikkTSpace ms(mdef, newVertices);
     64
     65    ms.generate();
     66}
     67
    6068// Helper function to copy object-space position and normal vectors into arrays.
    6169void ModelRenderer::CopyPositionAndNormals(
    6270        const CModelDefPtr& mdef,
     
    557565                m->vertexRenderer->BeginPass(streamflags);
    558566
    559567                CTexture* currentTex = NULL;
     568                CTexture* currentNorm = NULL;
     569                CTexture* currentSpec = NULL;
    560570                CModelDef* currentModeldef = NULL;
    561571                CShaderUniforms currentStaticUniforms;
    562572                // (Texture needs to be rebound after binding a new shader, so we
     
    581591                            modifier->PrepareTexture(shader, *currentTex);
    582592                        }
    583593
     594                        CTexture* newNorm = model->GetMaterial().GetNormalTexture().get();
     595                        if (newNorm != currentNorm)
     596                        {
     597                            currentNorm = newNorm;
     598                            modifier->PrepareNormal(shader, *currentNorm);
     599                        }   
     600
     601                        CTexture* newSpec = model->GetMaterial().GetSpecularTexture().get();
     602                        if (newSpec != currentSpec)
     603                        {
     604                            currentSpec = newSpec;
     605                            modifier->PrepareSpecular(shader, *currentSpec);
     606                        }   
     607
    584608                        // Bind modeldef when it changes
    585609                        CModelDef* newModeldef = model->GetModelDef().get();
    586610                        if (newModeldef != currentModeldef)
  • source/renderer/MikktspaceWrap.h

     
     1#ifndef INCLUDED_MIKKWRAP
     2#define INCLUDED_MIKKWRAP
     3
     4
     5#include <graphics/mikktspace.h>
     6
     7class MikkTSpace
     8{
     9
     10public:
     11   
     12    MikkTSpace(const CModelDefPtr& m, std::vector<float>& v);
     13
     14    void generate();
     15   
     16private:
     17   
     18    SMikkTSpaceInterface interface;
     19    SMikkTSpaceContext context;
     20
     21    const CModelDefPtr& model;
     22
     23    std::vector<float>& newVertices;
     24   
     25
     26    // Returns the number of faces (triangles/quads) on the mesh to be processed.
     27    static int getNumFaces(const SMikkTSpaceContext *pContext);
     28
     29
     30    // Returns the number of vertices on face number iFace
     31    // iFace is a number in the range {0, 1, ..., getNumFaces()-1}
     32    static int getNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace);
     33
     34
     35    // returns the position/normal/texcoord of the referenced face of vertex number iVert.
     36    // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads.
     37    static void getPosition(const SMikkTSpaceContext *pContext,
     38            float fvPosOut[], const int iFace, const int iVert);
     39
     40    static void getNormal(const SMikkTSpaceContext *pContext,
     41            float fvNormOut[], const int iFace, const int iVert);
     42
     43    static void getTexCoord(const SMikkTSpaceContext *pContext,
     44            float fvTexcOut[], const int iFace, const int iVert);
     45
     46
     47    // either (or both) of the two setTSpace callbacks can be set.
     48    // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping.
     49
     50    // This function is used to return the tangent and fSign to the application.
     51    // fvTangent is a unit length vector.
     52    // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
     53    // bitangent = fSign * cross(vN, tangent);
     54    // Note that the results are returned unindexed. It is possible to generate a new index list
     55    // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
     56    // DO NOT! use an already existing index list.
     57    //void setTSpaceBasic(const MikkTSpace *parent, const SMikkTSpaceContext *pContext,
     58    //      const float fvTangent[], const float fSign, const int iFace, const int iVert);
     59
     60
     61    // This function is used to return tangent space results to the application.
     62    // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their
     63    // true magnitudes which can be used for relief mapping effects.
     64    // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent.
     65    // However, both are perpendicular to the vertex normal.
     66    // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level.
     67    // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
     68    // bitangent = fSign * cross(vN, tangent);
     69    // Note that the results are returned unindexed. It is possible to generate a new index list
     70    // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results.
     71    // DO NOT! use an already existing index list.
     72    static void setTSpace(const SMikkTSpaceContext * pContext, const float fvTangent[],
     73            const float fvBiTangent[], const float fMagS, const float fMagT,
     74            const tbool bIsOrientationPreserving, const int iFace, const int iVert);
     75
     76
     77};
     78
     79
     80#endif // INCLUDED_MIKKWRAP
     81 No newline at end of file
  • source/renderer/InstancingModelRenderer.cpp

     
    3131#include "graphics/LightEnv.h"
    3232#include "graphics/Model.h"
    3333#include "graphics/ModelDef.h"
     34#include "graphics/weldmesh.h"
    3435
    3536#include "renderer/InstancingModelRenderer.h"
    3637#include "renderer/Renderer.h"
     
    4950    /// Position, normals and UV are all static
    5051    VertexArray::Attribute m_Position;
    5152    VertexArray::Attribute m_Normal;
     53    VertexArray::Attribute m_Tangent;
    5254    VertexArray::Attribute m_UV;
    5355    VertexArray::Attribute m_BlendJoints; // valid iff gpuSkinning == true
    5456    VertexArray::Attribute m_BlendWeights; // valid iff gpuSkinning == true
     
    7678    m_UV.type = GL_FLOAT;
    7779    m_UV.elems = 2;
    7880    m_Array.AddAttribute(&m_UV);
     81   
     82    m_Tangent.type = GL_FLOAT;
     83    m_Tangent.elems = 4;
     84    m_Array.AddAttribute(&m_Tangent);
    7985
    8086    if (gpuSkinning)
    8187    {
     
    8793        m_BlendWeights.elems = 4;
    8894        m_Array.AddAttribute(&m_BlendWeights);
    8995    }
     96   
    9097
    91     m_Array.SetNumVertices(numVertices);
     98    std::vector<float> newVertices;
     99   
     100    SModelVertex* vertices = mdef->GetVertices();
     101   
     102    ModelRenderer::GenTangents(mdef, newVertices);
     103   
     104    int numVertexAttrs = 3 + 3 + 2 + 4;
     105   
     106    int vert = newVertices.size() / numVertexAttrs;
     107   
     108    std::vector<int> remapTable(vert);
     109    std::vector<float> vertexDataIn(vert * numVertexAttrs);
     110    std::vector<float> vertexDataOut(vert * numVertexAttrs);
     111       
     112    vertexDataIn = newVertices;
     113
     114    int numVertices2 = WeldMesh(&remapTable[0], &vertexDataOut[0],
     115              &vertexDataIn[0], vert, numVertexAttrs);
     116
     117    m_Array.SetNumVertices(numVertices2);
    92118    m_Array.Layout();
    93119
    94120    VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>();
    95121    VertexArrayIterator<CVector3D> Normal = m_Normal.GetIterator<CVector3D>();
    96122    VertexArrayIterator<float[2]> UVit = m_UV.GetIterator<float[2]>();
     123    VertexArrayIterator<CVector4D> Tangent = m_Tangent.GetIterator<CVector4D>();
    97124
    98     ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal);
    99     ModelRenderer::BuildUV(mdef, UVit);
     125    for (int i = 0; i < numVertices2; i++)
     126    {   
     127        int p = remapTable[i];
     128       
     129        int q = numVertexAttrs * i;
     130       
     131        Position[i] = CVector3D(vertexDataOut[q + 0], vertexDataOut[q + 1], vertexDataOut[q + 2]);
     132       
     133        Normal[i] = CVector3D(vertexDataOut[q + 3], vertexDataOut[q + 4], vertexDataOut[q + 5]);
     134       
     135        UVit[i][0] = vertexDataOut[q + 6];
     136        UVit[i][1] = vertexDataOut[q + 7];
     137       
     138        Tangent[i] = CVector4D(vertexDataOut[q + 8], vertexDataOut[q + 9], vertexDataOut[q + 10],
     139                       vertexDataOut[q + 11]);
     140    }
    100141
    101142    if (gpuSkinning)
    102143    {
     
    118159
    119160    m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3);
    120161    m_IndexArray.Layout();
    121     ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator());
     162   
     163    VertexArrayIterator<u16> Indices = m_IndexArray.GetIterator();
     164   
     165    size_t idxidx = 0;
     166    SModelFace* faces = mdef->GetFaces();   
     167
     168    for (size_t j = 0; j < mdef->GetNumFaces(); ++j) { 
     169        Indices[idxidx++]=remapTable[j * 3 + 0];
     170        Indices[idxidx++]=remapTable[j * 3 + 1];
     171        Indices[idxidx++]=remapTable[j * 3 + 2];
     172    }
     173   
     174   
     175   
    122176    m_IndexArray.Upload();
    123177    m_IndexArray.FreeBackingStore();
    124178}
     
    211265    if (streamflags & STREAM_UV0)
    212266        shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + m->imodeldef->m_UV.offset);
    213267
     268    shader->VertexAttribPointer("a_tangent", 4, GL_FLOAT, GL_TRUE, stride, base + m->imodeldef->m_Tangent.offset);
     269
    214270    // GPU skinning requires extra attributes to compute positions/normals
    215271    if (m->gpuSkinning)
    216272    {
  • source/renderer/RenderModifiers.cpp

     
    104104    m_BindingShadingColor = shader->GetUniformBinding("shadingColor");
    105105    m_BindingPlayerColor = shader->GetUniformBinding("playerColor");
    106106    m_BindingBaseTex = shader->GetTextureBinding("baseTex");
     107    m_BindingNormTex = shader->GetTextureBinding("normTex");
     108    m_BindingSpecTex = shader->GetTextureBinding("specTex");
    107109}
    108110
    109111void ShaderRenderModifier::PrepareTexture(const CShaderProgramPtr& shader, CTexture& texture)
     
    112114        shader->BindTexture(m_BindingBaseTex, texture.GetHandle());
    113115}
    114116
     117void ShaderRenderModifier::PrepareNormal(const CShaderProgramPtr& shader, CTexture& normal)
     118{
     119    if (m_BindingNormTex.Active())
     120        shader->BindTexture(m_BindingNormTex, normal.GetHandle());
     121}
     122
     123void ShaderRenderModifier::PrepareSpecular(const CShaderProgramPtr& shader, CTexture& specular)
     124{
     125    if (m_BindingSpecTex.Active())
     126        shader->BindTexture(m_BindingSpecTex, specular.GetHandle());
     127}
     128
    115129void ShaderRenderModifier::PrepareModel(const CShaderProgramPtr& shader, CModel* model)
    116130{
    117131    if (m_BindingInstancingTransform.Active())
  • source/renderer/MikktspaceWrap.cpp

     
     1
     2#include "precompiled.h"
     3
     4#include <boost/bind.hpp>
     5
     6#include "graphics/Color.h"
     7#include "graphics/LightEnv.h"
     8#include "graphics/Model.h"
     9#include "graphics/ModelDef.h"
     10#include "graphics/ShaderManager.h"
     11#include "graphics/TextureManager.h"
     12#include <graphics/mikktspace.h>
     13
     14#include <renderer/MikktspaceWrap.h>
     15
     16
     17
     18MikkTSpace::MikkTSpace(const CModelDefPtr& m, std::vector<float>& v) : model(m), newVertices(v)
     19{   
     20    interface.m_getNumFaces = getNumFaces;
     21    interface.m_getNumVerticesOfFace = getNumVerticesOfFace;
     22    interface.m_getPosition = getPosition;
     23    interface.m_getNormal = getNormal;
     24    interface.m_getTexCoord = getTexCoord;
     25    interface.m_setTSpaceBasic = NULL;
     26    interface.m_setTSpace = setTSpace;
     27
     28    context.m_pInterface = &interface;
     29    context.m_pUserData = (void*)this;
     30}
     31
     32void MikkTSpace::generate()
     33{
     34    genTangSpaceDefault(&context);
     35}
     36
     37
     38int MikkTSpace::getNumFaces(const SMikkTSpaceContext *pContext)
     39{
     40    return ((MikkTSpace*)pContext->m_pUserData)->model->GetNumFaces();
     41}
     42
     43
     44int MikkTSpace::getNumVerticesOfFace(const SMikkTSpaceContext *pContext, const int iFace)
     45{
     46    return 3;
     47}
     48
     49
     50void MikkTSpace::getPosition(const SMikkTSpaceContext *pContext,
     51        float fvPosOut[], const int iFace, const int iVert)
     52{
     53    SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace];
     54    long i = face.m_Verts[iVert];
     55    const CVector3D &p = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Coords;
     56
     57    fvPosOut[0] = p.X;
     58    fvPosOut[1] = p.Y;
     59    fvPosOut[2] = p.Z;
     60}
     61
     62
     63void MikkTSpace::getNormal(const SMikkTSpaceContext *pContext,
     64        float fvNormOut[], const int iFace, const int iVert)
     65{
     66    SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace];
     67    long i = face.m_Verts[iVert];
     68    const CVector3D &n = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Norm;
     69
     70    fvNormOut[0] = n.X;
     71    fvNormOut[1] = n.Y;
     72    fvNormOut[2] = n.Z;
     73}
     74
     75
     76void MikkTSpace::getTexCoord(const SMikkTSpaceContext *pContext,
     77        float fvTexcOut[], const int iFace, const int iVert)
     78{
     79    SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace];
     80    long i = face.m_Verts[iVert];
     81    SModelVertex &v = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i];
     82
     83    fvTexcOut[0] = v.m_U;
     84    fvTexcOut[1] = 1.0-v.m_V;       
     85}
     86
     87
     88void MikkTSpace::setTSpace(const SMikkTSpaceContext * pContext, const float fvTangent[],
     89        const float fvBiTangent[], const float fMagS, const float fMagT,
     90        const tbool bIsOrientationPreserving, const int iFace, const int iVert)
     91{
     92    SModelFace &face = ((MikkTSpace*)pContext->m_pUserData)->model->GetFaces()[iFace];
     93    long i = face.m_Verts[iVert];
     94   
     95    const CVector3D &p = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Coords;
     96    const CVector3D &n = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_Norm;
     97    const float u = ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_U;
     98    const float v = 1.0 - ((MikkTSpace*)pContext->m_pUserData)->model->GetVertices()[i].m_V;
     99   
     100    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(p.X);
     101    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(p.Y);
     102    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(p.Z);
     103   
     104    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(n.X);
     105    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(n.Y);
     106    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(n.Z);
     107   
     108    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(u);
     109    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(v);
     110   
     111    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(fvTangent[0]);
     112    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(fvTangent[1]);
     113    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(fvTangent[2]);
     114    ((MikkTSpace*)pContext->m_pUserData)->newVertices.push_back(bIsOrientationPreserving > 0.5 ? 1.0f : (-1.0f));
     115}
     116
     117
     118
  • source/renderer/ModelRenderer.h

     
    253253    static void BuildIndices(
    254254            const CModelDefPtr& mdef,
    255255            const VertexArrayIterator<u16>& Indices);
     256
     257
     258    static void GenTangents(const CModelDefPtr& mdef, std::vector<float>& newVertices);
    256259};
    257260
    258261
  • binaries/data/mods/public/art/materials/player_trans_norm.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<material>
     3    <shader effect="model_norm"/>
     4    <define name="USE_PLAYERCOLOR" value="1"/>
     5</material>
  • binaries/data/mods/public/art/actors/structures/romans/civic_centre.xml

    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: binaries/data/mods/public/art/textures/skins/structural/rome_struct_spec.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: binaries/data/mods/public/art/textures/skins/structural/rome_struct_norm.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: binaries/data/mods/public/art/textures/skins/structural/rome_struct_arch_spec.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
    Cannot display: file marked as a binary type.
    svn:mime-type = application/octet-stream
    
    Property changes on: binaries/data/mods/public/art/textures/skins/structural/rome_struct_arch_norm.png
    ___________________________________________________________________
    Added: svn:mime-type
       + application/octet-stream
    
     
    1515        <prop actor="props/units/weapons/arrow_front.xml" attachpoint="projectile"/>
    1616      </props>
    1717      <texture>structural/rome_struct.png</texture>
     18      <normal>structural/rome_struct_norm.png</normal>
     19      <specular>structural/rome_struct_spec.png</specular>
    1820    </variant>
    1921  </group>
    2022  <group>
     
    3638      </props>
    3739    </variant>
    3840  </group>
    39   <material>player_trans.xml</material>
     41  <material>player_trans_norm.xml</material>
    4042</actor>
  • binaries/data/mods/public/art/actors/structures/romans/triumphal_arch.xml

     
    55    <variant frequency="100" name="Roman Triumphal Arch">
    66      <mesh>structural/rome_arch.dae</mesh>
    77      <texture>structural/rome_struct_arch.dds</texture>
     8      <normal>structural/rome_struct_arch_norm.png</normal>
     9      <specular>structural/rome_struct_arch_spec.png</specular>
    810    </variant>
    911  </group>
    1012  <group>
     
    1719      </props>
    1820    </variant>
    1921  </group>
    20   <material>player_trans.xml</material>
     22  <material>player_trans_norm.xml</material>
    2123</actor>
  • binaries/data/mods/public/shaders/effects/model_norm.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<effect>
     3
     4    <technique>
     5        <require context="MODE_SHADOWCAST || MODE_SILHOUETTEOCCLUDER"/>
     6        <require shaders="arb"/>
     7        <pass shader="arb/model_solid"/>
     8    </technique>
     9
     10    <technique>
     11        <require context="MODE_SHADOWCAST || MODE_SILHOUETTEOCCLUDER"/>
     12        <require shaders="glsl"/>
     13        <pass shader="glsl/model_solid"/>
     14    </technique>
     15
     16    <technique>
     17        <require context="MODE_SHADOWCAST || MODE_SILHOUETTEOCCLUDER"/>
     18        <require shaders="fixed"/>
     19        <define name="USE_PLAYERCOLOR" value="0"/>
     20        <define name="USE_OBJECTCOLOR" value="0"/>
     21        <pass shader="fixed:model_solid"/>
     22    </technique>
     23
     24
     25    <technique>
     26        <require context="MODE_SILHOUETTEDISPLAY || MODE_WIREFRAME"/>
     27        <require shaders="arb"/>
     28        <pass shader="arb/model_solid_player"/>
     29    </technique>
     30
     31    <technique>
     32        <require context="MODE_SILHOUETTEDISPLAY || MODE_WIREFRAME"/>
     33        <require shaders="glsl"/>
     34        <pass shader="glsl/model_solid_player"/>
     35    </technique>
     36
     37    <technique>
     38        <require context="MODE_SILHOUETTEDISPLAY || MODE_WIREFRAME"/>
     39        <require shaders="fixed"/>
     40        <define name="USE_PLAYERCOLOR" value="1"/>
     41        <define name="USE_OBJECTCOLOR" value="0"/>
     42        <pass shader="fixed:model_solid"/>
     43    </technique>
     44
     45
     46    <technique>
     47        <require shaders="arb"/>
     48        <pass shader="arb/model_common"/>
     49    </technique>
     50
     51    <technique>
     52        <require shaders="glsl"/>
     53        <pass shader="glsl/model_common_norm"/>
     54    </technique>
     55
     56    <technique>
     57        <require context="USE_PLAYERCOLOR || USE_OBJECTCOLOR"/>
     58        <require shaders="fixed"/>
     59        <pass shader="fixed:model_color"/>
     60    </technique>
     61
     62    <technique>
     63        <require shaders="fixed"/>
     64        <pass shader="fixed:model"/>
     65    </technique>
     66
     67</effect>
  • binaries/data/mods/public/shaders/glsl/terrain_common.fs

     
    4040        vec4 size = vec4(offset + 1.0, 2.0 - offset);
    4141        vec4 weight = (vec4(2.0 - 1.0 / size.xy, 1.0 / size.zw - 1.0) + (v_shadow.xy - offset).xyxy) * shadowScale.zwzw;
    4242        return (1.0/9.0)*dot(size.zxzx*size.wwyy,
    43           vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).a,
    44                shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).a,
    45                shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).a,
    46                shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).a));
     43          vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).r,
     44               shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).r,
     45               shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).r,
     46               shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).r));
    4747      #else
    48         return shadow2D(shadowTex, v_shadow.xyz).a;
     48        return shadow2D(shadowTex, v_shadow.xyz).r;
    4949      #endif
    5050    #else
    5151      if (v_shadow.z >= 1.0)
  • binaries/data/mods/public/shaders/glsl/model_common_norm.vs

     
     1#if USE_GPU_SKINNING
     2// Skinning requires GLSL 1.30 for ivec4 vertex attributes
     3#version 120
     4#else
     5#version 140
     6#endif
     7
     8uniform mat4 transform;
     9uniform vec3 cameraPos;
     10uniform vec3 sunDir;
     11uniform vec3 sunColor;
     12uniform vec2 losTransform;
     13uniform mat4 shadowTransform;
     14uniform mat4 instancingTransform;
     15uniform mat4 normalMatrix;
     16uniform mat4 orientation;
     17
     18//varying vec3 v_lighting;
     19varying vec2 v_tex;
     20varying vec4 v_shadow;
     21varying vec2 v_los;
     22
     23//#if USE_SPECULAR
     24  varying vec3 v_half;
     25//#endif
     26
     27attribute vec3 a_vertex;
     28attribute vec3 a_normal;
     29attribute vec2 a_uv0;
     30
     31attribute vec4 a_tangent;
     32
     33
     34
     35#if USE_GPU_SKINNING
     36  const int MAX_INFLUENCES = 4;
     37  const int MAX_BONES = 64;
     38  uniform mat4 skinBlendMatrices[MAX_BONES];
     39  attribute ivec4 a_skinJoints;
     40  attribute vec4 a_skinWeights;
     41#endif
     42
     43varying vec4 debugx;
     44
     45
     46
     47
     48varying vec3 lightVec;
     49varying vec3 eyeVec;
     50
     51varying vec3 v_normal;
     52varying vec3 v_tangent;
     53varying vec3 v_bitangent;
     54
     55
     56varying float sign;
     57
     58
     59void main()
     60{
     61  #if USE_GPU_SKINNING
     62    vec3 p = vec3(0.0);
     63    vec3 n = vec3(0.0);
     64    for (int i = 0; i < MAX_INFLUENCES; ++i) {
     65      int joint = a_skinJoints[i];
     66      if (joint != 0xff) {
     67        mat4 m = skinBlendMatrices[joint];
     68        p += vec3(m * vec4(a_vertex, 1.0)) * a_skinWeights[i];
     69        n += vec3(m * vec4(a_normal, 0.0)) * a_skinWeights[i];
     70      }
     71    }
     72    vec4 position = instancingTransform * vec4(p, 1.0);
     73    vec3 normal = mat3(instancingTransform) * normalize(n);
     74  #else
     75  #if USE_INSTANCING
     76    vec4 position = instancingTransform * vec4(a_vertex, 1.0);
     77    vec3 normal = mat3(instancingTransform) * a_normal;
     78    vec4 tangent = vec4(mat3(instancingTransform) * a_tangent.xyz, a_tangent.w);
     79  #else
     80    vec4 position = vec4(a_vertex, 1.0);
     81    vec3 normal = a_normal;
     82    vec4 tangent = a_tangent;   
     83  #endif
     84  #endif
     85
     86  gl_Position = transform * position;
     87
     88
     89
     90
     91    eyeVec = cameraPos.xyz - position.xyz;
     92    lightVec = -sunDir;
     93
     94
     95   
     96 
     97    vec3 sunVec = -sunDir;
     98   
     99    v_half = normalize(sunVec + eyeVec);
     100
     101   
     102    v_normal = normal;
     103    v_tangent = tangent.xyz;
     104    sign = tangent.w;
     105    v_bitangent = cross(v_normal, v_tangent);
     106   
     107
     108  //v_lighting = max(0.0, dot(normal, -sunDir)) * sunColor;
     109  v_tex = a_uv0;
     110  v_shadow = shadowTransform * position;
     111  v_los = position.xz * losTransform.x + losTransform.y;
     112}
  • binaries/data/mods/public/shaders/glsl/model_common_norm.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<program type="glsl">
     3
     4    <vertex file="glsl/model_common_norm.vs">
     5        <stream name="pos"/>
     6        <stream name="normal"/>
     7        <stream name="uv0"/>   
     8        <attrib name="a_vertex" semantics="gl_Vertex"/>
     9        <attrib name="a_normal" semantics="gl_Normal"/>
     10        <attrib name="a_uv0" semantics="gl_MultiTexCoord0"/>   
     11    <attrib name="a_tangent" semantics="CustomAttribute2"/>
     12    </vertex>
     13
     14    <fragment file="glsl/model_common_norm.fs"/>
     15
     16</program>
  • binaries/data/mods/public/shaders/glsl/model_common.fs

     
    4747        vec4 size = vec4(offset + 1.0, 2.0 - offset);
    4848        vec4 weight = (vec4(2.0 - 1.0 / size.xy, 1.0 / size.zw - 1.0) + (v_shadow.xy - offset).xyxy) * shadowScale.zwzw;
    4949        return (1.0/9.0)*dot(size.zxzx*size.wwyy,
    50           vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).a,
    51                shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).a,
    52                shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).a,
    53                shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).a));
     50          vec4(shadow2D(shadowTex, vec3(weight.zw, v_shadow.z)).r,
     51               shadow2D(shadowTex, vec3(weight.xw, v_shadow.z)).r,
     52               shadow2D(shadowTex, vec3(weight.zy, v_shadow.z)).r,
     53               shadow2D(shadowTex, vec3(weight.xy, v_shadow.z)).r));
    5454      #else
    55         return shadow2D(shadowTex, v_shadow.xyz).a;
     55        return shadow2D(shadowTex, v_shadow.xyz).r;
    5656      #endif
    5757    #else
    5858      if (v_shadow.z >= 1.0)
  • binaries/data/mods/public/shaders/glsl/model_common_norm.fs

     
     1#version 120
     2
     3uniform sampler2D baseTex;
     4uniform sampler2D normTex;
     5uniform sampler2D losTex;
     6uniform sampler2D specTex;
     7
     8#if USE_SHADOW
     9  #if USE_SHADOW_SAMPLER
     10    uniform sampler2DShadow shadowTex;
     11  #else
     12    uniform sampler2D shadowTex;
     13  #endif
     14#endif
     15
     16#if USE_OBJECTCOLOR
     17  uniform vec3 objectColor;
     18#else
     19#if USE_PLAYERCOLOR
     20  uniform vec3 playerColor;
     21#endif
     22#endif
     23
     24uniform vec3 shadingColor;
     25uniform vec3 ambient;
     26uniform vec4 shadowOffsets1;
     27uniform vec4 shadowOffsets2;
     28
     29//uniform mat4 normalMatrix;
     30
     31//varying vec3 v_lighting;
     32varying vec2 v_tex;
     33varying vec4 v_shadow;
     34varying vec2 v_los;
     35
     36uniform vec3 sunDir;
     37uniform vec3 sunColor;
     38
     39#if USE_SPECULAR
     40  uniform float specularPower;
     41  uniform vec3 specularColor;
     42  varying vec3 v_normal;
     43#endif
     44
     45varying vec3 v_half;
     46
     47varying vec3 lightVec; 
     48varying vec3 eyeVec;
     49varying vec3 v_normal;
     50varying vec3 v_tangent;
     51varying vec3 v_bitangent;
     52
     53
     54varying float sign;
     55
     56
     57float get_shadow()
     58{
     59  #if USE_SHADOW && !DISABLE_RECEIVE_SHADOWS
     60    #if USE_SHADOW_SAMPLER
     61      #if USE_SHADOW_PCF
     62        return 0.25 * (
     63          shadow2D(shadowTex, vec3(v_shadow.xy + shadowOffsets1.xy, v_shadow.z)).r +
     64          shadow2D(shadowTex, vec3(v_shadow.xy + shadowOffsets1.zw, v_shadow.z)).r +
     65          shadow2D(shadowTex, vec3(v_shadow.xy + shadowOffsets2.xy, v_shadow.z)).r +
     66          shadow2D(shadowTex, vec3(v_shadow.xy + shadowOffsets2.zw, v_shadow.z)).r
     67        );
     68      #else
     69        return shadow2D(shadowTex, v_shadow.xyz).r;
     70      #endif
     71    #else
     72      if (v_shadow.z >= 1.0)
     73        return 1.0;
     74      #if USE_SHADOW_PCF
     75        return (
     76          (v_shadow.z <= texture2D(shadowTex, v_shadow.xy + shadowOffsets1.xy).x ? 0.25 : 0.0) +
     77          (v_shadow.z <= texture2D(shadowTex, v_shadow.xy + shadowOffsets1.zw).x ? 0.25 : 0.0) +
     78          (v_shadow.z <= texture2D(shadowTex, v_shadow.xy + shadowOffsets2.xy).x ? 0.25 : 0.0) +
     79          (v_shadow.z <= texture2D(shadowTex, v_shadow.xy + shadowOffsets2.zw).x ? 0.25 : 0.0)
     80        );
     81      #else
     82        return (v_shadow.z <= texture2D(shadowTex, v_shadow.xy).x ? 1.0 : 0.0);
     83      #endif
     84    #endif
     85  #else
     86    return 1.0;
     87  #endif
     88}
     89
     90
     91#define USE_PARALLAX      1
     92#define USE_NORMAL_MAP    1
     93#define USE_SPECULAR_MAP  1
     94#define USE_SELF_LIGHT    1
     95
     96void main()
     97{
     98
     99
     100    vec3 color;
     101    mat3 tbn;
     102    vec2 coord = v_tex;
     103
     104
     105#if USE_PARALLAX
     106    float h = texture2D(normTex, coord).a;
     107
     108    tbn = transpose(mat3(v_tangent, v_bitangent * sign, v_normal));
     109
     110    vec3 eyeDir = normalize( tbn * eyeVec );
     111    float dist = length(eyeVec);
     112
     113    if (dist < 65 && h < 0.99)
     114    {
     115
     116        float scale = 0.015;
     117        float height = 0.5;
     118        float iter;
     119       
     120        float s;
     121        vec2 move;
     122
     123        iter = (40 - (dist - 25)) / 2 + 5;
     124
     125        if (dist < 25) iter = 25;
     126       
     127
     128        vec3 tangEye = -eyeDir;
     129
     130        iter = mix(iter * 2, iter, tangEye.z);
     131       
     132        s = 1.0 / iter;
     133        move = vec2(-eyeDir.x, eyeDir.y) * scale / (eyeDir.z * iter);
     134
     135        while (h < height)
     136        {
     137            height -= s;
     138            coord += move;
     139            h = texture2D(normTex, coord).a;
     140        }
     141
     142    }
     143#endif
     144   
     145
     146
     147    tbn = transpose(mat3(v_tangent, v_bitangent * -sign, v_normal));
     148    vec3 ntex = texture2D(normTex, coord).rgb * 2.0 - 1.0;
     149    vec3 vNout = normalize(ntex * tbn);
     150
     151
     152
     153#if USE_NORMAL_MAP
     154    float bump = max (dot (-sunDir, vNout), 0.0) ;
     155#else
     156    float bump = 1.0;
     157#endif
     158
     159
     160    vec3 specular;
     161#if USE_SPECULAR_MAP
     162    vec4 specColour = texture2D(specTex, coord);
     163    specular = bump * specColour.xyz * sunColor * pow(max(0.0, dot(normalize(vNout), v_half)), 12);
     164#else
     165    // if enabled, do normal specular
     166    specular = vec3(0.0);
     167#endif
     168   
     169
     170    vec4 diffuse = texture2D(baseTex, coord);
     171
     172#if USE_TRANSPARENT
     173    gl_FragColor.a = diffuse.a;
     174#else
     175    gl_FragColor.a = 1.0;
     176#endif
     177
     178    vec3 texdiffuse = diffuse.rgb;
     179
     180    // Apply-coloring based on texture alpha
     181#if USE_OBJECTCOLOR
     182    texdiffuse *= mix(objectColor, vec3(1.0, 1.0, 1.0), diffuse.a);
     183#else
     184#if USE_PLAYERCOLOR
     185    texdiffuse *= mix(playerColor, vec3(1.0, 1.0, 1.0), diffuse.a);
     186#endif
     187#endif
     188
     189
     190
     191
     192    color = texdiffuse * bump * sunColor * get_shadow() + texdiffuse * ambient + specular;
     193
     194#if USE_SELF_LIGHT
     195    color = mix(texdiffuse, color, specColour.a);
     196#endif
     197
     198   
     199
     200
     201#if !IGNORE_LOS
     202    float los = texture2D(losTex, v_los).a;
     203    color *= los;
     204#endif
     205
     206 
     207    gl_FragColor.rgb = color;
     208}
  • binaries/data/config/default.cfg

     
    5858renderpath = default
    5959
    6060; Prefer GLSL shaders over ARB shaders (not recommended)
    61 preferglsl = false
     61preferglsl = true
    6262
    6363; Replace alpha-blending with alpha-testing, for performance experiments
    6464forcealphatest = false