Sfoglia il codice sorgente

Added an experimental MikkTspace generator

Nehon 9 anni fa
parent
commit
44601cf5ff

+ 97 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java

@@ -0,0 +1,97 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface MikkTSpaceContext {
+
+    /**
+     * Returns the number of faces (triangles/quads) on the mesh to be
+     * processed.
+     *
+     * @return
+     */
+    public int getNumFaces();
+
+    /**
+     * Returns the number of vertices on face number iFace iFace is a number in
+     * the range {0, 1, ..., getNumFaces()-1}
+     *
+     * @param face
+     * @return
+     */
+    public int getNumVerticesOfFace(int face);
+
+    /**
+     * returns the position/normal/texcoord of the referenced face of vertex
+     * number iVert. iVert is in the range {0,1,2} for triangles and {0,1,2,3}
+     * for quads.
+     *
+     * @param posOut
+     * @param face
+     * @param vert
+     */
+    public void getPosition(float posOut[], int face, int vert);
+
+    public void getNormal(float normOut[], int face, int vert);
+
+    public void getTexCoord(float texOut[], int face, int vert);
+
+    /**
+     * The call-backsetTSpaceBasic() is sufficient for basic normal mapping.
+     * This function is used to return the tangent and sign to the application.
+     * tangent is a unit length vector. For normal maps it is sufficient to use
+     * the following simplified version of the bitangent which is generated at
+     * pixel/vertex level.
+     *
+     * bitangent = fSign * cross(vN, tangent);
+     *
+     * Note that the results are returned unindexed. It is possible to generate
+     * a new index list But averaging/overwriting tangent spaces by using an
+     * already existing index list WILL produce INCRORRECT results. DO NOT! use
+     * an already existing index list.
+     *
+     * @param tangent
+     * @param sign
+     * @param face
+     * @param vert
+     */
+    public void setTSpaceBasic(float tangent[], float sign, int face, int vert);
+
+    /**
+     * This function is used to return tangent space results to the application.
+     * tangent and biTangent are unit length vectors and fMagS and fMagT are
+     * their true magnitudes which can be used for relief mapping effects.
+     *
+     * biTangent is the "real" bitangent and thus may not be perpendicular to
+     * tangent. However, both are perpendicular to the vertex normal. For normal
+     * maps it is sufficient to use the following simplified version of the
+     * bitangent which is generated at pixel/vertex level.
+     *
+     * <pre>
+     * fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
+     * bitangent = fSign * cross(vN, tangent);
+     * </pre>
+     *
+     * Note that the results are returned unindexed. It is possible to generate
+     * a new index list. But averaging/overwriting tangent spaces by using an
+     * already existing index list WILL produce INCRORRECT results. DO NOT! use
+     * an already existing index list.
+     *
+     * @param tangent
+     * @param biTangent
+     * @param magS
+     * @param magT
+     * @param isOrientationPreserving
+     * @param face
+     * @param vert
+     */
+    void setTSpace(float tangent[], float biTangent[], float magS, float magT,
+            boolean isOrientationPreserving, int face, int vert);
+}

+ 100 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java

@@ -0,0 +1,100 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ *
+ * @author Nehon
+ */
+public class MikkTSpaceImpl implements MikkTSpaceContext {
+
+    Mesh mesh;
+
+    public MikkTSpaceImpl(Mesh mesh) {
+        this.mesh = mesh;
+        VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent);
+        if(tangentBuffer == null){
+            FloatBuffer fb = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 4);
+            mesh.setBuffer(VertexBuffer.Type.Tangent, 4, fb);            
+        }
+        
+        //TODO ensure the Tangent buffer exists, else create one.
+    }
+
+    @Override
+    public int getNumFaces() {
+        return mesh.getTriangleCount();        
+    }
+
+    @Override
+    public int getNumVerticesOfFace(int face) {
+        return 3;
+    }
+
+    @Override
+    public void getPosition(float[] posOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.Position);
+        FloatBuffer pos = (FloatBuffer) position.getData();
+        pos.position(vertIndex * 3);
+        posOut[0] = pos.get();
+        posOut[1] = pos.get();
+        posOut[2] = pos.get();
+    }
+
+    @Override
+    public void getNormal(float[] normOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer normal = mesh.getBuffer(VertexBuffer.Type.Normal);
+        FloatBuffer norm = (FloatBuffer) normal.getData();
+        norm.position(vertIndex * 3);
+        normOut[0] = norm.get();
+        normOut[1] = norm.get();
+        normOut[2] = norm.get();
+    }
+
+    @Override
+    public void getTexCoord(float[] texOut, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer texCoord = mesh.getBuffer(VertexBuffer.Type.TexCoord);
+        FloatBuffer tex = (FloatBuffer) texCoord.getData();
+        tex.position(vertIndex * 2);
+        texOut[0] = tex.get();
+        texOut[1] = tex.get();        
+    }
+
+    @Override
+    public void setTSpaceBasic(float[] tangent, float sign, int face, int vert) {
+        int vertIndex = getIndex(face, vert);
+        VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent);
+        FloatBuffer tan = (FloatBuffer) tangentBuffer.getData();
+        
+        tan.position(vertIndex * 4);
+        tan.put(tangent);
+        tan.put(sign);
+        
+        tan.rewind();
+        tangentBuffer.setUpdateNeeded();
+    }
+
+    @Override
+    public void setTSpace(float[] tangent, float[] biTangent, float magS, float magT, boolean isOrientationPreserving, int face, int vert) {
+        //Do nothing
+    }
+
+    private int getIndex(int face, int vert) {
+        IndexBuffer index = mesh.getIndexBuffer();
+        int vertIndex = index.get(face * 3 + vert);
+        return vertIndex;
+    }
+
+}

+ 1717 - 0
jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java

