Fixed TangentBinormalGenerator: Added grouping of vertices with the same position and normal. Re-added tangent orientation code to fix some visual artifacts.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8645 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
lex..82 13 years ago
parent 4ba21821a3
commit 687dad1a6b
  1. 249
      engine/src/core/com/jme3/util/TangentBinormalGenerator.java

@ -49,13 +49,13 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import static com.jme3.util.BufferUtils.*; import static com.jme3.util.BufferUtils.*;
/** /**
* @author Lex *
*/ * @author Lex (Aleksey Nikiforov)
*/
public class TangentBinormalGenerator { public class TangentBinormalGenerator {
private static final float ZERO_TOLERANCE = 0.0000001f; private static final float ZERO_TOLERANCE = 0.0000001f;
@ -68,36 +68,37 @@ public class TangentBinormalGenerator {
setToleranceAngle(45); setToleranceAngle(45);
} }
private static class VertexData {
private static class VertexInfo {
public final Vector3f tangent = new Vector3f(); public final Vector3f position;
public final Vector3f binormal = new Vector3f(); public final Vector3f normal;
public final List<TriangleData> triangles = public final ArrayList<Integer> indices = new ArrayList<Integer>();
new ArrayList<TriangleData>();
public VertexData() { public VertexInfo(Vector3f position, Vector3f normal) {
this.position = position;
this.normal = normal;
} }
} }
public static class TriangleData { /** Collects all the triangle data for one vertex.
*/
private static class VertexData {
public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>();
public VertexData() { }
}
/** Keeps track of tangent, binormal, and normal for one triangle.
*/
public static class TriangleData {
public final Vector3f tangent; public final Vector3f tangent;
public final Vector3f binormal; public final Vector3f binormal;
public final Vector3f normal; public final Vector3f normal;
public int index0;
public int index1;
public int index2;
public TriangleData(Vector3f tangent, Vector3f binormal, public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
Vector3f normal,
int index0, int index1, int index2) {
this.tangent = tangent; this.tangent = tangent;
this.binormal = binormal; this.binormal = binormal;
this.normal = normal; this.normal = normal;
this.index0 = index0;
this.index1 = index1;
this.index2 = index2;
} }
} }
@ -365,8 +366,7 @@ public class TangentBinormalGenerator {
return new TriangleData( return new TriangleData(
tangent, tangent,
binormal, binormal,
normal, normal);
index[0], index[1], index[2]);
} }
public static void setToleranceAngle(float angle) { public static void setToleranceAngle(float angle) {
@ -378,8 +378,57 @@ public class TangentBinormalGenerator {
toleranceAngle = angle; toleranceAngle = angle;
} }
private static boolean approxEqual(Vector3f u, Vector3f v) {
float tolerance = 1E-4f;
return (FastMath.abs(u.x - v.x) < tolerance) &&
(FastMath.abs(u.y - v.y) < tolerance) &&
(FastMath.abs(u.z - v.z) < tolerance);
}
private static ArrayList<VertexInfo> linkVertices(Mesh mesh) {
ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>();
FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
Vector3f position = new Vector3f();
Vector3f normal = new Vector3f();
final int size = vertexBuffer.capacity() / 3;
for (int i = 0; i < size; i++) {
populateFromBuffer(position, vertexBuffer, i);
populateFromBuffer(normal, normalBuffer, i);
boolean found = false;
for (int j = 0; j < vertexMap.size(); j++) {
VertexInfo vertexInfo = vertexMap.get(j);
if (approxEqual(vertexInfo.position, position) &&
approxEqual(vertexInfo.normal, normal))
{
vertexInfo.indices.add(i);
found = true;
break;
}
}
if (!found) {
VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone());
vertexInfo.indices.add(i);
vertexMap.add(vertexInfo);
}
}
return vertexMap;
}
private static void processTriangleData(Mesh mesh, VertexData[] vertices, private static void processTriangleData(Mesh mesh, VertexData[] vertices,
boolean approxTangent) { boolean approxTangent)
{
ArrayList<VertexInfo> vertexMap = linkVertices(mesh);
FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4); FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4);
@ -393,139 +442,159 @@ public class TangentBinormalGenerator {
Vector3f tangentUnit = new Vector3f(); Vector3f tangentUnit = new Vector3f();
Vector3f binormalUnit = new Vector3f(); Vector3f binormalUnit = new Vector3f();
for (int i = 0; i < vertices.length; i++) { for (int k = 0; k < vertexMap.size(); k++) {
float wCoord = -1; float wCoord = -1;
populateFromBuffer(givenNormal, normalBuffer, i); VertexInfo vertexInfo = vertexMap.get(k);
givenNormal.set(vertexInfo.normal);
givenNormal.normalizeLocal(); givenNormal.normalizeLocal();
VertexData currentVertex = vertices[i]; TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0);
List<TriangleData> triangles = currentVertex.triangles;
// check tangent and binormal consistency // check tangent and binormal consistency
tangent.set(triangles.get(0).tangent); tangent.set(firstTriangle.tangent);
tangent.normalizeLocal(); tangent.normalizeLocal();
binormal.set(triangles.get(0).binormal); binormal.set(firstTriangle.binormal);
binormal.normalizeLocal(); binormal.normalizeLocal();
for (int j = 1; j < triangles.size(); j++) { for (int i : vertexInfo.indices) {
TriangleData triangleData = triangles.get(j); ArrayList<TriangleData> triangles = vertices[i].triangles;
tangentUnit.set(triangleData.tangent); for (int j = 0; j < triangles.size(); j++) {
tangentUnit.normalizeLocal(); TriangleData triangleData = triangles.get(j);
if (tangent.dot(tangentUnit) < toleranceDot) {
log.log(Level.WARNING, tangentUnit.set(triangleData.tangent);
"Angle between tangents exceeds tolerance " tangentUnit.normalizeLocal();
+ "for vertex {0}.", i); if (tangent.dot(tangentUnit) < toleranceDot) {
break;
}
if (!approxTangent) {
binormalUnit.set(triangleData.binormal);
binormalUnit.normalizeLocal();
if (binormal.dot(binormalUnit) < toleranceDot) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Angle between binormals exceeds tolerance " "Angle between tangents exceeds tolerance "
+ "for vertex {0}.", i); + "for vertex {0}.", i);
break; break;
} }
if (!approxTangent) {
binormalUnit.set(triangleData.binormal);
binormalUnit.normalizeLocal();
if (binormal.dot(binormalUnit) < toleranceDot) {
log.log(Level.WARNING,
"Angle between binormals exceeds tolerance "
+ "for vertex {0}.", i);
break;
}
}
} }
} }
// find average tangent // find average tangent
tangent.set(0, 0, 0); tangent.set(0, 0, 0);
binormal.set(0, 0, 0); binormal.set(0, 0, 0);
boolean flippedNormal = false; int triangleCount = 0;
for (int j = 0; j < triangles.size(); j++) { for (int i : vertexInfo.indices) {
TriangleData triangleData = triangles.get(j); ArrayList<TriangleData> triangles = vertices[i].triangles;
tangent.addLocal(triangleData.tangent); triangleCount += triangles.size();
binormal.addLocal(triangleData.binormal);
if (givenNormal.dot(triangleData.normal) < 0) { boolean flippedNormal = false;
flippedNormal = true; for (int j = 0; j < triangles.size(); j++) {
TriangleData triangleData = triangles.get(j);
tangent.addLocal(triangleData.tangent);
binormal.addLocal(triangleData.binormal);
if (givenNormal.dot(triangleData.normal) < 0) {
flippedNormal = true;
}
} }
} if (flippedNormal /*&& approxTangent*/) {
if (flippedNormal /*&& approxTangent*/) { // Generated normal is flipped for this vertex,
// Generated normal is flipped for this vertex, // so binormal = normal.cross(tangent) will be flipped in the shader
// so binormal = normal.cross(tangent) will be flipped in the shader // log.log(Level.WARNING,
// log.log(Level.WARNING, // "Binormal is flipped for vertex {0}.", i);
// "Binormal is flipped for vertex {0}.", i);
wCoord = 1; wCoord = 1;
}
} }
int blameVertex = vertexInfo.indices.get(0);
if (tangent.length() < ZERO_TOLERANCE) { if (tangent.length() < ZERO_TOLERANCE) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Shared tangent is zero for vertex {0}.", i); "Shared tangent is zero for vertex {0}.", blameVertex);
// attempt to fix from binormal // attempt to fix from binormal
if (binormal.length() >= ZERO_TOLERANCE) { if (binormal.length() >= ZERO_TOLERANCE) {
binormal.cross(givenNormal, tangent); binormal.cross(givenNormal, tangent);
tangent.normalizeLocal(); tangent.normalizeLocal();
} // if all fails use the tangent from the first triangle } // if all fails use the tangent from the first triangle
else { else {
tangent.set(triangles.get(0).tangent); tangent.set(firstTriangle.tangent);
} }
} else { } else {
tangent.divideLocal(triangles.size()); tangent.divideLocal(triangleCount);
} }
tangentUnit.set(tangent); tangentUnit.set(tangent);
tangentUnit.normalizeLocal(); tangentUnit.normalizeLocal();
if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1)
< ZERO_TOLERANCE) { < ZERO_TOLERANCE) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Normal and tangent are parallel for vertex {0}.", i); "Normal and tangent are parallel for vertex {0}.", blameVertex);
} }
if (!approxTangent) { if (!approxTangent) {
if (binormal.length() < ZERO_TOLERANCE) { if (binormal.length() < ZERO_TOLERANCE) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Shared binormal is zero for vertex {0}.", i); "Shared binormal is zero for vertex {0}.", blameVertex);
// attempt to fix from tangent // attempt to fix from tangent
if (tangent.length() >= ZERO_TOLERANCE) { if (tangent.length() >= ZERO_TOLERANCE) {
givenNormal.cross(tangent, binormal); givenNormal.cross(tangent, binormal);
binormal.normalizeLocal(); binormal.normalizeLocal();
} // if all fails use the binormal from the first triangle } // if all fails use the binormal from the first triangle
else { else {
binormal.set(triangles.get(0).binormal); binormal.set(firstTriangle.binormal);
} }
} else { } else {
binormal.divideLocal(triangles.size()); binormal.divideLocal(triangleCount);
} }
binormalUnit.set(binormal); binormalUnit.set(binormal);
binormalUnit.normalizeLocal(); binormalUnit.normalizeLocal();
if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1)
< ZERO_TOLERANCE) { < ZERO_TOLERANCE) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Normal and binormal are parallel for vertex {0}.", i); "Normal and binormal are parallel for vertex {0}.", blameVertex);
} }
if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1)
< ZERO_TOLERANCE) { < ZERO_TOLERANCE) {
log.log(Level.WARNING, log.log(Level.WARNING,
"Tangent and binormal are parallel for vertex {0}.", i); "Tangent and binormal are parallel for vertex {0}.", blameVertex);
} }
} }
if (approxTangent) { for (int i : vertexInfo.indices) {
// givenNormal.cross(tangent, binormal); if (approxTangent) {
// binormal.cross(givenNormal, tangent); // This calculation ensures that normal and tagent have a 90 degree angle.
tangent.normalizeLocal(); // Removing this will lead to visual artifacts.
givenNormal.cross(tangent, binormal);
tangents.put((i * 4), tangent.x); binormal.cross(givenNormal, tangent);
tangents.put((i * 4) + 1, tangent.y);
tangents.put((i * 4) + 2, tangent.z); tangent.normalizeLocal();
tangents.put((i * 4) + 3, wCoord);
} else { tangents.put((i * 4), tangent.x);
tangents.put((i * 4), tangent.x); tangents.put((i * 4) + 1, tangent.y);
tangents.put((i * 4) + 1, tangent.y); tangents.put((i * 4) + 2, tangent.z);
tangents.put((i * 4) + 2, tangent.z); tangents.put((i * 4) + 3, wCoord);
tangents.put((i * 4) + 3, wCoord); } else {
tangents.put((i * 4), tangent.x);
tangents.put((i * 4) + 1, tangent.y);
tangents.put((i * 4) + 2, tangent.z);
tangents.put((i * 4) + 3, wCoord);
// setInBuffer(binormal, binormals, i); //setInBuffer(binormal, binormals, i);
}
} }
} }

Loading…
Cancel
Save