diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java
new file mode 100644
index 000000000..dc56ac240
--- /dev/null
+++ b/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.
+ *
+ *
+ * fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
+ * 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 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);
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java
new file mode 100644
index 000000000..190a2e2c4
--- /dev/null
+++ b/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;
+ }
+
+}
diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java
new file mode 100644
index 000000000..af20b842b
--- /dev/null
+++ b/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 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 faceIndices = new ArrayList();
+ 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];
+ }
+
+}