@@ -0,0 +1,1717 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.util.mikktspace;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This tangent generator is Highly experimental.
+ * This is the Java translation of The mikktspace generator made by Morten S. Mikkelsen
+ * C Source code can be found here
+ * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c
+ * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h
+ * 
+ * MikkTspace looks like the new standard of tengent generation in 3D softwares.
+ * Xnormal, Blender, Substance painter, and many more use it.
+ * 
+ * Usage is :
+ * MikkTSpaceTangentGenerator.generate(spatial);
+ * 
+ * 
+ * 
+ * @author Nehon
+ */
+public class MikktspaceTangentGenerator {
+
+    private final static int MARK_DEGENERATE = 1;
+    private final static int QUAD_ONE_DEGEN_TRI = 2;
+    private final static int GROUP_WITH_ANY = 4;
+    private final static int ORIENT_PRESERVING = 8;
+    private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL;
+    static final int CELLS = 2048;
+
+    static int makeIndex(final int face, final int vert) {
+        assert (vert >= 0 && vert < 4 && face >= 0);
+        return (face << 2) | (vert & 0x3);
+    }
+
+    private static void indexToData(int[] face, int[] vert, final int indexIn) {
+        vert[0] = indexIn & 0x3;
+        face[0] = indexIn >> 2;
+    }
+
+    static TSpace avgTSpace(final TSpace tS0, final TSpace tS1) {
+        TSpace tsRes = new TSpace();
+
+        // this if is important. Due to floating point precision
+        // averaging when s0 == s1 will cause a slight difference
+        // which results in tangent space splits later on
+        if (tS0.magS == tS1.magS && tS0.magT == tS1.magT && tS0.os.equals(tS1.os) && tS0.ot.equals(tS1.ot)) {
+            tsRes.magS = tS0.magS;
+            tsRes.magT = tS0.magT;
+            tsRes.os.set(tS0.os);
+            tsRes.ot.set(tS0.ot);
+        } else {
+            tsRes.magS = 0.5f * (tS0.magS + tS1.magS);
+            tsRes.magT = 0.5f * (tS0.magT + tS1.magT);
+            tsRes.os.set(tS0.os).addLocal(tS1.os).normalizeLocal();
+            tsRes.ot.set(tS0.ot).addLocal(tS1.ot).normalizeLocal();
+        }
+        return tsRes;
+    }
+
+    public static void generate(Spatial s){
+        if(s instanceof Node){
+            Node n = (Node)s;
+            for (Spatial child : n.getChildren()) {
+                generate(child);
+            }
+        } else if (s instanceof Geometry){
+            Geometry g = (Geometry)s;
+            MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh());
+            if(!genTangSpaceDefault(context)){
+                Logger.getLogger(MikktspaceTangentGenerator.class.getName()).log(Level.SEVERE, "Failed to generate tangents for geometry " + g.getName());
+            }
+        }
+    }
+    
+    public static boolean genTangSpaceDefault(MikkTSpaceContext mikkTSpace) {
+        return genTangSpace(mikkTSpace, 180.0f);
+    }
+
+    public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float angularThreshold) {
+
+        // count nr_triangles
+        int[] piTriListIn;
+        int[] piGroupTrianglesBuffer;
+        TriInfo[] pTriInfos;
+        Group[] pGroups;
+        TSpace[] psTspace;
+        int iNrTrianglesIn = 0;
+        int iNrTSPaces, iTotTris, iDegenTriangles, iNrMaxGroups;
+        int iNrActiveGroups, index;
+        final int iNrFaces = mikkTSpace.getNumFaces();
+        //boolean bRes = false;
+        final float fThresCos = (float) FastMath.cos((angularThreshold * (float) FastMath.PI) / 180.0f);
+
+        // count triangles on supported faces
+        for (int f = 0; f < iNrFaces; f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts == 3) {
+                ++iNrTrianglesIn;
+            } else if (verts == 4) {
+                iNrTrianglesIn += 2;
+            }
+        }
+        if (iNrTrianglesIn <= 0) {
+            return false;
+        }
+
+        piTriListIn = new int[3 * iNrTrianglesIn];
+        pTriInfos = new TriInfo[iNrTrianglesIn];
+
+        // make an initial triangle -. face index list
+        iNrTSPaces = generateInitialVerticesIndexList(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // make a welded index list of identical positions and attributes (pos, norm, texc)        
+        generateSharedVerticesIndexList(piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // Mark all degenerate triangles
+        iTotTris = iNrTrianglesIn;
+        iDegenTriangles = 0;
+        for (int t = 0; t < iTotTris; t++) {
+            final int i0 = piTriListIn[t * 3 + 0];
+            final int i1 = piTriListIn[t * 3 + 1];
+            final int i2 = piTriListIn[t * 3 + 2];
+            final Vector3f p0 = getPosition(mikkTSpace, i0);
+            final Vector3f p1 = getPosition(mikkTSpace, i1);
+            final Vector3f p2 = getPosition(mikkTSpace, i2);
+            if (p0.equals(p1) || p0.equals(p2) || p1.equals(p2)) {// degenerate
+                pTriInfos[t].flag |= MARK_DEGENERATE;
+                ++iDegenTriangles;
+            }
+        }
+        iNrTrianglesIn = iTotTris - iDegenTriangles;
+
+        // mark all triangle pairs that belong to a quad with only one
+        // good triangle. These need special treatment in DegenEpilogue().
+        // Additionally, move all good triangles to the start of
+        // pTriInfos[] and piTriListIn[] without changing order and
+        // put the degenerate triangles last.
+        degenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris);
+
+        // evaluate triangle level attributes and neighbor list        
+        initTriInfo(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn);
+
+        // based on the 4 rules, identify groups based on connectivity
+        iNrMaxGroups = iNrTrianglesIn * 3;
+        pGroups = new Group[iNrMaxGroups];
+        piGroupTrianglesBuffer = new int[iNrTrianglesIn * 3];
+
+        iNrActiveGroups
+                = build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn);
+
+        psTspace = new TSpace[iNrTSPaces];
+
+        for (int t = 0; t < iNrTSPaces; t++) {
+            TSpace tSpace = new TSpace();
+            tSpace.os.set(1.0f, 0.0f, 0.0f);
+            tSpace.magS = 1.0f;
+            tSpace.ot.set(0.0f, 1.0f, 0.0f);
+            tSpace.magT = 1.0f;
+            psTspace[t] = tSpace;
+        }
+
+        // make tspaces, each group is split up into subgroups if necessary
+        // based on fAngularThreshold. Finally a tangent space is made for
+        // every resulting subgroup
+        generateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, mikkTSpace);
+
+        // degenerate quads with one good triangle will be fixed by copying a space from
+        // the good triangle to the coinciding vertex.
+        // all other degenerate triangles will just copy a space from any good triangle
+        // with the same welded index in piTriListIn[].
+        DegenEpilogue(psTspace, pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn, iTotTris);
+
+        index = 0;
+        for (int f = 0; f < iNrFaces; f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts != 3 && verts != 4) {
+                continue;
+            }
+
+            // I've decided to let degenerate triangles and group-with-anythings
+            // vary between left/right hand coordinate systems at the vertices.
+            // All healthy triangles on the other hand are built to always be either or.
+
+            /*// force the coordinate system orientation to be uniform for every face.
+             // (this is already the case for good triangles but not for
+             // degenerate ones and those with bGroupWithAnything==true)
+             bool bOrient = psTspace[index].bOrient;
+             if (psTspace[index].iCounter == 0)  // tspace was not derived from a group
+             {
+             // look for a space created in GenerateTSpaces() by iCounter>0
+             bool bNotFound = true;
+             int i=1;
+             while (i<verts && bNotFound)
+             {
+             if (psTspace[index+i].iCounter > 0) bNotFound=false;
+             else ++i;
+             }
+             if (!bNotFound) bOrient = psTspace[index+i].bOrient;
+             }*/
+            // set data
+            for (int i = 0; i < verts; i++) {
+                final TSpace pTSpace = psTspace[index];
+                float tang[] = {pTSpace.os.x, pTSpace.os.y, pTSpace.os.z};
+                float bitang[] = {pTSpace.ot.x, pTSpace.ot.y, pTSpace.ot.z};
+                mikkTSpace.setTSpace(tang, bitang, pTSpace.magS, pTSpace.magT, pTSpace.orient, f, i);
+                mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? 1.0f : (-1.0f), f, i);
+                ++index;
+            }
+        }
+
+        return true;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // it is IMPORTANT that this function is called to evaluate the hash since
+    // inlining could potentially reorder instructions and generate different
+    // results for the same effective input value fVal.
+    //TODO nehon: Wuuttt? something is fishy about this. How the fuck inlining can reorder instructions? Is that a C thing?
+    static int findGridCell(final float min, final float max, final float val) {
+        final float fIndex = CELLS * ((val - min) / (max - min));
+        final int iIndex = (int) fIndex;
+        return iIndex < CELLS ? (iIndex >= 0 ? iIndex : 0) : (CELLS - 1);
+    }
+
+    static void generateSharedVerticesIndexList(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+
+        // Generate bounding box
+        TmpVert[] pTmpVert;
+        Vector3f vMin = getPosition(mikkTSpace, 0);
+        Vector3f vMax = vMin.clone();
+        Vector3f vDim;
+        float fMin, fMax;
+        for (int i = 1; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            if (vMin.x > vP.x) {
+                vMin.x = vP.x;
+            } else if (vMax.x < vP.x) {
+                vMax.x = vP.x;
+            }
+            if (vMin.y > vP.y) {
+                vMin.y = vP.y;
+            } else if (vMax.y < vP.y) {
+                vMax.y = vP.y;
+            }
+            if (vMin.z > vP.z) {
+                vMin.z = vP.z;
+            } else if (vMax.z < vP.z) {
+                vMax.z = vP.z;
+            }
+        }
+
+        vDim = vMax.subtract(vMin);
+        int iChannel = 0;
+        fMin = vMin.x;
+        fMax = vMax.x;
+        if (vDim.y > vDim.x && vDim.y > vDim.z) {
+            iChannel = 1;
+            fMin = vMin.y;
+            fMax = vMax.y;
+        } else if (vDim.z > vDim.x) {
+            iChannel = 2;
+            fMin = vMin.z;
+            fMax = vMax.z;
+        }
+
+        //TODO Nehon: this is really fishy... seems like a hashtable implementation with nasty array manipulation...
+        int[] piHashTable = new int[iNrTrianglesIn * 3];
+        int[] piHashCount = new int[CELLS];
+        int[] piHashOffsets = new int[CELLS];
+        int[] piHashCount2 = new int[CELLS];
+
+        // count amount of elements in each cell unit
+        for (int i = 0; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z);
+            final int iCell = findGridCell(fMin, fMax, fVal);
+            ++piHashCount[iCell];
+        }
+
+        // evaluate start index of each cell.
+        piHashOffsets[0] = 0;
+        for (int k = 1; k < CELLS; k++) {
+            piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1];
+        }
+
+        // insert vertices
+        for (int i = 0; i < (iNrTrianglesIn * 3); i++) {
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z);
+            final int iCell = findGridCell(fMin, fMax, fVal);
+
+            assert (piHashCount2[iCell] < piHashCount[iCell]);
+
+            //    int * pTable = &piHashTable[piHashOffsets[iCell]];
+            //    pTable[piHashCount2[iCell]] = i;  // vertex i has been inserted.
+            piHashTable[piHashOffsets[iCell] + piHashCount2[iCell]] = i;// vertex i has been inserted.     
+            ++piHashCount2[iCell];
+        }
+        for (int k = 0; k < CELLS; k++) {
+            assert (piHashCount2[k] == piHashCount[k]);  // verify the count
+        }
+
+        // find maximum amount of entries in any hash entry
+        int iMaxCount = piHashCount[0];
+        for (int k = 1; k < CELLS; k++) {
+            if (iMaxCount < piHashCount[k]) {
+                iMaxCount = piHashCount[k];
+            }
+        }
+
+        pTmpVert = new TmpVert[iMaxCount];
+
+        // complete the merge
+        for (int k = 0; k < CELLS; k++) {
+            // extract table of cell k and amount of entries in it
+            // int * pTable = &piHashTable[piHashOffsets[k]];
+            final int iEntries = piHashCount[k];
+            if (iEntries < 2) {
+                continue;
+            }
+
+            if (pTmpVert != null) {
+                for (int e = 0; e < iEntries; e++) {
+                    int j = piHashTable[piHashOffsets[k] + e];
+                    final Vector3f vP = getPosition(mikkTSpace, piTriList_in_and_out[j]);
+                    pTmpVert[e] = new TmpVert();
+                    pTmpVert[e].vert[0] = vP.x;
+                    pTmpVert[e].vert[1] = vP.y;
+                    pTmpVert[e].vert[2] = vP.z;
+                    pTmpVert[e].index = j;
+                }
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, 0, iEntries - 1);
+            } else {
+                //TODO Nehon: pTempVert is very unlikely to be null...maybe remove this...
+                int[] pTable = Arrays.copyOfRange(piHashTable, piHashOffsets[k], piHashOffsets[k] + iEntries);
+                MergeVertsSlow(piTriList_in_and_out, mikkTSpace, pTable, iEntries);
+            }
+        }
+    }
+
+    static void MergeVertsFast(int piTriList_in_and_out[], TmpVert pTmpVert[], final MikkTSpaceContext mikkTSpace, final int iL_in, final int iR_in) {
+        // make bbox        
+        float[] fvMin = new float[3];
+        float[] fvMax = new float[3];
+        for (int c = 0; c < 3; c++) {
+            fvMin[c] = pTmpVert[iL_in].vert[c];
+            fvMax[c] = fvMin[c];
+        }
+        for (int l = (iL_in + 1); l <= iR_in; l++) {
+            for (int c = 0; c < 3; c++) {
+                if (fvMin[c] > pTmpVert[l].vert[c]) {
+                    fvMin[c] = pTmpVert[l].vert[c];
+                } else if (fvMax[c] < pTmpVert[l].vert[c]) {
+                    fvMax[c] = pTmpVert[l].vert[c];
+                }
+            }
+        }
+
+        float dx = fvMax[0] - fvMin[0];
+        float dy = fvMax[1] - fvMin[1];
+        float dz = fvMax[2] - fvMin[2];
+
+        int channel = 0;
+        if (dy > dx && dy > dz) {
+            channel = 1;
+        } else if (dz > dx) {
+            channel = 2;
+        }
+
+        float fSep = 0.5f * (fvMax[channel] + fvMin[channel]);
+
+        // terminate recursion when the separation/average value
+        // is no longer strictly between fMin and fMax values.
+        if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) {
+            // complete the weld
+            for (int l = iL_in; l <= iR_in; l++) {
+                int i = pTmpVert[l].index;
+                final int index = piTriList_in_and_out[i];
+                final Vector3f vP = getPosition(mikkTSpace, index);
+                final Vector3f vN = getNormal(mikkTSpace, index);
+                final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+                boolean bNotFound = true;
+                int l2 = iL_in, i2rec = -1;
+                while (l2 < l && bNotFound) {
+                    final int i2 = pTmpVert[l2].index;
+                    final int index2 = piTriList_in_and_out[i2];
+                    final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                    final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                    final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+                    i2rec = i2;
+
+                    //if (vP==vP2 && vN==vN2 && vT==vT2)
+                    if (vP.x == vP2.x && vP.y == vP2.y && vP.z == vP2.z
+                            && vN.x == vN2.x && vN.y == vN2.y && vN.z == vN2.z
+                            && vT.x == vT2.x && vT.y == vT2.y && vT.z == vT2.z) {
+                        bNotFound = false;
+                    } else {
+                        ++l2;
+                    }
+                }
+
+                // merge if previously found
+                if (!bNotFound) {
+                    piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
+                }
+            }
+        } else {
+            int iL = iL_in, iR = iR_in;
+            assert ((iR_in - iL_in) > 0);  // at least 2 entries
+
+            // separate (by fSep) all points between iL_in and iR_in in pTmpVert[]
+            while (iL < iR) {
+                boolean bReadyLeftSwap = false, bReadyRightSwap = false;
+                while ((!bReadyLeftSwap) && iL < iR) {
+                    assert (iL >= iL_in && iL <= iR_in);
+                    bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep);
+                    if (!bReadyLeftSwap) {
+                        ++iL;
+                    }
+                }
+                while ((!bReadyRightSwap) && iL < iR) {
+                    assert (iR >= iL_in && iR <= iR_in);
+                    bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
+                    if (!bReadyRightSwap) {
+                        --iR;
+                    }
+                }
+                assert ((iL < iR) || !(bReadyLeftSwap && bReadyRightSwap));
+
+                if (bReadyLeftSwap && bReadyRightSwap) {
+                    final TmpVert sTmp = pTmpVert[iL];
+                    assert (iL < iR);
+                    pTmpVert[iL] = pTmpVert[iR];
+                    pTmpVert[iR] = sTmp;
+                    ++iL;
+                    --iR;
+                }
+            }
+
+            assert (iL == (iR + 1) || (iL == iR));
+            if (iL == iR) {
+                final boolean bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep;
+                if (bReadyRightSwap) {
+                    ++iL;
+                } else {
+                    --iR;
+                }
+            }
+
+            // only need to weld when there is more than 1 instance of the (x,y,z)
+            if (iL_in < iR) {
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL_in, iR);  // weld all left of fSep
+            }
+            if (iL < iR_in) {
+                MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL, iR_in);  // weld all right of (or equal to) fSep
+            }
+        }
+    }
+
+    //TODO Nehon: Used only if an array failed to be allocated... Can't happen in Java...
+    static void MergeVertsSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int pTable[], final int iEntries) {
+        // this can be optimized further using a tree structure or more hashing.
+        for (int e = 0; e < iEntries; e++) {
+            int i = pTable[e];
+            final int index = piTriList_in_and_out[i];
+            final Vector3f vP = getPosition(mikkTSpace, index);
+            final Vector3f vN = getNormal(mikkTSpace, index);
+            final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+            boolean bNotFound = true;
+            int e2 = 0, i2rec = -1;
+            while (e2 < e && bNotFound) {
+                final int i2 = pTable[e2];
+                final int index2 = piTriList_in_and_out[i2];
+                final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+                i2rec = i2;
+
+                if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) {
+                    bNotFound = false;
+                } else {
+                    ++e2;
+                }
+            }
+
+            // merge if previously found
+            if (!bNotFound) {
+                piTriList_in_and_out[i] = piTriList_in_and_out[i2rec];
+            }
+        }
+    }
+
+    //TODO Nehon : Not used...seemsit's used in the original version if the structure to store the data in the regular method failed...
+    static void generateSharedVerticesIndexListSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+        int iNumUniqueVerts = 0;
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            for (int i = 0; i < 3; i++) {
+                final int offs = t * 3 + i;
+                final int index = piTriList_in_and_out[offs];
+
+                final Vector3f vP = getPosition(mikkTSpace, index);
+                final Vector3f vN = getNormal(mikkTSpace, index);
+                final Vector3f vT = getTexCoord(mikkTSpace, index);
+
+                boolean bFound = false;
+                int t2 = 0, index2rec = -1;
+                while (!bFound && t2 <= t) {
+                    int j = 0;
+                    while (!bFound && j < 3) {
+                        final int index2 = piTriList_in_and_out[t2 * 3 + j];
+                        final Vector3f vP2 = getPosition(mikkTSpace, index2);
+                        final Vector3f vN2 = getNormal(mikkTSpace, index2);
+                        final Vector3f vT2 = getTexCoord(mikkTSpace, index2);
+
+                        if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) {
+                            bFound = true;
+                        } else {
+                            ++j;
+                        }
+                    }
+                    if (!bFound) {
+                        ++t2;
+                    }
+                }
+
+                assert (bFound);
+                // if we found our own
+                if (index2rec == index) {
+                    ++iNumUniqueVerts;
+                }
+
+                piTriList_in_and_out[offs] = index2rec;
+            }
+        }
+    }
+
+    static int generateInitialVerticesIndexList(TriInfo pTriInfos[], int piTriList_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+        int iTSpacesOffs = 0;
+        int iDstTriIndex = 0;
+        for (int f = 0; f < mikkTSpace.getNumFaces(); f++) {
+            final int verts = mikkTSpace.getNumVerticesOfFace(f);
+            if (verts != 3 && verts != 4) {
+                continue;
+            }
+
+            //TODO nehon : clean this, have a local TrinInfo and assign it to pTriInfo[iDstTriIndex] at the end... and change those variables names...
+            pTriInfos[iDstTriIndex] = new TriInfo();
+            pTriInfos[iDstTriIndex].orgFaceNumber = f;
+            pTriInfos[iDstTriIndex].tSpacesOffs = iTSpacesOffs;
+
+            if (verts == 3) {
+                //TODO same here it should be easy once the local TriInfo is created.
+                byte[] pVerts = pTriInfos[iDstTriIndex].vertNum;
+                pVerts[0] = 0;
+                pVerts[1] = 1;
+                pVerts[2] = 2;
+                piTriList_out[iDstTriIndex * 3 + 0] = makeIndex(f, 0);
+                piTriList_out[iDstTriIndex * 3 + 1] = makeIndex(f, 1);
+                piTriList_out[iDstTriIndex * 3 + 2] = makeIndex(f, 2);
+                ++iDstTriIndex;  // next
+            } else {
+                //Note, Nehon: we should never get there with JME, because we don't support quads... 
+                //but I'm going to let it there incase someone needs it... Just know this code is not tested.
+                {//TODO remove those useless brackets...
+                    pTriInfos[iDstTriIndex + 1].orgFaceNumber = f;
+                    pTriInfos[iDstTriIndex + 1].tSpacesOffs = iTSpacesOffs;
+                }
+
+                {
+                    // need an order independent way to evaluate
+                    // tspace on quads. This is done by splitting
+                    // along the shortest diagonal.
+                    final int i0 = makeIndex(f, 0);
+                    final int i1 = makeIndex(f, 1);
+                    final int i2 = makeIndex(f, 2);
+                    final int i3 = makeIndex(f, 3);
+                    final Vector3f T0 = getTexCoord(mikkTSpace, i0);
+                    final Vector3f T1 = getTexCoord(mikkTSpace, i1);
+                    final Vector3f T2 = getTexCoord(mikkTSpace, i2);
+                    final Vector3f T3 = getTexCoord(mikkTSpace, i3);
+                    final float distSQ_02 = T2.subtract(T0).lengthSquared();
+                    final float distSQ_13 = T3.subtract(T1).lengthSquared();
+                    boolean bQuadDiagIs_02;
+                    if (distSQ_02 < distSQ_13) {
+                        bQuadDiagIs_02 = true;
+                    } else if (distSQ_13 < distSQ_02) {
+                        bQuadDiagIs_02 = false;
+                    } else {
+                        final Vector3f P0 = getPosition(mikkTSpace, i0);
+                        final Vector3f P1 = getPosition(mikkTSpace, i1);
+                        final Vector3f P2 = getPosition(mikkTSpace, i2);
+                        final Vector3f P3 = getPosition(mikkTSpace, i3);
+                        final float distSQ_022 = P2.subtract(P0).lengthSquared();
+                        final float distSQ_132 = P3.subtract(P1).lengthSquared();
+
+                        bQuadDiagIs_02 = distSQ_132 >= distSQ_022;
+                    }
+
+                    if (bQuadDiagIs_02) {
+                        {
+                            byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_A[0] = 0;
+                            pVerts_A[1] = 1;
+                            pVerts_A[2] = 2;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i2;
+                        ++iDstTriIndex;  // next
+                        {
+                            byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_B[0] = 0;
+                            pVerts_B[1] = 2;
+                            pVerts_B[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i2;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                    } else {
+                        {
+                            byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_A[0] = 0;
+                            pVerts_A[1] = 1;
+                            pVerts_A[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i0;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                        {
+                            byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum;
+                            pVerts_B[0] = 1;
+                            pVerts_B[1] = 2;
+                            pVerts_B[2] = 3;
+                        }
+                        piTriList_out[iDstTriIndex * 3 + 0] = i1;
+                        piTriList_out[iDstTriIndex * 3 + 1] = i2;
+                        piTriList_out[iDstTriIndex * 3 + 2] = i3;
+                        ++iDstTriIndex;  // next
+                    }
+                }
+            }
+
+            iTSpacesOffs += verts;
+            assert (iDstTriIndex <= iNrTrianglesIn);
+        }
+
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            pTriInfos[t].flag = 0;
+        }
+
+        // return total amount of tspaces
+        return iTSpacesOffs;
+    }
+
+    static Vector3f getPosition(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] pos = new float[3];
+        indexToData(iF, iI, index);
+        mikkTSpace.getPosition(pos, iF[0], iI[0]);
+        return new Vector3f(pos[0], pos[1], pos[2]);
+    }
+
+    static Vector3f getNormal(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] norm = new float[3];
+        indexToData(iF, iI, index);
+        mikkTSpace.getNormal(norm, iF[0], iI[0]);
+        return new Vector3f(norm[0], norm[1], norm[2]);
+    }
+
+    static Vector3f getTexCoord(final MikkTSpaceContext mikkTSpace, final int index) {
+        //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData
+        int[] iF = new int[1];
+        int[] iI = new int[1];
+        float[] texc = new float[2];
+        indexToData(iF, iI, index);
+        mikkTSpace.getTexCoord(texc, iF[0], iI[0]);
+        return new Vector3f(texc[0], texc[1], 1.0f);
+    }
+
+    // returns the texture area times 2
+    static float calcTexArea(final MikkTSpaceContext mikkTSpace, final int indices[]) {
+        final Vector3f t1 = getTexCoord(mikkTSpace, indices[0]);
+        final Vector3f t2 = getTexCoord(mikkTSpace, indices[1]);
+        final Vector3f t3 = getTexCoord(mikkTSpace, indices[2]);
+
+        final float t21x = t2.x - t1.x;
+        final float t21y = t2.y - t1.y;
+        final float t31x = t3.x - t1.x;
+        final float t31y = t3.y - t1.y;
+
+        final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
+
+        return fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2;
+    }
+
+    private static boolean isNotZero(float v) {
+        return Math.abs(v) > 0;
+    }
+
+    static void initTriInfo(TriInfo pTriInfos[], final int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) {
+
+        // pTriInfos[f].flag is cleared in GenerateInitialVerticesIndexList() which is called before this function.
+        // generate neighbor info list
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                pTriInfos[f].faceNeighbors[i] = -1;
+                pTriInfos[f].assignedGroup[i] = null;
+
+                pTriInfos[f].os.x = 0.0f;
+                pTriInfos[f].os.y = 0.0f;
+                pTriInfos[f].os.z = 0.0f;
+                pTriInfos[f].ot.x = 0.0f;
+                pTriInfos[f].ot.y = 0.0f;
+                pTriInfos[f].ot.z = 0.0f;
+                pTriInfos[f].magS = 0;
+                pTriInfos[f].magT = 0;
+
+                // assumed bad
+                pTriInfos[f].flag |= GROUP_WITH_ANY;
+            }
+        }
+
+        // evaluate first order derivatives
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            // initial values
+            final Vector3f v1 = getPosition(mikkTSpace, piTriListIn[f * 3 + 0]);
+            final Vector3f v2 = getPosition(mikkTSpace, piTriListIn[f * 3 + 1]);
+            final Vector3f v3 = getPosition(mikkTSpace, piTriListIn[f * 3 + 2]);
+            final Vector3f t1 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 0]);
+            final Vector3f t2 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 1]);
+            final Vector3f t3 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 2]);
+
+            final float t21x = t2.x - t1.x;
+            final float t21y = t2.y - t1.y;
+            final float t31x = t3.x - t1.x;
+            final float t31y = t3.y - t1.y;
+            final Vector3f d1 = v2.subtract(v1);
+            final Vector3f d2 = v3.subtract(v1);
+
+            final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x;
+            //assert(fSignedAreaSTx2!=0);
+            Vector3f vOs = d1.mult(t31y).subtract(d2.mult(t21y));  // eq 18
+            Vector3f vOt = d1.mult(-t31x).add(d2.mult(t21x));  // eq 19
+
+            pTriInfos[f].flag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0);
+
+            if (isNotZero(fSignedAreaSTx2)) {
+                final float fAbsArea = Math.abs(fSignedAreaSTx2);
+                final float fLenOs = vOs.length();
+                final float fLenOt = vOt.length();
+                final float fS = (pTriInfos[f].flag & ORIENT_PRESERVING) == 0 ? (-1.0f) : 1.0f;
+                if (isNotZero(fLenOs)) {
+                    pTriInfos[f].os = vOs.multLocal(fS / fLenOs);
+                }
+                if (isNotZero(fLenOt)) {
+                    pTriInfos[f].ot = vOt.multLocal(fS / fLenOt);
+                }
+
+                // evaluate magnitudes prior to normalization of vOs and vOt
+                pTriInfos[f].magS = fLenOs / fAbsArea;
+                pTriInfos[f].magT = fLenOt / fAbsArea;
+
+                // if this is a good triangle
+                if (isNotZero(pTriInfos[f].magS) && isNotZero(pTriInfos[f].magT)) {
+                    pTriInfos[f].flag &= (~GROUP_WITH_ANY);
+                }
+            }
+        }
+
+        // force otherwise healthy quads to a fixed orientation
+        int t = 0;
+        while (t < (iNrTrianglesIn - 1)) {
+            final int iFO_a = pTriInfos[t].orgFaceNumber;
+            final int iFO_b = pTriInfos[t + 1].orgFaceNumber;
+            if (iFO_a == iFO_b) {
+                // this is a quad
+                final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0;
+                final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0;
+
+                // bad triangles should already have been removed by
+                // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false
+                if ((bIsDeg_a || bIsDeg_b) == false) {
+                    final boolean bOrientA = (pTriInfos[t].flag & ORIENT_PRESERVING) != 0;
+                    final boolean bOrientB = (pTriInfos[t + 1].flag & ORIENT_PRESERVING) != 0;
+                    // if this happens the quad has extremely bad mapping!!
+                    if (bOrientA != bOrientB) {
+                        //printf("found quad with bad mapping\n");
+                        boolean bChooseOrientFirstTri = false;
+                        if ((pTriInfos[t + 1].flag & GROUP_WITH_ANY) != 0) {
+                            bChooseOrientFirstTri = true;
+                        } else if (calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, t * 3 + 0, t * 3 + 3)) >= calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, (t + 1) * 3 + 0, (t + 1) * 3 + 3))) {
+                            bChooseOrientFirstTri = true;
+                        }
+
+                        // force match
+                        {
+                            final int t0 = bChooseOrientFirstTri ? t : (t + 1);
+                            final int t1 = bChooseOrientFirstTri ? (t + 1) : t;
+                            pTriInfos[t1].flag &= (~ORIENT_PRESERVING);  // clear first
+                            pTriInfos[t1].flag |= (pTriInfos[t0].flag & ORIENT_PRESERVING);  // copy bit
+                        }
+                    }
+                }
+                t += 2;
+            } else {
+                ++t;
+            }
+        }
+
+        // match up edge pairs
+        {
+            //Edge * pEdges = (Edge *) malloc(sizeof(Edge)*iNrTrianglesIn*3);
+            Edge[] pEdges = new Edge[iNrTrianglesIn * 3];
+
+            //TODO nehon weird... original algorithm check if pEdges is null but it's just been allocated... weirder, it does soemthing different if the edges are null...
+            //    if (pEdges==null)
+            //      BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn);
+            //    else
+            //    {
+            buildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn);
+
+            //    }
+        }
+    }
+
+    static int build4RuleGroups(TriInfo pTriInfos[], Group pGroups[], int piGroupTrianglesBuffer[], final int piTriListIn[], final int iNrTrianglesIn) {
+        final int iNrMaxGroups = iNrTrianglesIn * 3;
+        int iNrActiveGroups = 0;
+        int iOffset = 0;
+        // (void)iNrMaxGroups;  /* quiet warnings in non debug mode */
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                // if not assigned to a group
+                if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0 && pTriInfos[f].assignedGroup[i] == null) {
+                    boolean bOrPre;                    
+                    final int vert_index = piTriListIn[f * 3 + i];
+                    assert (iNrActiveGroups < iNrMaxGroups);
+                    pTriInfos[f].assignedGroup[i] = new Group(); 
+                    pGroups[iNrActiveGroups] = pTriInfos[f].assignedGroup[i];
+                    pTriInfos[f].assignedGroup[i].vertexRepresentitive = vert_index;
+                    pTriInfos[f].assignedGroup[i].orientPreservering = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0;
+                    pTriInfos[f].assignedGroup[i].nrFaces = 0;
+                    
+                    ++iNrActiveGroups;
+
+                    addTriToGroup(pTriInfos[f].assignedGroup[i], f);
+                    bOrPre = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0;
+                    int neigh_indexL = pTriInfos[f].faceNeighbors[i];
+                    int neigh_indexR = pTriInfos[f].faceNeighbors[i > 0 ? (i - 1) : 2];
+                    if (neigh_indexL >= 0) {
+                        // neighbor
+                        final boolean bAnswer
+                                = assignRecur(piTriListIn, pTriInfos, neigh_indexL,
+                                        pTriInfos[f].assignedGroup[i]);
+
+                        final boolean bOrPre2 = (pTriInfos[neigh_indexL].flag & ORIENT_PRESERVING) != 0;
+                        final boolean bDiff = bOrPre != bOrPre2;
+                        assert (bAnswer || bDiff);
+                        //(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */
+                    }
+                    if (neigh_indexR >= 0) {
+                        // neighbor
+                        final boolean bAnswer
+                                = assignRecur(piTriListIn, pTriInfos, neigh_indexR,
+                                        pTriInfos[f].assignedGroup[i]);
+
+                        final boolean bOrPre2 = (pTriInfos[neigh_indexR].flag & ORIENT_PRESERVING) != 0;
+                        final boolean bDiff = bOrPre != bOrPre2;
+                        assert (bAnswer || bDiff);
+                        //(void)bAnswer, (void)bDiff;  /* quiet warnings in non debug mode */
+                    }
+
+                    int[] faceIndices = new int[pTriInfos[f].assignedGroup[i].nrFaces];
+                    //pTriInfos[f].assignedGroup[i].faceIndices.toArray(faceIndices);
+                    for (int j = 0; j < faceIndices.length; j++) {
+                        faceIndices[j] = pTriInfos[f].assignedGroup[i].faceIndices.get(j);
+                    }
+                    
+                    //Nehon: copy back the faceIndices data into the groupTriangleBuffer.
+                    System.arraycopy( faceIndices, 0, piGroupTrianglesBuffer, iOffset, pTriInfos[f].assignedGroup[i].nrFaces);
+                    // update offset
+                    iOffset += pTriInfos[f].assignedGroup[i].nrFaces;
+                    // since the groups are disjoint a triangle can never
+                    // belong to more than 3 groups. Subsequently something
+                    // is completely screwed if this assertion ever hits.
+                    assert (iOffset <= iNrMaxGroups);
+                }
+            }
+        }
+
+        return iNrActiveGroups;
+    }
+
+    static void addTriToGroup(Group group, final int triIndex) {
+        //group.faceIndices[group.nrFaces] = triIndex;
+        group.faceIndices.add(triIndex);
+        ++group.nrFaces;
+    }
+
+    static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final int iMyTriIndex, Group pGroup) {
+        TriInfo pMyTriInfo = psTriInfos[iMyTriIndex];
+
+        // track down vertex
+        final int iVertRep = pGroup.vertexRepresentitive;
+        int index = 3 * iMyTriIndex;
+        int i = -1;
+        if (piTriListIn[index] == iVertRep) {
+            i = 0;
+        } else if (piTriListIn[index + 1] == iVertRep) {
+            i = 1;
+        } else if (piTriListIn[index + 2] == iVertRep) {
+            i = 2;
+        }
+        assert (i >= 0 && i < 3);
+
+        // early out
+        if (pMyTriInfo.assignedGroup[i] == pGroup) {
+            return true;
+        } else if (pMyTriInfo.assignedGroup[i] != null) {
+            return false;
+        }
+        if ((pMyTriInfo.flag & GROUP_WITH_ANY) != 0) {
+            // first to group with a group-with-anything triangle
+            // determines it's orientation.
+            // This is the only existing order dependency in the code!!
+            if (pMyTriInfo.assignedGroup[0] == null
+                    && pMyTriInfo.assignedGroup[1] == null
+                    && pMyTriInfo.assignedGroup[2] == null) {
+                pMyTriInfo.flag &= (~ORIENT_PRESERVING);
+                pMyTriInfo.flag |= (pGroup.orientPreservering ? ORIENT_PRESERVING : 0);
+            }
+        }
+        {
+            final boolean bOrient = (pMyTriInfo.flag & ORIENT_PRESERVING) != 0;
+            if (bOrient != pGroup.orientPreservering) {
+                return false;
+            }
+        }
+
+        addTriToGroup(pGroup, iMyTriIndex);
+        pMyTriInfo.assignedGroup[i] = pGroup;
+
+        {
+            final int neigh_indexL = pMyTriInfo.faceNeighbors[i];
+            final int neigh_indexR = pMyTriInfo.faceNeighbors[i > 0 ? (i - 1) : 2];
+            if (neigh_indexL >= 0) {
+                assignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup);
+            }
+            if (neigh_indexR >= 0) {
+                assignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup);
+            }
+        }
+
+        return true;
+    }
+
+    static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], final Group pGroups[],
+            final int iNrActiveGroups, final int piTriListIn[], final float fThresCos,
+            final MikkTSpaceContext mikkTSpace) {
+        TSpace[] pSubGroupTspace;
+        SubGroup[] pUniSubGroups;
+        int[] pTmpMembers;
+        int iMaxNrFaces = 0, iUniqueTspaces = 0, g = 0, i = 0;
+        for (g = 0; g < iNrActiveGroups; g++) {
+            if (iMaxNrFaces < pGroups[g].nrFaces) {
+                iMaxNrFaces = pGroups[g].nrFaces;
+            }
+        }
+
+        if (iMaxNrFaces == 0) {
+            return true;
+        }
+
+        // make initial allocations
+        pSubGroupTspace = new TSpace[iMaxNrFaces];
+        pUniSubGroups = new SubGroup[iMaxNrFaces];
+        pTmpMembers = new int[iMaxNrFaces];
+
+
+        iUniqueTspaces = 0;
+        for (g = 0; g < iNrActiveGroups; g++) {
+            final Group pGroup = pGroups[g];
+            int iUniqueSubGroups = 0, s = 0;
+
+            for (i = 0; i < pGroup.nrFaces; i++) // triangles
+            {
+                final int f = pGroup.faceIndices.get(i);  // triangle number
+                int index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0;
+                SubGroup tmp_group = new SubGroup();
+                boolean bFound;
+                Vector3f n, vOs, vOt;
+                if (pTriInfos[f].assignedGroup[0] == pGroup) {
+                    index = 0;
+                } else if (pTriInfos[f].assignedGroup[1] == pGroup) {
+                    index = 1;
+                } else if (pTriInfos[f].assignedGroup[2] == pGroup) {
+                    index = 2;
+                }
+                assert (index >= 0 && index < 3);
+
+                iVertIndex = piTriListIn[f * 3 + index];
+                assert (iVertIndex == pGroup.vertexRepresentitive);
+
+                // is normalized already
+                n = getNormal(mikkTSpace, iVertIndex);
+
+                // project
+                vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os)));
+                vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot)));
+                vOs.normalizeLocal();
+                vOt.normalizeLocal();
+
+                // original face number
+                iOF_1 = pTriInfos[f].orgFaceNumber;
+
+                iMembers = 0;
+                for (j = 0; j < pGroup.nrFaces; j++) {
+                    final int t = pGroup.faceIndices.get(j);  // triangle number
+                    final int iOF_2 = pTriInfos[t].orgFaceNumber;
+
+                    // project
+                    Vector3f vOs2 = pTriInfos[t].os.subtract(n.mult(n.dot(pTriInfos[t].os)));
+                    Vector3f vOt2 = pTriInfos[t].ot.subtract(n.mult(n.dot(pTriInfos[t].ot)));
+                    vOs2.normalizeLocal();
+                    vOt2.normalizeLocal();
+
+                    {
+                        final boolean bAny = ((pTriInfos[f].flag | pTriInfos[t].flag) & GROUP_WITH_ANY) != 0;
+                        // make sure triangles which belong to the same quad are joined.
+                        final boolean bSameOrgFace = iOF_1 == iOF_2;
+
+                        final float fCosS = vOs.dot(vOs2);
+                        final float fCosT = vOt.dot(vOt2);
+
+                        assert (f != t || bSameOrgFace);  // sanity check
+                        if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) {
+                            pTmpMembers[iMembers++] = t;
+                        }
+                    }
+                }
+
+                // sort pTmpMembers
+                tmp_group.nrFaces = iMembers;
+                tmp_group.triMembers = pTmpMembers;
+                if (iMembers > 1) {
+                    quickSort(pTmpMembers, 0, iMembers - 1, INTERNAL_RND_SORT_SEED);
+                }
+
+                // look for an existing match
+                bFound = false;
+                l = 0;
+                while (l < iUniqueSubGroups && !bFound) {
+                    bFound = compareSubGroups(tmp_group, pUniSubGroups[l]);
+                    if (!bFound) {
+                        ++l;
+                    }
+                }
+
+                // assign tangent space index
+                assert (bFound || l == iUniqueSubGroups);
+                //piTempTangIndices[f*3+index] = iUniqueTspaces+l;
+
+                // if no match was found we allocate a new subgroup
+                if (!bFound) {
+                    // insert new subgroup
+                    int[] pIndices = new int[iMembers];
+                    pUniSubGroups[iUniqueSubGroups] = new SubGroup();
+                    pUniSubGroups[iUniqueSubGroups].nrFaces = iMembers;
+                    pUniSubGroups[iUniqueSubGroups].triMembers = pIndices;
+                    System.arraycopy(tmp_group.triMembers, 0, pIndices, 0, iMembers);
+                    //memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int));
+                    pSubGroupTspace[iUniqueSubGroups]
+                            = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentitive);
+                    ++iUniqueSubGroups;
+                }
+
+                // output tspace
+                {
+                    final int iOffs = pTriInfos[f].tSpacesOffs;
+                    final int iVert = pTriInfos[f].vertNum[index];
+                    TSpace pTS_out = psTspace[iOffs + iVert];
+                    assert (pTS_out.counter < 2);
+                    assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientPreservering);
+                    if (pTS_out.counter == 1) {
+                        pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l]));
+                        pTS_out.counter = 2;  // update counter
+                        pTS_out.orient = pGroup.orientPreservering;
+                    } else {
+                        assert (pTS_out.counter == 0);
+                        pTS_out.set(pSubGroupTspace[l]);
+                        pTS_out.counter = 1;  // update counter
+                        pTS_out.orient = pGroup.orientPreservering;
+                    }
+                }
+            }
+
+            iUniqueTspaces += iUniqueSubGroups;
+        }
+
+        return true;
+    }
+
+    static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriListIn[], final TriInfo pTriInfos[],
+            final MikkTSpaceContext mikkTSpace, final int iVertexRepresentitive) {
+        TSpace res = new TSpace();
+        float fAngleSum = 0;        
+
+        for (int face = 0; face < iFaces; face++) {
+            final int f = face_indices[face];
+
+            // only valid triangles get to add their contribution
+            if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0) {
+                
+                int i = -1;
+                if (piTriListIn[3 * f + 0] == iVertexRepresentitive) {
+                    i = 0;
+                } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) {
+                    i = 1;
+                } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) {
+                    i = 2;
+                }
+                assert (i >= 0 && i < 3);
+
+                // project
+                int index = piTriListIn[3 * f + i];
+                Vector3f n = getNormal(mikkTSpace, index);
+                Vector3f vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os)));
+                Vector3f vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot)));
+                vOs.normalizeLocal();
+                vOt.normalizeLocal();
+
+                int i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)];
+                int i1 = piTriListIn[3 * f + i];
+                int i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)];
+
+                Vector3f p0 = getPosition(mikkTSpace, i0);
+                Vector3f p1 = getPosition(mikkTSpace, i1);
+                Vector3f  p2 = getPosition(mikkTSpace, i2);
+                Vector3f v1 = p0.subtract(p1);
+                Vector3f v2 = p2.subtract(p1);
+
+                // project
+                v1.subtractLocal(n.mult(n.dot(v1))).normalizeLocal();
+                v2.subtractLocal(n.mult(n.dot(v2))).normalizeLocal();
+
+                // weight contribution by the angle
+                // between the two edge vectors
+                float fCos = v1.dot(v2);
+                fCos = fCos > 1 ? 1 : (fCos < (-1) ? (-1) : fCos);
+                float fAngle = (float) Math.acos(fCos);
+                float fMagS = pTriInfos[f].magS;
+                float fMagT = pTriInfos[f].magT;
+
+                res.os.addLocal(vOs.multLocal(fAngle));
+                res.ot.addLocal(vOt.multLocal(fAngle));
+                res.magS += (fAngle * fMagS);
+                res.magT += (fAngle * fMagT);
+                fAngleSum += fAngle;
+            }
+        }
+
+        // normalize
+        res.os.normalizeLocal();
+        res.ot.normalizeLocal();
+
+        if (fAngleSum > 0) {
+            res.magS /= fAngleSum;
+            res.magT /= fAngleSum;
+        }
+
+        return res;
+    }
+
+    static boolean compareSubGroups(final SubGroup pg1, final SubGroup pg2) {
+        if(pg2 == null || (pg1.nrFaces != pg2.nrFaces)){
+            return false;
+        }
+        boolean stillSame = true;
+        int i = 0;        
+        while (i < pg1.nrFaces && stillSame) {
+            stillSame = pg1.triMembers[i] == pg2.triMembers[i];
+            if (stillSame) {
+                ++i;
+            }
+        }
+        return stillSame;
+    }
+
+    static void quickSort(int[] pSortBuffer, int iLeft, int iRight, long uSeed) {
+        int iL, iR, n, index, iMid, iTmp;
+
+        // Random
+        long t = uSeed & 31;
+        t = (uSeed << t) | (uSeed >> (32 - t));
+        uSeed = uSeed + t + 3;
+        // Random end
+        uSeed = uSeed & 0xffffffffL;
+
+        iL = iLeft;
+        iR = iRight;
+        n = (iR - iL) + 1;
+        assert (n >= 0);
+        index = (int) ((uSeed & 0xffffffffL) % n);
+
+        iMid = pSortBuffer[index + iL];
+
+        do {
+            while (pSortBuffer[iL] < iMid) {
+                ++iL;
+            }
+            while (pSortBuffer[iR] > iMid) {
+                --iR;
+            }
+
+            if (iL <= iR) {
+                iTmp = pSortBuffer[iL];
+                pSortBuffer[iL] = pSortBuffer[iR];
+                pSortBuffer[iR] = iTmp;
+                ++iL;
+                --iR;
+            }
+        } while (iL <= iR);
+
+        if (iLeft < iR) {
+            quickSort(pSortBuffer, iLeft, iR, uSeed);
+        }
+        if (iL < iRight) {
+            quickSort(pSortBuffer, iL, iRight, uSeed);
+        }
+    }
+
+    static void buildNeighborsFast(TriInfo pTriInfos[], Edge[] pEdges, final int piTriListIn[], final int iNrTrianglesIn) {
+        // build array of edges
+        long uSeed = INTERNAL_RND_SORT_SEED;        // could replace with a random seed?
+        
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                final int i0 = piTriListIn[f * 3 + i];
+                final int i1 = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
+                pEdges[f * 3 + i] = new Edge();
+                pEdges[f * 3 + i].setI0(i0 < i1 ? i0 : i1);      // put minimum index in i0
+                pEdges[f * 3 + i].setI1(!(i0 < i1) ? i0 : i1);    // put maximum index in i1
+                pEdges[f * 3 + i].setF(f);              // record face number
+            }
+        }
+
+        // sort over all edges by i0, this is the pricy one.
+        quickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed);  // sort channel 0 which is i0
+
+        // sub sort over i1, should be fast.
+        // could replace this with a 64 bit int sort over (i0,i1)
+        // with i0 as msb in the quicksort call above.
+        int iEntries = iNrTrianglesIn * 3;
+        int iCurStartIndex = 0;
+        for (int i = 1; i < iEntries; i++) {
+            if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0()) {
+                final int iL = iCurStartIndex;
+                final int iR = i - 1;
+                //final int iElems = i-iL;
+                iCurStartIndex = i;
+                quickSortEdges(pEdges, iL, iR, 1, uSeed);  // sort channel 1 which is i1
+            }
+        }
+
+        // sub sort over f, which should be fast.
+        // this step is to remain compliant with BuildNeighborsSlow() when
+        // more than 2 triangles use the same edge (such as a butterfly topology).
+        iCurStartIndex = 0;
+        for (int i = 1; i < iEntries; i++) {
+            if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0() || pEdges[iCurStartIndex].getI1() != pEdges[i].getI1()) {
+                final int iL = iCurStartIndex;
+                final int iR = i - 1;
+                //final int iElems = i-iL;
+                iCurStartIndex = i;
+                quickSortEdges(pEdges, iL, iR, 2, uSeed);  // sort channel 2 which is f
+            }
+        }
+
+        // pair up, adjacent triangles
+        for (int i = 0; i < iEntries; i++) {
+            final int i0 = pEdges[i].getI0();
+            final int i1 = pEdges[i].getI1();
+            final int g = pEdges[i].getF();
+            boolean bUnassigned_A;
+
+            int[] i0_A = new int[1];
+            int[] i1_A = new int[1];
+            int[] edgenum_A = new int[1];
+            int[] edgenum_B = new int[1];
+            //int edgenum_B=0;  // 0,1 or 2
+            int[] triList = new int[3];
+            System.arraycopy(piTriListIn, g * 3, triList, 0, 3);
+            getEdge(i0_A, i1_A, edgenum_A, triList, i0, i1);  // resolve index ordering and edge_num
+            bUnassigned_A = pTriInfos[g].faceNeighbors[edgenum_A[0]] == -1;
+
+            if (bUnassigned_A) {
+                // get true index ordering
+                int j = i + 1, t;
+                boolean bNotFound = true;
+                while (j < iEntries && i0 == pEdges[j].getI0() && i1 == pEdges[j].getI1() && bNotFound) {
+                    boolean bUnassigned_B;
+                    int[] i0_B = new int[1];
+                    int[] i1_B = new int[1];
+                    t = pEdges[j].getF();
+                    // flip i0_B and i1_B
+                    System.arraycopy(piTriListIn, t * 3, triList, 0, 3);
+                    getEdge(i1_B, i0_B, edgenum_B, triList, pEdges[j].getI0(), pEdges[j].getI1());  // resolve index ordering and edge_num
+                    //assert(!(i0_A==i1_B && i1_A==i0_B));
+                    bUnassigned_B = pTriInfos[t].faceNeighbors[edgenum_B[0]] == -1;
+                    if (i0_A[0] == i0_B[0] && i1_A[0] == i1_B[0] && bUnassigned_B) {
+                        bNotFound = false;
+                    } else {
+                        ++j;
+                    }
+                }
+
+                if (!bNotFound) {
+                    int t2 = pEdges[j].getF();
+                    pTriInfos[g].faceNeighbors[edgenum_A[0]] = t2;
+                    //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1);
+                    pTriInfos[t2].faceNeighbors[edgenum_B[0]] = g;
+                }
+            }
+        }
+    }
+
+    static void buildNeighborsSlow(TriInfo pTriInfos[], final int piTriListIn[], final int iNrTrianglesIn) {
+        
+        for (int f = 0; f < iNrTrianglesIn; f++) {
+            for (int i = 0; i < 3; i++) {
+                // if unassigned
+                if (pTriInfos[f].faceNeighbors[i] == -1) {
+                    final int i0_A = piTriListIn[f * 3 + i];
+                    final int i1_A = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)];
+
+                    // search for a neighbor
+                    boolean bFound = false;
+                    int t = 0, j = 0;
+                    while (!bFound && t < iNrTrianglesIn) {
+                        if (t != f) {
+                            j = 0;
+                            while (!bFound && j < 3) {
+                                // in rev order
+                                final int i1_B = piTriListIn[t * 3 + j];
+                                final int i0_B = piTriListIn[t * 3 + (j < 2 ? (j + 1) : 0)];
+                                //assert(!(i0_A==i1_B && i1_A==i0_B));
+                                if (i0_A == i0_B && i1_A == i1_B) {
+                                    bFound = true;
+                                } else {
+                                    ++j;
+                                }
+                            }
+                        }
+
+                        if (!bFound) {
+                            ++t;
+                        }
+                    }
+
+                    // assign neighbors
+                    if (bFound) {
+                        pTriInfos[f].faceNeighbors[i] = t;
+                        //assert(pTriInfos[t].FaceNeighbors[j]==-1);
+                        pTriInfos[t].faceNeighbors[j] = f;
+                    }
+                }
+            }
+        }
+    }
+
+    static void quickSortEdges(Edge[] pSortBuffer, int iLeft, int iRight, final int channel, long uSeed) {
+        // early out
+        Edge sTmp;
+        final int iElems = iRight - iLeft + 1;
+        if (iElems < 2) {
+            return;
+        } else if (iElems == 2) {
+            if (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel]) {
+                sTmp = pSortBuffer[iLeft];
+                pSortBuffer[iLeft] = pSortBuffer[iRight];
+                pSortBuffer[iRight] = sTmp;
+            }
+            return;
+        }
+
+        // Random
+        long t = uSeed & 31;
+        t = (uSeed << t) | (uSeed >> (32 - t));
+        uSeed = uSeed + t + 3;
+        // Random end
+        
+        uSeed = uSeed & 0xffffffffL;
+
+        int iL = iLeft;
+        int iR = iRight;
+        int n = (iR - iL) + 1;
+        assert (n >= 0);
+        int index = (int) (uSeed % n);
+
+        int iMid = pSortBuffer[index + iL].array[channel];
+
+        do {
+            while (pSortBuffer[iL].array[channel] < iMid) {
+                ++iL;
+            }
+            while (pSortBuffer[iR].array[channel] > iMid) {
+                --iR;
+            }
+
+            if (iL <= iR) {
+                sTmp = pSortBuffer[iL];
+                pSortBuffer[iL] = pSortBuffer[iR];
+                pSortBuffer[iR] = sTmp;
+                ++iL;
+                --iR;
+            }
+        } while (iL <= iR);
+
+        if (iLeft < iR) {
+            quickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed);
+        }
+        if (iL < iRight) {
+            quickSortEdges(pSortBuffer, iL, iRight, channel, uSeed);
+        }
+    }
+
+// resolve ordering and edge number
+    static void getEdge(int[] i0_out, int[] i1_out, int[] edgenum_out, final int[] indices, final int i0_in, final int i1_in) {
+        edgenum_out[0] = -1;
+
+        // test if first index is on the edge
+        if (indices[0] == i0_in || indices[0] == i1_in) {
+            // test if second index is on the edge
+            if (indices[1] == i0_in || indices[1] == i1_in) {
+                edgenum_out[0] = 0;  // first edge
+                i0_out[0] = indices[0];
+                i1_out[0] = indices[1];
+            } else {
+                edgenum_out[0] = 2;  // third edge
+                i0_out[0] = indices[2];
+                i1_out[0] = indices[0];
+            }
+        } else {
+            // only second and third index is on the edge
+            edgenum_out[0] = 1;  // second edge
+            i0_out[0] = indices[1];
+            i1_out[0] = indices[2];
+        }
+    }
+
+    static void degenPrologue(TriInfo pTriInfos[], int piTriList_out[], final int iNrTrianglesIn, final int iTotTris) {
+        
+        // locate quads with only one good triangle
+        int t = 0;
+        while (t < (iTotTris - 1)) {
+            final int iFO_a = pTriInfos[t].orgFaceNumber;
+            final int iFO_b = pTriInfos[t + 1].orgFaceNumber;
+            if (iFO_a == iFO_b) {
+                // this is a quad
+                final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0;
+                final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0;
+                //TODO nehon : Check this in detail as this operation is utterly strange
+                if ((bIsDeg_a ^ bIsDeg_b) != false) {
+                    pTriInfos[t].flag |= QUAD_ONE_DEGEN_TRI;
+                    pTriInfos[t + 1].flag |= QUAD_ONE_DEGEN_TRI;
+                }
+                t += 2;
+            } else {
+                ++t;
+            }
+        }
+
+        // reorder list so all degen triangles are moved to the back
+        // without reordering the good triangles
+        int iNextGoodTriangleSearchIndex = 1;
+        t = 0;
+        boolean bStillFindingGoodOnes = true;
+        while (t < iNrTrianglesIn && bStillFindingGoodOnes) {
+            final boolean bIsGood = (pTriInfos[t].flag & MARK_DEGENERATE) == 0;
+            if (bIsGood) {
+                if (iNextGoodTriangleSearchIndex < (t + 2)) {
+                    iNextGoodTriangleSearchIndex = t + 2;
+                }
+            } else {                
+                // search for the first good triangle.
+                boolean bJustADegenerate = true;
+                while (bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris) {
+                    final boolean bIsGood2 = (pTriInfos[iNextGoodTriangleSearchIndex].flag & MARK_DEGENERATE) == 0;
+                    if (bIsGood2) {
+                        bJustADegenerate = false;
+                    } else {
+                        ++iNextGoodTriangleSearchIndex;
+                    }
+                }
+
+                int t0 = t;
+                int t1 = iNextGoodTriangleSearchIndex;
+                ++iNextGoodTriangleSearchIndex;
+                assert (iNextGoodTriangleSearchIndex > (t + 1));
+
+                // swap triangle t0 and t1
+                if (!bJustADegenerate) {                    
+                    for (int i = 0; i < 3; i++) {
+                        final int index = piTriList_out[t0 * 3 + i];
+                        piTriList_out[t0 * 3 + i] = piTriList_out[t1 * 3 + i];
+                        piTriList_out[t1 * 3 + i] = index;
+                    }
+                    {
+                        final TriInfo tri_info = pTriInfos[t0];
+                        pTriInfos[t0] = pTriInfos[t1];
+                        pTriInfos[t1] = tri_info;
+                    }
+                } else {
+                    bStillFindingGoodOnes = false;  // this is not supposed to happen
+                }
+            }
+
+            if (bStillFindingGoodOnes) {
+                ++t;
+            }
+        }
+
+        assert (bStillFindingGoodOnes);  // code will still work.
+        assert (iNrTrianglesIn == t);
+    }
+
+    static void DegenEpilogue(TSpace psTspace[], TriInfo pTriInfos[], int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn, final int iTotTris) {
+        
+        // deal with degenerate triangles
+        // punishment for degenerate triangles is O(N^2)
+        for (int t = iNrTrianglesIn; t < iTotTris; t++) {
+            // degenerate triangles on a quad with one good triangle are skipped
+            // here but processed in the next loop
+            final boolean bSkip = (pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0;
+
+            if (!bSkip) {
+                for (int i = 0; i < 3; i++) {
+                    final int index1 = piTriListIn[t * 3 + i];
+                    // search through the good triangles
+                    boolean bNotFound = true;
+                    int j = 0;
+                    while (bNotFound && j < (3 * iNrTrianglesIn)) {
+                        final int index2 = piTriListIn[j];
+                        if (index1 == index2) {
+                            bNotFound = false;
+                        } else {
+                            ++j;
+                        }
+                    }
+
+                    if (!bNotFound) {
+                        final int iTri = j / 3;
+                        final int iVert = j % 3;
+                        final int iSrcVert = pTriInfos[iTri].vertNum[iVert];
+                        final int iSrcOffs = pTriInfos[iTri].tSpacesOffs;
+                        final int iDstVert = pTriInfos[t].vertNum[i];
+                        final int iDstOffs = pTriInfos[t].tSpacesOffs;
+
+                        // copy tspace
+                        psTspace[iDstOffs + iDstVert] = psTspace[iSrcOffs + iSrcVert];
+                    }
+                }
+            }
+        }
+
+        // deal with degenerate quads with one good triangle
+        for (int t = 0; t < iNrTrianglesIn; t++) {
+            // this triangle belongs to a quad where the
+            // other triangle is degenerate
+            if ((pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0) {
+               
+                byte[] pV = pTriInfos[t].vertNum;
+                int iFlag = (1 << pV[0]) | (1 << pV[1]) | (1 << pV[2]);
+                int iMissingIndex = 0;
+                if ((iFlag & 2) == 0) {
+                    iMissingIndex = 1;
+                } else if ((iFlag & 4) == 0) {
+                    iMissingIndex = 2;
+                } else if ((iFlag & 8) == 0) {
+                    iMissingIndex = 3;
+                }
+
+                int iOrgF = pTriInfos[t].orgFaceNumber;
+                Vector3f vDstP = getPosition(mikkTSpace, makeIndex(iOrgF, iMissingIndex));
+                boolean bNotFound = true;
+                int i = 0;
+                while (bNotFound && i < 3) {
+                    final int iVert = pV[i];
+                    final Vector3f vSrcP = getPosition(mikkTSpace, makeIndex(iOrgF, iVert));
+                    if (vSrcP.equals(vDstP)) {
+                        final int iOffs = pTriInfos[t].tSpacesOffs;
+                        psTspace[iOffs + iMissingIndex] = psTspace[iOffs + iVert];
+                        bNotFound = false;
+                    } else {
+                        ++i;
+                    }
+                }
+                assert (!bNotFound);
+            }
+        }
+
+    }    
+
+    /**
+     * SubGroup inner class
+     */
+    private static class SubGroup {
+        int nrFaces;
+        int[] triMembers;
+    }
+
+    private static class Group {
+        int nrFaces;
+        List<Integer> faceIndices = new ArrayList<Integer>();
+        int vertexRepresentitive;
+        boolean orientPreservering;
+    }
+
+    private static class TriInfo {
+
+        int[] faceNeighbors = new int[3];
+        Group[] assignedGroup = new Group[3];
+
+        // normalized first order face derivatives
+        Vector3f os = new Vector3f();
+        Vector3f ot = new Vector3f();
+        float magS, magT;  // original magnitudes
+
+        // determines if the current and the next triangle are a quad.
+        int orgFaceNumber;
+        int flag, tSpacesOffs;
+        byte[] vertNum = new byte[4];
+    }
+
+    private static class TSpace {
+
+        Vector3f os = new Vector3f();
+        float magS;
+        Vector3f ot = new Vector3f();
+        float magT;
+        int counter;  // this is to average back into quads.
+        boolean orient;
+        
+        void set(TSpace ts){
+            os.set(ts.os);
+            magS = ts.magS;
+            ot.set(ts.ot);
+            magT = ts.magT;
+            counter = ts.counter;
+            orient = ts.orient;
+        }
+    }
+
+    private static class TmpVert {
+
+        float vert[] = new float[3];
+        int index;
+    }
+
+    private static class Edge {
+
+        void setI0(int i){            
+            array[0] = i;
+        }
+        
+        void setI1(int i){            
+            array[1] = i;
+        }
+        
+        void setF(int i){            
+            array[2] = i;
+        }
+        
+        int getI0(){            
+            return array[0];
+        }
+        
+        int getI1(){            
+            return array[1];
+        }
+        
+        int getF(){            
+            return array[2];
+        }
+        
+        int[] array = new int[3];
+    }
+
+}