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 */
32package com.jme3.util;
33
34import com.jme3.math.ColorRGBA;
35import com.jme3.math.FastMath;
36import com.jme3.math.Vector2f;
37import com.jme3.math.Vector3f;
38import com.jme3.scene.*;
39import com.jme3.scene.VertexBuffer.Format;
40import com.jme3.scene.VertexBuffer.Type;
41import com.jme3.scene.VertexBuffer.Usage;
42import com.jme3.scene.mesh.IndexBuffer;
43import static com.jme3.util.BufferUtils.*;
44import java.nio.FloatBuffer;
45import java.nio.IntBuffer;
46import java.util.ArrayList;
47import java.util.logging.Level;
48import java.util.logging.Logger;
49
50/**
51 *
52 * @author Lex (Aleksey Nikiforov)
53  */
54public 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