Commits

Michael Ludwig  committed 37225a8

Add ideal tangent vectors to sphere and cylinder geometries, and add a utility function to compute tangent vectors.

This is used to provide tangents for box and teapot.
Begin fleshing out iterator utilities for meshes that should make editing geometry easier.

  • Participants
  • Parent commits cd3e011

Comments (0)

Files changed (10)

File ferox-renderer/src/main/java/com/ferox/renderer/geom/AbstractTriangleIterator.java

+package com.ferox.renderer.geom;
+
+import com.ferox.math.Const;
+import com.ferox.math.Vector3;
+import com.ferox.math.Vector4;
+
+import java.util.Map;
+
+/**
+ *
+ */
+abstract class AbstractTriangleIterator implements TriangleIterator {
+    public static final String ATTR_VERTEX = "reserved_Vertex";
+    public static final String ATTR_NORMAL = "reserved_Normal";
+    public static final String ATTR_TANGENT = "reserved_Tangent";
+    public static final String ATTR_TEX_COORD = "reserved_TexCoord";
+
+    private final Map<String, Attribute> attrs;
+
+    // one for each vertex of the triangle
+    private final Vector3[] vertex;
+    private final Vector3[] normal;
+    private final Vector4[] tangent;
+
+    public AbstractTriangleIterator(Map<String, Attribute> attrs) {
+        this.attrs = attrs;
+        vertex = new Vector3[] { new Vector3(), new Vector3(), new Vector3() };
+        normal = new Vector3[] { new Vector3(), new Vector3(), new Vector3() };
+        tangent = new Vector4[] { new Vector4(), new Vector4(), new Vector4() };
+    }
+
+    protected void configureBuilder(Builder b) {
+        for (Map.Entry<String, Attribute> e : attrs.entrySet()) {
+            Attribute a = e.getValue();
+            b.attrImpl(e.getKey(), a.data, a.offset, a.elementSize, a.stride);
+        }
+    }
+
+    protected abstract int getVertexIndex(int p);
+
+    protected abstract void ensureAvailable(int p);
+
+    @Override
+    public Vector3 getVertex(int p) {
+        ensureAvailable(p);
+        Attribute v = attrs.get(ATTR_VERTEX);
+        if (v == null) {
+            throw new IllegalStateException("No vertex attribute defined");
+        }
+        int idx = v.getDataIndex(getVertexIndex(p));
+        return vertex[p].set(v.data, idx);
+    }
+
+    @Override
+    public Vector3 getNormal(int p) {
+        ensureAvailable(p);
+        Attribute n = attrs.get(ATTR_NORMAL);
+        if (n == null) {
+            throw new IllegalStateException("No normal attribute defined");
+        }
+        int idx = n.getDataIndex(getVertexIndex(p));
+        return normal[p].set(n.data, idx);
+    }
+
+    @Override
+    public Vector4 getTangent(int p) {
+        ensureAvailable(p);
+        Attribute t = attrs.get(ATTR_TANGENT);
+        if (t == null) {
+            throw new IllegalStateException("No tangent attribute defined");
+        }
+        int idx = t.getDataIndex(getVertexIndex(p));
+        return tangent[p].set(t.data, idx);
+    }
+
+    @Override
+    public double getTextureCoordinateU(int p) {
+        ensureAvailable(p);
+        Attribute t = attrs.get(ATTR_TEX_COORD);
+        if (t == null) {
+            throw new IllegalStateException("No texture coordinate attribute defined");
+        }
+        int idx = t.getDataIndex(getVertexIndex(p));
+        float tc = t.data[idx];
+        //        if (tc > 1.0)
+        //            return 2.0 - tc;
+        //        else
+        return tc;
+    }
+
+    @Override
+    public double getTextureCoordinateV(int p) {
+        ensureAvailable(p);
+        Attribute t = attrs.get(ATTR_TEX_COORD);
+        if (t == null) {
+            throw new IllegalStateException("No texture coordinate attribute defined");
+        }
+        int idx = t.getDataIndex(getVertexIndex(p));
+        float tc = t.data[idx + 1];
+        //        if (tc > 1.0)
+        //            return 2.0 - tc;
+        //        else
+        return tc;
+    }
+
+    @Override
+    public void setVertex(int p, @Const Vector3 v) {
+        ensureAvailable(p);
+        Attribute va = attrs.get(ATTR_VERTEX);
+        if (va == null) {
+            throw new IllegalStateException("No vertex attribute defined");
+        }
+        int idx = va.getDataIndex(getVertexIndex(p));
+        v.get(va.data, idx);
+    }
+
+    @Override
+    public void setNormal(int p, @Const Vector3 n) {
+        ensureAvailable(p);
+        Attribute na = attrs.get(ATTR_NORMAL);
+        if (na == null) {
+            throw new IllegalStateException("No normal attribute defined");
+        }
+        int idx = na.getDataIndex(getVertexIndex(p));
+        n.get(na.data, idx);
+    }
+
+    @Override
+    public void setTangent(int p, @Const Vector4 t) {
+        ensureAvailable(p);
+        Attribute ta = attrs.get(ATTR_TANGENT);
+        if (ta == null) {
+            throw new IllegalStateException("No tangent attribute defined");
+        }
+        int idx = ta.getDataIndex(getVertexIndex(p));
+        t.get(ta.data, idx);
+    }
+
+    @Override
+    public void setTextureCoordinate(int p, double u, double v) {
+        ensureAvailable(p);
+        Attribute t = attrs.get(ATTR_TEX_COORD);
+        if (t == null) {
+            throw new IllegalStateException("No texture coordinate attribute defined");
+        }
+        int idx = t.getDataIndex(getVertexIndex(p));
+        t.data[idx] = (float) u;
+        t.data[idx + 1] = (float) v;
+    }
+
+    @Override
+    public int getAttributeIndex(String attrName, int p) {
+        ensureAvailable(p);
+        Attribute a = attrs.get(attrName);
+        if (a == null) {
+            throw new IllegalStateException("No attribute defined for name " + attrName);
+        }
+        return a.getDataIndex(getVertexIndex(p));
+    }
+
+    @Override
+    public Vector3 getAttribute(String attrName, int p, Vector3 v) {
+        ensureAvailable(p);
+        Attribute a = attrs.get(attrName);
+        if (a == null) {
+            throw new IllegalStateException("No attribute defined for name " + attrName);
+        }
+        if (a.elementSize != 3) {
+            throw new IllegalStateException("Attribute must have element size of 3");
+        }
+        if (v == null) {
+            v = new Vector3();
+        }
+        v.set(a.data, a.getDataIndex(getVertexIndex(p)));
+        return v;
+    }
+
+    @Override
+    public Vector4 getAttribute(String attrName, int p, Vector4 v) {
+        ensureAvailable(p);
+        Attribute a = attrs.get(attrName);
+        if (a == null) {
+            throw new IllegalStateException("No attribute defined for name " + attrName);
+        }
+        if (a.elementSize != 4) {
+            throw new IllegalStateException("Attribute must have element size of 4");
+        }
+        if (v == null) {
+            v = new Vector4();
+        }
+        return v.set(a.data, a.getDataIndex(getVertexIndex(p)));
+    }
+
+    @Override
+    public void setAttribute(String attrName, int p, @Const Vector3 v) {
+        ensureAvailable(p);
+        Attribute a = attrs.get(attrName);
+        if (a == null) {
+            throw new IllegalStateException("No attribute defined for name " + attrName);
+        }
+        if (a.elementSize != 3) {
+            throw new IllegalStateException("Attribute must have element size of 3");
+        }
+        v.get(a.data, a.getDataIndex(getVertexIndex(p)));
+    }
+
+    @Override
+    public void setAttribute(String attrName, int p, @Const Vector4 v) {
+        ensureAvailable(p);
+        Attribute a = attrs.get(attrName);
+        if (a == null) {
+            throw new IllegalStateException("No attribute defined for name " + attrName);
+        }
+        if (a.elementSize != 4) {
+            throw new IllegalStateException("Attribute must have element size of 4");
+        }
+        v.get(a.data, a.getDataIndex(getVertexIndex(p)));
+    }
+
+    public static class Attribute {
+        final int offset;
+        final int elementSize;
+        final int stride;
+
+        final float[] data;
+
+        public Attribute(float[] data, int offset, int elementSize, int stride) {
+            this.data = data;
+            this.offset = offset;
+            this.elementSize = elementSize;
+            this.stride = stride;
+        }
+
+        private int getDataIndex(int vertexIndex) {
+            return offset + (elementSize + stride) * vertexIndex;
+        }
+    }
+}

