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 = FastMath.cos((angularThreshold * 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;
    }
A private constructor to inhibit instantiation of this class.
jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java