aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/util/TangentBinormalGenerator.java')
-rw-r--r--engine/src/core/com/jme3/util/TangentBinormalGenerator.java739
1 files changed, 739 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
new file mode 100644
index 0000000..88f6822
--- /dev/null
+++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.IndexBuffer;
+import static com.jme3.util.BufferUtils.*;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Lex (Aleksey Nikiforov)
+ */
+public class TangentBinormalGenerator {
+
+ private static final float ZERO_TOLERANCE = 0.0000001f;
+ private static final Logger log = Logger.getLogger(
+ TangentBinormalGenerator.class.getName());
+ private static float toleranceAngle;
+ private static float toleranceDot;
+
+ static {
+ setToleranceAngle(45);
+ }
+
+
+ private static class VertexInfo {
+ public final Vector3f position;
+ public final Vector3f normal;
+ public final ArrayList<Integer> indices = new ArrayList<Integer>();
+
+ public VertexInfo(Vector3f position, Vector3f normal) {
+ this.position = position;
+ this.normal = normal;
+ }
+ }
+
+ /** Collects all the triangle data for one vertex.
+ */
+ private static class VertexData {
+ public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>();
+
+ public VertexData() { }
+ }
+
+ /** Keeps track of tangent, binormal, and normal for one triangle.
+ */
+ public static class TriangleData {
+ public final Vector3f tangent;
+ public final Vector3f binormal;
+ public final Vector3f normal;
+
+ public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
+ this.tangent = tangent;
+ this.binormal = binormal;
+ this.normal = normal;
+ }
+ }
+
+ private static VertexData[] initVertexData(int size) {
+ VertexData[] vertices = new VertexData[size];
+ for (int i = 0; i < size; i++) {
+ vertices[i] = new VertexData();
+ }
+ return vertices;
+ }
+
+ public static void generate(Mesh mesh) {
+ generate(mesh, true);
+ }
+
+ public static void generate(Spatial scene) {
+ if (scene instanceof Node) {
+ Node node = (Node) scene;
+ for (Spatial child : node.getChildren()) {
+ generate(child);
+ }
+ } else {
+ Geometry geom = (Geometry) scene;
+ generate(geom.getMesh());
+ }
+ }
+
+ public static void generate(Mesh mesh, boolean approxTangents) {
+ int[] index = new int[3];
+ Vector3f[] v = new Vector3f[3];
+ Vector2f[] t = new Vector2f[3];
+ for (int i = 0; i < 3; i++) {
+ v[i] = new Vector3f();
+ t[i] = new Vector2f();
+ }
+
+ if (mesh.getBuffer(Type.Normal) == null) {
+ throw new IllegalArgumentException("The given mesh has no normal data!");
+ }
+
+ VertexData[] vertices;
+ switch (mesh.getMode()) {
+ case Triangles:
+ vertices = processTriangles(mesh, index, v, t);
+ break;
+ case TriangleStrip:
+ vertices = processTriangleStrip(mesh, index, v, t);
+ break;
+ case TriangleFan:
+ vertices = processTriangleFan(mesh, index, v, t);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ mesh.getMode() + " is not supported.");
+ }
+
+ processTriangleData(mesh, vertices, approxTangents);
+
+ //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer
+ if (mesh.getBuffer(Type.BindPosePosition) != null) {
+
+ VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+ if (tangents != null) {
+ VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+ bindTangents.setupData(Usage.CpuOnly,
+ 4,
+ Format.Float,
+ BufferUtils.clone(tangents.getData()));
+
+ if (mesh.getBuffer(Type.BindPoseTangent) != null) {
+ mesh.clearBuffer(Type.BindPoseTangent);
+ }
+ mesh.setBuffer(bindTangents);
+ tangents.setUsage(Usage.Stream);
+ }
+ }
+ }
+
+ private static VertexData[] processTriangles(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ if (mesh.getBuffer(Type.TexCoord) == null) {
+ throw new IllegalArgumentException("Can only generate tangents for "
+ + "meshes with texture coordinates");
+ }
+
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ for (int i = 0; i < indexBuffer.size() / 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ index[j] = indexBuffer.get(i * 3 + j);
+ populateFromBuffer(v[j], vertexBuffer, index[j]);
+ populateFromBuffer(t[j], textureBuffer, index[j]);
+ }
+
+ TriangleData triData = processTriangle(index, v, t);
+ if (triData != null) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+ }
+
+ return vertices;
+ }
+
+ private static VertexData[] processTriangleStrip(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ index[0] = indexBuffer.get(0);
+ index[1] = indexBuffer.get(1);
+
+ populateFromBuffer(v[0], vertexBuffer, index[0]);
+ populateFromBuffer(v[1], vertexBuffer, index[1]);
+
+ populateFromBuffer(t[0], textureBuffer, index[0]);
+ populateFromBuffer(t[1], textureBuffer, index[1]);
+
+ for (int i = 2; i < indexBuffer.size(); i++) {
+ index[2] = indexBuffer.get(i);
+ BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]);
+ BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]);
+
+ boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]);
+ TriangleData triData = processTriangle(index, v, t);
+
+ if (triData != null && !isDegenerate) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+
+ Vector3f vTemp = v[0];
+ v[0] = v[1];
+ v[1] = v[2];
+ v[2] = vTemp;
+
+ Vector2f tTemp = t[0];
+ t[0] = t[1];
+ t[1] = t[2];
+ t[2] = tTemp;
+
+ index[0] = index[1];
+ index[1] = index[2];
+ }
+
+ return vertices;
+ }
+
+ private static VertexData[] processTriangleFan(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ index[0] = indexBuffer.get(0);
+ index[1] = indexBuffer.get(1);
+
+ populateFromBuffer(v[0], vertexBuffer, index[0]);
+ populateFromBuffer(v[1], vertexBuffer, index[1]);
+
+ populateFromBuffer(t[0], textureBuffer, index[0]);
+ populateFromBuffer(t[1], textureBuffer, index[1]);
+
+ for (int i = 2; i < vertexBuffer.capacity() / 3; i++) {
+ index[2] = indexBuffer.get(i);
+ populateFromBuffer(v[2], vertexBuffer, index[2]);
+ populateFromBuffer(t[2], textureBuffer, index[2]);
+
+ TriangleData triData = processTriangle(index, v, t);
+ if (triData != null) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+
+ Vector3f vTemp = v[1];
+ v[1] = v[2];
+ v[2] = vTemp;
+
+ Vector2f tTemp = t[1];
+ t[1] = t[2];
+ t[2] = tTemp;
+
+ index[1] = index[2];
+ }
+
+ return vertices;
+ }
+
+ // check if the area is greater than zero
+ private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) {
+ return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0;
+ }
+
+ public static TriangleData processTriangle(int[] index,
+ Vector3f[] v, Vector2f[] t) {
+ Vector3f edge1 = new Vector3f();
+ Vector3f edge2 = new Vector3f();
+ Vector2f edge1uv = new Vector2f();
+ Vector2f edge2uv = new Vector2f();
+
+ Vector3f tangent = new Vector3f();
+ Vector3f binormal = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ t[1].subtract(t[0], edge1uv);
+ t[2].subtract(t[0], edge2uv);
+ float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x;
+
+ boolean normalize = false;
+ if (Math.abs(det) < ZERO_TOLERANCE) {
+ log.log(Level.WARNING, "Colinear uv coordinates for triangle "
+ + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], "
+ + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]",
+ new Object[]{index[0], index[1], index[2],
+ t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y});
+ det = 1;
+ normalize = true;
+ }
+
+ v[1].subtract(v[0], edge1);
+ v[2].subtract(v[0], edge2);
+
+ tangent.set(edge1);
+ tangent.normalizeLocal();
+ binormal.set(edge2);
+ binormal.normalizeLocal();
+
+ if (Math.abs(Math.abs(tangent.dot(binormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING, "Vertices are on the same line "
+ + "for triangle [{0}, {1}, {2}].",
+ new Object[]{index[0], index[1], index[2]});
+ }
+
+ float factor = 1 / det;
+ tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor;
+ tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor;
+ tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor;
+ if (normalize) {
+ tangent.normalizeLocal();
+ }
+
+ binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor;
+ binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor;
+ binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor;
+ if (normalize) {
+ binormal.normalizeLocal();
+ }
+
+ tangent.cross(binormal, normal);
+ normal.normalizeLocal();
+
+ return new TriangleData(
+ tangent,
+ binormal,
+ normal);
+ }
+
+ public static void setToleranceAngle(float angle) {
+ if (angle < 0 || angle > 179) {
+ throw new IllegalArgumentException(
+ "The angle must be between 0 and 179 degrees.");
+ }
+ toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD);
+ toleranceAngle = angle;
+ }
+
+
+ private static boolean approxEqual(Vector3f u, Vector3f v) {
+ float tolerance = 1E-4f;
+ return (FastMath.abs(u.x - v.x) < tolerance) &&
+ (FastMath.abs(u.y - v.y) < tolerance) &&
+ (FastMath.abs(u.z - v.z) < tolerance);
+ }
+
+ private static ArrayList<VertexInfo> linkVertices(Mesh mesh) {
+ ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>();
+
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ Vector3f position = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ final int size = vertexBuffer.capacity() / 3;
+ for (int i = 0; i < size; i++) {
+
+ populateFromBuffer(position, vertexBuffer, i);
+ populateFromBuffer(normal, normalBuffer, i);
+
+ boolean found = false;
+
+ for (int j = 0; j < vertexMap.size(); j++) {
+ VertexInfo vertexInfo = vertexMap.get(j);
+ if (approxEqual(vertexInfo.position, position) &&
+ approxEqual(vertexInfo.normal, normal))
+ {
+ vertexInfo.indices.add(i);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone());
+ vertexInfo.indices.add(i);
+ vertexMap.add(vertexInfo);
+ }
+ }
+
+ return vertexMap;
+ }
+
+ private static void processTriangleData(Mesh mesh, VertexData[] vertices,
+ boolean approxTangent)
+ {
+ ArrayList<VertexInfo> vertexMap = linkVertices(mesh);
+
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4);
+// FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3);
+
+ Vector3f tangent = new Vector3f();
+ Vector3f binormal = new Vector3f();
+ Vector3f normal = new Vector3f();
+ Vector3f givenNormal = new Vector3f();
+
+ Vector3f tangentUnit = new Vector3f();
+ Vector3f binormalUnit = new Vector3f();
+
+ for (int k = 0; k < vertexMap.size(); k++) {
+ float wCoord = -1;
+
+ VertexInfo vertexInfo = vertexMap.get(k);
+
+ givenNormal.set(vertexInfo.normal);
+ givenNormal.normalizeLocal();
+
+ TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0);
+
+ // check tangent and binormal consistency
+ tangent.set(firstTriangle.tangent);
+ tangent.normalizeLocal();
+ binormal.set(firstTriangle.binormal);
+ binormal.normalizeLocal();
+
+ for (int i : vertexInfo.indices) {
+ ArrayList<TriangleData> triangles = vertices[i].triangles;
+
+ for (int j = 0; j < triangles.size(); j++) {
+ TriangleData triangleData = triangles.get(j);
+
+ tangentUnit.set(triangleData.tangent);
+ tangentUnit.normalizeLocal();
+ if (tangent.dot(tangentUnit) < toleranceDot) {
+ log.log(Level.WARNING,
+ "Angle between tangents exceeds tolerance "
+ + "for vertex {0}.", i);
+ break;
+ }
+
+ if (!approxTangent) {
+ binormalUnit.set(triangleData.binormal);
+ binormalUnit.normalizeLocal();
+ if (binormal.dot(binormalUnit) < toleranceDot) {
+ log.log(Level.WARNING,
+ "Angle between binormals exceeds tolerance "
+ + "for vertex {0}.", i);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // find average tangent
+ tangent.set(0, 0, 0);
+ binormal.set(0, 0, 0);
+
+ int triangleCount = 0;
+ for (int i : vertexInfo.indices) {
+ ArrayList<TriangleData> triangles = vertices[i].triangles;
+ triangleCount += triangles.size();
+
+ boolean flippedNormal = false;
+ for (int j = 0; j < triangles.size(); j++) {
+ TriangleData triangleData = triangles.get(j);
+ tangent.addLocal(triangleData.tangent);
+ binormal.addLocal(triangleData.binormal);
+
+ if (givenNormal.dot(triangleData.normal) < 0) {
+ flippedNormal = true;
+ }
+ }
+ if (flippedNormal /*&& approxTangent*/) {
+ // Generated normal is flipped for this vertex,
+ // so binormal = normal.cross(tangent) will be flipped in the shader
+ // log.log(Level.WARNING,
+ // "Binormal is flipped for vertex {0}.", i);
+
+ wCoord = 1;
+ }
+ }
+
+
+ int blameVertex = vertexInfo.indices.get(0);
+
+ if (tangent.length() < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Shared tangent is zero for vertex {0}.", blameVertex);
+ // attempt to fix from binormal
+ if (binormal.length() >= ZERO_TOLERANCE) {
+ binormal.cross(givenNormal, tangent);
+ tangent.normalizeLocal();
+ } // if all fails use the tangent from the first triangle
+ else {
+ tangent.set(firstTriangle.tangent);
+ }
+ } else {
+ tangent.divideLocal(triangleCount);
+ }
+
+ tangentUnit.set(tangent);
+ tangentUnit.normalizeLocal();
+ if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Normal and tangent are parallel for vertex {0}.", blameVertex);
+ }
+
+
+ if (!approxTangent) {
+ if (binormal.length() < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Shared binormal is zero for vertex {0}.", blameVertex);
+ // attempt to fix from tangent
+ if (tangent.length() >= ZERO_TOLERANCE) {
+ givenNormal.cross(tangent, binormal);
+ binormal.normalizeLocal();
+ } // if all fails use the binormal from the first triangle
+ else {
+ binormal.set(firstTriangle.binormal);
+ }
+ } else {
+ binormal.divideLocal(triangleCount);
+ }
+
+ binormalUnit.set(binormal);
+ binormalUnit.normalizeLocal();
+ if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Normal and binormal are parallel for vertex {0}.", blameVertex);
+ }
+
+ if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Tangent and binormal are parallel for vertex {0}.", blameVertex);
+ }
+ }
+
+ for (int i : vertexInfo.indices) {
+ if (approxTangent) {
+ // This calculation ensures that normal and tagent have a 90 degree angle.
+ // Removing this will lead to visual artifacts.
+ givenNormal.cross(tangent, binormal);
+ binormal.cross(givenNormal, tangent);
+
+ tangent.normalizeLocal();
+
+ tangents.put((i * 4), tangent.x);
+ tangents.put((i * 4) + 1, tangent.y);
+ tangents.put((i * 4) + 2, tangent.z);
+ tangents.put((i * 4) + 3, wCoord);
+ } else {
+ tangents.put((i * 4), tangent.x);
+ tangents.put((i * 4) + 1, tangent.y);
+ tangents.put((i * 4) + 2, tangent.z);
+ tangents.put((i * 4) + 3, wCoord);
+
+ //setInBuffer(binormal, binormals, i);
+ }
+ }
+ }
+
+ mesh.setBuffer(Type.Tangent, 4, tangents);
+// if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals);
+ }
+
+ public static Mesh genTbnLines(Mesh mesh, float scale) {
+ if (mesh.getBuffer(Type.Tangent) == null) {
+ return genNormalLines(mesh, scale);
+ } else {
+ return genTangentLines(mesh, scale);
+ }
+ }
+
+ public static Mesh genNormalLines(Mesh mesh, float scale) {
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ ColorRGBA originColor = ColorRGBA.White;
+ ColorRGBA normalColor = ColorRGBA.Blue;
+
+ Mesh lineMesh = new Mesh();
+ lineMesh.setMode(Mesh.Mode.Lines);
+
+ Vector3f origin = new Vector3f();
+ Vector3f point = new Vector3f();
+
+ FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2);
+ FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2);
+
+ for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+ populateFromBuffer(origin, vertexBuffer, i);
+ populateFromBuffer(point, normalBuffer, i);
+
+ int index = i * 2;
+
+ setInBuffer(origin, lineVertex, index);
+ setInBuffer(originColor, lineColor, index);
+
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 1);
+ setInBuffer(normalColor, lineColor, index + 1);
+ }
+
+ lineMesh.setBuffer(Type.Position, 3, lineVertex);
+ lineMesh.setBuffer(Type.Color, 4, lineColor);
+
+ lineMesh.setStatic();
+ lineMesh.setInterleaved();
+ return lineMesh;
+ }
+
+ private static Mesh genTangentLines(Mesh mesh, float scale) {
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+ FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData();
+
+ FloatBuffer binormalBuffer = null;
+ if (mesh.getBuffer(Type.Binormal) != null) {
+ binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData();
+ }
+
+ ColorRGBA originColor = ColorRGBA.White;
+ ColorRGBA tangentColor = ColorRGBA.Red;
+ ColorRGBA binormalColor = ColorRGBA.Green;
+ ColorRGBA normalColor = ColorRGBA.Blue;
+
+ Mesh lineMesh = new Mesh();
+ lineMesh.setMode(Mesh.Mode.Lines);
+
+ Vector3f origin = new Vector3f();
+ Vector3f point = new Vector3f();
+ Vector3f tangent = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.capacity() / 3 * 6);
+ FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 4);
+ FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 4);
+
+ boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4;
+ float tangentW = 1;
+
+ for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+ populateFromBuffer(origin, vertexBuffer, i);
+ populateFromBuffer(normal, normalBuffer, i);
+
+ if (hasParity) {
+ tangent.x = tangentBuffer.get(i * 4);
+ tangent.y = tangentBuffer.get(i * 4 + 1);
+ tangent.z = tangentBuffer.get(i * 4 + 2);
+ tangentW = tangentBuffer.get(i * 4 + 3);
+ } else {
+ populateFromBuffer(tangent, tangentBuffer, i);
+ }
+
+ int index = i * 4;
+
+ int id = i * 6;
+ lineIndex.put(id, index);
+ lineIndex.put(id + 1, index + 1);
+ lineIndex.put(id + 2, index);
+ lineIndex.put(id + 3, index + 2);
+ lineIndex.put(id + 4, index);
+ lineIndex.put(id + 5, index + 3);
+
+ setInBuffer(origin, lineVertex, index);
+ setInBuffer(originColor, lineColor, index);
+
+ point.set(tangent);
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 1);
+ setInBuffer(tangentColor, lineColor, index + 1);
+
+ // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w
+
+ if (binormalBuffer == null) {
+ normal.cross(tangent, point);
+ point.multLocal(-tangentW);
+ point.normalizeLocal();
+ } else {
+ populateFromBuffer(point, binormalBuffer, i);
+ }
+
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 2);
+ setInBuffer(binormalColor, lineColor, index + 2);
+
+ point.set(normal);
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 3);
+ setInBuffer(normalColor, lineColor, index + 3);
+ }
+
+ lineMesh.setBuffer(Type.Index, 1, lineIndex);
+ lineMesh.setBuffer(Type.Position, 3, lineVertex);
+ lineMesh.setBuffer(Type.Color, 4, lineColor);
+
+ lineMesh.setStatic();
+ lineMesh.setInterleaved();
+ return lineMesh;
+ }
+}