File ferox-renderer/src/main/java/com/ferox/renderer/geom/Box.java

     }
 
     private static class BoxImpl implements Geometry {
-        // Holds vertices, normals, texture coordinates packed as V3F_N3F_T2F
+        // Holds vertices, normals, texture coordinates packed as V3F_N3F_TC2F_T4F
         private final VertexBuffer vertexAttributes;
         private final ElementBuffer indices;
 
         private final VertexAttribute vertices;
         private final VertexAttribute normals;
         private final VertexAttribute texCoords;
+        private final VertexAttribute tangents;
 
         private final AxisAlignedBox bounds;
 
             float minZ = (float) min.z;
 
             int i = 0;
-            float[] va = new float[192]; // 72v + 72n + 48t
+            float[] va = new float[288]; // 72v + 72n + 48tc + 96t
 
             // 3 verts per triangle, 2 triangles per face, 6 faces = 36
-            int[] indices = new int[] {
-                                              0, 1, 2, 2, 3, 0, // BACK
-                                              4, 5, 6, 6, 7, 4, // RIGHT
-                                              8, 9, 10, 10, 11, 8, // FRONT
-                                              12, 13, 14, 14, 15, 12, // LEFT
-                                              16, 17, 18, 18, 19, 16, // TOP
-                                              20, 21, 22, 22, 23, 20, // BOTTOM
+            int[] indices = new int[] { 0, 1, 2, 2, 3, 0, // BACK
+                                        4, 5, 6, 6, 7, 4, // RIGHT
+                                        8, 9, 10, 10, 11, 8, // FRONT
+                                        12, 13, 14, 14, 15, 12, // LEFT
+                                        16, 17, 18, 18, 19, 16, // TOP
+                                        20, 21, 22, 22, 23, 20, // BOTTOM
             };
             this.indices = framework.newElementBuffer().fromUnsigned(indices).build();
 
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = minY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
 
             // right
             /* v */
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
 
             // front
             /* v */
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
 
             // left
             /* v */
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = minY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
 
             // top
             /* v */
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = maxY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = maxY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
 
             // bottom
             /* v */
             /* t */
             va[i++] = 1f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 1f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = maxX;
             va[i++] = minY;
             /* t */
             va[i++] = 0f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
             /* v */
             va[i++] = minX;
             va[i++] = minY;
             /* t */
             va[i++] = 1f;
             va[i++] = 0f;
+            // skip tangent
+            i += 4;
+
+            TriangleIterator ti = TriangleIterator.Builder.newBuilder().vertices(va, 0, 9).normals(va, 3, 9)
+                                                          .textureCoordinates(va, 6, 10).tangents(va, 8, 8)
+                                                          .fromElements(indices, 0, indices.length).build();
+            Tangents.compute(ti);
 
             vertexAttributes = framework.newVertexBuffer().from(va).build();
-            vertices = new VertexAttribute(vertexAttributes, 3, 0, 5);
-            normals = new VertexAttribute(vertexAttributes, 3, 3, 5);
-            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 6);
+            vertices = new VertexAttribute(vertexAttributes, 3, 0, 9);
+            normals = new VertexAttribute(vertexAttributes, 3, 3, 9);
+            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 10);
+            tangents = new VertexAttribute(vertexAttributes, 4, 8, 8);
 
             bounds = new AxisAlignedBox(new Vector3(minX, minY, minZ), new Vector3(maxX, maxY, maxZ));
         }
 
         @Override
         public VertexAttribute getTangents() {
-            throw new UnsupportedOperationException("NOT IMPLEMENTED YET");
+            return tangents;
         }
 
         @Override

