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.vertexRepresentative);

                // 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.vertexRepresentative);
                    ++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.orientationPreserving);
                    if (pTS_out.counter == 1) {
                        pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l]));
                        pTS_out.counter = 2;  // update counter
                        pTS_out.orient = pGroup.orientationPreserving;
                    } else {
                        assert (pTS_out.counter == 0);
                        pTS_out.set(pSubGroupTspace[l]);
                        pTS_out.counter = 1;  // update counter
                        pTS_out.orient = pGroup.orientationPreserving;
                    }
                }
            }

            iUniqueTspaces += iUniqueSubGroups;
        }

        return true;
    }
A private constructor to inhibit instantiation of this class.
jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java