/*
* 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.*;
import com.jme3.util.*;
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 tangent 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());
}
TangentUtils.generateBindPoseTangentsIfNecessary(g.getMesh());
}
}
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...seems it'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];
}
}