File ferox-renderer/src/main/java/com/ferox/renderer/geom/Cylinder.java

     }
 
     private static class CylinderImpl implements Geometry {
-        // Holds vertices, normals, texture coordinates packed as V3F_N3F_T2F
+        // Holds vertices, normals, texture coordinates packed as V3F_N3F_TC2F_T4F
         private final VertexBuffer vertexAttributes;
 
         private final VertexAttribute vertices;
         private final VertexAttribute normals;
         private final VertexAttribute texCoords;
+        private final VertexAttribute tangents;
 
         private final ElementBuffer indices;
 
             // center points (for different TCs) in the center of the caps
             int vertexCount = 6 * (res + 1);
 
-            float[] va = new float[vertexCount * 8]; // 3v + 3n + 2tc
-            int[] indices = new int[18 * res];
+            float[] va = new float[vertexCount * 12]; // 3v + 3n + 2tc + 4t
+            int[] indices = new int[12 * res]; // 4 sections of res tris
 
             int vi = 0;
             int ii = 0;
                 va[vi++] = uCoord[i]; // tx
                 va[vi++] = 1; // ty
 
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
+
                 // center
                 va[vi++] = 0; // vx
                 va[vi++] = (float) (.5 * height); // vy
                 va[vi++] = uCoord[i]; // tx
                 va[vi++] = 1; // ty
 
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
+
                 if (i != res) {
                     // form triangle with proper winding
                     indices[ii++] = i * 2;
             }
 
             // second cap
-            int offset = vi / 8;
+            int offset = vi / 12;
             for (int i = 0; i <= res; i++) {
                 // outer point
                 va[vi++] = xCoord[i]; // vx
                 va[vi++] = uCoord[i]; // tx
                 va[vi++] = 0; // ty
 
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
+
                 // center
                 va[vi++] = 0; // vx
                 va[vi++] = (float) (-.5 * height); // vy
                 va[vi++] = uCoord[i]; // tx
                 va[vi++] = 0; // ty
 
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
+
                 if (i != res) {
                     // form a triangle with proper winding
                     indices[ii++] = offset + i * 2;
             }
 
             // tube
-            offset = vi / 8;
+            offset = vi / 12;
             for (int i = 0; i <= res; i++) {
                 // place two vertices in panel
                 va[vi++] = xCoord[i];
+                va[vi++] = (float) (-.5 * height);
+                va[vi++] = zCoord[i];
+
+                va[vi++] = xCoord[i];
+                va[vi++] = 0;
+                va[vi++] = zCoord[i];
+
+                va[vi++] = uCoord[i];
+                va[vi++] = 1;
+
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
+
+                va[vi++] = xCoord[i];
                 va[vi++] = (float) (.5 * height);
                 va[vi++] = zCoord[i];
 
                 va[vi++] = zCoord[i];
 
                 va[vi++] = uCoord[i];
-                va[vi++] = 1;
+                va[vi++] = 0;
 
-                va[vi++] = xCoord[i];
-                va[vi++] = (float) (-.5 * height);
-                va[vi++] = zCoord[i];
-
-                va[vi++] = xCoord[i];
-                va[vi++] = 0;
-                va[vi++] = zCoord[i];
-
-                va[vi++] = uCoord[i];
-                va[vi++] = 0;
+                // calculate ideal normalized tangent vector
+                va[vi++] = (float) (zCoord[i] / radius);
+                va[vi++] = 0.0f;
+                va[vi++] = (float) (-xCoord[i] / radius);
+                va[vi++] = 1.0f;
 
                 if (i != res) {
                     // form two triangles with proper winding
                     indices[ii++] = offset + i * 2;
-                    indices[ii++] = offset + i * 2 + 2; // (i + 1) * 2
                     indices[ii++] = offset + i * 2 + 1;
+                    indices[ii++] = offset + i * 2 + 2;
 
-                    indices[ii++] = offset + i * 2 + 2; // (i + 1) * 2 + 1
+                    indices[ii++] = offset + i * 2 + 2;
+                    indices[ii++] = offset + i * 2 + 1;
                     indices[ii++] = offset + i * 2 + 3;
-                    indices[ii++] = offset + i * 2 + 1;
                 }
             }
 
 
             Matrix3 n = new Matrix3(m).inverse();
 
-            for (int i = 0; i < va.length; i += 8) {
+            for (int i = 0; i < va.length; i += 12) {
                 // vertex
                 u.set(va, i);
                 u.mul(m, u).add(origin);
                 u.set(va, i + 3);
                 u.mul(u, n);
                 u.get(va, i + 3);
+
+                // tangent
+                u.set(va, i + 8);
+                u.mul(u, n);
+                u.get(va, i + 8);
             }
 
             this.indices = framework.newElementBuffer().fromUnsigned(indices).build();
             vertexAttributes = framework.newVertexBuffer().from(va).build();
-            vertices = new VertexAttribute(vertexAttributes, 3, 0, 5);
-            normals = new VertexAttribute(vertexAttributes, 3, 3, 5);
-            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 6);
+            vertices = new VertexAttribute(vertexAttributes, 3, 0, 9);
+            normals = new VertexAttribute(vertexAttributes, 3, 3, 9);
+            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 10);
+            tangents = new VertexAttribute(vertexAttributes, 4, 8, 8);
 
             bounds = new AxisAlignedBox(new Vector3(-radius, -height, -radius),
                                         new Vector3(radius, height, radius));
 
         @Override
         public VertexAttribute getTangents() {
-            throw new UnsupportedOperationException("NOT IMPLEMENTED YET");
+            return tangents;
         }
 
         @Override

File ferox-renderer/src/main/java/com/ferox/renderer/geom/Rectangle.java

     }
 
     private static class RectangleImpl implements Geometry {
-        // Holds vertices, normals, texture coordinates packed as V3F_N3F_T2F
+        // Holds vertices, normals, texture coordinates packed as V3F_N3F_TC2F_T4F
         // ordered in such a way as to not need indices
         private final VertexBuffer vertexAttributes;
 
         private final VertexAttribute vertices;
         private final VertexAttribute normals;
         private final VertexAttribute texCoords;
+        private final VertexAttribute tangents;
 
         private final AxisAlignedBox bounds;
 
             va[i++] = 1f;
             va[i++] = 1f;
 
+
+            TriangleIterator ti = TriangleIterator.Builder.newBuilder().vertices(va, 0, 9).normals(va, 3, 9)
+                                                          .textureCoordinates(va, 6, 10).tangents(va, 8, 8)
+                                                          .fromStripArray(0, 4).build();
+            Tangents.compute(ti);
+
             vertexAttributes = framework.newVertexBuffer().from(va).build();
-            vertices = new VertexAttribute(vertexAttributes, 3, 0, 5);
-            normals = new VertexAttribute(vertexAttributes, 3, 3, 5);
-            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 6);
+            vertices = new VertexAttribute(vertexAttributes, 3, 0, 9);
+            normals = new VertexAttribute(vertexAttributes, 3, 3, 9);
+            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 10);
+            tangents = new VertexAttribute(vertexAttributes, 4, 8, 8);
 
             bounds = new AxisAlignedBox(va, 0, 5, 4);
         }
 
         @Override
         public VertexAttribute getTangents() {
-            throw new UnsupportedOperationException("NOT IMPLEMENTED YET");
+            return tangents;
         }
 
         @Override

