/* * Copyright (c) 2009-2012 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.util; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; 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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Lex (Aleksey Nikiforov) */ public class TangentBinormalGenerator { private static final float ZERO_TOLERANCE = 0.0000001f; private static final Logger log = Logger.getLogger( TangentBinormalGenerator.class.getName()); private static float toleranceDot; public static boolean debug = false; static { setToleranceAngle(45); } private static class VertexInfo { public final Vector3f position; public final Vector3f normal; public final Vector2f texCoord; public final ArrayList<Integer> indices = new ArrayList<Integer>(); public VertexInfo(Vector3f position, Vector3f normal, Vector2f texCoord) { this.position = position; this.normal = normal; this.texCoord = texCoord; } } /** 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 binormal; 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 List<VertexData> initVertexData(int size) { List<VertexData> vertices = new ArrayList<VertexData>(size); for (int i = 0; i < size; i++) { vertices.add(new VertexData()); } return vertices; } public static void generate(Mesh mesh) { generate(mesh, true, false); } public static void generate(Spatial scene, boolean splitMirrored) { if (scene instanceof Node) { Node node = (Node) scene; for (Spatial child : node.getChildren()) { generate(child, splitMirrored); } } else { Geometry geom = (Geometry) scene; Mesh mesh = geom.getMesh(); // Check to ensure mesh has texcoords and normals before generating if (mesh.getBuffer(Type.TexCoord) != null && mesh.getBuffer(Type.Normal) != null){ generate(geom.getMesh(),true, splitMirrored); } } } public static void generate(Spatial scene) { generate(scene, false); } public static void generateParallel(Spatial scene, ExecutorService executor) { final Set<Mesh> meshes = new HashSet<Mesh>(); scene.breadthFirstTraversal(new SceneGraphVisitor() { @Override public void visit(Spatial spatial) { if (spatial instanceof Geometry) { Geometry geom = (Geometry) spatial; Mesh mesh = geom.getMesh(); // Check to ensure mesh has texcoords and normals before generating if (mesh.getBuffer(Type.TexCoord) != null && mesh.getBuffer(Type.Normal) != null) { meshes.add(mesh); } } } }); List<Future<?>> futures = new ArrayList<Future<?>>(); for (final Mesh m : meshes) { futures.add(executor.submit(new Runnable() { @Override public void run() { generate(m, true, false); } })); } for (Future<?> f : futures) { try { f.get(); } catch (Exception exc) { log.log(Level.WARNING, "Error while computing tangents", exc); } } } 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]; for (int i = 0; i < 3; i++) { v[i] = new Vector3f(); t[i] = new Vector2f(); } if (mesh.getBuffer(Type.Normal) == null) { throw new IllegalArgumentException("The given mesh has no normal data!"); } List<VertexData> vertices; switch (mesh.getMode()) { case Triangles: vertices = processTriangles(mesh, index, v, t, splitMirrored); if(splitMirrored){ splitVertices(mesh, vertices, splitMirrored); } break; case TriangleStrip: vertices = processTriangleStrip(mesh, index, v, t); break; case TriangleFan: vertices = processTriangleFan(mesh, index, v, t); break; default: throw new UnsupportedOperationException( mesh.getMode() + " is not supported."); } processTriangleData(mesh, vertices, approxTangents,splitMirrored); //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer TangentUtils.generateBindPoseTangentsIfNecessary(mesh); } public static void generate(Mesh mesh, boolean approxTangents) { generate(mesh, approxTangents, false); } private static List<VertexData> 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) { throw new IllegalArgumentException("Can only generate tangents for " + "meshes with texture coordinates"); } FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); List<VertexData> vertices = initVertexData(vertexBuffer.limit() / 3); for (int i = 0; i < indexBuffer.size() / 3; i++) { for (int j = 0; j < 3; j++) { index[j] = indexBuffer.get(i * 3 + j); populateFromBuffer(v[j], vertexBuffer, index[j]); populateFromBuffer(t[j], textureBuffer, index[j]); } TriangleData triData = processTriangle(index, v, t); if(splitMirrored){ triData.setIndex(index); triData.triangleOffset = i * 3 ; } vertices.get(index[0]).triangles.add(triData); vertices.get(index[1]).triangles.add(triData); vertices.get(index[2]).triangles.add(triData); } return vertices; } //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<VertexData> splitVertices(Mesh mesh, List<VertexData> vertexData, boolean splitMirorred) { int nbVertices = mesh.getBuffer(Type.Position).getNumElements(); List<VertexData> newVertices = new ArrayList<VertexData>(); Map<Integer, Integer> indiceMap = new HashMap<Integer, Integer>(); FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); for (int i = 0; i < vertexData.size(); i++) { ArrayList<TriangleData> triangles = vertexData.get(i).triangles; Vector3f givenNormal = new Vector3f(); populateFromBuffer(givenNormal, normalBuffer, i); ArrayList<TriangleData> trianglesUp = new ArrayList<TriangleData>(); ArrayList<TriangleData> trianglesDown = new ArrayList<TriangleData>(); 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<VertexData> 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(); List<VertexData> vertices = initVertexData(vertexBuffer.limit() / 3); index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); for (int i = 2; i < indexBuffer.size(); i++) { index[2] = indexBuffer.get(i); BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]); BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]); boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]); TriangleData triData = processTriangle(index, v, t); if (!isDegenerate) { 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]; v[0] = v[1]; v[1] = v[2]; v[2] = vTemp; Vector2f tTemp = t[0]; t[0] = t[1]; t[1] = t[2]; t[2] = tTemp; index[0] = index[1]; index[1] = index[2]; } return vertices; } private static List<VertexData> 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(); List<VertexData> vertices = initVertexData(vertexBuffer.limit() / 3); index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); for (int i = 2; i < vertexBuffer.limit() / 3; i++) { index[2] = indexBuffer.get(i); populateFromBuffer(v[2], vertexBuffer, index[2]); populateFromBuffer(t[2], textureBuffer, index[2]); TriangleData triData = processTriangle(index, v, t); 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]; v[1] = v[2]; v[2] = vTemp; Vector2f tTemp = t[1]; t[1] = t[2]; t[2] = tTemp; index[1] = index[2]; } return vertices; } // check if the area is greater than zero private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) { return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0; } public static TriangleData processTriangle(int[] index, Vector3f[] v, Vector2f[] t) { TempVars tmp = TempVars.get(); try { Vector3f edge1 = tmp.vect1; Vector3f edge2 = tmp.vect2; Vector2f edge1uv = tmp.vect2d; Vector2f edge2uv = tmp.vect2d2; Vector3f tangent = tmp.vect3; Vector3f binormal = tmp.vect4; Vector3f normal = tmp.vect5; t[1].subtract(t[0], edge1uv); t[2].subtract(t[0], edge2uv); float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x; boolean normalize = false; if (Math.abs(det) < ZERO_TOLERANCE) { log.log(Level.WARNING, "Colinear uv coordinates for triangle " + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", new Object[]{index[0], index[1], index[2], t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y}); det = 1; normalize = true; } v[1].subtract(v[0], edge1); v[2].subtract(v[0], edge2); tangent.set(edge1); tangent.normalizeLocal(); binormal.set(edge2); binormal.normalizeLocal(); if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) < ZERO_TOLERANCE) { log.log(Level.WARNING, "Vertices are on the same line " + "for triangle [{0}, {1}, {2}].", new Object[]{index[0], index[1], index[2]}); } float factor = 1 / det; tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor; tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor; tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor; if (normalize) { tangent.normalizeLocal(); } binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor; binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor; binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor; if (normalize) { binormal.normalizeLocal(); } tangent.cross(binormal, normal); normal.normalizeLocal(); return new TriangleData( tangent.clone(), binormal.clone(), normal.clone()); } finally { tmp.release(); } } public static void setToleranceAngle(float angle) { if (angle < 0 || angle > 179) { throw new IllegalArgumentException( "The angle must be between 0 and 179 degrees."); } toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); } 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 boolean approxEqual(Vector2f u, Vector2f v) { float tolerance = 1E-4f; return (FastMath.abs(u.x - v.x) < tolerance) && (FastMath.abs(u.y - v.y) < tolerance); } private static ArrayList<VertexInfo> linkVertices(Mesh mesh, boolean splitMirrored) { ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>(); FloatBuffer vertexBuffer = mesh.getFloatBuffer(Type.Position); FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); FloatBuffer texcoordBuffer = mesh.getFloatBuffer(Type.TexCoord); Vector3f position = new Vector3f(); Vector3f normal = new Vector3f(); Vector2f texCoord = new Vector2f(); final int size = vertexBuffer.limit() / 3; for (int i = 0; i < size; i++) { populateFromBuffer(position, vertexBuffer, i); populateFromBuffer(normal, normalBuffer, i); populateFromBuffer(texCoord, texcoordBuffer, i); boolean found = false; //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 if(!splitMirrored){ 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()); vertexInfo.indices.add(i); vertexMap.add(vertexInfo); } } return vertexMap; } private static void processTriangleData(Mesh mesh, List<VertexData> vertices, boolean approxTangent, boolean splitMirrored) { ArrayList<VertexInfo> vertexMap = linkVertices(mesh,splitMirrored); 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.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<TriangleData> triangles = vertices.get(i).triangles; for (int j = 0; j < triangles.size(); j++) { TriangleData triangleData = triangles.get(j); tangentUnit.set(triangleData.tangent); tangentUnit.normalizeLocal(); if (tangent.dot(tangentUnit) < toleranceDot) { log.log(Level.WARNING, "Angle between tangents exceeds tolerance " + "for vertex {0}.", i); 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 tangent.set(0, 0, 0); binormal.set(0, 0, 0); int triangleCount = 0; for (int i : vertexInfo.indices) { ArrayList<TriangleData> triangles = vertices.get(i).triangles; triangleCount += triangles.size(); 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); } } int blameVertex = vertexInfo.indices.get(0); if (tangent.length() < ZERO_TOLERANCE) { log.log(Level.WARNING, "Shared tangent is zero for vertex {0}.", blameVertex); // attempt to fix from binormal if (binormal.length() >= ZERO_TOLERANCE) { binormal.cross(givenNormal, tangent); tangent.normalizeLocal(); } // if all fails use the tangent from the first triangle else { tangent.set(firstTriangle.tangent); } } else { tangent.divideLocal(triangleCount); } tangentUnit.set(tangent); tangentUnit.normalizeLocal(); if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) < ZERO_TOLERANCE) { log.log(Level.WARNING, "Normal and tangent are parallel for vertex {0}.", blameVertex); } if (!approxTangent) { if (binormal.length() < ZERO_TOLERANCE) { log.log(Level.WARNING, "Shared binormal is zero for vertex {0}.", blameVertex); // attempt to fix from tangent if (tangent.length() >= ZERO_TOLERANCE) { givenNormal.cross(tangent, binormal); binormal.normalizeLocal(); } // if all fails use the binormal from the first triangle else { binormal.set(firstTriangle.binormal); } } else { binormal.divideLocal(triangleCount); } binormalUnit.set(binormal); binormalUnit.normalizeLocal(); if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) < ZERO_TOLERANCE) { log.log(Level.WARNING, "Normal and binormal are parallel for vertex {0}.", blameVertex); } if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) < ZERO_TOLERANCE) { log.log(Level.WARNING, "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.normalizeLocal(); 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); } 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); } } } tangents.limit(tangents.capacity()); // If the model already had a tangent buffer, replace it with the regenerated one mesh.clearBuffer(Type.Tangent); mesh.setBuffer(Type.Tangent, 4, tangents); 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<VertexData> 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); } else { return genTangentLines(mesh, scale); } } public static Mesh genNormalLines(Mesh mesh, float scale) { FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); ColorRGBA originColor = ColorRGBA.White; ColorRGBA normalColor = ColorRGBA.Blue; Mesh lineMesh = new Mesh(); lineMesh.setMode(Mesh.Mode.Lines); Vector3f origin = new Vector3f(); Vector3f point = new Vector3f(); FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 2); FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 2); for (int i = 0; i < vertexBuffer.limit() / 3; i++) { populateFromBuffer(origin, vertexBuffer, i); populateFromBuffer(point, normalBuffer, i); int index = i * 2; setInBuffer(origin, lineVertex, index); setInBuffer(originColor, lineColor, index); point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 1); setInBuffer(normalColor, lineColor, index + 1); } lineMesh.setBuffer(Type.Position, 3, lineVertex); lineMesh.setBuffer(Type.Color, 4, lineColor); lineMesh.setStatic(); //lineMesh.setInterleaved(); return lineMesh; } private static Mesh genTangentLines(Mesh mesh, float scale) { FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData(); FloatBuffer binormalBuffer = null; if (mesh.getBuffer(Type.Binormal) != null) { binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData(); } ColorRGBA originColor = ColorRGBA.White; ColorRGBA tangentColor = ColorRGBA.Red; ColorRGBA binormalColor = ColorRGBA.Green; ColorRGBA normalColor = ColorRGBA.Blue; Mesh lineMesh = new Mesh(); lineMesh.setMode(Mesh.Mode.Lines); Vector3f origin = new Vector3f(); Vector3f point = new Vector3f(); Vector3f tangent = new Vector3f(); Vector3f normal = new Vector3f(); IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.limit() / 3 * 6); FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 4); FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 4); boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4; float tangentW = 1; for (int i = 0; i < vertexBuffer.limit() / 3; i++) { populateFromBuffer(origin, vertexBuffer, i); populateFromBuffer(normal, normalBuffer, i); if (hasParity) { tangent.x = tangentBuffer.get(i * 4); tangent.y = tangentBuffer.get(i * 4 + 1); tangent.z = tangentBuffer.get(i * 4 + 2); tangentW = tangentBuffer.get(i * 4 + 3); } else { populateFromBuffer(tangent, tangentBuffer, i); } int index = i * 4; int id = i * 6; lineIndex.put(id, index); lineIndex.put(id + 1, index + 1); lineIndex.put(id + 2, index); lineIndex.put(id + 3, index + 2); lineIndex.put(id + 4, index); lineIndex.put(id + 5, index + 3); setInBuffer(origin, lineVertex, index); setInBuffer(originColor, lineColor, index); point.set(tangent); point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 1); setInBuffer(tangentColor, lineColor, index + 1); // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w if (binormalBuffer == null) { normal.cross(tangent, point); point.multLocal(-tangentW); point.normalizeLocal(); } else { populateFromBuffer(point, binormalBuffer, i); } point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 2); setInBuffer(binormalColor, lineColor, index + 2); point.set(normal); point.multLocal(scale); point.addLocal(origin); setInBuffer(point, lineVertex, index + 3); setInBuffer(normalColor, lineColor, index + 3); } lineMesh.setBuffer(Type.Index, 1, lineIndex); lineMesh.setBuffer(Type.Position, 3, lineVertex); lineMesh.setBuffer(Type.Color, 4, lineColor); lineMesh.setStatic(); //lineMesh.setInterleaved(); return lineMesh; } }