├── .gitattributes ├── README.md └── source ├── Calculator.c ├── Calculator.h ├── SOP_ComputeTangents.c ├── SOP_ComputeTangents.h ├── SOP_ComputeTangents.props ├── SOP_ComputeTangents.sln ├── SOP_ComputeTangents.vcxproj ├── mikktspace.c └── mikktspace.h /.gitattributes: -------------------------------------------------------------------------------- 1 | mikktspace.c linguist-vendored 2 | mikktspace.h linguist-vendored 3 | *.c linguist-language=C++ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Compute Tangents SOP 2 | 3 | Houdini plug-in bringing [mikktspace] library written by Morten S. Mikkelsen. 4 | Computed tangents can be used to bake normal maps. Resulting normal maps will 5 | be synced with xNormal, Blender, 3D Coat, Unreal Engine 4, Unity and other 6 | software using this library, and may not be synced with Houdini native tangents 7 | well. 8 | 9 | [mikktspace]: http://wiki.blender.org/index.php/Dev:Shading/Tangent_Space_Normal_Maps 10 | 11 | 12 | ### Houdini 16 Note 13 | Since Houdini 16.0.514, MikkTSpace supported at render-time via 14 | [`vm_bake_usemikkt`](http://www.sidefx.com/docs/houdini/props/mantra#vm_bake_usemikkt) 15 | property. Try it at first, if you don't need Mikk's tangents in SOP level as 16 | geometry attributes. 17 | 18 | 19 | ### Installation 20 | See [Releases] for Windows binaries. To compile on Windows with Visual Studio 21 | or Build Tools, [update] `HDK_*` macros inside `.props` file with your Houdini 22 | version and build `.sln` file. To compile on other operation systems, follow 23 | [tutorial] on HDK building and use source code files directly. 24 | 25 | [Releases]: https://github.com/teared/mikktspace-for-houdini/releases 26 | [update]: https://github.com/teared/mikktspace-for-houdini/commit/6a9a7518e4e08735da99bb0808a3dfb3a2a12db9#diff-c3420d76b2c2d06ac74397cb0b7d9374 27 | [tutorial]: http://www.sidefx.com/docs/hdk/_h_d_k__intro__compiling.html 28 | 29 | 30 | #### License 31 | Public domain. 32 | -------------------------------------------------------------------------------- /source/Calculator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Calculator.h" 3 | 4 | // Initialize MikkTSpaceInterface with callbacks and run calculator. 5 | void Calculator::callMorty(GU_Detail *gdp, bool basic) 6 | { 7 | SMikkTSpaceInterface iface; 8 | iface.m_getNumFaces = getNumFaces; 9 | iface.m_getNumVerticesOfFace = getNumVerticesOfFace; 10 | iface.m_getPosition = getPosition; 11 | iface.m_getNormal = getNormal; 12 | iface.m_getTexCoord = getTexCoord; 13 | iface.m_setTSpaceBasic = basic ? setTSpaceBasic : NULL; 14 | iface.m_setTSpace = basic ? NULL : setTSpace; 15 | 16 | SMikkTSpaceContext context; 17 | context.m_pInterface = &iface; 18 | context.m_pUserData = gdp; 19 | 20 | genTangSpaceDefault(&context); 21 | } 22 | 23 | // Return number of primitives in the geometry. 24 | int Calculator::getNumFaces(const SMikkTSpaceContext *context) 25 | { 26 | // Cast the void pointer from context data to our GU_Detail pointer. 27 | GU_Detail *gdp = static_cast (context->m_pUserData); 28 | return gdp->getNumPrimitives(); 29 | } 30 | 31 | // Return number of vertices in the primitive given by index. 32 | int Calculator::getNumVerticesOfFace(const SMikkTSpaceContext *context, 33 | const int primnum) 34 | { 35 | GU_Detail *gdp = static_cast (context->m_pUserData); 36 | return gdp->getPrimitiveByIndex(primnum)->getVertexCount(); 37 | } 38 | 39 | // Write 3-float position of the vertex's point. 40 | void Calculator::getPosition(const SMikkTSpaceContext *context, 41 | float outpos[], 42 | const int primnum, 43 | const int vtxnum) 44 | { 45 | GU_Detail *gdp = static_cast (context->m_pUserData); 46 | // Compute the offset of element using indices. 47 | GA_Primitive *prim = gdp->getPrimitiveByIndex(primnum); 48 | GA_Offset ptnum = prim->getPointOffset(vtxnum); 49 | 50 | // Access point position using offset. 51 | GA_ROHandleV3 handle(gdp->getP()); 52 | UT_Vector3F pos = handle.get(ptnum); 53 | 54 | // Write into the input 3-float array. 55 | outpos[0] = pos[0]; 56 | outpos[1] = pos[1]; 57 | outpos[2] = pos[2]; 58 | } 59 | 60 | // Write 3-float vertex normal. 61 | void Calculator::getNormal(const SMikkTSpaceContext *context, 62 | float outnormal[], 63 | const int primnum, 64 | const int vtxnum) 65 | { 66 | GU_Detail *gdp = static_cast (context->m_pUserData); 67 | GA_Primitive *prim = gdp->getPrimitiveByIndex(primnum); 68 | GA_Offset lvtxnum = prim->getVertexOffset(vtxnum); 69 | 70 | GA_ROHandleV3 handle(gdp, GA_ATTRIB_VERTEX, "N"); 71 | UT_Vector3F normal = handle.get(lvtxnum); 72 | 73 | outnormal[0] = normal[0]; 74 | outnormal[1] = normal[1]; 75 | outnormal[2] = normal[2]; 76 | } 77 | 78 | // Write 2-float vertex uv. 79 | void Calculator::getTexCoord(const SMikkTSpaceContext *context, 80 | float outuv[], 81 | const int primnum, 82 | const int vtxnum) 83 | { 84 | GU_Detail *gdp = static_cast (context->m_pUserData); 85 | GA_Primitive *prim = gdp->getPrimitiveByIndex(primnum); 86 | GA_Offset lvtxnum = prim->getVertexOffset(vtxnum); 87 | 88 | GA_ROHandleV3 handle(gdp, GA_ATTRIB_VERTEX, "uv"); 89 | UT_Vector3F uv = handle.get(lvtxnum); 90 | 91 | outuv[0] = uv[0]; 92 | outuv[1] = uv[1]; 93 | } 94 | 95 | // Compute and set attributes on the geometry vertex. Basic version. 96 | void Calculator::setTSpaceBasic(const SMikkTSpaceContext *context, 97 | const float tangentu[], 98 | const float sign, 99 | const int primnum, 100 | const int vtxnum) 101 | { 102 | GU_Detail *gdp = static_cast (context->m_pUserData); 103 | GA_Primitive *prim = gdp->getPrimitiveByIndex(primnum); 104 | GA_Offset lvtxnum = prim->getVertexOffset(vtxnum); 105 | 106 | GA_RWHandleV3 tangentuHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "tangentu", 3)); 107 | GA_RWHandleF signHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "sign")); 108 | 109 | tangentuHandle.set(lvtxnum, UT_Vector3F(tangentu[0], tangentu[1], tangentu[2])); 110 | signHandle.set(lvtxnum, sign); 111 | } 112 | 113 | // Compute and set attributes on the geometry vertex. 114 | void Calculator::setTSpace(const SMikkTSpaceContext *context, 115 | const float tangentu[], 116 | const float tangentv[], 117 | const float magu, 118 | const float magv, 119 | const tbool keep, 120 | const int primnum, 121 | const int vtxnum) 122 | { 123 | GU_Detail *gdp = static_cast (context->m_pUserData); 124 | GA_Primitive *prim = gdp->getPrimitiveByIndex(primnum); 125 | GA_Offset lvtxnum = prim->getVertexOffset(vtxnum); 126 | 127 | GA_RWHandleV3 tangentuHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "tangentu", 3)); 128 | GA_RWHandleV3 tangentvHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "tangentv", 3)); 129 | GA_RWHandleF maguHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "magu")); 130 | GA_RWHandleF magvHandle(gdp->findFloatTuple(GA_ATTRIB_VERTEX, "magv")); 131 | GA_RWHandleI keepHandle(gdp->findIntTuple(GA_ATTRIB_VERTEX, "keep")); 132 | 133 | tangentuHandle.set(lvtxnum, UT_Vector3F(tangentu[0], tangentu[1], tangentu[2])); 134 | tangentvHandle.set(lvtxnum, UT_Vector3F(tangentv[0], tangentv[1], tangentv[2])); 135 | maguHandle.set(lvtxnum, magu); 136 | magvHandle.set(lvtxnum, magv); 137 | keepHandle.set(lvtxnum, keep); 138 | } 139 | -------------------------------------------------------------------------------- /source/Calculator.h: -------------------------------------------------------------------------------- 1 | #ifndef __Calculator_h__ 2 | #define __Calculator_h__ 3 | 4 | #include "mikktspace.h" 5 | 6 | class Calculator 7 | { 8 | public: 9 | // Initialize MikkTSpaceInterface with callbacks and run calculator. 10 | void callMorty(GU_Detail * gdp, bool basic = true); 11 | 12 | // Return number of primitives in the geometry. 13 | static int getNumFaces(const SMikkTSpaceContext *context); 14 | 15 | // Return number of vertices in the primitive given by index. 16 | static int getNumVerticesOfFace(const SMikkTSpaceContext *context, const int primnum); 17 | 18 | // Write 3-float position of the vertex's point. 19 | static void getPosition(const SMikkTSpaceContext *context, float pos[], const int primnum, const int vtxnum); 20 | 21 | // Write 3-float vertex normal. 22 | static void getNormal(const SMikkTSpaceContext *context, float normal[], const int primnum, const int vtxnum); 23 | 24 | // Write 2-float vertex uv. 25 | static void getTexCoord(const SMikkTSpaceContext *context, float uv[], const int primnum, const int vtxnum); 26 | 27 | // Compute and set attributes on the geometry vertex. 28 | static void setTSpaceBasic(const SMikkTSpaceContext *context, const float tangentu[], const float sign, const int primnum, const int vtxnum); 29 | static void setTSpace(const SMikkTSpaceContext *context, const float tangentu[], const float tangentv[], const float magu, const float magv, const tbool keep, const int primnum, const int vtxnum); 30 | }; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /source/SOP_ComputeTangents.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "Calculator.h" 9 | #include "SOP_ComputeTangents.h" 10 | 11 | static PRM_Name modeName("basic", "Basic Mode"); 12 | 13 | PRM_Template SOP_ComputeTangents::myTemplateList[] = { 14 | PRM_Template(PRM_TOGGLE, 1, &modeName, PRMoneDefaults), 15 | PRM_Template() 16 | }; 17 | 18 | OP_Node *SOP_ComputeTangents::myConstructor(OP_Network *net, const char *name, OP_Operator *op) 19 | { 20 | return new SOP_ComputeTangents(net, name, op); 21 | } 22 | 23 | SOP_ComputeTangents::SOP_ComputeTangents(OP_Network *net, const char *name, OP_Operator *op) : SOP_Node(net, name, op) 24 | { 25 | mySopFlags.setManagesDataIDs(true); 26 | } 27 | 28 | SOP_ComputeTangents::~SOP_ComputeTangents() 29 | { 30 | } 31 | 32 | OP_ERROR SOP_ComputeTangents::cookMySop(OP_Context &context) 33 | { 34 | OP_AutoLockInputs inputs(this); 35 | if (inputs.lock(context) >= UT_ERROR_ABORT) 36 | { 37 | return error(); 38 | } 39 | duplicateSource(0, context); 40 | 41 | // Perform basic checks to avoid segfaults. We won't fix anything here 42 | // because it is easier to do inside Houdini. 43 | GA_ROHandleV3 normalHandle(gdp, GA_ATTRIB_VERTEX, "N"); 44 | if (normalHandle.isInvalid()) 45 | { 46 | addError(SOP_ERR_INVALID_SRC, "(no vertex normals)"); 47 | return error(); 48 | } 49 | 50 | GA_ROHandleV3 uvHandle(gdp, GA_ATTRIB_VERTEX, "uv"); 51 | if (uvHandle.isInvalid()) 52 | { 53 | addError(SOP_ERR_INVALID_SRC, "(no vertex uvs)"); 54 | return error(); 55 | } 56 | 57 | for (GA_Iterator i(gdp->getPrimitiveRange()); !i.atEnd(); i.advance()) 58 | { 59 | GA_Size numvtx = gdp->getPrimitive(*i)->getVertexCount(); 60 | if (numvtx != 3 && numvtx != 4) 61 | { 62 | addError(SOP_ERR_INVALID_SRC, "(only quads and triangles allowed)"); 63 | return error(); 64 | } 65 | } 66 | 67 | bool basic = evalInt("basic", 0, context.getTime()); 68 | 69 | if (basic) 70 | { 71 | GA_RWHandleV3 tangentuHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "tangentu", 3)); 72 | GA_RWHandleV3 tangentvHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "tangentv", 3)); 73 | GA_RWHandleF signHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "sign", 1)); 74 | 75 | // Change type to "normal" from "3 floats". It allows to retain proper 76 | // tangents directions after transforming geometry. PolyFrame SOP uses 77 | // vectors for tangents, which is inconsistent with normals. 78 | gdp->findAttribute(GA_ATTRIB_VERTEX, "tangentu")->setTypeInfo(GA_TYPE_NORMAL); 79 | gdp->findAttribute(GA_ATTRIB_VERTEX, "tangentv")->setTypeInfo(GA_TYPE_NORMAL); 80 | 81 | Calculator().callMorty(gdp, basic); 82 | 83 | // Calculate "basic" tangentv. 84 | for (GA_Iterator i(gdp->getVertexRange()); !i.atEnd(); i.advance()) 85 | { 86 | UT_Vector3F normal, tangentu, tangentv; 87 | normal = normalHandle.get(*i); 88 | tangentu = tangentuHandle.get(*i); 89 | tangentv = signHandle.get(*i) * cross(normal, tangentu); 90 | tangentvHandle.set(*i, tangentv); 91 | } 92 | 93 | tangentuHandle.bumpDataId(); 94 | tangentvHandle.bumpDataId(); 95 | signHandle.bumpDataId(); 96 | } 97 | else 98 | { 99 | // Looks like a gun. 100 | GA_RWHandleV3 tangentuHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "tangentu", 3)); 101 | GA_RWHandleV3 tangentvHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "tangentv", 3)); 102 | gdp->findAttribute(GA_ATTRIB_VERTEX, "tangentu")->setTypeInfo(GA_TYPE_NORMAL); 103 | gdp->findAttribute(GA_ATTRIB_VERTEX, "tangentv")->setTypeInfo(GA_TYPE_NORMAL); 104 | GA_RWHandleF maguHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "magu", 1)); 105 | GA_RWHandleF magvHandle(gdp->addFloatTuple(GA_ATTRIB_VERTEX, "magv", 1)); 106 | GA_RWHandleI keepHandle(gdp->addIntTuple(GA_ATTRIB_VERTEX, "keep", 1)); 107 | Calculator().callMorty(gdp, basic); 108 | tangentuHandle.bumpDataId(); 109 | tangentvHandle.bumpDataId(); 110 | maguHandle.bumpDataId(); 111 | magvHandle.bumpDataId(); 112 | keepHandle.bumpDataId(); 113 | } 114 | return error(); 115 | } 116 | 117 | void newSopOperator(OP_OperatorTable *table) 118 | { 119 | table->addOperator(new OP_Operator("computetangents", "Compute Tangents", 120 | SOP_ComputeTangents::myConstructor, 121 | SOP_ComputeTangents::myTemplateList, 122 | 1, 1)); 123 | } 124 | -------------------------------------------------------------------------------- /source/SOP_ComputeTangents.h: -------------------------------------------------------------------------------- 1 | #ifndef __SOP_ComputeTangents_h__ 2 | #define __SOP_ComputeTangents_h__ 3 | 4 | #include 5 | 6 | // Compute Tangents SOP 7 | // 8 | // Computes and stores tangents suitable for normal map baking using a robust 9 | // tangent basis calculator written by Morten S. Mikkelsen. All operations 10 | // performing via Calculator class. 11 | class SOP_ComputeTangents : public SOP_Node 12 | { 13 | public: 14 | SOP_ComputeTangents(OP_Network *net, const char *name, OP_Operator *op); 15 | static PRM_Template myTemplateList[]; 16 | static OP_Node *myConstructor(OP_Network *net, const char *name, OP_Operator *op); 17 | virtual ~SOP_ComputeTangents(); 18 | 19 | protected: 20 | virtual OP_ERROR cookMySop(OP_Context &context); 21 | }; 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /source/SOP_ComputeTangents.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | D:\Projects\houdini\dso 6 | C:\Program Files\Side Effects Software\Houdini 16.0.600\bin\hmaster.exe 7 | D:\Projects\houdini 8 | C:\Program Files\Side Effects Software\Houdini 16.0.600 9 | 16.0.600 10 | C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC 11 | C:\Program Files (x86)\Windows Kits\8.1 12 | 13 | 14 | <_PropertySheetDisplayName>HDK 15 | $(HDK_DSO_PATH)\ 16 | 17 | 18 | 19 | .;.\include;$(HDK_GLOBAL)\toolkit\include;$(HDK_HFS)\toolkit\include;$(MSVCDir)\include;$(WIN32_SDK)\Include\um;$(WIN32_SDK)\Include\shared;%(AdditionalIncludeDirectories) 20 | CompileAsCpp 21 | /bigobj 22 | /w14996 23 | /wd4355 %(AdditionalOptions) 24 | false 25 | true 26 | _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_SCL_SECURE_NO_WARNINGS;_USE_MATH_DEFINES;_WIN32_WINNT=0x0502;AMD64;BOOST_ALL_NO_LIB;FBX_ENABLED=1;I386;MAKING_DSO;NDEBUG;NOMINMAX;OPENCL_ENABLED=1;OPENVDB_ENABLED=1;SESI_LITTLE_ENDIAN;SIZEOF_VOID_P=8;STRICT;SWAP_BITFIELDS;VERSION="$(HDK_VERSION)";WIN32;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) 27 | true 28 | None 29 | true 30 | Sync 31 | MultiThreadedDLL 32 | 33 | 34 | /LIBPATH:"$(MSVCDir)\lib\amd64" 35 | /LIBPATH:"$(WIN32_SDK)\Lib\win8\um\x64" 36 | /LIBPATH:"$(HDK_HFS)\custom\houdini\dsolib" "$(HDK_HFS)\custom\houdini\dsolib\*.a" "$(HDK_HFS)\custom\houdini\dsolib\*.lib" %(AdditionalOptions) 37 | 38 | 39 | 40 | 41 | $(HDK_DSO_PATH) 42 | 43 | 44 | $(HDK_EXE) 45 | 46 | 47 | $(HDK_GLOBAL) 48 | 49 | 50 | $(HDK_HFS) 51 | 52 | 53 | $(HDK_VERSION) 54 | 55 | 56 | $(MSVCDir) 57 | 58 | 59 | $(WIN32_SDK) 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /source/SOP_ComputeTangents.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SOP_ComputeTangents", "SOP_ComputeTangents.vcxproj", "{ECA737DE-6781-4F5C-8A15-177914EFD5EE}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Release|x64 = Release|x64 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {ECA737DE-6781-4F5C-8A15-177914EFD5EE}.Release|x64.ActiveCfg = Release|x64 12 | {ECA737DE-6781-4F5C-8A15-177914EFD5EE}.Release|x64.Build.0 = Release|x64 13 | EndGlobalSection 14 | GlobalSection(SolutionProperties) = preSolution 15 | HideSolutionNode = FALSE 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /source/SOP_ComputeTangents.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Release 6 | x64 7 | 8 | 9 | 10 | {ECA737DE-6781-4F5C-8A15-177914EFD5EE} 11 | SOP_ComputeTangents 12 | 13 | 14 | 15 | v140 16 | DynamicLibrary 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /source/mikktspace.c: -------------------------------------------------------------------------------- 1 | /** \file mikktspace/mikktspace.c 2 | * \ingroup mikktspace 3 | */ 4 | /** 5 | * Copyright (C) 2011 by Morten S. Mikkelsen 6 | * 7 | * This software is provided 'as-is', without any express or implied 8 | * warranty. In no event will the authors be held liable for any damages 9 | * arising from the use of this software. 10 | * 11 | * Permission is granted to anyone to use this software for any purpose, 12 | * including commercial applications, and to alter it and redistribute it 13 | * freely, subject to the following restrictions: 14 | * 15 | * 1. The origin of this software must not be misrepresented; you must not 16 | * claim that you wrote the original software. If you use this software 17 | * in a product, an acknowledgment in the product documentation would be 18 | * appreciated but is not required. 19 | * 2. Altered source versions must be plainly marked as such, and must not be 20 | * misrepresented as being the original software. 21 | * 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "mikktspace.h" 32 | 33 | #define TFALSE 0 34 | #define TTRUE 1 35 | 36 | #ifndef M_PI 37 | #define M_PI 3.1415926535897932384626433832795 38 | #endif 39 | 40 | #define INTERNAL_RND_SORT_SEED 39871946 41 | 42 | // internal structure 43 | typedef struct { 44 | float x, y, z; 45 | } SVec3; 46 | 47 | static tbool veq( const SVec3 v1, const SVec3 v2 ) 48 | { 49 | return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); 50 | } 51 | 52 | static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) 53 | { 54 | SVec3 vRes; 55 | 56 | vRes.x = v1.x + v2.x; 57 | vRes.y = v1.y + v2.y; 58 | vRes.z = v1.z + v2.z; 59 | 60 | return vRes; 61 | } 62 | 63 | 64 | static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) 65 | { 66 | SVec3 vRes; 67 | 68 | vRes.x = v1.x - v2.x; 69 | vRes.y = v1.y - v2.y; 70 | vRes.z = v1.z - v2.z; 71 | 72 | return vRes; 73 | } 74 | 75 | static SVec3 vscale(const float fS, const SVec3 v) 76 | { 77 | SVec3 vRes; 78 | 79 | vRes.x = fS * v.x; 80 | vRes.y = fS * v.y; 81 | vRes.z = fS * v.z; 82 | 83 | return vRes; 84 | } 85 | 86 | static float LengthSquared( const SVec3 v ) 87 | { 88 | return v.x*v.x + v.y*v.y + v.z*v.z; 89 | } 90 | 91 | static float Length( const SVec3 v ) 92 | { 93 | return sqrtf(LengthSquared(v)); 94 | } 95 | 96 | static SVec3 Normalize( const SVec3 v ) 97 | { 98 | return vscale(1 / Length(v), v); 99 | } 100 | 101 | static float vdot( const SVec3 v1, const SVec3 v2) 102 | { 103 | return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; 104 | } 105 | 106 | 107 | static tbool NotZero(const float fX) 108 | { 109 | // could possibly use FLT_EPSILON instead 110 | return fabsf(fX) > FLT_MIN; 111 | } 112 | 113 | static tbool VNotZero(const SVec3 v) 114 | { 115 | // might change this to an epsilon based test 116 | return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); 117 | } 118 | 119 | 120 | 121 | typedef struct { 122 | int iNrFaces; 123 | int * pTriMembers; 124 | } SSubGroup; 125 | 126 | typedef struct { 127 | int iNrFaces; 128 | int * pFaceIndices; 129 | int iVertexRepresentitive; 130 | tbool bOrientPreservering; 131 | } SGroup; 132 | 133 | // 134 | #define MARK_DEGENERATE 1 135 | #define QUAD_ONE_DEGEN_TRI 2 136 | #define GROUP_WITH_ANY 4 137 | #define ORIENT_PRESERVING 8 138 | 139 | 140 | 141 | typedef struct { 142 | int FaceNeighbors[3]; 143 | SGroup * AssignedGroup[3]; 144 | 145 | // normalized first order face derivatives 146 | SVec3 vOs, vOt; 147 | float fMagS, fMagT; // original magnitudes 148 | 149 | // determines if the current and the next triangle are a quad. 150 | int iOrgFaceNumber; 151 | int iFlag, iTSpacesOffs; 152 | unsigned char vert_num[4]; 153 | } STriInfo; 154 | 155 | typedef struct { 156 | SVec3 vOs; 157 | float fMagS; 158 | SVec3 vOt; 159 | float fMagT; 160 | int iCounter; // this is to average back into quads. 161 | tbool bOrient; 162 | } STSpace; 163 | 164 | static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 165 | static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 166 | static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 167 | static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); 168 | static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], 169 | const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, 170 | const SMikkTSpaceContext * pContext); 171 | 172 | static int MakeIndex(const int iFace, const int iVert) 173 | { 174 | assert(iVert>=0 && iVert<4 && iFace>=0); 175 | return (iFace<<2) | (iVert&0x3); 176 | } 177 | 178 | static void IndexToData(int * piFace, int * piVert, const int iIndexIn) 179 | { 180 | piVert[0] = iIndexIn&0x3; 181 | piFace[0] = iIndexIn>>2; 182 | } 183 | 184 | static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) 185 | { 186 | STSpace ts_res; 187 | 188 | // this if is important. Due to floating point precision 189 | // averaging when ts0==ts1 will cause a slight difference 190 | // which results in tangent space splits later on 191 | if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && 192 | veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) 193 | { 194 | ts_res.fMagS = pTS0->fMagS; 195 | ts_res.fMagT = pTS0->fMagT; 196 | ts_res.vOs = pTS0->vOs; 197 | ts_res.vOt = pTS0->vOt; 198 | } 199 | else 200 | { 201 | ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); 202 | ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); 203 | ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); 204 | ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); 205 | if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); 206 | if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); 207 | } 208 | 209 | return ts_res; 210 | } 211 | 212 | 213 | 214 | static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); 215 | static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); 216 | static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); 217 | 218 | 219 | // degen triangles 220 | static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); 221 | static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); 222 | 223 | 224 | tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) 225 | { 226 | return genTangSpace(pContext, 180.0f); 227 | } 228 | 229 | tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) 230 | { 231 | // count nr_triangles 232 | int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; 233 | STriInfo * pTriInfos = NULL; 234 | SGroup * pGroups = NULL; 235 | STSpace * psTspace = NULL; 236 | int iNrTrianglesIn = 0, f=0, t=0, i=0; 237 | int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; 238 | int iNrActiveGroups = 0, index = 0; 239 | const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); 240 | tbool bRes = TFALSE; 241 | const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); 242 | 243 | // verify all call-backs have been set 244 | if ( pContext->m_pInterface->m_getNumFaces==NULL || 245 | pContext->m_pInterface->m_getNumVerticesOfFace==NULL || 246 | pContext->m_pInterface->m_getPosition==NULL || 247 | pContext->m_pInterface->m_getNormal==NULL || 248 | pContext->m_pInterface->m_getTexCoord==NULL ) 249 | return TFALSE; 250 | 251 | // count triangles on supported faces 252 | for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); 255 | if (verts==3) ++iNrTrianglesIn; 256 | else if (verts==4) iNrTrianglesIn += 2; 257 | } 258 | if (iNrTrianglesIn<=0) return TFALSE; 259 | 260 | // allocate memory for an index list 261 | piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); 262 | pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); 263 | if (piTriListIn==NULL || pTriInfos==NULL) 264 | { 265 | if (piTriListIn!=NULL) free(piTriListIn); 266 | if (pTriInfos!=NULL) free(pTriInfos); 267 | return TFALSE; 268 | } 269 | 270 | // make an initial triangle --> face index list 271 | iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); 272 | 273 | // make a welded index list of identical positions and attributes (pos, norm, texc) 274 | //printf("gen welded index list begin\n"); 275 | GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); 276 | //printf("gen welded index list end\n"); 277 | 278 | // Mark all degenerate triangles 279 | iTotTris = iNrTrianglesIn; 280 | iDegenTriangles = 0; 281 | for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); 377 | if (verts!=3 && verts!=4) continue; 378 | 379 | 380 | // I've decided to let degenerate triangles and group-with-anythings 381 | // vary between left/right hand coordinate systems at the vertices. 382 | // All healthy triangles on the other hand are built to always be either or. 383 | 384 | /*// force the coordinate system orientation to be uniform for every face. 385 | // (this is already the case for good triangles but not for 386 | // degenerate ones and those with bGroupWithAnything==true) 387 | bool bOrient = psTspace[index].bOrient; 388 | if (psTspace[index].iCounter == 0) // tspace was not derived from a group 389 | { 390 | // look for a space created in GenerateTSpaces() by iCounter>0 391 | bool bNotFound = true; 392 | int i=1; 393 | while (i 0) bNotFound=false; 396 | else ++i; 397 | } 398 | if (!bNotFound) bOrient = psTspace[index+i].bOrient; 399 | }*/ 400 | 401 | // set data 402 | for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; 406 | float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; 407 | if (pContext->m_pInterface->m_setTSpace!=NULL) 408 | pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); 409 | if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) 410 | pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); 411 | 412 | ++index; 413 | } 414 | } 415 | 416 | free(psTspace); 417 | 418 | 419 | return TTRUE; 420 | } 421 | 422 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 423 | 424 | typedef struct { 425 | float vert[3]; 426 | int index; 427 | } STmpVert; 428 | 429 | static const int g_iCells = 2048; 430 | 431 | #ifdef _MSC_VER 432 | #define NOINLINE __declspec(noinline) 433 | #else 434 | #define NOINLINE __attribute__ ((noinline)) 435 | #endif 436 | 437 | // it is IMPORTANT that this function is called to evaluate the hash since 438 | // inlining could potentially reorder instructions and generate different 439 | // results for the same effective input value fVal. 440 | static NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) 441 | { 442 | const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); 443 | const int iIndex = (int)fIndex; 444 | return iIndex < g_iCells ? (iIndex >= 0 ? iIndex : 0) : (g_iCells - 1); 445 | } 446 | 447 | static void MergeVertsFast(int piTriList_in_and_out[], STmpVert pTmpVert[], const SMikkTSpaceContext * pContext, const int iL_in, const int iR_in); 448 | static void MergeVertsSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int pTable[], const int iEntries); 449 | static void GenerateSharedVerticesIndexListSlow(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); 450 | 451 | static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) 452 | { 453 | 454 | // Generate bounding box 455 | int * piHashTable=NULL, * piHashCount=NULL, * piHashOffsets=NULL, * piHashCount2=NULL; 456 | STmpVert * pTmpVert = NULL; 457 | int i=0, iChannel=0, k=0, e=0; 458 | int iMaxCount=0; 459 | SVec3 vMin = GetPosition(pContext, 0), vMax = vMin, vDim; 460 | float fMin, fMax; 461 | for (i=1; i<(iNrTrianglesIn*3); i++) 462 | { 463 | const int index = piTriList_in_and_out[i]; 464 | 465 | const SVec3 vP = GetPosition(pContext, index); 466 | if (vMin.x > vP.x) vMin.x = vP.x; 467 | else if (vMax.x < vP.x) vMax.x = vP.x; 468 | if (vMin.y > vP.y) vMin.y = vP.y; 469 | else if (vMax.y < vP.y) vMax.y = vP.y; 470 | if (vMin.z > vP.z) vMin.z = vP.z; 471 | else if (vMax.z < vP.z) vMax.z = vP.z; 472 | } 473 | 474 | vDim = vsub(vMax,vMin); 475 | iChannel = 0; 476 | fMin = vMin.x; fMax=vMax.x; 477 | if (vDim.y>vDim.x && vDim.y>vDim.z) 478 | { 479 | iChannel=1; 480 | fMin = vMin.y, fMax=vMax.y; 481 | } 482 | else if (vDim.z>vDim.x) 483 | { 484 | iChannel=2; 485 | fMin = vMin.z, fMax=vMax.z; 486 | } 487 | 488 | // make allocations 489 | piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); 490 | piHashCount = (int *) malloc(sizeof(int)*g_iCells); 491 | piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); 492 | piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); 493 | 494 | if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) 495 | { 496 | if (piHashTable!=NULL) free(piHashTable); 497 | if (piHashCount!=NULL) free(piHashCount); 498 | if (piHashOffsets!=NULL) free(piHashOffsets); 499 | if (piHashCount2!=NULL) free(piHashCount2); 500 | GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); 501 | return; 502 | } 503 | memset(piHashCount, 0, sizeof(int)*g_iCells); 504 | memset(piHashCount2, 0, sizeof(int)*g_iCells); 505 | 506 | // count amount of elements in each cell unit 507 | for (i=0; i<(iNrTrianglesIn*3); i++) 508 | { 509 | const int index = piTriList_in_and_out[i]; 510 | const SVec3 vP = GetPosition(pContext, index); 511 | const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); 512 | const int iCell = FindGridCell(fMin, fMax, fVal); 513 | ++piHashCount[iCell]; 514 | } 515 | 516 | // evaluate start index of each cell. 517 | piHashOffsets[0]=0; 518 | for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; 587 | else if (fvMax[c]dx && dy>dz) channel=1; 595 | else if (dz>dx) channel=2; 596 | 597 | fSep = 0.5f*(fvMax[channel]+fvMin[channel]); 598 | 599 | // terminate recursion when the separation/average value 600 | // is no longer strictly between fMin and fMax values. 601 | if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) 602 | { 603 | // complete the weld 604 | for (l=iL_in; l<=iR_in; l++) 605 | { 606 | int i = pTmpVert[l].index; 607 | const int index = piTriList_in_and_out[i]; 608 | const SVec3 vP = GetPosition(pContext, index); 609 | const SVec3 vN = GetNormal(pContext, index); 610 | const SVec3 vT = GetTexCoord(pContext, index); 611 | 612 | tbool bNotFound = TTRUE; 613 | int l2=iL_in, i2rec=-1; 614 | while (l20); // at least 2 entries 641 | 642 | // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] 643 | while (iL < iR) 644 | { 645 | tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; 646 | while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); 649 | bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); 655 | bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) 769 | { 770 | const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); 771 | if (verts!=3 && verts!=4) continue; 772 | 773 | pTriInfos[iDstTriIndex].iOrgFaceNumber = f; 774 | pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; 775 | 776 | if (verts==3) 777 | { 778 | unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; 779 | pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; 780 | piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); 781 | piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); 782 | piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); 783 | ++iDstTriIndex; // next 784 | } 785 | else 786 | { 787 | { 788 | pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; 789 | pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; 790 | } 791 | 792 | { 793 | // need an order independent way to evaluate 794 | // tspace on quads. This is done by splitting 795 | // along the shortest diagonal. 796 | const int i0 = MakeIndex(f, 0); 797 | const int i1 = MakeIndex(f, 1); 798 | const int i2 = MakeIndex(f, 2); 799 | const int i3 = MakeIndex(f, 3); 800 | const SVec3 T0 = GetTexCoord(pContext, i0); 801 | const SVec3 T1 = GetTexCoord(pContext, i1); 802 | const SVec3 T2 = GetTexCoord(pContext, i2); 803 | const SVec3 T3 = GetTexCoord(pContext, i3); 804 | const float distSQ_02 = LengthSquared(vsub(T2,T0)); 805 | const float distSQ_13 = LengthSquared(vsub(T3,T1)); 806 | tbool bQuadDiagIs_02; 807 | if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); 881 | res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; 882 | return res; 883 | } 884 | 885 | static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) 886 | { 887 | int iF, iI; 888 | SVec3 res; float norm[3]; 889 | IndexToData(&iF, &iI, index); 890 | pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); 891 | res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; 892 | return res; 893 | } 894 | 895 | static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) 896 | { 897 | int iF, iI; 898 | SVec3 res; float texc[2]; 899 | IndexToData(&iF, &iI, index); 900 | pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); 901 | res.x=texc[0]; res.y=texc[1]; res.z=1.0f; 902 | return res; 903 | } 904 | 905 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 906 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 907 | 908 | typedef union { 909 | struct 910 | { 911 | int i0, i1, f; 912 | }; 913 | int array[3]; 914 | } SEdge; 915 | 916 | static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); 917 | static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); 918 | 919 | // returns the texture area times 2 920 | static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) 921 | { 922 | const SVec3 t1 = GetTexCoord(pContext, indices[0]); 923 | const SVec3 t2 = GetTexCoord(pContext, indices[1]); 924 | const SVec3 t3 = GetTexCoord(pContext, indices[2]); 925 | 926 | const float t21x = t2.x-t1.x; 927 | const float t21y = t2.y-t1.y; 928 | const float t31x = t3.x-t1.x; 929 | const float t31y = t3.y-t1.y; 930 | 931 | const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; 932 | 933 | return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; 934 | } 935 | 936 | static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) 937 | { 938 | int f=0, i=0, t=0; 939 | // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. 940 | 941 | // generate neighbor info list 942 | for (f=0; f0 ? ORIENT_PRESERVING : 0); 981 | 982 | if ( NotZero(fSignedAreaSTx2) ) 983 | { 984 | const float fAbsArea = fabsf(fSignedAreaSTx2); 985 | const float fLenOs = Length(vOs); 986 | const float fLenOt = Length(vOt); 987 | const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; 988 | if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); 989 | if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); 990 | 991 | // evaluate magnitudes prior to normalization of vOs and vOt 992 | pTriInfos[f].fMagS = fLenOs / fAbsArea; 993 | pTriInfos[f].fMagT = fLenOt / fAbsArea; 994 | 995 | // if this is a good triangle 996 | if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) 997 | pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); 998 | } 999 | } 1000 | 1001 | // force otherwise healthy quads to a fixed orientation 1002 | while (t<(iNrTrianglesIn-1)) 1003 | { 1004 | const int iFO_a = pTriInfos[t].iOrgFaceNumber; 1005 | const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; 1006 | if (iFO_a==iFO_b) // this is a quad 1007 | { 1008 | const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1009 | const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1010 | 1011 | // bad triangles should already have been removed by 1012 | // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false 1013 | if ((bIsDeg_a||bIsDeg_b)==TFALSE) 1014 | { 1015 | const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1016 | const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1017 | // if this happens the quad has extremely bad mapping!! 1018 | if (bOrientA!=bOrientB) 1019 | { 1020 | //printf("found quad with bad mapping\n"); 1021 | tbool bChooseOrientFirstTri = TFALSE; 1022 | if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; 1023 | else if ( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) 1024 | bChooseOrientFirstTri = TTRUE; 1025 | 1026 | // force match 1027 | { 1028 | const int t0 = bChooseOrientFirstTri ? t : (t+1); 1029 | const int t1 = bChooseOrientFirstTri ? (t+1) : t; 1030 | pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first 1031 | pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit 1032 | } 1033 | } 1034 | } 1035 | t += 2; 1036 | } 1037 | else 1038 | ++t; 1039 | } 1040 | 1041 | // match up edge pairs 1042 | { 1043 | SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); 1044 | if (pEdges==NULL) 1045 | BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); 1046 | else 1047 | { 1048 | BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); 1049 | 1050 | free(pEdges); 1051 | } 1052 | } 1053 | } 1054 | 1055 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1056 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1057 | 1058 | static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); 1059 | static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); 1060 | 1061 | static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) 1062 | { 1063 | const int iNrMaxGroups = iNrTrianglesIn*3; 1064 | int iNrActiveGroups = 0; 1065 | int iOffset = 0, f=0, i=0; 1066 | (void)iNrMaxGroups; /* quiet warnings in non debug mode */ 1067 | for (f=0; fiVertexRepresentitive = vert_index; 1080 | pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; 1081 | pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; 1082 | pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; 1083 | ++iNrActiveGroups; 1084 | 1085 | AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); 1086 | bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1087 | neigh_indexL = pTriInfos[f].FaceNeighbors[i]; 1088 | neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; 1089 | if (neigh_indexL>=0) // neighbor 1090 | { 1091 | const tbool bAnswer = 1092 | AssignRecur(piTriListIn, pTriInfos, neigh_indexL, 1093 | pTriInfos[f].AssignedGroup[i] ); 1094 | 1095 | const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1096 | const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; 1097 | assert(bAnswer || bDiff); 1098 | (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ 1099 | } 1100 | if (neigh_indexR>=0) // neighbor 1101 | { 1102 | const tbool bAnswer = 1103 | AssignRecur(piTriListIn, pTriInfos, neigh_indexR, 1104 | pTriInfos[f].AssignedGroup[i] ); 1105 | 1106 | const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1107 | const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; 1108 | assert(bAnswer || bDiff); 1109 | (void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ 1110 | } 1111 | 1112 | // update offset 1113 | iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; 1114 | // since the groups are disjoint a triangle can never 1115 | // belong to more than 3 groups. Subsequently something 1116 | // is completely screwed if this assertion ever hits. 1117 | assert(iOffset <= iNrMaxGroups); 1118 | } 1119 | } 1120 | } 1121 | 1122 | return iNrActiveGroups; 1123 | } 1124 | 1125 | static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) 1126 | { 1127 | pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; 1128 | ++pGroup->iNrFaces; 1129 | } 1130 | 1131 | static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], 1132 | const int iMyTriIndex, SGroup * pGroup) 1133 | { 1134 | STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; 1135 | 1136 | // track down vertex 1137 | const int iVertRep = pGroup->iVertexRepresentitive; 1138 | const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; 1139 | int i=-1; 1140 | if (pVerts[0]==iVertRep) i=0; 1141 | else if (pVerts[1]==iVertRep) i=1; 1142 | else if (pVerts[2]==iVertRep) i=2; 1143 | assert(i>=0 && i<3); 1144 | 1145 | // early out 1146 | if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; 1147 | else if (pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; 1148 | if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) 1149 | { 1150 | // first to group with a group-with-anything triangle 1151 | // determines it's orientation. 1152 | // This is the only existing order dependency in the code!! 1153 | if ( pMyTriInfo->AssignedGroup[0] == NULL && 1154 | pMyTriInfo->AssignedGroup[1] == NULL && 1155 | pMyTriInfo->AssignedGroup[2] == NULL ) 1156 | { 1157 | pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); 1158 | pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); 1159 | } 1160 | } 1161 | { 1162 | const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; 1163 | if (bOrient != pGroup->bOrientPreservering) return TFALSE; 1164 | } 1165 | 1166 | AddTriToGroup(pGroup, iMyTriIndex); 1167 | pMyTriInfo->AssignedGroup[i] = pGroup; 1168 | 1169 | { 1170 | const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; 1171 | const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; 1172 | if (neigh_indexL>=0) 1173 | AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); 1174 | if (neigh_indexR>=0) 1175 | AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); 1176 | } 1177 | 1178 | 1179 | 1180 | return TTRUE; 1181 | } 1182 | 1183 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1184 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 1185 | 1186 | static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); 1187 | static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); 1188 | static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); 1189 | 1190 | static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], 1191 | const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, 1192 | const SMikkTSpaceContext * pContext) 1193 | { 1194 | STSpace * pSubGroupTspace = NULL; 1195 | SSubGroup * pUniSubGroups = NULL; 1196 | int * pTmpMembers = NULL; 1197 | int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; 1198 | for (g=0; giNrFaces; i++) // triangles 1224 | { 1225 | const int f = pGroup->pFaceIndices[i]; // triangle number 1226 | int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; 1227 | SSubGroup tmp_group; 1228 | tbool bFound; 1229 | SVec3 n, vOs, vOt; 1230 | if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; 1231 | else if (pTriInfos[f].AssignedGroup[1]==pGroup) index=1; 1232 | else if (pTriInfos[f].AssignedGroup[2]==pGroup) index=2; 1233 | assert(index>=0 && index<3); 1234 | 1235 | iVertIndex = piTriListIn[f*3+index]; 1236 | assert(iVertIndex==pGroup->iVertexRepresentitive); 1237 | 1238 | // is normalized already 1239 | n = GetNormal(pContext, iVertIndex); 1240 | 1241 | // project 1242 | vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); 1243 | vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); 1244 | if ( VNotZero(vOs) ) vOs = Normalize(vOs); 1245 | if ( VNotZero(vOt) ) vOt = Normalize(vOt); 1246 | 1247 | // original face number 1248 | iOF_1 = pTriInfos[f].iOrgFaceNumber; 1249 | 1250 | iMembers = 0; 1251 | for (j=0; jiNrFaces; j++) 1252 | { 1253 | const int t = pGroup->pFaceIndices[j]; // triangle number 1254 | const int iOF_2 = pTriInfos[t].iOrgFaceNumber; 1255 | 1256 | // project 1257 | SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); 1258 | SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); 1259 | if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); 1260 | if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); 1261 | 1262 | { 1263 | const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; 1264 | // make sure triangles which belong to the same quad are joined. 1265 | const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; 1266 | 1267 | const float fCosS = vdot(vOs,vOs2); 1268 | const float fCosT = vdot(vOt,vOt2); 1269 | 1270 | assert(f!=t || bSameOrgFace); // sanity check 1271 | if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) 1272 | pTmpMembers[iMembers++] = t; 1273 | } 1274 | } 1275 | 1276 | // sort pTmpMembers 1277 | tmp_group.iNrFaces = iMembers; 1278 | tmp_group.pTriMembers = pTmpMembers; 1279 | if (iMembers>1) 1280 | { 1281 | unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? 1282 | QuickSort(pTmpMembers, 0, iMembers-1, uSeed); 1283 | } 1284 | 1285 | // look for an existing match 1286 | bFound = TFALSE; 1287 | l=0; 1288 | while (liVertexRepresentitive); 1319 | ++iUniqueSubGroups; 1320 | } 1321 | 1322 | // output tspace 1323 | { 1324 | const int iOffs = pTriInfos[f].iTSpacesOffs; 1325 | const int iVert = pTriInfos[f].vert_num[index]; 1326 | STSpace * pTS_out = &psTspace[iOffs+iVert]; 1327 | assert(pTS_out->iCounter<2); 1328 | assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); 1329 | if (pTS_out->iCounter==1) 1330 | { 1331 | *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); 1332 | pTS_out->iCounter = 2; // update counter 1333 | pTS_out->bOrient = pGroup->bOrientPreservering; 1334 | } 1335 | else 1336 | { 1337 | assert(pTS_out->iCounter==0); 1338 | *pTS_out = pSubGroupTspace[l]; 1339 | pTS_out->iCounter = 1; // update counter 1340 | pTS_out->bOrient = pGroup->bOrientPreservering; 1341 | } 1342 | } 1343 | } 1344 | 1345 | // clean up and offset iUniqueTspaces 1346 | for (s=0; s=0 && i<3); 1383 | 1384 | // project 1385 | index = piTriListIn[3*f+i]; 1386 | n = GetNormal(pContext, index); 1387 | vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); 1388 | vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); 1389 | if ( VNotZero(vOs) ) vOs = Normalize(vOs); 1390 | if ( VNotZero(vOt) ) vOt = Normalize(vOt); 1391 | 1392 | i2 = piTriListIn[3*f + (i<2?(i+1):0)]; 1393 | i1 = piTriListIn[3*f + i]; 1394 | i0 = piTriListIn[3*f + (i>0?(i-1):2)]; 1395 | 1396 | p0 = GetPosition(pContext, i0); 1397 | p1 = GetPosition(pContext, i1); 1398 | p2 = GetPosition(pContext, i2); 1399 | v1 = vsub(p0,p1); 1400 | v2 = vsub(p2,p1); 1401 | 1402 | // project 1403 | v1 = vsub(v1, vscale(vdot(n,v1),n)); if ( VNotZero(v1) ) v1 = Normalize(v1); 1404 | v2 = vsub(v2, vscale(vdot(n,v2),n)); if ( VNotZero(v2) ) v2 = Normalize(v2); 1405 | 1406 | // weight contribution by the angle 1407 | // between the two edge vectors 1408 | fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); 1409 | fAngle = (float) acos(fCos); 1410 | fMagS = pTriInfos[f].fMagS; 1411 | fMagT = pTriInfos[f].fMagT; 1412 | 1413 | res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); 1414 | res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); 1415 | res.fMagS+=(fAngle*fMagS); 1416 | res.fMagT+=(fAngle*fMagT); 1417 | fAngleSum += fAngle; 1418 | } 1419 | } 1420 | 1421 | // normalize 1422 | if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); 1423 | if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); 1424 | if (fAngleSum>0) 1425 | { 1426 | res.fMagS /= fAngleSum; 1427 | res.fMagT /= fAngleSum; 1428 | } 1429 | 1430 | return res; 1431 | } 1432 | 1433 | static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) 1434 | { 1435 | tbool bStillSame=TTRUE; 1436 | int i=0; 1437 | if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; 1438 | while (iiNrFaces && bStillSame) 1439 | { 1440 | bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; 1441 | if (bStillSame) ++i; 1442 | } 1443 | return bStillSame; 1444 | } 1445 | 1446 | static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) 1447 | { 1448 | int iL, iR, n, index, iMid, iTmp; 1449 | 1450 | // Random 1451 | unsigned int t=uSeed&31; 1452 | t=(uSeed<>(32-t)); 1453 | uSeed=uSeed+t+3; 1454 | // Random end 1455 | 1456 | iL=iLeft; iR=iRight; 1457 | n = (iR-iL)+1; 1458 | assert(n>=0); 1459 | index = (int) (uSeed%n); 1460 | 1461 | iMid=pSortBuffer[index + iL]; 1462 | 1463 | 1464 | do 1465 | { 1466 | while (pSortBuffer[iL] < iMid) 1467 | ++iL; 1468 | while (pSortBuffer[iR] > iMid) 1469 | --iR; 1470 | 1471 | if (iL <= iR) 1472 | { 1473 | iTmp = pSortBuffer[iL]; 1474 | pSortBuffer[iL] = pSortBuffer[iR]; 1475 | pSortBuffer[iR] = iTmp; 1476 | ++iL; --iR; 1477 | } 1478 | } 1479 | while (iL <= iR); 1480 | 1481 | if (iLeft < iR) 1482 | QuickSort(pSortBuffer, iLeft, iR, uSeed); 1483 | if (iL < iRight) 1484 | QuickSort(pSortBuffer, iL, iRight, uSeed); 1485 | } 1486 | 1487 | ///////////////////////////////////////////////////////////////////////////////////////////// 1488 | ///////////////////////////////////////////////////////////////////////////////////////////// 1489 | 1490 | static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); 1491 | static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); 1492 | 1493 | static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) 1494 | { 1495 | // build array of edges 1496 | unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? 1497 | int iEntries=0, iCurStartIndex=-1, f=0, i=0; 1498 | for (f=0; f pSortBuffer[iRight].array[channel]) 1649 | { 1650 | sTmp = pSortBuffer[iLeft]; 1651 | pSortBuffer[iLeft] = pSortBuffer[iRight]; 1652 | pSortBuffer[iRight] = sTmp; 1653 | } 1654 | return; 1655 | } 1656 | 1657 | // Random 1658 | t=uSeed&31; 1659 | t=(uSeed<>(32-t)); 1660 | uSeed=uSeed+t+3; 1661 | // Random end 1662 | 1663 | iL=iLeft, iR=iRight; 1664 | n = (iR-iL)+1; 1665 | assert(n>=0); 1666 | index = (int) (uSeed%n); 1667 | 1668 | iMid=pSortBuffer[index + iL].array[channel]; 1669 | 1670 | do 1671 | { 1672 | while (pSortBuffer[iL].array[channel] < iMid) 1673 | ++iL; 1674 | while (pSortBuffer[iR].array[channel] > iMid) 1675 | --iR; 1676 | 1677 | if (iL <= iR) 1678 | { 1679 | sTmp = pSortBuffer[iL]; 1680 | pSortBuffer[iL] = pSortBuffer[iR]; 1681 | pSortBuffer[iR] = sTmp; 1682 | ++iL; --iR; 1683 | } 1684 | } 1685 | while (iL <= iR); 1686 | 1687 | if (iLeft < iR) 1688 | QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); 1689 | if (iL < iRight) 1690 | QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); 1691 | } 1692 | 1693 | // resolve ordering and edge number 1694 | static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) 1695 | { 1696 | *edgenum_out = -1; 1697 | 1698 | // test if first index is on the edge 1699 | if (indices[0]==i0_in || indices[0]==i1_in) 1700 | { 1701 | // test if second index is on the edge 1702 | if (indices[1]==i0_in || indices[1]==i1_in) 1703 | { 1704 | edgenum_out[0]=0; // first edge 1705 | i0_out[0]=indices[0]; 1706 | i1_out[0]=indices[1]; 1707 | } 1708 | else 1709 | { 1710 | edgenum_out[0]=2; // third edge 1711 | i0_out[0]=indices[2]; 1712 | i1_out[0]=indices[0]; 1713 | } 1714 | } 1715 | else 1716 | { 1717 | // only second and third index is on the edge 1718 | edgenum_out[0]=1; // second edge 1719 | i0_out[0]=indices[1]; 1720 | i1_out[0]=indices[2]; 1721 | } 1722 | } 1723 | 1724 | 1725 | ///////////////////////////////////////////////////////////////////////////////////////////// 1726 | /////////////////////////////////// Degenerate triangles //////////////////////////////////// 1727 | 1728 | static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) 1729 | { 1730 | int iNextGoodTriangleSearchIndex=-1; 1731 | tbool bStillFindingGoodOnes; 1732 | 1733 | // locate quads with only one good triangle 1734 | int t=0; 1735 | while (t<(iTotTris-1)) 1736 | { 1737 | const int iFO_a = pTriInfos[t].iOrgFaceNumber; 1738 | const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; 1739 | if (iFO_a==iFO_b) // this is a quad 1740 | { 1741 | const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1742 | const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; 1743 | if ((bIsDeg_a^bIsDeg_b)!=0) 1744 | { 1745 | pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; 1746 | pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; 1747 | } 1748 | t += 2; 1749 | } 1750 | else 1751 | ++t; 1752 | } 1753 | 1754 | // reorder list so all degen triangles are moved to the back 1755 | // without reordering the good triangles 1756 | iNextGoodTriangleSearchIndex = 1; 1757 | t=0; 1758 | bStillFindingGoodOnes = TTRUE; 1759 | while (t (t+1)); 1783 | 1784 | // swap triangle t0 and t1 1785 | if (!bJustADegenerate) 1786 | { 1787 | int i=0; 1788 | for (i=0; i<3; i++) 1789 | { 1790 | const int index = piTriList_out[t0*3+i]; 1791 | piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; 1792 | piTriList_out[t1*3+i] = index; 1793 | } 1794 | { 1795 | const STriInfo tri_info = pTriInfos[t0]; 1796 | pTriInfos[t0] = pTriInfos[t1]; 1797 | pTriInfos[t1] = tri_info; 1798 | } 1799 | } 1800 | else 1801 | bStillFindingGoodOnes = TFALSE; // this is not supposed to happen 1802 | } 1803 | 1804 | if (bStillFindingGoodOnes) ++t; 1805 | } 1806 | 1807 | assert(bStillFindingGoodOnes); // code will still work. 1808 | assert(iNrTrianglesIn == t); 1809 | } 1810 | 1811 | static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) 1812 | { 1813 | int t=0, i=0; 1814 | // deal with degenerate triangles 1815 | // punishment for degenerate triangles is O(N^2) 1816 | for (t=iNrTrianglesIn; t http://image.diku.dk/projects/media/morten.mikkelsen.08.pdf 52 | * Note that though the tangent spaces at the vertices are generated in an order-independent way, 53 | * by this implementation, the interpolated tangent space is still affected by which diagonal is 54 | * chosen to split each quad. A sensible solution is to have your tools pipeline always 55 | * split quads by the shortest diagonal. This choice is order-independent and works with mirroring. 56 | * If these have the same length then compare the diagonals defined by the texture coordinates. 57 | * XNormal which is a tool for baking normal maps allows you to write your own tangent space plugin 58 | * and also quad triangulator plugin. 59 | */ 60 | 61 | 62 | typedef int tbool; 63 | typedef struct SMikkTSpaceContext SMikkTSpaceContext; 64 | 65 | typedef struct { 66 | // Returns the number of faces (triangles/quads) on the mesh to be processed. 67 | int (*m_getNumFaces)(const SMikkTSpaceContext * pContext); 68 | 69 | // Returns the number of vertices on face number iFace 70 | // iFace is a number in the range {0, 1, ..., getNumFaces()-1} 71 | int (*m_getNumVerticesOfFace)(const SMikkTSpaceContext * pContext, const int iFace); 72 | 73 | // returns the position/normal/texcoord of the referenced face of vertex number iVert. 74 | // iVert is in the range {0,1,2} for triangles and {0,1,2,3} for quads. 75 | void (*m_getPosition)(const SMikkTSpaceContext * pContext, float fvPosOut[], const int iFace, const int iVert); 76 | void (*m_getNormal)(const SMikkTSpaceContext * pContext, float fvNormOut[], const int iFace, const int iVert); 77 | void (*m_getTexCoord)(const SMikkTSpaceContext * pContext, float fvTexcOut[], const int iFace, const int iVert); 78 | 79 | // either (or both) of the two setTSpace callbacks can be set. 80 | // The call-back m_setTSpaceBasic() is sufficient for basic normal mapping. 81 | 82 | // This function is used to return the tangent and fSign to the application. 83 | // fvTangent is a unit length vector. 84 | // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. 85 | // bitangent = fSign * cross(vN, tangent); 86 | // Note that the results are returned unindexed. It is possible to generate a new index list 87 | // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. 88 | // DO NOT! use an already existing index list. 89 | void (*m_setTSpaceBasic)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fSign, const int iFace, const int iVert); 90 | 91 | // This function is used to return tangent space results to the application. 92 | // fvTangent and fvBiTangent are unit length vectors and fMagS and fMagT are their 93 | // true magnitudes which can be used for relief mapping effects. 94 | // fvBiTangent is the "real" bitangent and thus may not be perpendicular to fvTangent. 95 | // However, both are perpendicular to the vertex normal. 96 | // For normal maps it is sufficient to use the following simplified version of the bitangent which is generated at pixel/vertex level. 97 | // fSign = bIsOrientationPreserving ? 1.0f : (-1.0f); 98 | // bitangent = fSign * cross(vN, tangent); 99 | // Note that the results are returned unindexed. It is possible to generate a new index list 100 | // But averaging/overwriting tangent spaces by using an already existing index list WILL produce INCRORRECT results. 101 | // DO NOT! use an already existing index list. 102 | void (*m_setTSpace)(const SMikkTSpaceContext * pContext, const float fvTangent[], const float fvBiTangent[], const float fMagS, const float fMagT, 103 | const tbool bIsOrientationPreserving, const int iFace, const int iVert); 104 | } SMikkTSpaceInterface; 105 | 106 | struct SMikkTSpaceContext 107 | { 108 | SMikkTSpaceInterface * m_pInterface; // initialized with callback functions 109 | void * m_pUserData; // pointer to client side mesh data etc. (passed as the first parameter with every interface call) 110 | }; 111 | 112 | // these are both thread safe! 113 | tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext); // Default (recommended) fAngularThreshold is 180 degrees (which means threshold disabled) 114 | tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold); 115 | 116 | 117 | // To avoid visual errors (distortions/unwanted hard edges in lighting), when using sampled normal maps, the 118 | // normal map sampler must use the exact inverse of the pixel shader transformation. 119 | // The most efficient transformation we can possibly do in the pixel shader is 120 | // achieved by using, directly, the "unnormalized" interpolated tangent, bitangent and vertex normal: vT, vB and vN. 121 | // pixel shader (fast transform out) 122 | // vNout = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); 123 | // where vNt is the tangent space normal. The normal map sampler must likewise use the 124 | // interpolated and "unnormalized" tangent, bitangent and vertex normal to be compliant with the pixel shader. 125 | // sampler does (exact inverse of pixel shader): 126 | // float3 row0 = cross(vB, vN); 127 | // float3 row1 = cross(vN, vT); 128 | // float3 row2 = cross(vT, vB); 129 | // float fSign = dot(vT, row0)<0 ? -1 : 1; 130 | // vNt = normalize( fSign * float3(dot(vNout,row0), dot(vNout,row1), dot(vNout,row2)) ); 131 | // where vNout is the sampled normal in some chosen 3D space. 132 | // 133 | // Should you choose to reconstruct the bitangent in the pixel shader instead 134 | // of the vertex shader, as explained earlier, then be sure to do this in the normal map sampler also. 135 | // Finally, beware of quad triangulations. If the normal map sampler doesn't use the same triangulation of 136 | // quads as your renderer then problems will occur since the interpolated tangent spaces will differ 137 | // eventhough the vertex level tangent spaces match. This can be solved either by triangulating before 138 | // sampling/exporting or by using the order-independent choice of diagonal for splitting quads suggested earlier. 139 | // However, this must be used both by the sampler and your tools/rendering pipeline. 140 | 141 | #ifdef __cplusplus 142 | } 143 | #endif 144 | 145 | #endif 146 | --------------------------------------------------------------------------------