File ferox-renderer/src/main/java/com/ferox/renderer/geom/Sphere.java

     }
 
     private static class SphereImpl implements Geometry {
-        // Holds vertices, normals, texture coordinates packed as V3F_N3F_T2F
+        // Holds vertices, normals, tangents, texture coordinates packed as V3F_N3F_TC2F_T4F
         private final VertexBuffer vertexAttributes;
 
         private final VertexAttribute vertices;
         private final VertexAttribute normals;
+        private final VertexAttribute tangents;
         private final VertexAttribute texCoords;
 
         private final ElementBuffer indices;
             zCoord[res] = zCoord[0];
             u[res] = 1f;
 
-            float[] va = new float[vertexCount * 8]; // 3v + 3n + 2tc
+            float[] va = new float[vertexCount * 12]; // 3v + 3n + 2tc + 4t
 
             float floatRadius = (float) radius;
             float yAngle = PI;
 
                     va[index++] = u[du]; // tx
                     va[index++] = tv; // ty
+
+                    // calculate ideal tangent vectors
+                    va[index++] = -zCoord[du];
+                    va[index++] = 0.0f;
+                    va[index++] = xCoord[du];
+                    va[index++] = 1.0f;
                 }
             }
 
                 }
             }
 
+            if (index != indices.length) {
+                throw new RuntimeException("bad length computation");
+            }
+
             this.indices = framework.newElementBuffer().fromUnsigned(indices).build();
             vertexAttributes = framework.newVertexBuffer().from(va).build();
