From a1533a407b7491ca1361d2779cb6ed81fec1b5fd Mon Sep 17 00:00:00 2001 From: "rem..om" Date: Sun, 7 Jul 2013 21:24:02 +0000 Subject: [PATCH] - TangentBinormalGenerator now splits vertices with mirrored uvs. This is an optional operation. It fixes the issue in this thread : http://hub.jmonkeyengine.org/forum/topic/normalmapslighting-issue/ Next step is to split vertices that have triangles with heavily rotated tangent spaces. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10698 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../jme3/util/TangentBinormalGenerator.java | 403 ++++++++++++++---- 1 file changed, 315 insertions(+), 88 deletions(-) diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java index 21a029a53..f1e176616 100644 --- a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java +++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java @@ -35,16 +35,22 @@ import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; -import com.jme3.math.Vector4f; import com.jme3.scene.*; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.mesh.IndexBuffer; import static com.jme3.util.BufferUtils.*; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.nio.ShortBuffer; import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,13 +63,13 @@ public class TangentBinormalGenerator { private static final float ZERO_TOLERANCE = 0.0000001f; private static final Logger log = Logger.getLogger( TangentBinormalGenerator.class.getName()); - private static float toleranceAngle; private static float toleranceDot; + public static boolean debug = false; static { setToleranceAngle(45); } - + private static class VertexInfo { public final Vector3f position; @@ -91,32 +97,39 @@ public class TangentBinormalGenerator { public static class TriangleData { public final Vector3f tangent; public final Vector3f binormal; - public final Vector3f normal; + public final Vector3f normal; + public int[] index = new int[3]; + public int triangleOffset; public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) { this.tangent = tangent; this.binormal = binormal; this.normal = normal; } + public void setIndex(int[] index) { + for (int i = 0; i < index.length; i++) { + this.index[i] = index[i]; + } + } } - private static VertexData[] initVertexData(int size) { - VertexData[] vertices = new VertexData[size]; + private static List initVertexData(int size) { + List vertices = new ArrayList(size); for (int i = 0; i < size; i++) { - vertices[i] = new VertexData(); + vertices.add(new VertexData()); } return vertices; } public static void generate(Mesh mesh) { - generate(mesh, true); + generate(mesh, true, false); } - public static void generate(Spatial scene) { + public static void generate(Spatial scene, boolean splitMirrored) { if (scene instanceof Node) { Node node = (Node) scene; for (Spatial child : node.getChildren()) { - generate(child); + generate(child, splitMirrored); } } else { Geometry geom = (Geometry) scene; @@ -125,12 +138,16 @@ public class TangentBinormalGenerator { // Check to ensure mesh has texcoords and normals before generating if (mesh.getBuffer(Type.TexCoord) != null && mesh.getBuffer(Type.Normal) != null){ - generate(geom.getMesh()); + generate(geom.getMesh(),true, splitMirrored); } } } - public static void generate(Mesh mesh, boolean approxTangents) { + public static void generate(Spatial scene) { + generate(scene, false); + } + + public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirrored) { int[] index = new int[3]; Vector3f[] v = new Vector3f[3]; Vector2f[] t = new Vector2f[3]; @@ -143,10 +160,13 @@ public class TangentBinormalGenerator { throw new IllegalArgumentException("The given mesh has no normal data!"); } - VertexData[] vertices; + List vertices; switch (mesh.getMode()) { case Triangles: - vertices = processTriangles(mesh, index, v, t); + vertices = processTriangles(mesh, index, v, t, splitMirrored); + if(splitMirrored){ + splitVertices(mesh, vertices, splitMirrored); + } break; case TriangleStrip: vertices = processTriangleStrip(mesh, index, v, t); @@ -181,8 +201,8 @@ public class TangentBinormalGenerator { } } - private static VertexData[] processTriangles(Mesh mesh, - int[] index, Vector3f[] v, Vector2f[] t) { + private static List processTriangles(Mesh mesh, + int[] index, Vector3f[] v, Vector2f[] t, boolean splitMirrored) { IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); if (mesh.getBuffer(Type.TexCoord) == null) { @@ -192,7 +212,7 @@ public class TangentBinormalGenerator { FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - VertexData[] vertices = initVertexData(vertexBuffer.limit() / 3); + List vertices = initVertexData(vertexBuffer.limit() / 3); for (int i = 0; i < indexBuffer.size() / 3; i++) { for (int j = 0; j < 3; j++) { @@ -202,23 +222,201 @@ public class TangentBinormalGenerator { } TriangleData triData = processTriangle(index, v, t); + if(splitMirrored){ + triData.setIndex(index); + triData.triangleOffset = i * 3 ; + } if (triData != null) { - vertices[index[0]].triangles.add(triData); - vertices[index[1]].triangles.add(triData); - vertices[index[2]].triangles.add(triData); + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); } } return vertices; } - private static VertexData[] processTriangleStrip(Mesh mesh, + //Don't remove splitmirorred boolean,It's not used right now, but i intend to + //make this method also split vertice with rotated tangent space and I'll + //add another splitRotated boolean + private static List splitVertices(Mesh mesh, List vertexData, boolean splitMirorred) { + int nbVertices = mesh.getBuffer(Type.Position).getNumElements(); + List newVertices = new ArrayList(); + Map indiceMap = new HashMap(); + FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); + + for (int i = 0; i < vertexData.size(); i++) { + ArrayList triangles = vertexData.get(i).triangles; + Vector3f givenNormal = new Vector3f(); + populateFromBuffer(givenNormal, normalBuffer, i); + + ArrayList trianglesUp = new ArrayList(); + ArrayList trianglesDown = new ArrayList(); + for (int j = 0; j < triangles.size(); j++) { + TriangleData triangleData = triangles.get(j); + if(parity(givenNormal, triangleData.normal) > 0){ + trianglesUp.add(triangleData); + }else{ + trianglesDown.add(triangleData); + } + } + + //if the vertex has triangles with opposite parity it has to be split + if(!trianglesUp.isEmpty() && !trianglesDown.isEmpty()){ + log.log(Level.FINE, "Splitting vertex {0}", i); + //assigning triangle with the same parity to the original vertex + vertexData.get(i).triangles.clear(); + vertexData.get(i).triangles.addAll(trianglesUp); + + //creating a new vertex + VertexData newVert = new VertexData(); + //assigning triangles with opposite parity to it + newVert.triangles.addAll(trianglesDown); + + newVertices.add(newVert); + //keep vertex index to fix the index buffers later + indiceMap.put(nbVertices, i); + for (TriangleData tri : newVert.triangles) { + for (int j = 0; j < tri.index.length; j++) { + if(tri.index[j] == i){ + tri.index[j] = nbVertices; + } + } + } + nbVertices++; + + } + + + } + + if(!newVertices.isEmpty()){ + + //we have new vertices, we need to update the mesh's buffers. + for (Type type : VertexBuffer.Type.values()) { + //skip tangent buffer as we're gonna overwrite it later + if(type == Type.Tangent || type == Type.BindPoseTangent) continue; + VertexBuffer vb = mesh.getBuffer(type); + //Some buffer (hardware skinning ones) can be there but not + //initialized, they must be skipped. + //They'll be initialized when Hardware Skinning is engaged + if(vb==null || vb.getNumComponents() == 0) continue; + + Buffer buffer = vb.getData(); + //IndexBuffer has special treatement, only swapping the vertex indices is needed + if(type == Type.Index){ + boolean isShortBuffer = vb.getFormat() == VertexBuffer.Format.UnsignedShort; + for (VertexData vertex : newVertices) { + for (TriangleData tri : vertex.triangles) { + for (int i = 0; i < tri.index.length; i++) { + if (isShortBuffer) { + ((ShortBuffer) buffer).put(tri.triangleOffset + i, (short) tri.index[i]); + } else { + ((IntBuffer) buffer).put(tri.triangleOffset + i, tri.index[i]); + } + } + } + } + vb.setUpdateNeeded(); + }else{ + //copy the buffer in a bigger one and append nex vertices to the end + Buffer newVerts = VertexBuffer.createBuffer(vb.getFormat(), vb.getNumComponents(), nbVertices); + if (buffer != null) { + buffer.rewind(); + bulkPut(vb.getFormat(), newVerts,buffer); + + int index = vertexData.size(); + newVerts.position(vertexData.size() * vb.getNumComponents()); + for (int j = 0; j < newVertices.size(); j++) { + int oldInd = indiceMap.get(index) ; + for (int i = 0; i < vb.getNumComponents(); i++) { + putValue(vb.getFormat(), newVerts, buffer, oldInd* vb.getNumComponents() + i); + } + index++; + } + vb.updateData(newVerts); + //destroy previous buffer as it's no longer needed + destroyDirectBuffer(buffer); + } + } + } + vertexData.addAll(newVertices); + + mesh.updateCounts(); + } + + return vertexData; + } + + private static void bulkPut(VertexBuffer.Format format, Buffer buf1, Buffer buf2) { + switch (format) { + case Byte: + case Half: + case UnsignedByte: + ((ByteBuffer) buf1).put((ByteBuffer) buf2); + break; + case Short: + case UnsignedShort: + + ((ShortBuffer) buf1).put((ShortBuffer) buf2); + break; + + case Int: + case UnsignedInt: + ((IntBuffer) buf1).put((IntBuffer) buf2); + break; + case Float: + + ((FloatBuffer) buf1).put((FloatBuffer) buf2); + break; + case Double: + ((DoubleBuffer) buf1).put((DoubleBuffer) buf2); + break; + + default: + throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + } + } + + private static void putValue(VertexBuffer.Format format, Buffer buf1, Buffer buf2,int index) { + switch (format) { + case Byte: + case Half: + case UnsignedByte: + byte b = ((ByteBuffer) buf2).get(index); + ((ByteBuffer) buf1).put(b); + break; + case Short: + case UnsignedShort: + short s = ((ShortBuffer) buf2).get(index); + ((ShortBuffer) buf1).put(s); + break; + + case Int: + case UnsignedInt: + int i = ((IntBuffer) buf2).get(index); + ((IntBuffer) buf1).put(i); + break; + case Float: + float f = ((FloatBuffer) buf2).get(index); + ((FloatBuffer) buf1).put(f); + break; + case Double: + double d = ((DoubleBuffer) buf2).get(index); + ((DoubleBuffer) buf1).put(d); + break; + default: + throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + } + } + + private static List processTriangleStrip(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t) { IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - VertexData[] vertices = initVertexData(vertexBuffer.limit() / 3); + List vertices = initVertexData(vertexBuffer.limit() / 3); index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); @@ -238,9 +436,9 @@ public class TangentBinormalGenerator { TriangleData triData = processTriangle(index, v, t); if (triData != null && !isDegenerate) { - vertices[index[0]].triangles.add(triData); - vertices[index[1]].triangles.add(triData); - vertices[index[2]].triangles.add(triData); + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); } Vector3f vTemp = v[0]; @@ -260,13 +458,13 @@ public class TangentBinormalGenerator { return vertices; } - private static VertexData[] processTriangleFan(Mesh mesh, + private static List processTriangleFan(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t) { IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - VertexData[] vertices = initVertexData(vertexBuffer.limit() / 3); + List vertices = initVertexData(vertexBuffer.limit() / 3); index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); @@ -284,9 +482,9 @@ public class TangentBinormalGenerator { TriangleData triData = processTriangle(index, v, t); if (triData != null) { - vertices[index[0]].triangles.add(triData); - vertices[index[1]].triangles.add(triData); - vertices[index[2]].triangles.add(triData); + vertices.get(index[0]).triangles.add(triData); + vertices.get(index[1]).triangles.add(triData); + vertices.get(index[2]).triangles.add(triData); } Vector3f vTemp = v[1]; @@ -379,7 +577,6 @@ public class TangentBinormalGenerator { "The angle must be between 0 and 179 degrees."); } toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); - toleranceAngle = angle; } @@ -415,18 +612,20 @@ public class TangentBinormalGenerator { populateFromBuffer(texCoord, texcoordBuffer, 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) && - approxEqual(vertexInfo.texCoord, texCoord)) - { - vertexInfo.indices.add(i); - found = true; - break; - } - } + //Nehon 07/07/2013 + //Removed this part, joining splitted vertice to compute tangent space makes no sense to me + //separate vertice should have separate tangent space +// for (int j = 0; j < vertexMap.size(); j++) { +// VertexInfo vertexInfo = vertexMap.get(j); +// if (approxEqual(vertexInfo.position, position) && +// approxEqual(vertexInfo.normal, normal) && +// approxEqual(vertexInfo.texCoord, texCoord)) +// { +// vertexInfo.indices.add(i); +// found = true; +// break; +// } +// } if (!found) { VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone(), texCoord.clone()); @@ -438,43 +637,44 @@ public class TangentBinormalGenerator { return vertexMap; } - private static void processTriangleData(Mesh mesh, VertexData[] vertices, - boolean approxTangent) - { + private static void processTriangleData(Mesh mesh, List vertices, + boolean approxTangent) { ArrayList vertexMap = linkVertices(mesh); - - // FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); - - FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4); -// FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3); + + FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.size() * 4); + + ColorRGBA[] cols = null; + if (debug) { + cols = new ColorRGBA[vertices.size()]; + } Vector3f tangent = new Vector3f(); Vector3f binormal = new Vector3f(); //Vector3f normal = new Vector3f(); Vector3f givenNormal = new Vector3f(); - + Vector3f tangentUnit = new Vector3f(); Vector3f binormalUnit = new Vector3f(); - + for (int k = 0; k < vertexMap.size(); k++) { float wCoord = -1; - + VertexInfo vertexInfo = vertexMap.get(k); - + givenNormal.set(vertexInfo.normal); givenNormal.normalizeLocal(); - - TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0); + + TriangleData firstTriangle = vertices.get(vertexInfo.indices.get(0)).triangles.get(0); // check tangent and binormal consistency tangent.set(firstTriangle.tangent); tangent.normalizeLocal(); binormal.set(firstTriangle.binormal); binormal.normalizeLocal(); - + for (int i : vertexInfo.indices) { - ArrayList triangles = vertices[i].triangles; - + ArrayList triangles = vertices.get(i).triangles; + for (int j = 0; j < triangles.size(); j++) { TriangleData triangleData = triangles.get(j); @@ -499,40 +699,31 @@ public class TangentBinormalGenerator { } } } - - + + // find average tangent tangent.set(0, 0, 0); binormal.set(0, 0, 0); - + int triangleCount = 0; for (int i : vertexInfo.indices) { - ArrayList triangles = vertices[i].triangles; + ArrayList triangles = vertices.get(i).triangles; triangleCount += triangles.size(); - - // boolean flippedNormal = false; + if (debug) { + cols[i] = ColorRGBA.White; + } + 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*/) { -// // Generated normal is flipped for this vertex, -// // so binormal = normal.cross(tangent) will be flipped in the shader -// // log.log(Level.WARNING, -// // "Binormal is flipped for vertex {0}.", i); -// -// wCoord = 1; -// } } - + int blameVertex = vertexInfo.indices.get(0); - + if (tangent.length() < ZERO_TOLERANCE) { log.log(Level.WARNING, "Shared tangent is zero for vertex {0}.", blameVertex); @@ -587,21 +778,21 @@ public class TangentBinormalGenerator { "Tangent and binormal are parallel for vertex {0}.", blameVertex); } } - + Vector3f finalTangent = new Vector3f(); Vector3f tmp = new Vector3f(); for (int i : vertexInfo.indices) { if (approxTangent) { // Gram-Schmidt orthogonalize - finalTangent.set(tangent).subtractLocal(tmp.set(givenNormal).multLocal(givenNormal.dot(tangent))); + finalTangent.set(tangent).subtractLocal(tmp.set(givenNormal).multLocal(givenNormal.dot(tangent))); finalTangent.normalizeLocal(); - - wCoord = tmp.set(givenNormal).crossLocal(tangent).dot(binormal) <0f? -1f:1f; + + wCoord = tmp.set(givenNormal).crossLocal(tangent).dot(binormal) < 0f ? -1f : 1f; tangents.put((i * 4), finalTangent.x); tangents.put((i * 4) + 1, finalTangent.y); tangents.put((i * 4) + 2, finalTangent.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); @@ -612,13 +803,49 @@ public class TangentBinormalGenerator { } } } - + tangents.limit(tangents.capacity()); // If the model already had a tangent buffer, replace it with the regenerated one - mesh.clearBuffer(Type.Tangent); + mesh.clearBuffer(Type.Tangent); mesh.setBuffer(Type.Tangent, 4, tangents); -// if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals); - } + + + + if(mesh.isAnimated()){ + mesh.clearBuffer(Type.BindPoseNormal); + mesh.clearBuffer(Type.BindPosePosition); + mesh.clearBuffer(Type.BindPoseTangent); + mesh.generateBindPose(true); + } + + if (debug) { + writeColorBuffer( vertices, cols, mesh); + } + mesh.updateBound(); + mesh.updateCounts(); + } + private static void writeColorBuffer(List vertices, ColorRGBA[] cols, Mesh mesh) { + FloatBuffer colors = BufferUtils.createFloatBuffer(vertices.size() * 4); + colors.rewind(); + for (ColorRGBA color : cols) { + colors.put(color.r); + colors.put(color.g); + colors.put(color.b); + colors.put(color.a); + } + mesh.clearBuffer(Type.Color); + mesh.setBuffer(Type.Color, 4, colors); + } + + private static int parity(Vector3f n1, Vector3f n) { + if (n1.dot(n) < 0) { + return -1; + } else { + return 1; + } + + } + public static Mesh genTbnLines(Mesh mesh, float scale) { if (mesh.getBuffer(Type.Tangent) == null) { return genNormalLines(mesh, scale);