• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VertexInfo(Vector3f position, Vector3f normal)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 
VertexData()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 
TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal)93         public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
94             this.tangent = tangent;
95             this.binormal = binormal;
96             this.normal = normal;
97         }
98     }
99 
initVertexData(int size)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 
generate(Mesh mesh)108     public static void generate(Mesh mesh) {
109         generate(mesh, true);
110     }
111 
generate(Spatial scene)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 
generate(Mesh mesh, boolean approxTangents)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 
processTriangles(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t)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 
processTriangleStrip(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t)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 
processTriangleFan(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t)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
isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c)304     private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) {
305         return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0;
306     }
307 
processTriangle(int[] index, Vector3f[] v, Vector2f[] t)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 
setToleranceAngle(float angle)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 
approxEqual(Vector3f u, Vector3f v)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 
linkVertices(Mesh mesh)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 
processTriangleData(Mesh mesh, VertexData[] vertices, boolean approxTangent)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 
genTbnLines(Mesh mesh, float scale)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 
genNormalLines(Mesh mesh, float scale)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 
genTangentLines(Mesh mesh, float scale)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