-            vertices = new VertexAttribute(vertexAttributes, 3, 0, 5);
-            normals = new VertexAttribute(vertexAttributes, 3, 3, 5);
-            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 6);
+            vertices = new VertexAttribute(vertexAttributes, 3, 0, 9);
+            normals = new VertexAttribute(vertexAttributes, 3, 3, 9);
+            texCoords = new VertexAttribute(vertexAttributes, 2, 6, 10);
+            tangents = new VertexAttribute(vertexAttributes, 4, 8, 8);
 
             bounds = new AxisAlignedBox(new Vector3(-radius, -radius, -radius),
                                         new Vector3(radius, radius, radius));
 
         @Override
         public VertexAttribute getTangents() {
-            throw new UnsupportedOperationException("NOT IMPLEMENTED YET");
+            return tangents;
         }
 
         @Override

File ferox-renderer/src/main/java/com/ferox/renderer/geom/Tangents.java

+package com.ferox.renderer.geom;
+
+import com.ferox.math.Vector3;
+import com.ferox.math.Vector4;
+
+/**
+ *
+ */
+public final class Tangents {
+    private Tangents() {
+    }
+
+    public static void compute(TriangleIterator ti) {
+        // clone the iterator and add two temporary attributes to accumulate the tangent data
+        TriangleIterator.Builder b = TriangleIterator.Builder.newBuilder().set(ti);
+        b.createAttribute("tan", 3);
+        b.createAttribute("bitan", 3);
+        ti = b.build();
+
+        Vector3 d1 = new Vector3();
+        Vector3 d2 = new Vector3();
+        Vector3 tan = new Vector3();
+        Vector3 dTan = new Vector3();
+        Vector4 invalid = new Vector4(0.0, 0.0, 0.0, Double.NaN);
+
+        while (ti.next()) {
+            d1.sub(ti.getVertex(1), ti.getVertex(0));
+            d2.sub(ti.getVertex(2), ti.getVertex(0));
+
+            double s1 = ti.getTextureCoordinateU(1) - ti.getTextureCoordinateU(0);
+            double s2 = ti.getTextureCoordinateU(2) - ti.getTextureCoordinateU(0);
+            double t1 = ti.getTextureCoordinateV(1) - ti.getTextureCoordinateV(0);
+            double t2 = ti.getTextureCoordinateV(2) - ti.getTextureCoordinateV(0);
+
+            double r = 1.0 / (s1 * t2 - s2 * t1);
+            // primary tangent direction and accumulate
+            dTan.scale(d1, t2).addScaled(-t1, d2).scale(r);
+            ti.setAttribute("tan", 0, ti.getAttribute("tan", 0, tan).add(dTan));
+            ti.setAttribute("tan", 1, ti.getAttribute("tan", 1, tan).add(dTan));
+            ti.setAttribute("tan", 2, ti.getAttribute("tan", 2, tan).add(dTan));
+
+            // secondary tangent direction and accumulate
+            dTan.scale(d1, -s2).addScaled(s1, d2).scale(r);
+            ti.setAttribute("bitan", 0, ti.getAttribute("bitan", 0, tan).add(dTan));
+            ti.setAttribute("bitan", 1, ti.getAttribute("bitan", 1, tan).add(dTan));
+            ti.setAttribute("bitan", 2, ti.getAttribute("bitan", 2, tan).add(dTan));
+
+            // invalidate actual tangent vector
+            ti.setTangent(0, invalid);
+            ti.setTangent(1, invalid);
+            ti.setTangent(2, invalid);
+        }
+
+        ti.reset();
+        while (ti.next()) {
+            // normalize and orthogonalize each vertex
+            Vector4 t = ti.getTangent(0);
+            if (Double.isNaN(t.w)) {
+                Vector3 n = ti.getNormal(0);
+                double l = ti.getAttribute("tan", 0, tan).ortho(n).length();
+                if (l > 0.00001) {
+                    tan.scale(1.0 / l);
+                    t.set(tan.x, tan.y, tan.z,
+                          d1.cross(n, tan).dot(ti.getAttribute("bitan", 0, d2)) < 0.0 ? -1.0 : 1.0);
+                    ti.setTangent(0, t);
+                } else {
+                    // degenerate triangle
+                    ti.setTangent(0, t.set(0.0, 0.0, 0.0, 0.0));
+                }
+            } // else already been processed
+
+            t = ti.getTangent(1);
+            if (Double.isNaN(t.w)) {
+                Vector3 n = ti.getNormal(1);
+                double l = ti.getAttribute("tan", 1, tan).ortho(n).length();
+                if (l > 0.00001) {
+                    tan.scale(1.0 / l);
+                    t.set(tan.x, tan.y, tan.z,
+                          d1.cross(n, tan).dot(ti.getAttribute("bitan", 1, d2)) < 0.0 ? -1.0 : 1.0);
+                    ti.setTangent(1, t);
+
+                } else {
+                    // degenerate triangle
+                    ti.setTangent(1, t.set(0.0, 0.0, 0.0, 0.0));
+                }
+            } // else already been processed
+
+            t = ti.getTangent(2);
+            if (Double.isNaN(t.w)) {
+                Vector3 n = ti.getNormal(2);
+                double l = ti.getAttribute("tan", 2, tan).ortho(n).length();
+                if (l > 0.00001) {
+                    tan.scale(1.0 / l);
+                    t.set(tan.x, tan.y, tan.z,
+                          d1.cross(n, tan).dot(ti.getAttribute("bitan", 2, d2)) < 0.0 ? -1.0 : 1.0);
+                    ti.setTangent(2, t);
+
+                } else {
+                    // degenerate triangle
+                    ti.setTangent(2, t.set(0.0, 0.0, 0.0, 0.0));
+                }
+            } // else already processed
+        }
+    }
+}