Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.util;
     33 
     34 import com.jme3.math.ColorRGBA;
     35 import com.jme3.math.FastMath;
     36 import com.jme3.math.Vector2f;
     37 import com.jme3.math.Vector3f;
     38 import com.jme3.scene.*;
     39 import com.jme3.scene.VertexBuffer.Format;
     40 import com.jme3.scene.VertexBuffer.Type;
     41 import com.jme3.scene.VertexBuffer.Usage;
     42 import com.jme3.scene.mesh.IndexBuffer;
     43 import static com.jme3.util.BufferUtils.*;
     44 import java.nio.FloatBuffer;
     45 import java.nio.IntBuffer;
     46 import java.util.ArrayList;
     47 import java.util.logging.Level;
     48 import java.util.logging.Logger;
     49 
     50 /**
     51  *
     52  * @author Lex (Aleksey Nikiforov)
     53   */
     54 public class TangentBinormalGenerator {
     55 
     56     private static final float ZERO_TOLERANCE = 0.0000001f;
     57     private static final Logger log = Logger.getLogger(
     58             TangentBinormalGenerator.class.getName());
     59     private static float toleranceAngle;
     60     private static float toleranceDot;
     61 
     62     static {
     63         setToleranceAngle(45);
     64     }
     65 
     66 
     67     private static class VertexInfo {
     68         public final Vector3f position;
     69         public final Vector3f normal;
     70         public final ArrayList<Integer> indices = new ArrayList<Integer>();
     71 
     72         public VertexInfo(Vector3f position, Vector3f normal) {
     73             this.position = position;
     74             this.normal = normal;
     75         }
     76     }
     77 
     78     /** Collects all the triangle data for one vertex.
     79      */
     80     private static class VertexData {
     81         public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>();
     82 
     83         public VertexData() { }
     84     }
     85 
     86     /** Keeps track of tangent, binormal, and normal for one triangle.
     87      */
     88     public static class TriangleData {
     89         public final Vector3f tangent;
     90         public final Vector3f binormal;
     91         public final Vector3f normal;
     92 
     93         public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
     94             this.tangent = tangent;
     95             this.binormal = binormal;
     96             this.normal = normal;
     97         }
     98     }
     99 
    100     private static VertexData[] initVertexData(int size) {
    101         VertexData[] vertices = new VertexData[size];
    102         for (int i = 0; i < size; i++) {
    103             vertices[i] = new VertexData();
    104         }
    105         return vertices;
    106     }
    107 
    108     public static void generate(Mesh mesh) {
    109         generate(mesh, true);
    110     }
    111 
    112     public static void generate(Spatial scene) {
    113         if (scene instanceof Node) {
    114             Node node = (Node) scene;
    115             for (Spatial child : node.getChildren()) {
    116                 generate(child);
    117             }
    118         } else {
    119             Geometry geom = (Geometry) scene;
    120             Mesh mesh = geom.getMesh();
    121 
    122             // Check to ensure mesh has texcoords and normals before generating
    123             if (mesh.getBuffer(Type.TexCoord) != null
    124              && mesh.getBuffer(Type.Normal) != null){
    125                 generate(geom.getMesh());
    126             }
    127         }
    128     }
    129 
    130     public static void generate(Mesh mesh, boolean approxTangents) {
    131         int[] index = new int[3];
    132         Vector3f[] v = new Vector3f[3];
    133         Vector2f[] t = new Vector2f[3];
    134         for (int i = 0; i < 3; i++) {
    135             v[i] = new Vector3f();
    136             t[i] = new Vector2f();
    137         }
    138 
    139         if (mesh.getBuffer(Type.Normal) == null) {
    140             throw new IllegalArgumentException("The given mesh has no normal data!");
    141         }
    142 
    143         VertexData[] vertices;
    144         switch (mesh.getMode()) {
    145             case Triangles:
    146                 vertices = processTriangles(mesh, index, v, t);
    147                 break;
    148             case TriangleStrip:
    149                 vertices = processTriangleStrip(mesh, index, v, t);
    150                 break;
    151             case TriangleFan:
    152                 vertices = processTriangleFan(mesh, index, v, t);
    153                 break;
    154             default:
    155                 throw new UnsupportedOperationException(
    156                         mesh.getMode() + " is not supported.");
    157         }
    158 
    159         processTriangleData(mesh, vertices, approxTangents);
    160 
    161         //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer
    162         if (mesh.getBuffer(Type.BindPosePosition) != null) {
    163 
    164             VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
    165             if (tangents != null) {
    166                 VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
    167                 bindTangents.setupData(Usage.CpuOnly,
    168                         4,
    169                         Format.Float,
    170                         BufferUtils.clone(tangents.getData()));
    171 
    172                 if (mesh.getBuffer(Type.BindPoseTangent) != null) {
    173                     mesh.clearBuffer(Type.BindPoseTangent);
    174                 }
    175                 mesh.setBuffer(bindTangents);
    176                 tangents.setUsage(Usage.Stream);
    177             }
    178         }
    179     }
    180 
    181     private static VertexData[] processTriangles(Mesh mesh,
    182             int[] index, Vector3f[] v, Vector2f[] t) {
    183         IndexBuffer indexBuffer = mesh.getIndexBuffer();
    184         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    185         if (mesh.getBuffer(Type.TexCoord) == null) {
    186             throw new IllegalArgumentException("Can only generate tangents for "
    187                     + "meshes with texture coordinates");
    188         }
    189 
    190         FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
    191 
    192         VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
    193 
    194         for (int i = 0; i < indexBuffer.size() / 3; i++) {
    195             for (int j = 0; j < 3; j++) {
    196                 index[j] = indexBuffer.get(i * 3 + j);
    197                 populateFromBuffer(v[j], vertexBuffer, index[j]);
    198                 populateFromBuffer(t[j], textureBuffer, index[j]);
    199             }
    200 
    201             TriangleData triData = processTriangle(index, v, t);
    202             if (triData != null) {
    203                 vertices[index[0]].triangles.add(triData);
    204                 vertices[index[1]].triangles.add(triData);
    205                 vertices[index[2]].triangles.add(triData);
    206             }
    207         }
    208 
    209         return vertices;
    210     }
    211 
    212     private static VertexData[] processTriangleStrip(Mesh mesh,
    213             int[] index, Vector3f[] v, Vector2f[] t) {
    214         IndexBuffer indexBuffer = mesh.getIndexBuffer();
    215         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    216         FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
    217 
    218         VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
    219 
    220         index[0] = indexBuffer.get(0);
    221         index[1] = indexBuffer.get(1);
    222 
    223         populateFromBuffer(v[0], vertexBuffer, index[0]);
    224         populateFromBuffer(v[1], vertexBuffer, index[1]);
    225 
    226         populateFromBuffer(t[0], textureBuffer, index[0]);
    227         populateFromBuffer(t[1], textureBuffer, index[1]);
    228 
    229         for (int i = 2; i < indexBuffer.size(); i++) {
    230             index[2] = indexBuffer.get(i);
    231             BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]);
    232             BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]);
    233 
    234             boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]);
    235             TriangleData triData = processTriangle(index, v, t);
    236 
    237             if (triData != null && !isDegenerate) {
    238                 vertices[index[0]].triangles.add(triData);
    239                 vertices[index[1]].triangles.add(triData);
    240                 vertices[index[2]].triangles.add(triData);
    241             }
    242 
    243             Vector3f vTemp = v[0];
    244             v[0] = v[1];
    245             v[1] = v[2];
    246             v[2] = vTemp;
    247 
    248             Vector2f tTemp = t[0];
    249             t[0] = t[1];
    250             t[1] = t[2];
    251             t[2] = tTemp;
    252 
    253             index[0] = index[1];
    254             index[1] = index[2];
    255         }
    256 
    257         return vertices;
    258     }
    259 
    260     private static VertexData[] processTriangleFan(Mesh mesh,
    261             int[] index, Vector3f[] v, Vector2f[] t) {
    262         IndexBuffer indexBuffer = mesh.getIndexBuffer();
    263         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    264         FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
    265 
    266         VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
    267 
    268         index[0] = indexBuffer.get(0);
    269         index[1] = indexBuffer.get(1);
    270 
    271         populateFromBuffer(v[0], vertexBuffer, index[0]);
    272         populateFromBuffer(v[1], vertexBuffer, index[1]);
    273 
    274         populateFromBuffer(t[0], textureBuffer, index[0]);
    275         populateFromBuffer(t[1], textureBuffer, index[1]);
    276 
    277         for (int i = 2; i < vertexBuffer.capacity() / 3; i++) {
    278             index[2] = indexBuffer.get(i);
    279             populateFromBuffer(v[2], vertexBuffer, index[2]);
    280             populateFromBuffer(t[2], textureBuffer, index[2]);
    281 
    282             TriangleData triData = processTriangle(index, v, t);
    283             if (triData != null) {
    284                 vertices[index[0]].triangles.add(triData);
    285                 vertices[index[1]].triangles.add(triData);
    286                 vertices[index[2]].triangles.add(triData);
    287             }
    288 
    289             Vector3f vTemp = v[1];
    290             v[1] = v[2];
    291             v[2] = vTemp;
    292 
    293             Vector2f tTemp = t[1];
    294             t[1] = t[2];
    295             t[2] = tTemp;
    296 
    297             index[1] = index[2];
    298         }
    299 
    300         return vertices;
    301     }
    302 
    303     // check if the area is greater than zero
    304     private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) {
    305         return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0;
    306     }
    307 
    308     public static TriangleData processTriangle(int[] index,
    309             Vector3f[] v, Vector2f[] t) {
    310         Vector3f edge1 = new Vector3f();
    311         Vector3f edge2 = new Vector3f();
    312         Vector2f edge1uv = new Vector2f();
    313         Vector2f edge2uv = new Vector2f();
    314 
    315         Vector3f tangent = new Vector3f();
    316         Vector3f binormal = new Vector3f();
    317         Vector3f normal = new Vector3f();
    318 
    319         t[1].subtract(t[0], edge1uv);
    320         t[2].subtract(t[0], edge2uv);
    321         float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x;
    322 
    323         boolean normalize = false;
    324         if (Math.abs(det) < ZERO_TOLERANCE) {
    325             log.log(Level.WARNING, "Colinear uv coordinates for triangle "
    326                     + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], "
    327                     + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]",
    328                     new Object[]{index[0], index[1], index[2],
    329                         t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y});
    330             det = 1;
    331             normalize = true;
    332         }
    333 
    334         v[1].subtract(v[0], edge1);
    335         v[2].subtract(v[0], edge2);
    336 
    337         tangent.set(edge1);
    338         tangent.normalizeLocal();
    339         binormal.set(edge2);
    340         binormal.normalizeLocal();
    341 
    342         if (Math.abs(Math.abs(tangent.dot(binormal)) - 1)
    343                 < ZERO_TOLERANCE) {
    344             log.log(Level.WARNING, "Vertices are on the same line "
    345                     + "for triangle [{0}, {1}, {2}].",
    346                     new Object[]{index[0], index[1], index[2]});
    347         }
    348 
    349         float factor = 1 / det;
    350         tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor;
    351         tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor;
    352         tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor;
    353         if (normalize) {
    354             tangent.normalizeLocal();
    355         }
    356 
    357         binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor;
    358         binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor;
    359         binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor;
    360         if (normalize) {
    361             binormal.normalizeLocal();
    362         }
    363 
    364         tangent.cross(binormal, normal);
    365         normal.normalizeLocal();
    366 
    367         return new TriangleData(
    368                 tangent,
    369                 binormal,
    370                 normal);
    371     }
    372 
    373     public static void setToleranceAngle(float angle) {
    374         if (angle < 0 || angle > 179) {
    375             throw new IllegalArgumentException(
    376                     "The angle must be between 0 and 179 degrees.");
    377         }
    378         toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD);
    379         toleranceAngle = angle;
    380     }
    381 
    382 
    383     private static boolean approxEqual(Vector3f u, Vector3f v) {
    384         float tolerance = 1E-4f;
    385         return (FastMath.abs(u.x - v.x) < tolerance) &&
    386                (FastMath.abs(u.y - v.y) < tolerance) &&
    387                (FastMath.abs(u.z - v.z) < tolerance);
    388     }
    389 
    390     private static ArrayList<VertexInfo> linkVertices(Mesh mesh) {
    391         ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>();
    392 
    393         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    394         FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
    395 
    396         Vector3f position = new Vector3f();
    397         Vector3f normal = new Vector3f();
    398 
    399         final int size = vertexBuffer.capacity() / 3;
    400         for (int i = 0; i < size; i++) {
    401 
    402             populateFromBuffer(position, vertexBuffer, i);
    403             populateFromBuffer(normal, normalBuffer, i);
    404 
    405             boolean found = false;
    406 
    407             for (int j = 0; j < vertexMap.size(); j++) {
    408                 VertexInfo vertexInfo = vertexMap.get(j);
    409                 if (approxEqual(vertexInfo.position, position) &&
    410                     approxEqual(vertexInfo.normal, normal))
    411                 {
    412                     vertexInfo.indices.add(i);
    413                     found = true;
    414                     break;
    415                 }
    416             }
    417 
    418             if (!found) {
    419                 VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone());
    420                 vertexInfo.indices.add(i);
    421                 vertexMap.add(vertexInfo);
    422             }
    423         }
    424 
    425         return vertexMap;
    426     }
    427 
    428     private static void processTriangleData(Mesh mesh, VertexData[] vertices,
    429             boolean approxTangent)
    430     {
    431         ArrayList<VertexInfo> vertexMap = linkVertices(mesh);
    432 
    433         FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
    434 
    435         FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4);
    436 //        FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3);
    437 
    438         Vector3f tangent = new Vector3f();
    439         Vector3f binormal = new Vector3f();
    440         Vector3f normal = new Vector3f();
    441         Vector3f givenNormal = new Vector3f();
    442 
    443         Vector3f tangentUnit = new Vector3f();
    444         Vector3f binormalUnit = new Vector3f();
    445 
    446         for (int k = 0; k < vertexMap.size(); k++) {
    447             float wCoord = -1;
    448 
    449             VertexInfo vertexInfo = vertexMap.get(k);
    450 
    451             givenNormal.set(vertexInfo.normal);
    452             givenNormal.normalizeLocal();
    453 
    454             TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0);
    455 
    456             // check tangent and binormal consistency
    457             tangent.set(firstTriangle.tangent);
    458             tangent.normalizeLocal();
    459             binormal.set(firstTriangle.binormal);
    460             binormal.normalizeLocal();
    461 
    462             for (int i : vertexInfo.indices) {
    463                 ArrayList<TriangleData> triangles = vertices[i].triangles;
    464 
    465                 for (int j = 0; j < triangles.size(); j++) {
    466                     TriangleData triangleData = triangles.get(j);
    467 
    468                     tangentUnit.set(triangleData.tangent);
    469                     tangentUnit.normalizeLocal();
    470                     if (tangent.dot(tangentUnit) < toleranceDot) {
    471                         log.log(Level.WARNING,
    472                                 "Angle between tangents exceeds tolerance "
    473                                 + "for vertex {0}.", i);
    474                         break;
    475                     }
    476 
    477                     if (!approxTangent) {
    478                         binormalUnit.set(triangleData.binormal);
    479                         binormalUnit.normalizeLocal();
    480                         if (binormal.dot(binormalUnit) < toleranceDot) {
    481                             log.log(Level.WARNING,
    482                                     "Angle between binormals exceeds tolerance "
    483                                     + "for vertex {0}.", i);
    484                             break;
    485                         }
    486                     }
    487                 }
    488             }
    489 
    490 
    491             // find average tangent
    492             tangent.set(0, 0, 0);
    493             binormal.set(0, 0, 0);
    494 
    495             int triangleCount = 0;
    496             for (int i : vertexInfo.indices) {
    497                 ArrayList<TriangleData> triangles = vertices[i].triangles;
    498                 triangleCount += triangles.size();
    499 
    500                 boolean flippedNormal = false;
    501                 for (int j = 0; j < triangles.size(); j++) {
    502                     TriangleData triangleData = triangles.get(j);
    503                     tangent.addLocal(triangleData.tangent);
    504                     binormal.addLocal(triangleData.binormal);
    505 
    506                     if (givenNormal.dot(triangleData.normal) < 0) {
    507                         flippedNormal = true;
    508                     }
    509                 }
    510                 if (flippedNormal /*&& approxTangent*/) {
    511                     // Generated normal is flipped for this vertex,
    512                     // so binormal = normal.cross(tangent) will be flipped in the shader
    513     //                log.log(Level.WARNING,
    514     //                        "Binormal is flipped for vertex {0}.", i);
    515 
    516                     wCoord = 1;
    517                 }
    518             }
    519 
    520 
    521             int blameVertex = vertexInfo.indices.get(0);
    522 
    523             if (tangent.length() < ZERO_TOLERANCE) {
    524                 log.log(Level.WARNING,
    525                         "Shared tangent is zero for vertex {0}.", blameVertex);
    526                 // attempt to fix from binormal
    527                 if (binormal.length() >= ZERO_TOLERANCE) {
    528                     binormal.cross(givenNormal, tangent);
    529                     tangent.normalizeLocal();
    530                 } // if all fails use the tangent from the first triangle
    531                 else {
    532                     tangent.set(firstTriangle.tangent);
    533                 }
    534             } else {
    535                 tangent.divideLocal(triangleCount);
    536             }
    537 
    538             tangentUnit.set(tangent);
    539             tangentUnit.normalizeLocal();
    540             if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1)
    541                     < ZERO_TOLERANCE) {
    542                 log.log(Level.WARNING,
    543                         "Normal and tangent are parallel for vertex {0}.", blameVertex);
    544             }
    545 
    546 
    547             if (!approxTangent) {
    548                 if (binormal.length() < ZERO_TOLERANCE) {
    549                     log.log(Level.WARNING,
    550                             "Shared binormal is zero for vertex {0}.", blameVertex);
    551                     // attempt to fix from tangent
    552                     if (tangent.length() >= ZERO_TOLERANCE) {
    553                         givenNormal.cross(tangent, binormal);
    554                         binormal.normalizeLocal();
    555                     } // if all fails use the binormal from the first triangle
    556                     else {
    557                         binormal.set(firstTriangle.binormal);
    558                     }
    559                 } else {
    560                     binormal.divideLocal(triangleCount);
    561                 }
    562 
    563                 binormalUnit.set(binormal);
    564                 binormalUnit.normalizeLocal();
    565                 if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1)
    566                         < ZERO_TOLERANCE) {
    567                     log.log(Level.WARNING,
    568                             "Normal and binormal are parallel for vertex {0}.", blameVertex);
    569                 }
    570 
    571                 if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1)
    572                         < ZERO_TOLERANCE) {
    573                     log.log(Level.WARNING,
    574                             "Tangent and binormal are parallel for vertex {0}.", blameVertex);
    575                 }
    576             }
    577 
    578             for (int i : vertexInfo.indices) {
    579                 if (approxTangent) {
    580                     // This calculation ensures that normal and tagent have a 90 degree angle.
    581                     // Removing this will lead to visual artifacts.
    582                     givenNormal.cross(tangent, binormal);
    583                     binormal.cross(givenNormal, tangent);
    584 
    585                     tangent.normalizeLocal();
    586 
    587                     tangents.put((i * 4), tangent.x);
    588                     tangents.put((i * 4) + 1, tangent.y);
    589                     tangents.put((i * 4) + 2, tangent.z);
    590                     tangents.put((i * 4) + 3, wCoord);
    591                 } else {
    592                     tangents.put((i * 4), tangent.x);
    593                     tangents.put((i * 4) + 1, tangent.y);
    594                     tangents.put((i * 4) + 2, tangent.z);
    595                     tangents.put((i * 4) + 3, wCoord);
    596 
    597                     //setInBuffer(binormal, binormals, i);
    598                 }
    599             }
    600         }
    601 
    602         mesh.setBuffer(Type.Tangent, 4, tangents);
    603 //        if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals);
    604     }
    605 
    606     public static Mesh genTbnLines(Mesh mesh, float scale) {
    607         if (mesh.getBuffer(Type.Tangent) == null) {
    608             return genNormalLines(mesh, scale);
    609         } else {
    610             return genTangentLines(mesh, scale);
    611         }
    612     }
    613 
    614     public static Mesh genNormalLines(Mesh mesh, float scale) {
    615         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    616         FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
    617 
    618         ColorRGBA originColor = ColorRGBA.White;
    619         ColorRGBA normalColor = ColorRGBA.Blue;
    620 
    621         Mesh lineMesh = new Mesh();
    622         lineMesh.setMode(Mesh.Mode.Lines);
    623 
    624         Vector3f origin = new Vector3f();
    625         Vector3f point = new Vector3f();
    626 
    627         FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2);
    628         FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2);
    629 
    630         for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
    631             populateFromBuffer(origin, vertexBuffer, i);
    632             populateFromBuffer(point, normalBuffer, i);
    633 
    634             int index = i * 2;
    635 
    636             setInBuffer(origin, lineVertex, index);
    637             setInBuffer(originColor, lineColor, index);
    638 
    639             point.multLocal(scale);
    640             point.addLocal(origin);
    641             setInBuffer(point, lineVertex, index + 1);
    642             setInBuffer(normalColor, lineColor, index + 1);
    643         }
    644 
    645         lineMesh.setBuffer(Type.Position, 3, lineVertex);
    646         lineMesh.setBuffer(Type.Color, 4, lineColor);
    647 
    648         lineMesh.setStatic();
    649         //lineMesh.setInterleaved();
    650         return lineMesh;
    651     }
    652 
    653     private static Mesh genTangentLines(Mesh mesh, float scale) {
    654         FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
    655         FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
    656         FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData();
    657 
    658         FloatBuffer binormalBuffer = null;
    659         if (mesh.getBuffer(Type.Binormal) != null) {
    660             binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData();
    661         }
    662 
    663         ColorRGBA originColor = ColorRGBA.White;
    664         ColorRGBA tangentColor = ColorRGBA.Red;
    665         ColorRGBA binormalColor = ColorRGBA.Green;
    666         ColorRGBA normalColor = ColorRGBA.Blue;
    667 
    668         Mesh lineMesh = new Mesh();
    669         lineMesh.setMode(Mesh.Mode.Lines);
    670 
    671         Vector3f origin = new Vector3f();
    672         Vector3f point = new Vector3f();
    673         Vector3f tangent = new Vector3f();
    674         Vector3f normal = new Vector3f();
    675 
    676         IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.capacity() / 3 * 6);
    677         FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 4);
    678         FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 4);
    679 
    680         boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4;
    681         float tangentW = 1;
    682 
    683         for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
    684             populateFromBuffer(origin, vertexBuffer, i);
    685             populateFromBuffer(normal, normalBuffer, i);
    686 
    687             if (hasParity) {
    688                 tangent.x = tangentBuffer.get(i * 4);
    689                 tangent.y = tangentBuffer.get(i * 4 + 1);
    690                 tangent.z = tangentBuffer.get(i * 4 + 2);
    691                 tangentW = tangentBuffer.get(i * 4 + 3);
    692             } else {
    693                 populateFromBuffer(tangent, tangentBuffer, i);
    694             }
    695 
    696             int index = i * 4;
    697 
    698             int id = i * 6;
    699             lineIndex.put(id, index);
    700             lineIndex.put(id + 1, index + 1);
    701             lineIndex.put(id + 2, index);
    702             lineIndex.put(id + 3, index + 2);
    703             lineIndex.put(id + 4, index);
    704             lineIndex.put(id + 5, index + 3);
    705 
    706             setInBuffer(origin, lineVertex, index);
    707             setInBuffer(originColor, lineColor, index);
    708 
    709             point.set(tangent);
    710             point.multLocal(scale);
    711             point.addLocal(origin);
    712             setInBuffer(point, lineVertex, index + 1);
    713             setInBuffer(tangentColor, lineColor, index + 1);
    714 
    715             // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w
    716 
    717             if (binormalBuffer == null) {
    718                 normal.cross(tangent, point);
    719                 point.multLocal(-tangentW);
    720                 point.normalizeLocal();
    721             } else {
    722                 populateFromBuffer(point, binormalBuffer, i);
    723             }
    724 
    725             point.multLocal(scale);
    726             point.addLocal(origin);
    727             setInBuffer(point, lineVertex, index + 2);
    728             setInBuffer(binormalColor, lineColor, index + 2);
    729 
    730             point.set(normal);
    731             point.multLocal(scale);
    732             point.addLocal(origin);
    733             setInBuffer(point, lineVertex, index + 3);
    734             setInBuffer(normalColor, lineColor, index + 3);
    735         }
    736 
    737         lineMesh.setBuffer(Type.Index, 1, lineIndex);
    738         lineMesh.setBuffer(Type.Position, 3, lineVertex);
    739         lineMesh.setBuffer(Type.Color, 4, lineColor);
    740 
    741         lineMesh.setStatic();
    742         //lineMesh.setInterleaved();
    743         return lineMesh;
    744     }
    745 }
    746